diff options
Diffstat (limited to 'scene')
286 files changed, 11808 insertions, 6939 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 6eaf31d701..6d380aed3c 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -31,7 +31,6 @@ #include "animated_sprite_2d.h" #include "scene/main/viewport.h" -#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED Dictionary AnimatedSprite2D::_edit_get_state() const { @@ -202,7 +201,7 @@ void AnimatedSprite2D::_notification(int p_what) { } else { frame = last_frame; pause(); - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal(SceneStringName(animation_finished)); return; } } else { @@ -211,7 +210,7 @@ void AnimatedSprite2D::_notification(int p_what) { _calc_frame_speed_scale(); frame_progress = 0.0; queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } double to_process = MIN((1.0 - frame_progress) / abs_speed, remaining); frame_progress += to_process * abs_speed; @@ -226,7 +225,7 @@ void AnimatedSprite2D::_notification(int p_what) { } else { frame = 0; pause(); - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal(SceneStringName(animation_finished)); return; } } else { @@ -235,7 +234,7 @@ void AnimatedSprite2D::_notification(int p_what) { _calc_frame_speed_scale(); frame_progress = 1.0; queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } double to_process = MIN(frame_progress / abs_speed, remaining); frame_progress -= to_process * abs_speed; @@ -291,12 +290,12 @@ void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { } if (frames.is_valid()) { - frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed)); + frames->disconnect(CoreStringName(changed), callable_mp(this, &AnimatedSprite2D::_res_changed)); } stop(); frames = p_frames; if (frames.is_valid()) { - frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed)); + frames->connect(CoreStringName(changed), callable_mp(this, &AnimatedSprite2D::_res_changed)); List<StringName> al; frames->get_animation_list(&al); @@ -305,7 +304,7 @@ void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { autoplay = String(); } else { if (!frames->has_animation(animation)) { - set_animation(al[0]); + set_animation(al.front()->get()); } if (!frames->has_animation(autoplay)) { autoplay = String(); @@ -363,7 +362,7 @@ void AnimatedSprite2D::set_frame_and_progress(int p_frame, real_t p_progress) { return; // No change, don't redraw. } queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } void AnimatedSprite2D::set_speed_scale(float p_speed_scale) { @@ -482,7 +481,7 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool } else { set_frame_and_progress(0, 0.0); } - emit_signal("animation_changed"); + emit_signal(SceneStringName(animation_changed)); } else { bool is_backward = signbit(speed_scale * custom_speed_scale); if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) { @@ -537,7 +536,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) { animation = p_name; - emit_signal("animation_changed"); + emit_signal(SceneStringName(animation_changed)); if (frames == nullptr) { animation = StringName(); diff --git a/scene/2d/audio_listener_2d.cpp b/scene/2d/audio_listener_2d.cpp index b4484694a5..cff0654ecc 100644 --- a/scene/2d/audio_listener_2d.cpp +++ b/scene/2d/audio_listener_2d.cpp @@ -45,7 +45,7 @@ bool AudioListener2D::_set(const StringName &p_name, const Variant &p_value) { bool AudioListener2D::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "current") { - if (is_inside_tree() && get_tree()->is_node_being_edited(this)) { + if (is_part_of_edited_scene()) { r_ret = current; } else { r_ret = is_current(); @@ -63,13 +63,13 @@ void AudioListener2D::_get_property_list(List<PropertyInfo> *p_list) const { void AudioListener2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - if (!get_tree()->is_node_being_edited(this) && current) { + if (!is_part_of_edited_scene() && current) { make_current(); } } break; case NOTIFICATION_EXIT_TREE: { - if (!get_tree()->is_node_being_edited(this)) { + if (!is_part_of_edited_scene()) { if (is_current()) { clear_current(); current = true; // Keep it true. @@ -98,7 +98,7 @@ void AudioListener2D::clear_current() { } bool AudioListener2D::is_current() const { - if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { + if (is_inside_tree() && !is_part_of_edited_scene()) { return get_viewport()->get_audio_listener_2d() == this; } else { return current; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index cbd3c244d9..f88db0e3f4 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -37,7 +37,6 @@ #include "scene/audio/audio_stream_player_internal.h" #include "scene/main/viewport.h" #include "scene/resources/world_2d.h" -#include "scene/scene_string_names.h" #include "servers/audio/audio_stream.h" #include "servers/audio_server.h" @@ -81,10 +80,10 @@ StringName AudioStreamPlayer2D::_get_actual_bus() { //check if any area is diverting sound into a bus Ref<World2D> world_2d = get_world_2d(); - ERR_FAIL_COND_V(world_2d.is_null(), SceneStringNames::get_singleton()->Master); + ERR_FAIL_COND_V(world_2d.is_null(), SceneStringName(Master)); PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); - ERR_FAIL_NULL_V(space_state, SceneStringNames::get_singleton()->Master); + ERR_FAIL_NULL_V(space_state, SceneStringName(Master)); PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS]; PhysicsDirectSpaceState2D::PointParameters point_params; @@ -196,6 +195,7 @@ Ref<AudioStream> AudioStreamPlayer2D::get_stream() const { } void AudioStreamPlayer2D::set_volume_db(float p_volume) { + ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN."); internal->volume_db = p_volume; } diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 722858b674..18ef2d8505 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -54,11 +54,18 @@ void Camera2D::_update_scroll() { if (is_current()) { ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id)); - Transform2D xform = get_camera_transform(); + Size2 screen_size = _get_camera_screen_size(); + + Transform2D xform; + if (is_physics_interpolated_and_enabled()) { + xform = _interpolation_data.xform_prev.interpolate_with(_interpolation_data.xform_curr, Engine::get_singleton()->get_physics_interpolation_fraction()); + camera_screen_center = xform.affine_inverse().xform(0.5 * screen_size); + } else { + xform = get_camera_transform(); + } viewport->set_canvas_transform(xform); - Size2 screen_size = _get_camera_screen_size(); Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2()); Point2 adj_screen_pos = camera_screen_center - (screen_size * 0.5); @@ -68,15 +75,26 @@ void Camera2D::_update_scroll() { } void Camera2D::_update_process_callback() { - if (_is_editing_in_editor()) { + if (is_physics_interpolated_and_enabled()) { + set_process_internal(is_current()); + set_physics_process_internal(is_current()); + +#ifdef TOOLS_ENABLED + if (process_callback == CAMERA2D_PROCESS_IDLE) { + WARN_PRINT_ONCE("Camera2D overridden to physics process mode due to use of physics interpolation."); + } +#endif + } else if (_is_editing_in_editor()) { set_process_internal(false); set_physics_process_internal(false); - } else if (process_callback == CAMERA2D_PROCESS_IDLE) { - set_process_internal(true); - set_physics_process_internal(false); } else { - set_process_internal(false); - set_physics_process_internal(true); + if (process_callback == CAMERA2D_PROCESS_IDLE) { + set_process_internal(true); + set_physics_process_internal(false); + } else { + set_process_internal(false); + set_physics_process_internal(true); + } } } @@ -161,8 +179,15 @@ Transform2D Camera2D::get_camera_transform() { } } + // FIXME: There is a bug here, introduced before physics interpolation. + // Smoothing occurs rather confusingly during the call to get_camera_transform(). + // It may be called MULTIPLE TIMES on certain frames, + // therefore smoothing is not currently applied only once per frame / tick, + // which will result in some haphazard results. if (position_smoothing_enabled && !_is_editing_in_editor()) { - real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); + bool physics_process = (process_callback == CAMERA2D_PROCESS_PHYSICS) || is_physics_interpolated_and_enabled(); + real_t delta = physics_process ? get_physics_process_delta_time() : get_process_delta_time(); + real_t c = position_smoothing_speed * delta; smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos; ret_camera_pos = smoothed_camera_pos; //camera_pos=camera_pos*(1.0-position_smoothing_speed)+new_camera_pos*position_smoothing_speed; @@ -223,17 +248,52 @@ Transform2D Camera2D::get_camera_transform() { return xform.affine_inverse(); } +void Camera2D::_ensure_update_interpolation_data() { + // The "curr -> previous" update can either occur + // on NOTIFICATION_INTERNAL_PHYSICS_PROCESS, OR + // on NOTIFICATION_TRANSFORM_CHANGED, + // if NOTIFICATION_TRANSFORM_CHANGED takes place earlier than + // NOTIFICATION_INTERNAL_PHYSICS_PROCESS on a tick. + // This is to ensure that the data keeps flowing, but the new data + // doesn't overwrite before prev has been set. + + // Keep the data flowing. + uint64_t tick = Engine::get_singleton()->get_physics_frames(); + if (_interpolation_data.last_update_physics_tick != tick) { + _interpolation_data.xform_prev = _interpolation_data.xform_curr; + _interpolation_data.last_update_physics_tick = tick; + } +} + void Camera2D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_INTERNAL_PROCESS: - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + case NOTIFICATION_INTERNAL_PROCESS: { _update_scroll(); } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (is_physics_interpolated_and_enabled()) { + _ensure_update_interpolation_data(); + _interpolation_data.xform_curr = get_camera_transform(); + } else { + _update_scroll(); + } + } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + // Force the limits etc. to update. + _interpolation_data.xform_curr = get_camera_transform(); + _interpolation_data.xform_prev = _interpolation_data.xform_curr; + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { - if (!position_smoothing_enabled || _is_editing_in_editor()) { + if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) { _update_scroll(); } + if (is_physics_interpolated_and_enabled()) { + _ensure_update_interpolation_data(); + _interpolation_data.xform_curr = get_camera_transform(); + } } break; case NOTIFICATION_ENTER_TREE: { @@ -260,6 +320,15 @@ void Camera2D::_notification(int p_what) { _update_process_callback(); first = true; _update_scroll(); + + // Note that NOTIFICATION_RESET_PHYSICS_INTERPOLATION + // is automatically called before this because Camera2D is inherited + // from CanvasItem. However, the camera transform is not up to date + // until this point, so we do an extra manual reset. + if (is_physics_interpolated_and_enabled()) { + _interpolation_data.xform_curr = get_camera_transform(); + _interpolation_data.xform_prev = _interpolation_data.xform_curr; + } } break; case NOTIFICATION_EXIT_TREE: { @@ -431,12 +500,17 @@ void Camera2D::_make_current(Object *p_which) { queue_redraw(); - if (p_which == this) { + bool was_current = viewport->get_camera_2d() == this; + bool is_current = p_which == this; + + if (is_current) { viewport->_camera_2d_set(this); - } else { - if (viewport->get_camera_2d() == this) { - viewport->_camera_2d_set(nullptr); - } + } else if (was_current) { + viewport->_camera_2d_set(nullptr); + } + + if (is_current != was_current) { + _update_process_callback(); } } @@ -456,6 +530,7 @@ void Camera2D::make_current() { _make_current(this); } _update_scroll(); + _update_process_callback(); } void Camera2D::clear_current() { @@ -468,6 +543,8 @@ void Camera2D::clear_current() { if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) { viewport->assign_next_enabled_camera_2d(group_name); } + + _update_process_callback(); } bool Camera2D::is_current() const { @@ -552,7 +629,7 @@ void Camera2D::align() { } void Camera2D::set_position_smoothing_speed(real_t p_speed) { - position_smoothing_speed = p_speed; + position_smoothing_speed = MAX(0, p_speed); _update_process_internal_for_smoothing(); } @@ -561,7 +638,7 @@ real_t Camera2D::get_position_smoothing_speed() const { } void Camera2D::set_rotation_smoothing_speed(real_t p_speed) { - rotation_smoothing_speed = p_speed; + rotation_smoothing_speed = MAX(0, p_speed); _update_process_internal_for_smoothing(); } @@ -802,7 +879,7 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_margin_drawing_enabled"), &Camera2D::is_margin_drawing_enabled); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed TopLeft,Drag Center"), "set_anchor_mode", "get_anchor_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed Top Left,Drag Center"), "set_anchor_mode", "get_anchor_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_rotation"), "set_ignore_rotation", "is_ignoring_rotation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom", PROPERTY_HINT_LINK), "set_zoom", "get_zoom"); diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 5693d05ee5..bf25267aa8 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -102,6 +102,14 @@ protected: Camera2DProcessCallback process_callback = CAMERA2D_PROCESS_IDLE; + struct InterpolationData { + Transform2D xform_curr; + Transform2D xform_prev; + uint32_t last_update_physics_tick = 0; + } _interpolation_data; + + void _ensure_update_interpolation_data(); + Size2 _get_camera_screen_size() const; protected: diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index e04e6d7dce..9c9ba93b41 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -35,7 +35,6 @@ #include "scene/resources/curve_texture.h" #include "scene/resources/gradient_texture.h" #include "scene/resources/particle_process_material.h" -#include "scene/scene_string_names.h" void CPUParticles2D::set_emitting(bool p_emitting) { if (emitting == p_emitting) { @@ -753,10 +752,10 @@ void CPUParticles2D::_particles_process(double p_delta) { p.custom[0] = 0.0; // unused p.custom[1] = 0.0; // phase [0..1] p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); - p.custom[3] = 0.0; + p.custom[3] = (1.0 - Math::randf() * lifetime_randomness); p.transform = Transform2D(); p.time = 0; - p.lifetime = lifetime * (1.0 - Math::randf() * lifetime_randomness); + p.lifetime = lifetime * p.custom[3]; p.base_color = Color(1, 1, 1, 1); switch (emission_shape) { @@ -997,7 +996,7 @@ void CPUParticles2D::_particles_process(double p_delta) { } if (!Math::is_equal_approx(time, 0.0) && active && !should_be_active) { active = false; - emit_signal(SceneStringNames::get_singleton()->finished); + emit_signal(SceneStringName(finished)); } } diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index bc39513c03..1d3f1ceada 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -35,7 +35,6 @@ #include "scene/resources/curve_texture.h" #include "scene/resources/gradient_texture.h" #include "scene/resources/particle_process_material.h" -#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED #include "core/config/engine.h" @@ -731,7 +730,7 @@ void GPUParticles2D::_notification(int p_what) { } if (time > active_time) { if (active && !signal_canceled) { - emit_signal(SceneStringNames::get_singleton()->finished); + emit_signal(SceneStringName(finished)); } active = false; if (!emitting) { diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index c03786caef..5ce26b3ed4 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -197,9 +197,13 @@ Light2D::BlendMode Light2D::get_blend_mode() const { return blend_mode; } +void Light2D::_physics_interpolated_changed() { + RenderingServer::get_singleton()->canvas_light_set_interpolated(canvas_light, is_physics_interpolated()); +} + void Light2D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { + case NOTIFICATION_ENTER_CANVAS: { RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, get_canvas()); _update_light_visibility(); } break; @@ -212,7 +216,18 @@ void Light2D::_notification(int p_what) { _update_light_visibility(); } break; - case NOTIFICATION_EXIT_TREE: { + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_visible_in_tree() && is_physics_interpolated()) { + // Explicitly make sure the transform is up to date in RenderingServer before + // resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED + // is normally deferred, and a client change to transform will not always be sent + // before the reset, so we need to guarantee this. + RS::get_singleton()->canvas_light_set_transform(canvas_light, get_global_transform()); + RS::get_singleton()->canvas_light_reset_physics_interpolation(canvas_light); + } + } break; + + case NOTIFICATION_EXIT_CANVAS: { RS::get_singleton()->canvas_light_attach_to_canvas(canvas_light, RID()); _update_light_visibility(); } break; diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 3c1171deae..8a0c2a2a92 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -74,6 +74,7 @@ private: void _update_light_visibility(); virtual void owner_changed_notify() override; + virtual void _physics_interpolated_changed() override; protected: _FORCE_INLINE_ RID _get_light() const { return canvas_light; } diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 61f5d5cd46..092c987ac0 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -158,6 +158,10 @@ void LightOccluder2D::_poly_changed() { #endif } +void LightOccluder2D::_physics_interpolated_changed() { + RenderingServer::get_singleton()->canvas_light_occluder_set_interpolated(occluder, is_physics_interpolated()); +} + void LightOccluder2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_CANVAS: { @@ -199,6 +203,17 @@ void LightOccluder2D::_notification(int p_what) { case NOTIFICATION_EXIT_CANVAS: { RS::get_singleton()->canvas_light_occluder_attach_to_canvas(occluder, RID()); } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_visible_in_tree() && is_physics_interpolated()) { + // Explicitly make sure the transform is up to date in RenderingServer before + // resetting. This is necessary because NOTIFICATION_TRANSFORM_CHANGED + // is normally deferred, and a client change to transform will not always be sent + // before the reset, so we need to guarantee this. + RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform()); + RS::get_singleton()->canvas_light_occluder_reset_physics_interpolation(occluder); + } + } break; } } diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index dd3130394e..4c499d0465 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -86,6 +86,8 @@ class LightOccluder2D : public Node2D { bool sdf_collision = false; void _poly_changed(); + virtual void _physics_interpolated_changed() override; + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index e898dc7fab..f8a8aa487c 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -353,7 +353,20 @@ void LineBuilder::build() { } else if (current_joint_mode == Line2D::LINE_JOINT_ROUND && !(wrap_around && i == segments_count)) { Vector2 vbegin = cbegin - pos1; Vector2 vend = cend - pos1; - strip_add_arc(pos1, vbegin.angle_to(vend), orientation); + // We want to use vbegin.angle_to(vend) below, which evaluates to + // Math::atan2(vbegin.cross(vend), vbegin.dot(vend)) but we need to + // calculate this ourselves as we need to check if the cross product + // in that calculation ends up being -0.f and flip it if so, effectively + // flipping the resulting angle_delta to not return -PI but +PI instead + float cross_product = vbegin.cross(vend); + float dot_product = vbegin.dot(vend); + // Note that we're comparing against -0.f for clarity but 0.f would + // match as well, therefore we need the explicit signbit check too. + if (cross_product == -0.f && signbit(cross_product)) { + cross_product = 0.f; + } + float angle_delta = Math::atan2(cross_product, dot_product); + strip_add_arc(pos1, angle_delta, orientation); } if (!is_intersecting) { diff --git a/scene/2d/marker_2d.cpp b/scene/2d/marker_2d.cpp index 39026591b0..b1b9705bf8 100644 --- a/scene/2d/marker_2d.cpp +++ b/scene/2d/marker_2d.cpp @@ -48,7 +48,7 @@ void Marker2D::_draw_cross() { // Use a darkened axis color for the negative axis. // This makes it possible to see in which direction the Marker3D node is rotated // (which can be important depending on how it's used). - // Axis colors are taken from `axis_x_color` and `axis_y_color` (defined in `editor/editor_themes.cpp`). + // Axis colors are taken from `axis_x_color` and `axis_y_color` (defined in `editor/themes/editor_theme_manager.cpp`). const Color color_x = Color(0.96, 0.20, 0.32); const Color color_y = Color(0.53, 0.84, 0.01); PackedColorArray colors = { diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp index 4fc375ff8d..ae45d431fe 100644 --- a/scene/2d/mesh_instance_2d.cpp +++ b/scene/2d/mesh_instance_2d.cpp @@ -30,8 +30,6 @@ #include "mesh_instance_2d.h" -#include "scene/scene_string_names.h" - void MeshInstance2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { @@ -70,7 +68,7 @@ void MeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) { } texture = p_texture; queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->texture_changed); + emit_signal(SceneStringName(texture_changed)); } Ref<Texture2D> MeshInstance2D::get_texture() const { diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp index 9631b2cc4e..417e628517 100644 --- a/scene/2d/multimesh_instance_2d.cpp +++ b/scene/2d/multimesh_instance_2d.cpp @@ -30,8 +30,6 @@ #include "multimesh_instance_2d.h" -#include "scene/scene_string_names.h" - void MultiMeshInstance2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { @@ -79,7 +77,7 @@ void MultiMeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) { } texture = p_texture; queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->texture_changed); + emit_signal(SceneStringName(texture_changed)); } Ref<Texture2D> MultiMeshInstance2D::get_texture() const { diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index fee774fe2e..9e3e6ea583 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -89,6 +89,12 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_position", "position"), &NavigationAgent2D::set_target_position); ClassDB::bind_method(D_METHOD("get_target_position"), &NavigationAgent2D::get_target_position); + ClassDB::bind_method(D_METHOD("set_simplify_path", "enabled"), &NavigationAgent2D::set_simplify_path); + ClassDB::bind_method(D_METHOD("get_simplify_path"), &NavigationAgent2D::get_simplify_path); + + ClassDB::bind_method(D_METHOD("set_simplify_epsilon", "epsilon"), &NavigationAgent2D::set_simplify_epsilon); + ClassDB::bind_method(D_METHOD("get_simplify_epsilon"), &NavigationAgent2D::get_simplify_epsilon); + ClassDB::bind_method(D_METHOD("get_next_path_position"), &NavigationAgent2D::get_next_path_position); ClassDB::bind_method(D_METHOD("set_velocity_forced", "velocity"), &NavigationAgent2D::set_velocity_forced); @@ -129,6 +135,8 @@ void NavigationAgent2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "pathfinding_algorithm", PROPERTY_HINT_ENUM, "AStar"), "set_pathfinding_algorithm", "get_pathfinding_algorithm"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_postprocessing", PROPERTY_HINT_ENUM, "Corridorfunnel,Edgecentered"), "set_path_postprocessing", "get_path_postprocessing"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_metadata_flags", PROPERTY_HINT_FLAGS, "Include Types,Include RIDs,Include Owners"), "set_path_metadata_flags", "get_path_metadata_flags"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simplify_path"), "set_simplify_path", "get_simplify_path"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "simplify_epsilon", PROPERTY_HINT_RANGE, "0.0,10.0,0.001,or_greater,suffix:px"), "set_simplify_epsilon", "get_simplify_epsilon"); ADD_GROUP("Avoidance", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); @@ -427,6 +435,24 @@ void NavigationAgent2D::set_path_postprocessing(const NavigationPathQueryParamet navigation_query->set_path_postprocessing(path_postprocessing); } +void NavigationAgent2D::set_simplify_path(bool p_enabled) { + simplify_path = p_enabled; + navigation_query->set_simplify_path(simplify_path); +} + +bool NavigationAgent2D::get_simplify_path() const { + return simplify_path; +} + +void NavigationAgent2D::set_simplify_epsilon(real_t p_epsilon) { + simplify_epsilon = MAX(0.0, p_epsilon); + navigation_query->set_simplify_epsilon(simplify_epsilon); +} + +real_t NavigationAgent2D::get_simplify_epsilon() const { + return simplify_epsilon; +} + void NavigationAgent2D::set_path_metadata_flags(BitField<NavigationPathQueryParameters2D::PathMetadataFlags> p_path_metadata_flags) { if (path_metadata_flags == p_path_metadata_flags) { return; @@ -770,7 +796,7 @@ void NavigationAgent2D::_trigger_waypoint_reached() { Dictionary details; const Vector2 waypoint = navigation_path[navigation_path_index]; - details[SNAME("position")] = waypoint; + details[CoreStringName(position)] = waypoint; int waypoint_type = -1; if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) { diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index 0e46086a81..0acfc82162 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -63,6 +63,8 @@ class NavigationAgent2D : public Node { real_t time_horizon_obstacles = 0.0; real_t max_speed = 100.0; real_t path_max_distance = 100.0; + bool simplify_path = false; + real_t simplify_epsilon = 0.0; Vector2 target_position; @@ -179,6 +181,12 @@ public: void set_target_position(Vector2 p_position); Vector2 get_target_position() const; + void set_simplify_path(bool p_enabled); + bool get_simplify_path() const; + + void set_simplify_epsilon(real_t p_epsilon); + real_t get_simplify_epsilon() const; + Vector2 get_next_path_position(); Ref<NavigationPathQueryResult2D> get_current_navigation_result() const { return navigation_result; } diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 07a3910720..3bf90249f8 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -55,14 +55,24 @@ void NavigationObstacle2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle2D::set_avoidance_layers); ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle2D::get_avoidance_layers); + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle2D::set_avoidance_layer_value); ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle2D::get_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle2D::set_affect_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle2D::get_affect_navigation_mesh); + + ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle2D::set_carve_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle2D::get_carve_navigation_mesh); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_GROUP("NavigationMesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh"); ADD_GROUP("Avoidance", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); } @@ -277,6 +287,22 @@ void NavigationObstacle2D::set_velocity(const Vector2 p_velocity) { velocity_submitted = true; } +void NavigationObstacle2D::set_affect_navigation_mesh(bool p_enabled) { + affect_navigation_mesh = p_enabled; +} + +bool NavigationObstacle2D::get_affect_navigation_mesh() const { + return affect_navigation_mesh; +} + +void NavigationObstacle2D::set_carve_navigation_mesh(bool p_enabled) { + carve_navigation_mesh = p_enabled; +} + +bool NavigationObstacle2D::get_carve_navigation_mesh() const { + return carve_navigation_mesh; +} + void NavigationObstacle2D::_update_map(RID p_map) { map_current = p_map; NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map); diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index f9d0e27714..30328f7086 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -54,6 +54,9 @@ class NavigationObstacle2D : public Node2D { Vector2 previous_velocity; bool velocity_submitted = false; + bool affect_navigation_mesh = false; + bool carve_navigation_mesh = false; + #ifdef DEBUG_ENABLED private: RID debug_canvas_item; @@ -97,6 +100,12 @@ public: void _avoidance_done(Vector3 p_new_velocity); // Dummy + void set_affect_navigation_mesh(bool p_enabled); + bool get_affect_navigation_mesh() const; + + void set_carve_navigation_mesh(bool p_enabled); + bool get_carve_navigation_mesh() const; + private: void _update_map(RID p_map); void _update_position(const Vector2 p_position); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 5510b59903..bad9de5daa 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -31,7 +31,6 @@ #include "navigation_region_2d.h" #include "core/math/geometry_2d.h" -#include "scene/2d/navigation_obstacle_2d.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" @@ -163,8 +162,19 @@ void NavigationRegion2D::_notification(int p_what) { set_physics_process_internal(true); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { +#ifdef DEBUG_ENABLED + if (debug_instance_rid.is_valid()) { + RS::get_singleton()->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree()); + } +#endif // DEBUG_ENABLED + } break; + case NOTIFICATION_EXIT_TREE: { _region_exit_navigation_map(); +#ifdef DEBUG_ENABLED + _free_debug(); +#endif // DEBUG_ENABLED } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -190,6 +200,9 @@ void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_ } navigation_polygon = p_navigation_polygon; +#ifdef DEBUG_ENABLED + debug_mesh_dirty = true; +#endif // DEBUG_ENABLED NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, p_navigation_polygon); if (navigation_polygon.is_valid()) { @@ -212,11 +225,6 @@ void NavigationRegion2D::set_navigation_map(RID p_navigation_map) { map_override = p_navigation_map; NavigationServer2D::get_singleton()->region_set_map(region, map_override); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], map_override); - } - } } RID NavigationRegion2D::get_navigation_map() const { @@ -265,7 +273,6 @@ void NavigationRegion2D::_navigation_polygon_changed() { if (navigation_polygon.is_valid()) { NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon); } - _update_avoidance_constrain(); } #ifdef DEBUG_ENABLED @@ -317,13 +324,6 @@ void NavigationRegion2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion2D::set_navigation_layer_value); ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion2D::get_navigation_layer_value); - ClassDB::bind_method(D_METHOD("set_constrain_avoidance", "enabled"), &NavigationRegion2D::set_constrain_avoidance); - ClassDB::bind_method(D_METHOD("get_constrain_avoidance"), &NavigationRegion2D::get_constrain_avoidance); - ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationRegion2D::set_avoidance_layers); - ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationRegion2D::get_avoidance_layers); - ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationRegion2D::set_avoidance_layer_value); - ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationRegion2D::get_avoidance_layer_value); - ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion2D::get_region_rid); ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion2D::set_enter_cost); @@ -343,8 +343,6 @@ void NavigationRegion2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constrain_avoidance"), "set_constrain_avoidance", "get_constrain_avoidance"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); ADD_SIGNAL(MethodInfo("navigation_polygon_changed")); ADD_SIGNAL(MethodInfo("bake_finished")); @@ -391,131 +389,12 @@ NavigationRegion2D::~NavigationRegion2D() { ERR_FAIL_NULL(NavigationServer2D::get_singleton()); NavigationServer2D::get_singleton()->free(region); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]); - } - } - constrain_avoidance_obstacles.clear(); - #ifdef DEBUG_ENABLED NavigationServer2D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed)); NavigationServer2D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed)); #endif // DEBUG_ENABLED } -void NavigationRegion2D::_update_avoidance_constrain() { - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]); - constrain_avoidance_obstacles[i] = RID(); - } - } - constrain_avoidance_obstacles.clear(); - - if (!constrain_avoidance) { - return; - } - - if (get_navigation_polygon() == nullptr) { - return; - } - - Ref<NavigationPolygon> _navpoly = get_navigation_polygon(); - int _outline_count = _navpoly->get_outline_count(); - if (_outline_count == 0) { - return; - } - - for (int outline_index(0); outline_index < _outline_count; outline_index++) { - const Vector<Vector2> &_outline = _navpoly->get_outline(outline_index); - - const int outline_size = _outline.size(); - if (outline_size < 3) { - ERR_FAIL_COND_MSG(_outline.size() < 3, "NavigationPolygon outline needs to have at least 3 vertex to create avoidance obstacles to constrain avoidance agent's"); - continue; - } - - RID obstacle_rid = NavigationServer2D::get_singleton()->obstacle_create(); - constrain_avoidance_obstacles.push_back(obstacle_rid); - - Vector<Vector2> new_obstacle_outline; - - if (outline_index == 0) { - for (int i(0); i < outline_size; i++) { - new_obstacle_outline.push_back(_outline[outline_size - i - 1]); - } - ERR_FAIL_COND_MSG(Geometry2D::is_polygon_clockwise(_outline), "Outer most outline needs to be clockwise to push avoidance agent inside"); - } else { - for (int i(0); i < outline_size; i++) { - new_obstacle_outline.push_back(_outline[i]); - } - } - new_obstacle_outline.resize(outline_size); - - NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle_rid, new_obstacle_outline); - NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle_rid, avoidance_layers); - if (is_inside_tree()) { - if (map_override.is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(obstacle_rid, map_override); - } else { - NavigationServer2D::get_singleton()->obstacle_set_map(obstacle_rid, get_world_2d()->get_navigation_map()); - } - NavigationServer2D::get_singleton()->obstacle_set_position(obstacle_rid, get_global_position()); - } - } - constrain_avoidance_obstacles.resize(_outline_count); -} - -void NavigationRegion2D::set_constrain_avoidance(bool p_enabled) { - constrain_avoidance = p_enabled; - _update_avoidance_constrain(); - notify_property_list_changed(); -} - -bool NavigationRegion2D::get_constrain_avoidance() const { - return constrain_avoidance; -} - -void NavigationRegion2D::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "avoidance_layers") { - if (!constrain_avoidance) { - p_property.usage = PROPERTY_USAGE_NO_EDITOR; - } - } -} - -void NavigationRegion2D::set_avoidance_layers(uint32_t p_layers) { - avoidance_layers = p_layers; - if (constrain_avoidance_obstacles.size() > 0) { - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(constrain_avoidance_obstacles[i], avoidance_layers); - } - } -} - -uint32_t NavigationRegion2D::get_avoidance_layers() const { - return avoidance_layers; -} - -void NavigationRegion2D::set_avoidance_layer_value(int p_layer_number, bool p_value) { - ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive."); - ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive."); - uint32_t avoidance_layers_new = get_avoidance_layers(); - if (p_value) { - avoidance_layers_new |= 1 << (p_layer_number - 1); - } else { - avoidance_layers_new &= ~(1 << (p_layer_number - 1)); - } - set_avoidance_layers(avoidance_layers_new); -} - -bool NavigationRegion2D::get_avoidance_layer_value(int p_layer_number) const { - ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive."); - ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive."); - return get_avoidance_layers() & (1 << (p_layer_number - 1)); -} - void NavigationRegion2D::_region_enter_navigation_map() { if (!is_inside_tree()) { return; @@ -523,27 +402,12 @@ void NavigationRegion2D::_region_enter_navigation_map() { if (map_override.is_valid()) { NavigationServer2D::get_singleton()->region_set_map(region, map_override); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], map_override); - } - } } else { NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map()); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], get_world_2d()->get_navigation_map()); - } - } } current_global_transform = get_global_transform(); NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position()); - } - } NavigationServer2D::get_singleton()->region_set_enabled(region, enabled); @@ -552,11 +416,6 @@ void NavigationRegion2D::_region_enter_navigation_map() { void NavigationRegion2D::_region_exit_navigation_map() { NavigationServer2D::get_singleton()->region_set_map(region, RID()); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], RID()); - } - } } void NavigationRegion2D::_region_update_transform() { @@ -568,11 +427,6 @@ void NavigationRegion2D::_region_update_transform() { if (current_global_transform != new_global_transform) { current_global_transform = new_global_transform; NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform); - for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) { - if (constrain_avoidance_obstacles[i].is_valid()) { - NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position()); - } - } } queue_redraw(); @@ -580,12 +434,42 @@ void NavigationRegion2D::_region_update_transform() { #ifdef DEBUG_ENABLED void NavigationRegion2D::_update_debug_mesh() { - Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); - if (navigation_polygon_vertices.size() < 3) { + if (!is_inside_tree()) { + _free_debug(); return; } const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); + RenderingServer *rs = RenderingServer::get_singleton(); + + if (!debug_instance_rid.is_valid()) { + debug_instance_rid = rs->canvas_item_create(); + } + if (!debug_mesh_rid.is_valid()) { + debug_mesh_rid = rs->mesh_create(); + } + + const Transform2D region_gt = get_global_transform(); + + rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas()); + rs->canvas_item_set_transform(debug_instance_rid, region_gt); + + if (!debug_mesh_dirty) { + return; + } + + rs->mesh_clear(debug_mesh_rid); + debug_mesh_dirty = false; + + const Vector<Vector2> &vertices = navigation_polygon->get_vertices(); + if (vertices.size() < 3) { + return; + } + + int polygon_count = navigation_polygon->get_polygon_count(); + if (polygon_count == 0) { + return; + } bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color(); bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines(); @@ -598,39 +482,109 @@ void NavigationRegion2D::_update_debug_mesh() { debug_edge_color = ns2d->get_debug_navigation_geometry_edge_disabled_color(); } + int vertex_count = 0; + int line_count = 0; + + for (int i = 0; i < polygon_count; i++) { + const Vector<int> &polygon = navigation_polygon->get_polygon(i); + int polygon_size = polygon.size(); + if (polygon_size < 3) { + continue; + } + line_count += polygon_size * 2; + vertex_count += (polygon_size - 2) * 3; + } + + Vector<Vector2> face_vertex_array; + face_vertex_array.resize(vertex_count); + + Vector<Color> face_color_array; + if (enabled_geometry_face_random_color) { + face_color_array.resize(vertex_count); + } + + Vector<Vector2> line_vertex_array; + if (enabled_edge_lines) { + line_vertex_array.resize(line_count); + } + RandomPCG rand; + Color polygon_color = debug_face_color; + + int face_vertex_index = 0; + int line_vertex_index = 0; - for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { - // An array of vertices for this polygon. - Vector<int> polygon = navigation_polygon->get_polygon(i); - Vector<Vector2> debug_polygon_vertices; - debug_polygon_vertices.resize(polygon.size()); - for (int j = 0; j < polygon.size(); j++) { - ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); - debug_polygon_vertices.write[j] = navigation_polygon_vertices[polygon[j]]; + Vector2 *face_vertex_array_ptrw = face_vertex_array.ptrw(); + Color *face_color_array_ptrw = face_color_array.ptrw(); + Vector2 *line_vertex_array_ptrw = line_vertex_array.ptrw(); + + for (int polygon_index = 0; polygon_index < polygon_count; polygon_index++) { + const Vector<int> &polygon_indices = navigation_polygon->get_polygon(polygon_index); + int polygon_indices_size = polygon_indices.size(); + if (polygon_indices_size < 3) { + continue; } - // Generate the polygon color, slightly randomly modified from the settings one. - Color random_variation_color = debug_face_color; if (enabled_geometry_face_random_color) { - random_variation_color.set_hsv( - debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, - debug_face_color.get_s(), - debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); + // Generate the polygon color, slightly randomly modified from the settings one. + polygon_color.set_hsv(debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, debug_face_color.get_s(), debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); + polygon_color.a = debug_face_color.a; } - random_variation_color.a = debug_face_color.a; - Vector<Color> debug_face_colors; - debug_face_colors.push_back(random_variation_color); - RS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), debug_polygon_vertices, debug_face_colors); + for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size - 2; polygon_indices_index++) { + face_vertex_array_ptrw[face_vertex_index] = vertices[polygon_indices[0]]; + face_vertex_array_ptrw[face_vertex_index + 1] = vertices[polygon_indices[polygon_indices_index + 1]]; + face_vertex_array_ptrw[face_vertex_index + 2] = vertices[polygon_indices[polygon_indices_index + 2]]; + if (enabled_geometry_face_random_color) { + face_color_array_ptrw[face_vertex_index] = polygon_color; + face_color_array_ptrw[face_vertex_index + 1] = polygon_color; + face_color_array_ptrw[face_vertex_index + 2] = polygon_color; + } + face_vertex_index += 3; + } if (enabled_edge_lines) { - Vector<Color> debug_edge_colors; - debug_edge_colors.push_back(debug_edge_color); - debug_polygon_vertices.push_back(debug_polygon_vertices[0]); // Add first again for closing polyline. - RS::get_singleton()->canvas_item_add_polyline(get_canvas_item(), debug_polygon_vertices, debug_edge_colors); + for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size; polygon_indices_index++) { + line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index]]; + line_vertex_index += 1; + if (polygon_indices_index + 1 == polygon_indices_size) { + line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[0]]; + line_vertex_index += 1; + } else { + line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index + 1]]; + line_vertex_index += 1; + } + } } } + + if (!enabled_geometry_face_random_color) { + face_color_array.resize(face_vertex_array.size()); + face_color_array.fill(debug_face_color); + } + + Array face_mesh_array; + face_mesh_array.resize(Mesh::ARRAY_MAX); + face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array; + face_mesh_array[Mesh::ARRAY_COLOR] = face_color_array; + + rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_TRIANGLES, face_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + + if (enabled_edge_lines) { + Vector<Color> line_color_array; + line_color_array.resize(line_vertex_array.size()); + line_color_array.fill(debug_edge_color); + + Array line_mesh_array; + line_mesh_array.resize(Mesh::ARRAY_MAX); + line_mesh_array[Mesh::ARRAY_VERTEX] = line_vertex_array; + line_mesh_array[Mesh::ARRAY_COLOR] = line_color_array; + + rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_LINES, line_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + } + + rs->canvas_item_add_mesh(debug_instance_rid, debug_mesh_rid, Transform2D()); + rs->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree()); } #endif // DEBUG_ENABLED @@ -672,3 +626,19 @@ void NavigationRegion2D::_update_debug_baking_rect() { } } #endif // DEBUG_ENABLED + +#ifdef DEBUG_ENABLED +void NavigationRegion2D::_free_debug() { + RenderingServer *rs = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rs); + if (debug_instance_rid.is_valid()) { + rs->canvas_item_clear(debug_instance_rid); + rs->free(debug_instance_rid); + debug_instance_rid = RID(); + } + if (debug_mesh_rid.is_valid()) { + rs->free(debug_mesh_rid); + debug_mesh_rid = RID(); + } +} +#endif // DEBUG_ENABLED diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 87c2365b15..52101cb93e 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -31,7 +31,7 @@ #ifndef NAVIGATION_REGION_2D_H #define NAVIGATION_REGION_2D_H -#include "scene/resources/navigation_polygon.h" +#include "scene/resources/2d/navigation_polygon.h" class NavigationRegion2D : public Node2D { GDCLASS(NavigationRegion2D, Node2D); @@ -46,16 +46,18 @@ class NavigationRegion2D : public Node2D { real_t travel_cost = 1.0; Ref<NavigationPolygon> navigation_polygon; - bool constrain_avoidance = false; - LocalVector<RID> constrain_avoidance_obstacles; - uint32_t avoidance_layers = 1; - Transform2D current_global_transform; void _navigation_polygon_changed(); #ifdef DEBUG_ENABLED private: + RID debug_mesh_rid; + RID debug_instance_rid; + + bool debug_mesh_dirty = true; + + void _free_debug(); void _update_debug_mesh(); void _update_debug_edge_connections_mesh(); void _update_debug_baking_rect(); @@ -65,7 +67,6 @@ private: protected: void _notification(int p_what); - void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); #ifndef DISABLE_DEPRECATED @@ -106,15 +107,6 @@ public: void set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon); Ref<NavigationPolygon> get_navigation_polygon() const; - void set_constrain_avoidance(bool p_enabled); - bool get_constrain_avoidance() const; - - void set_avoidance_layers(uint32_t p_layers); - uint32_t get_avoidance_layers() const; - - void set_avoidance_layer_value(int p_layer_number, bool p_value); - bool get_avoidance_layer_value(int p_layer_number) const; - PackedStringArray get_configuration_warnings() const override; void bake_navigation_polygon(bool p_on_thread); @@ -125,7 +117,6 @@ public: ~NavigationRegion2D(); private: - void _update_avoidance_constrain(); void _region_enter_navigation_map(); void _region_exit_navigation_map(); void _region_update_transform(); diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp index f516fd41ac..aacab3213d 100644 --- a/scene/2d/parallax_2d.cpp +++ b/scene/2d/parallax_2d.cpp @@ -102,14 +102,14 @@ void Parallax2D::_update_scroll() { scroll_ofs *= scroll_scale; if (repeat_size.x) { - real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x); + real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x * get_scale().x); scroll_ofs.x = screen_offset.x - mod; } else { scroll_ofs.x = screen_offset.x + scroll_offset.x - scroll_ofs.x; } if (repeat_size.y) { - real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y); + real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y * get_scale().y); scroll_ofs.y = screen_offset.y - mod; } else { scroll_ofs.y = screen_offset.y + scroll_offset.y - scroll_ofs.y; @@ -127,8 +127,8 @@ void Parallax2D::_update_repeat() { return; } - Point2 repeat_scale = repeat_size * get_scale(); - RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_scale, repeat_times); + RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_size, repeat_times); + RenderingServer::get_singleton()->canvas_item_set_interpolated(get_canvas_item(), false); } void Parallax2D::set_scroll_scale(const Size2 &p_scale) { @@ -144,7 +144,7 @@ void Parallax2D::set_repeat_size(const Size2 &p_repeat_size) { return; } - repeat_size = p_repeat_size.max(Vector2(0, 0)); + repeat_size = p_repeat_size.maxf(0); _update_process(); _update_repeat(); @@ -287,4 +287,6 @@ void Parallax2D::_bind_methods() { } Parallax2D::Parallax2D() { + // Parallax2D is always updated every frame so there is no need to interpolate. + set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF); } diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 3dd0d7b61c..e2a7e9e154 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -73,6 +73,7 @@ void ParallaxLayer::_update_mirroring() { RID ci = get_canvas_item(); Point2 mirrorScale = mirroring * get_scale(); RenderingServer::get_singleton()->canvas_set_item_mirroring(c, ci, mirrorScale); + RenderingServer::get_singleton()->canvas_item_set_interpolated(ci, false); } } @@ -162,4 +163,6 @@ void ParallaxLayer::_bind_methods() { } ParallaxLayer::ParallaxLayer() { + // ParallaxLayer is always updated every frame so there is no need to interpolate. + set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF); } diff --git a/scene/2d/physics/area_2d.cpp b/scene/2d/physics/area_2d.cpp index b1ff94dda4..305ac8248e 100644 --- a/scene/2d/physics/area_2d.cpp +++ b/scene/2d/physics/area_2d.cpp @@ -30,7 +30,6 @@ #include "area_2d.h" -#include "scene/scene_string_names.h" #include "servers/audio_server.h" void Area2D::set_gravity_space_override_mode(SpaceOverride p_mode) { @@ -142,9 +141,9 @@ void Area2D::_body_enter_tree(ObjectID p_id) { ERR_FAIL_COND(E->value.in_tree); E->value.in_tree = true; - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); + emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -156,9 +155,9 @@ void Area2D::_body_exit_tree(ObjectID p_id) { ERR_FAIL_COND(!E); ERR_FAIL_COND(!E->value.in_tree); E->value.in_tree = false; - emit_signal(SceneStringNames::get_singleton()->body_exited, node); + emit_signal(SceneStringName(body_exited), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); + emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -172,9 +171,9 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i lock_callback(); locked = true; if (body_in) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, (Node *)nullptr, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_entered), p_body, (Node *)nullptr, p_body_shape, p_area_shape); } else { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, (Node *)nullptr, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_exited), p_body, (Node *)nullptr, p_body_shape, p_area_shape); } locked = false; unlock_callback(); @@ -200,10 +199,10 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i E->value.rc = 0; E->value.in_tree = node && node->is_inside_tree(); if (node) { - node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree).bind(objid)); - node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree).bind(objid)); + node->connect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_body_enter_tree).bind(objid)); + node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_body_exit_tree).bind(objid)); if (E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); } } } @@ -213,7 +212,7 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i } if (!node || E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_area_shape); } } else { @@ -227,15 +226,15 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i if (E->value.rc == 0) { body_map.remove(E); if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_body_exit_tree)); if (in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_exited, obj); + emit_signal(SceneStringName(body_exited), obj); } } } if (!node || in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_exited), p_body, obj, p_body_shape, p_area_shape); } } @@ -253,9 +252,9 @@ void Area2D::_area_enter_tree(ObjectID p_id) { ERR_FAIL_COND(E->value.in_tree); E->value.in_tree = true; - emit_signal(SceneStringNames::get_singleton()->area_entered, node); + emit_signal(SceneStringName(area_entered), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); + emit_signal(SceneStringName(area_shape_entered), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -267,9 +266,9 @@ void Area2D::_area_exit_tree(ObjectID p_id) { ERR_FAIL_COND(!E); ERR_FAIL_COND(!E->value.in_tree); E->value.in_tree = false; - emit_signal(SceneStringNames::get_singleton()->area_exited, node); + emit_signal(SceneStringName(area_exited), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); + emit_signal(SceneStringName(area_shape_exited), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -283,9 +282,9 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i lock_callback(); locked = true; if (area_in) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, (Node *)nullptr, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_entered), p_area, (Node *)nullptr, p_area_shape, p_self_shape); } else { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, (Node *)nullptr, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_exited), p_area, (Node *)nullptr, p_area_shape, p_self_shape); } locked = false; unlock_callback(); @@ -311,10 +310,10 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i E->value.rc = 0; E->value.in_tree = node && node->is_inside_tree(); if (node) { - node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree).bind(objid)); - node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree).bind(objid)); + node->connect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_area_enter_tree).bind(objid)); + node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_area_exit_tree).bind(objid)); if (E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_entered, node); + emit_signal(SceneStringName(area_entered), node); } } } @@ -324,7 +323,7 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i } if (!node || E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, node, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_entered), p_area, node, p_area_shape, p_self_shape); } } else { @@ -338,15 +337,15 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i if (E->value.rc == 0) { area_map.remove(E); if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_area_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_area_exit_tree)); if (in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_exited, obj); + emit_signal(SceneStringName(area_exited), obj); } } } if (!node || in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, obj, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_exited), p_area, obj, p_area_shape, p_self_shape); } } @@ -370,18 +369,18 @@ void Area2D::_clear_monitoring() { continue; } - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_body_exit_tree)); if (!E.value.in_tree) { continue; } for (int i = 0; i < E.value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape); + emit_signal(SceneStringName(body_shape_exited), E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape); } - emit_signal(SceneStringNames::get_singleton()->body_exited, obj); + emit_signal(SceneStringName(body_exited), obj); } } @@ -398,18 +397,18 @@ void Area2D::_clear_monitoring() { continue; } - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_area_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_area_exit_tree)); if (!E.value.in_tree) { continue; } for (int i = 0; i < E.value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape); + emit_signal(SceneStringName(area_shape_exited), E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape); } - emit_signal(SceneStringNames::get_singleton()->area_exited, obj); + emit_signal(SceneStringName(area_exited), obj); } } } @@ -538,7 +537,7 @@ StringName Area2D::get_audio_bus_name() const { return audio_bus; } } - return SceneStringNames::get_singleton()->Master; + return SceneStringName(Master); } void Area2D::_validate_property(PropertyInfo &p_property) const { diff --git a/scene/2d/physics/character_body_2d.cpp b/scene/2d/physics/character_body_2d.cpp index e5d575a159..a503f3cb78 100644 --- a/scene/2d/physics/character_body_2d.cpp +++ b/scene/2d/physics/character_body_2d.cpp @@ -501,7 +501,7 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { // Create a new instance when the cached reference is invalid or still in use in script. if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->get_reference_count() > 1) { slide_colliders.write[p_bounce].instantiate(); - slide_colliders.write[p_bounce]->owner = this; + slide_colliders.write[p_bounce]->owner_id = get_instance_id(); } slide_colliders.write[p_bounce]->result = motion_results[p_bounce]; @@ -745,11 +745,3 @@ void CharacterBody2D::_bind_methods() { CharacterBody2D::CharacterBody2D() : PhysicsBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) { } - -CharacterBody2D::~CharacterBody2D() { - for (int i = 0; i < slide_colliders.size(); i++) { - if (slide_colliders[i].is_valid()) { - slide_colliders.write[i]->owner = nullptr; - } - } -} diff --git a/scene/2d/physics/character_body_2d.h b/scene/2d/physics/character_body_2d.h index 395438a1f1..536d0a1ebd 100644 --- a/scene/2d/physics/character_body_2d.h +++ b/scene/2d/physics/character_body_2d.h @@ -111,7 +111,6 @@ public: PlatformOnLeave get_platform_on_leave() const; CharacterBody2D(); - ~CharacterBody2D(); private: real_t margin = 0.08; diff --git a/scene/2d/physics/collision_object_2d.cpp b/scene/2d/physics/collision_object_2d.cpp index 4e5852984b..00b6085f0c 100644 --- a/scene/2d/physics/collision_object_2d.cpp +++ b/scene/2d/physics/collision_object_2d.cpp @@ -31,7 +31,6 @@ #include "collision_object_2d.h" #include "scene/resources/world_2d.h" -#include "scene/scene_string_names.h" void CollisionObject2D::_notification(int p_what) { switch (p_what) { @@ -519,27 +518,27 @@ bool CollisionObject2D::is_pickable() const { void CollisionObject2D::_input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) { GDVIRTUAL_CALL(_input_event, p_viewport, p_input_event, p_shape); - emit_signal(SceneStringNames::get_singleton()->input_event, p_viewport, p_input_event, p_shape); + emit_signal(SceneStringName(input_event), p_viewport, p_input_event, p_shape); } void CollisionObject2D::_mouse_enter() { GDVIRTUAL_CALL(_mouse_enter); - emit_signal(SceneStringNames::get_singleton()->mouse_entered); + emit_signal(SceneStringName(mouse_entered)); } void CollisionObject2D::_mouse_exit() { GDVIRTUAL_CALL(_mouse_exit); - emit_signal(SceneStringNames::get_singleton()->mouse_exited); + emit_signal(SceneStringName(mouse_exited)); } void CollisionObject2D::_mouse_shape_enter(int p_shape) { GDVIRTUAL_CALL(_mouse_shape_enter, p_shape); - emit_signal(SceneStringNames::get_singleton()->mouse_shape_entered, p_shape); + emit_signal(SceneStringName(mouse_shape_entered), p_shape); } void CollisionObject2D::_mouse_shape_exit(int p_shape) { GDVIRTUAL_CALL(_mouse_shape_exit, p_shape); - emit_signal(SceneStringNames::get_singleton()->mouse_shape_exited, p_shape); + emit_signal(SceneStringName(mouse_shape_exited), p_shape); } void CollisionObject2D::set_only_update_transform_changes(bool p_enable) { diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp index 96ef346d23..a9b47ef4d4 100644 --- a/scene/2d/physics/collision_polygon_2d.cpp +++ b/scene/2d/physics/collision_polygon_2d.cpp @@ -132,20 +132,19 @@ void CollisionPolygon2D::_notification(int p_what) { } if (polygon.size() > 2) { -#define DEBUG_DECOMPOSE -#if defined(TOOLS_ENABLED) && defined(DEBUG_DECOMPOSE) - Vector<Vector<Vector2>> decomp = _decompose_in_convex(); - - Color c(0.4, 0.9, 0.1); - for (int i = 0; i < decomp.size(); i++) { - c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5); - draw_colored_polygon(decomp[i], c); +#ifdef TOOLS_ENABLED + if (build_mode == BUILD_SOLIDS) { + Vector<Vector<Vector2>> decomp = _decompose_in_convex(); + + Color c(0.4, 0.9, 0.1); + for (int i = 0; i < decomp.size(); i++) { + c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5); + draw_colored_polygon(decomp[i], c); + } } -#else - draw_colored_polygon(polygon, get_tree()->get_debug_collisions_color()); #endif - const Color stroke_color = Color(0.9, 0.2, 0.0); + const Color stroke_color = get_tree()->get_debug_collisions_color(); draw_polyline(polygon, stroke_color); // Draw the last segment. draw_line(polygon[polygon.size() - 1], polygon[0], stroke_color); diff --git a/scene/2d/physics/joints/joint_2d.cpp b/scene/2d/physics/joints/joint_2d.cpp index dd1697a29c..a32bcbae78 100644 --- a/scene/2d/physics/joints/joint_2d.cpp +++ b/scene/2d/physics/joints/joint_2d.cpp @@ -31,19 +31,18 @@ #include "joint_2d.h" #include "scene/2d/physics/physics_body_2d.h" -#include "scene/scene_string_names.h" void Joint2D::_disconnect_signals() { Node *node_a = get_node_or_null(a); PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a); if (body_a) { - body_a->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); + body_a->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree)); } Node *node_b = get_node_or_null(b); PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b); if (body_b) { - body_b->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); + body_b->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree)); } } @@ -117,8 +116,12 @@ void Joint2D::_update_joint(bool p_only_free) { ba = body_a->get_rid(); bb = body_b->get_rid(); - body_a->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); - body_b->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree)); + if (!body_a->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree))) { + body_a->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree)); + } + if (!body_b->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree))) { + body_b->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree)); + } PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision); } @@ -136,7 +139,7 @@ void Joint2D::set_node_a(const NodePath &p_node_a) { if (Engine::get_singleton()->is_editor_hint()) { // When in editor, the setter may be called as a result of node rename. // It happens before the node actually changes its name, which triggers false warning. - callable_mp(this, &Joint2D::_update_joint).call_deferred(); + callable_mp(this, &Joint2D::_update_joint).call_deferred(false); } else { _update_joint(); } @@ -157,7 +160,7 @@ void Joint2D::set_node_b(const NodePath &p_node_b) { b = p_node_b; if (Engine::get_singleton()->is_editor_hint()) { - callable_mp(this, &Joint2D::_update_joint).call_deferred(); + callable_mp(this, &Joint2D::_update_joint).call_deferred(false); } else { _update_joint(); } diff --git a/scene/2d/physics/kinematic_collision_2d.cpp b/scene/2d/physics/kinematic_collision_2d.cpp index 7e7c33b259..18b0254769 100644 --- a/scene/2d/physics/kinematic_collision_2d.cpp +++ b/scene/2d/physics/kinematic_collision_2d.cpp @@ -59,6 +59,7 @@ real_t KinematicCollision2D::get_depth() const { } Object *KinematicCollision2D::get_local_shape() const { + PhysicsBody2D *owner = Object::cast_to<PhysicsBody2D>(ObjectDB::get_instance(owner_id)); if (!owner) { return nullptr; } diff --git a/scene/2d/physics/kinematic_collision_2d.h b/scene/2d/physics/kinematic_collision_2d.h index 0d187b87a5..8d3d6ca8c1 100644 --- a/scene/2d/physics/kinematic_collision_2d.h +++ b/scene/2d/physics/kinematic_collision_2d.h @@ -40,7 +40,7 @@ class PhysicsBody2D; class KinematicCollision2D : public RefCounted { GDCLASS(KinematicCollision2D, RefCounted); - PhysicsBody2D *owner = nullptr; + ObjectID owner_id; friend class PhysicsBody2D; friend class CharacterBody2D; PhysicsServer2D::MotionResult result; diff --git a/scene/2d/physics/physics_body_2d.cpp b/scene/2d/physics/physics_body_2d.cpp index 81120d0b01..fc14e6ed62 100644 --- a/scene/2d/physics/physics_body_2d.cpp +++ b/scene/2d/physics/physics_body_2d.cpp @@ -30,8 +30,6 @@ #include "physics_body_2d.h" -#include "scene/scene_string_names.h" - void PhysicsBody2D::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_collide", "motion", "test_only", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08), DEFVAL(false)); ClassDB::bind_method(D_METHOD("test_move", "from", "motion", "collision", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08), DEFVAL(false)); @@ -48,12 +46,6 @@ PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) : set_pickable(false); } -PhysicsBody2D::~PhysicsBody2D() { - if (motion_cache.is_valid()) { - motion_cache->owner = nullptr; - } -} - Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin, bool p_recovery_as_collision) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), p_motion, p_margin); parameters.recovery_as_collision = p_recovery_as_collision; @@ -64,7 +56,7 @@ Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_t // Create a new instance when the cached reference is invalid or still in use in script. if (motion_cache.is_null() || motion_cache->get_reference_count() > 1) { motion_cache.instantiate(); - motion_cache->owner = this; + motion_cache->owner_id = get_instance_id(); } motion_cache->result = result; diff --git a/scene/2d/physics/physics_body_2d.h b/scene/2d/physics/physics_body_2d.h index 43bc479881..d44eebabee 100644 --- a/scene/2d/physics/physics_body_2d.h +++ b/scene/2d/physics/physics_body_2d.h @@ -56,8 +56,6 @@ public: TypedArray<PhysicsBody2D> get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody void remove_collision_exception_with(Node *p_node); - - virtual ~PhysicsBody2D(); }; #endif // PHYSICS_BODY_2D_H diff --git a/scene/2d/physics/rigid_body_2d.cpp b/scene/2d/physics/rigid_body_2d.cpp index 5e05c563a4..402e5c8b95 100644 --- a/scene/2d/physics/rigid_body_2d.cpp +++ b/scene/2d/physics/rigid_body_2d.cpp @@ -30,8 +30,6 @@ #include "rigid_body_2d.h" -#include "scene/scene_string_names.h" - void RigidBody2D::_body_enter_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); @@ -44,10 +42,10 @@ void RigidBody2D::_body_enter_tree(ObjectID p_id) { contact_monitor->locked = true; E->value.in_scene = true; - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); + emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -65,10 +63,10 @@ void RigidBody2D::_body_exit_tree(ObjectID p_id) { contact_monitor->locked = true; - emit_signal(SceneStringNames::get_singleton()->body_exited, node); + emit_signal(SceneStringName(body_exited), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); + emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -93,10 +91,10 @@ void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan //E->value.rc=0; E->value.in_scene = node && node->is_inside_tree(); if (node) { - node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody2D::_body_enter_tree).bind(objid)); - node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody2D::_body_exit_tree).bind(objid)); + node->connect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree).bind(objid)); + node->connect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree).bind(objid)); if (E->value.in_scene) { - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); } } @@ -108,7 +106,7 @@ void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan } if (E->value.in_scene) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_local_shape); + emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_local_shape); } } else { @@ -122,17 +120,17 @@ void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan if (E->value.shapes.is_empty()) { if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody2D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody2D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree)); if (in_scene) { - emit_signal(SceneStringNames::get_singleton()->body_exited, node); + emit_signal(SceneStringName(body_exited), node); } } contact_monitor->body_map.remove(E); } if (node && in_scene) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, node, p_body_shape, p_local_shape); + emit_signal(SceneStringName(body_shape_exited), p_body, node, p_body_shape, p_local_shape); } } } @@ -158,7 +156,7 @@ void RigidBody2D::_sync_body_state(PhysicsDirectBodyState2D *p_state) { if (sleeping != p_state->is_sleeping()) { sleeping = p_state->is_sleeping(); - emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); + emit_signal(SceneStringName(sleeping_state_changed)); } } @@ -605,8 +603,8 @@ void RigidBody2D::set_contact_monitor(bool p_enabled) { Node *node = Object::cast_to<Node>(obj); if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody2D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody2D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree)); } } diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 69e0414855..fe21c7f21b 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -635,36 +635,47 @@ Bone2D *Skeleton2D::get_bone(int p_idx) { } void Skeleton2D::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - if (bone_setup_dirty) { - _update_bone_setup(); - } - if (transform_dirty) { - _update_transform(); - } - request_ready(); - } + switch (p_what) { + case NOTIFICATION_READY: { + if (bone_setup_dirty) { + _update_bone_setup(); + } + if (transform_dirty) { + _update_transform(); + } + request_ready(); + } break; - if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); - } else if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (modification_stack.is_valid()) { - execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process); - } - } else if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { - if (modification_stack.is_valid()) { - execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process); - } - } -#ifdef TOOLS_ENABLED - else if (p_what == NOTIFICATION_DRAW) { - if (Engine::get_singleton()->is_editor_hint()) { + case NOTIFICATION_TRANSFORM_CHANGED: { + RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (modification_stack.is_valid()) { - modification_stack->draw_editor_gizmos(); + execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process); } - } - } + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (modification_stack.is_valid()) { + execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process); + } + } break; + + case NOTIFICATION_POST_ENTER_TREE: { + set_modification_stack(modification_stack); + } break; + +#ifdef TOOLS_ENABLED + case NOTIFICATION_DRAW: { + if (Engine::get_singleton()->is_editor_hint()) { + if (modification_stack.is_valid()) { + modification_stack->draw_editor_gizmos(); + } + } + } break; #endif // TOOLS_ENABLED + } } RID Skeleton2D::get_skeleton() const { @@ -692,7 +703,7 @@ void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack set_physics_process_internal(false); } modification_stack = p_stack; - if (modification_stack.is_valid()) { + if (modification_stack.is_valid() && is_inside_tree()) { modification_stack->set_skeleton(this); modification_stack->setup(); diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp index 3c19dd0020..efb5029ac4 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -31,7 +31,6 @@ #include "sprite_2d.h" #include "scene/main/window.h" -#include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED Dictionary Sprite2D::_edit_get_state() const { @@ -146,7 +145,7 @@ void Sprite2D::set_texture(const Ref<Texture2D> &p_texture) { } queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->texture_changed); + emit_signal(SceneStringName(texture_changed)); item_rect_changed(); } @@ -260,7 +259,7 @@ void Sprite2D::set_frame(int p_frame) { frame = p_frame; item_rect_changed(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } int Sprite2D::get_frame() const { @@ -374,8 +373,7 @@ bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const { q.y = texture->get_size().height - q.y - 1; } } else { - q.x = MIN(q.x, texture->get_size().width - 1); - q.y = MIN(q.y, texture->get_size().height - 1); + q = q.min(texture->get_size() - Vector2(1, 1)); } return texture->is_pixel_opaque((int)q.x, (int)q.y); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index f8737730ba..f7d672620d 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -31,8 +31,7 @@ #include "tile_map.h" #include "tile_map.compat.inc" -#include "core/core_string_names.h" -#include "scene/2d/tile_map_layer.h" +#include "core/io/marshalls.h" #include "scene/gui/control.h" #define TILEMAP_CALL_FOR_LAYER(layer, function, ...) \ @@ -49,8 +48,120 @@ ERR_FAIL_INDEX_V(layer, (int)layers.size(), err_value); \ return layers[layer]->function(__VA_ARGS__); +void TileMap::_tile_set_changed() { + update_configuration_warnings(); +} + void TileMap::_emit_changed() { - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); +} + +void TileMap::_set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data) { + ERR_FAIL_INDEX(p_layer, (int)layers.size()); + ERR_FAIL_COND(p_format >= TileMapDataFormat::TILE_MAP_DATA_FORMAT_MAX); +#ifndef DISABLE_DEPRECATED + ERR_FAIL_COND_MSG(p_format != (TileMapDataFormat)(TILE_MAP_DATA_FORMAT_MAX - 1), "Old TileMap data format detected despite DISABLE_DEPRECATED being set compilation time."); +#endif // DISABLE_DEPRECATED + + // Set data for a given tile from raw data. + int c = p_data.size(); + const int *r = p_data.ptr(); + + int offset = (p_format >= TileMapDataFormat::TILE_MAP_DATA_FORMAT_2) ? 3 : 2; + ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %d. Expected modulo: %d", c, offset)); + + layers[p_layer]->clear(); + + for (int i = 0; i < c; i += offset) { + const uint8_t *ptr = (const uint8_t *)&r[i]; + uint8_t local[12]; + for (int j = 0; j < ((p_format >= TileMapDataFormat::TILE_MAP_DATA_FORMAT_2) ? 12 : 8); j++) { + local[j] = ptr[j]; + } + +#ifdef BIG_ENDIAN_ENABLED + SWAP(local[0], local[3]); + SWAP(local[1], local[2]); + SWAP(local[4], local[7]); + SWAP(local[5], local[6]); + //TODO: ask someone to check this... + if (FORMAT >= FORMAT_2) { + SWAP(local[8], local[11]); + SWAP(local[9], local[10]); + } +#endif + // Extracts position in TileMap. + int16_t x = decode_uint16(&local[0]); + int16_t y = decode_uint16(&local[2]); + + if (p_format == TileMapDataFormat::TILE_MAP_DATA_FORMAT_3) { + uint16_t source_id = decode_uint16(&local[4]); + uint16_t atlas_coords_x = decode_uint16(&local[6]); + uint16_t atlas_coords_y = decode_uint16(&local[8]); + uint16_t alternative_tile = decode_uint16(&local[10]); + layers[p_layer]->set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); + } else { +#ifndef DISABLE_DEPRECATED + // Previous decated format. + uint32_t v = decode_uint32(&local[4]); + // Extract the transform flags that used to be in the tilemap. + bool flip_h = v & (1UL << 29); + bool flip_v = v & (1UL << 30); + bool transpose = v & (1UL << 31); + v &= (1UL << 29) - 1; + + // Extract autotile/atlas coords. + int16_t coord_x = 0; + int16_t coord_y = 0; + if (p_format == TileMapDataFormat::TILE_MAP_DATA_FORMAT_2) { + coord_x = decode_uint16(&local[8]); + coord_y = decode_uint16(&local[10]); + } + + if (tile_set.is_valid()) { + Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); + if (a.size() == 3) { + layers[p_layer]->set_cell(Vector2i(x, y), a[0], a[1], a[2]); + } else { + ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose)); + } + } else { + int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); + layers[p_layer]->set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); + } +#endif // DISABLE_DEPRECATED + } + } +} + +Vector<int> TileMap::_get_tile_map_data_using_compatibility_format(int p_layer) const { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), Vector<int>()); + + // Export tile data to raw format. + const HashMap<Vector2i, CellData> tile_map_layer_data = layers[p_layer]->get_tile_map_layer_data(); + Vector<int> tile_data; + tile_data.resize(tile_map_layer_data.size() * 3); + int *w = tile_data.ptrw(); + + // Save in highest format. + + int idx = 0; + for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { + uint8_t *ptr = (uint8_t *)&w[idx]; + encode_uint16((int16_t)(E.key.x), &ptr[0]); + encode_uint16((int16_t)(E.key.y), &ptr[2]); + encode_uint16(E.value.cell.source_id, &ptr[4]); + encode_uint16(E.value.cell.coord_x, &ptr[6]); + encode_uint16(E.value.cell.coord_y, &ptr[8]); + encode_uint16(E.value.cell.alternative_tile, &ptr[10]); + idx += 3; + } + + return tile_data; +} + +void TileMap::_set_layer_tile_data(int p_layer, const PackedInt32Array &p_data) { + _set_tile_map_data_using_compatibility_format(p_layer, format, p_data); } void TileMap::_notification(int p_what) { @@ -114,91 +225,34 @@ int TileMap::get_rendering_quadrant_size() const { return rendering_quadrant_size; } -void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_normalized_animation_offset) { - ERR_FAIL_COND(!p_tile_set.is_valid()); - ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); - ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); - TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Check for the frame. - if (p_frame >= 0) { - ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); - } - - // Get the texture. - Ref<Texture2D> tex = atlas_source->get_runtime_texture(); - if (!tex.is_valid()) { - return; - } - - // Check if we are in the texture, return otherwise. - Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { - return; - } - - // Get tile data. - const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); - - // Get the tile modulation. - Color modulate = tile_data->get_modulate() * p_modulation; - - // Compute the offset. - Vector2 tile_offset = tile_data->get_texture_origin(); - - // Get destination rect. - Rect2 dest_rect; - dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; - dest_rect.size.x += FP_ADJUST; - dest_rect.size.y += FP_ADJUST; +void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { + if (p_tileset == tile_set) { + return; + } - bool transpose = tile_data->get_transpose() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - if (transpose) { - dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); - } else { - dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); - } + // Set the tileset, registering to its changes. + if (tile_set.is_valid()) { + tile_set->disconnect_changed(callable_mp(this, &TileMap::_tile_set_changed)); + } - if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) { - dest_rect.size.x = -dest_rect.size.x; - } + tile_set = p_tileset; - if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) { - dest_rect.size.y = -dest_rect.size.y; - } + if (tile_set.is_valid()) { + tile_set->connect_changed(callable_mp(this, &TileMap::_tile_set_changed)); + } - // Draw the tile. - if (p_frame >= 0) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - } else { - real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); - real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; - real_t animation_offset = p_normalized_animation_offset * animation_duration; - // Accumulate durations unaffected by the speed to avoid accumulating floating point division errors. - // Aka do `sum(duration[i]) / speed` instead of `sum(duration[i] / speed)`. - real_t time_unscaled = 0.0; - for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { - real_t frame_duration_unscaled = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame); - real_t slice_start = time_unscaled / speed; - real_t slice_end = (time_unscaled + frame_duration_unscaled) / speed; - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, slice_start, slice_end, animation_offset); - - Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); - tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); - - time_unscaled += frame_duration_unscaled; - } - RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); + for (int i = 0; i < get_child_count(); i++) { + TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); + if (layer) { + layer->set_tile_set(tile_set); } } } +Ref<TileSet> TileMap::get_tileset() const { + return tile_set; +} + int TileMap::get_layers_count() const { return layers.size(); } @@ -215,11 +269,12 @@ void TileMap::add_layer(int p_to_pos) { layers.insert(p_to_pos, new_layer); add_child(new_layer, false, INTERNAL_MODE_FRONT); new_layer->set_name(vformat("Layer%d", p_to_pos)); + new_layer->set_tile_set(tile_set); move_child(new_layer, p_to_pos); for (uint32_t i = 0; i < layers.size(); i++) { layers[i]->set_as_tile_map_internal_node(i); } - new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); + new_layer->connect(CoreStringName(changed), callable_mp(this, &TileMap::_emit_changed)); notify_property_list_changed(); @@ -251,8 +306,11 @@ void TileMap::remove_layer(int p_layer) { ERR_FAIL_INDEX(p_layer, (int)layers.size()); // Clear before removing the layer. - layers[p_layer]->queue_free(); + TileMapLayer *removed = layers[p_layer]; layers.remove_at(p_layer); + remove_child(removed); + removed->queue_free(); + for (uint32_t i = 0; i < layers.size(); i++) { layers[i]->set_as_tile_map_internal_node(i); } @@ -289,6 +347,7 @@ Color TileMap::get_layer_modulate(int p_layer) const { void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) { TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_enabled, p_y_sort_enabled); + update_configuration_warnings(); } bool TileMap::is_layer_y_sort_enabled(int p_layer) const { @@ -297,6 +356,7 @@ bool TileMap::is_layer_y_sort_enabled(int p_layer) const { void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) { TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_origin, p_y_sort_origin); + update_configuration_warnings(); } int TileMap::get_layer_y_sort_origin(int p_layer) const { @@ -349,7 +409,7 @@ void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_colli } collision_visibility_mode = p_show_collision; for (TileMapLayer *layer : layers) { - layer->set_collision_visibility_mode(TileMapLayer::VisibilityMode(p_show_collision)); + layer->set_collision_visibility_mode(TileMapLayer::DebugVisibilityMode(p_show_collision)); } _emit_changed(); } @@ -364,7 +424,7 @@ void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navi } navigation_visibility_mode = p_show_navigation; for (TileMapLayer *layer : layers) { - layer->set_navigation_visibility_mode(TileMapLayer::VisibilityMode(p_show_navigation)); + layer->set_navigation_visibility_mode(TileMapLayer::DebugVisibilityMode(p_show_navigation)); } _emit_changed(); } @@ -378,9 +438,6 @@ void TileMap::set_y_sort_enabled(bool p_enable) { return; } Node2D::set_y_sort_enabled(p_enable); - for (TileMapLayer *layer : layers) { - layer->set_y_sort_enabled(p_enable); - } _emit_changed(); update_configuration_warnings(); } @@ -394,19 +451,85 @@ void TileMap::erase_cell(int p_layer, const Vector2i &p_coords) { } int TileMap::get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSet::INVALID_SOURCE, get_cell_source_id, p_coords, p_use_proxies); + if (p_use_proxies && tile_set.is_valid()) { + if (p_layer < 0) { + p_layer = layers.size() + p_layer; + } + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSet::INVALID_SOURCE); + + int source_id = layers[p_layer]->get_cell_source_id(p_coords); + Vector2i atlas_coords = layers[p_layer]->get_cell_atlas_coords(p_coords); + int alternative_id = layers[p_layer]->get_cell_alternative_tile(p_coords); + + Array arr = tile_set->map_tile_proxy(source_id, atlas_coords, alternative_id); + ERR_FAIL_COND_V(arr.size() != 3, TileSet::INVALID_SOURCE); + return arr[0]; + } else { + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSet::INVALID_SOURCE, get_cell_source_id, p_coords); + } } Vector2i TileMap::get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_ATLAS_COORDS, get_cell_atlas_coords, p_coords, p_use_proxies); + if (p_use_proxies && tile_set.is_valid()) { + if (p_layer < 0) { + p_layer = layers.size() + p_layer; + } + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetAtlasSource::INVALID_ATLAS_COORDS); + + int source_id = layers[p_layer]->get_cell_source_id(p_coords); + Vector2i atlas_coords = layers[p_layer]->get_cell_atlas_coords(p_coords); + int alternative_id = layers[p_layer]->get_cell_alternative_tile(p_coords); + + Array arr = tile_set->map_tile_proxy(source_id, atlas_coords, alternative_id); + ERR_FAIL_COND_V(arr.size() != 3, TileSetSource::INVALID_ATLAS_COORDS); + return arr[1]; + } else { + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_ATLAS_COORDS, get_cell_atlas_coords, p_coords); + } } int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_TILE_ALTERNATIVE, get_cell_alternative_tile, p_coords, p_use_proxies); + if (p_use_proxies && tile_set.is_valid()) { + if (p_layer < 0) { + p_layer = layers.size() + p_layer; + } + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); + + int source_id = layers[p_layer]->get_cell_source_id(p_coords); + Vector2i atlas_coords = layers[p_layer]->get_cell_atlas_coords(p_coords); + int alternative_id = layers[p_layer]->get_cell_alternative_tile(p_coords); + + Array arr = tile_set->map_tile_proxy(source_id, atlas_coords, alternative_id); + ERR_FAIL_COND_V(arr.size() != 3, TileSetSource::INVALID_TILE_ALTERNATIVE); + return arr[2]; + } else { + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileSetSource::INVALID_TILE_ALTERNATIVE, get_cell_alternative_tile, p_coords); + } } TileData *TileMap::get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - TILEMAP_CALL_FOR_LAYER_V(p_layer, nullptr, get_cell_tile_data, p_coords, p_use_proxies); + if (p_use_proxies && tile_set.is_valid()) { + if (p_layer < 0) { + p_layer = layers.size() + p_layer; + } + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); + + int source_id = layers[p_layer]->get_cell_source_id(p_coords); + Vector2i atlas_coords = layers[p_layer]->get_cell_atlas_coords(p_coords); + int alternative_id = layers[p_layer]->get_cell_alternative_tile(p_coords); + + Array arr = tile_set->map_tile_proxy(source_id, atlas_coords, alternative_id); + ERR_FAIL_COND_V(arr.size() != 3, nullptr); + + Ref<TileSetAtlasSource> atlas_source = tile_set->get_source(arr[0]); + if (atlas_source.is_valid()) { + return atlas_source->get_tile_data(arr[1], arr[2]); + } else { + return nullptr; + } + } else { + TILEMAP_CALL_FOR_LAYER_V(p_layer, nullptr, get_cell_tile_data, p_coords); + } } Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { @@ -451,7 +574,10 @@ void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, i } TileMapCell TileMap::get_cell(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { - TILEMAP_CALL_FOR_LAYER_V(p_layer, TileMapCell(), get_cell, p_coords, p_use_proxies); + if (p_use_proxies) { + WARN_DEPRECATED_MSG("use_proxies is deprecated."); + } + TILEMAP_CALL_FOR_LAYER_V(p_layer, TileMapCell(), get_cell, p_coords); } Vector2i TileMap::get_coords_for_body_rid(RID p_physics_body) { @@ -478,6 +604,13 @@ void TileMap::fix_invalid_tiles() { } } +#ifdef TOOLS_ENABLED +TileMapLayer *TileMap::duplicate_layer_from_internal(int p_layer) { + ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); + return Object::cast_to<TileMapLayer>(layers[p_layer]->duplicate(DUPLICATE_USE_INSTANTIATION | DUPLICATE_FROM_EDITOR)); +} +#endif // TOOLS_ENABLED + void TileMap::clear_layer(int p_layer) { TILEMAP_CALL_FOR_LAYER(p_layer, clear) } @@ -525,47 +658,31 @@ Rect2 TileMap::_edit_get_rect() const { #endif bool TileMap::_set(const StringName &p_name, const Variant &p_value) { + int index; + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); - if (p_name == "format") { + if (sname == "format") { if (p_value.get_type() == Variant::INT) { format = (TileMapDataFormat)(p_value.operator int64_t()); // Set format used for loading. return true; } } #ifndef DISABLE_DEPRECATED - else if (p_name == "tile_data") { // Kept for compatibility reasons. - if (p_value.is_array()) { - if (layers.size() == 0) { - TileMapLayer *new_layer = memnew(TileMapLayer); - add_child(new_layer, false, INTERNAL_MODE_FRONT); - new_layer->set_as_tile_map_internal_node(0); - new_layer->set_name("Layer0"); - new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); - layers.push_back(new_layer); - } - layers[0]->set_tile_data(format, p_value); - _emit_changed(); - return true; - } - return false; - } else if (p_name == "cell_quadrant_size") { + else if (sname == "cell_quadrant_size") { set_rendering_quadrant_size(p_value); return true; } #endif // DISABLE_DEPRECATED - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0) { - return false; - } - + else if (property_helper.is_property_valid(sname, &index)) { if (index >= (int)layers.size()) { while (index >= (int)layers.size()) { TileMapLayer *new_layer = memnew(TileMapLayer); add_child(new_layer, false, INTERNAL_MODE_FRONT); new_layer->set_as_tile_map_internal_node(index); new_layer->set_name(vformat("Layer%d", index)); - new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); + new_layer->set_tile_set(tile_set); + new_layer->connect(CoreStringName(changed), callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); } @@ -574,172 +691,38 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { update_configuration_warnings(); } - if (components[1] == "name") { - set_layer_name(index, p_value); - return true; - } else if (components[1] == "enabled") { - set_layer_enabled(index, p_value); - return true; - } else if (components[1] == "modulate") { - set_layer_modulate(index, p_value); - return true; - } else if (components[1] == "y_sort_enabled") { - set_layer_y_sort_enabled(index, p_value); - return true; - } else if (components[1] == "y_sort_origin") { - set_layer_y_sort_origin(index, p_value); - return true; - } else if (components[1] == "z_index") { - set_layer_z_index(index, p_value); - return true; - } else if (components[1] == "navigation_enabled") { - set_layer_navigation_enabled(index, p_value); - return true; - } else if (components[1] == "tile_data") { - layers[index]->set_tile_data(format, p_value); - _emit_changed(); + if (property_helper.property_set_value(sname, p_value)) { + if (components[1] == "tile_data") { + _emit_changed(); + } return true; - } else { - return false; } } return false; } bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { - r_ret = TileMapDataFormat::FORMAT_MAX - 1; // When saving, always save highest format. + r_ret = TileMapDataFormat::TILE_MAP_DATA_FORMAT_MAX - 1; // When saving, always save highest format. return true; } #ifndef DISABLE_DEPRECATED - else if (p_name == "cell_quadrant_size") { // Kept for compatibility reasons. + else if (sname == "cell_quadrant_size") { // Kept for compatibility reasons. r_ret = get_rendering_quadrant_size(); return true; } #endif - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_ret = get_layer_name(index); - return true; - } else if (components[1] == "enabled") { - r_ret = is_layer_enabled(index); - return true; - } else if (components[1] == "modulate") { - r_ret = get_layer_modulate(index); - return true; - } else if (components[1] == "y_sort_enabled") { - r_ret = is_layer_y_sort_enabled(index); - return true; - } else if (components[1] == "y_sort_origin") { - r_ret = get_layer_y_sort_origin(index); - return true; - } else if (components[1] == "z_index") { - r_ret = get_layer_z_index(index); - return true; - } else if (components[1] == "navigation_enabled") { - r_ret = is_layer_navigation_enabled(index); - return true; - } else if (components[1] == "tile_data") { - r_ret = layers[index]->get_tile_data(); - return true; - } else { - return false; - } + else { + return property_helper.property_get_value(sname, r_ret); } - return false; } void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - -#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \ - { \ - const String property_name = vformat("layer_%d/" m_name, i); \ - p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \ - } - - for (uint32_t i = 0; i < layers.size(); i++) { - MAKE_LAYER_PROPERTY(Variant::STRING, "name", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", ""); - MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", ""); - MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px"); - MAKE_LAYER_PROPERTY(Variant::INT, "z_index", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", ""); - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } - -#undef MAKE_LAYER_PROPERTY -} - -bool TileMap::_property_can_revert(const StringName &p_name) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - return layers[index]->get_name() != default_layer->get_name(); - } else if (components[1] == "enabled") { - return layers[index]->is_enabled() != default_layer->is_enabled(); - } else if (components[1] == "modulate") { - return layers[index]->get_modulate() != default_layer->get_modulate(); - } else if (components[1] == "y_sort_enabled") { - return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled(); - } else if (components[1] == "y_sort_origin") { - return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin(); - } else if (components[1] == "z_index") { - return layers[index]->get_z_index() != default_layer->get_z_index(); - } else if (components[1] == "navigation_enabled") { - return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled(); - } - } - - return false; -} - -bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_property = default_layer->get_name(); - return true; - } else if (components[1] == "enabled") { - r_property = default_layer->is_enabled(); - return true; - } else if (components[1] == "modulate") { - r_property = default_layer->get_modulate(); - return true; - } else if (components[1] == "y_sort_enabled") { - r_property = default_layer->is_y_sort_enabled(); - return true; - } else if (components[1] == "y_sort_origin") { - r_property = default_layer->get_y_sort_origin(); - return true; - } else if (components[1] == "z_index") { - r_property = default_layer->get_z_index(); - return true; - } else if (components[1] == "navigation_enabled") { - r_property = default_layer->is_navigation_enabled(); - return true; - } - } - - return false; + property_helper.get_property_list(p_list, layers.size()); } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { @@ -899,6 +882,9 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("force_update", "layer"), &TileMap::force_update, DEFVAL(-1)); #endif // DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMap::set_tileset); + ClassDB::bind_method(D_METHOD("get_tileset"), &TileMap::get_tileset); + ClassDB::bind_method(D_METHOD("set_rendering_quadrant_size", "size"), &TileMap::set_rendering_quadrant_size); ClassDB::bind_method(D_METHOD("get_rendering_quadrant_size"), &TileMap::get_rendering_quadrant_size); @@ -969,6 +955,7 @@ void TileMap::_bind_methods() { GDVIRTUAL_BIND(_use_tile_data_runtime_update, "layer", "coords"); GDVIRTUAL_BIND(_tile_data_runtime_update, "layer", "coords", "tile_data"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_rendering_quadrant_size", "get_rendering_quadrant_size"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_animatable"), "set_collision_animatable", "is_collision_animatable"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); @@ -976,9 +963,9 @@ void TileMap::_bind_methods() { ADD_ARRAY("layers", "layer_"); - ADD_PROPERTY_DEFAULT("format", TileMapDataFormat::FORMAT_1); + ADD_PROPERTY_DEFAULT("format", TileMapDataFormat::TILE_MAP_DATA_FORMAT_1); - ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed)); + ADD_SIGNAL(MethodInfo(CoreStringName(changed))); BIND_ENUM_CONSTANT(VISIBILITY_MODE_DEFAULT); BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_HIDE); @@ -990,13 +977,30 @@ TileMap::TileMap() { add_child(new_layer, false, INTERNAL_MODE_FRONT); new_layer->set_as_tile_map_internal_node(0); new_layer->set_name("Layer0"); - new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); + new_layer->set_tile_set(tile_set); + new_layer->connect(CoreStringName(changed), callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); - default_layer = memnew(TileMapLayer); -} -TileMap::~TileMap() { - memdelete(default_layer); + if (!base_property_helper.is_initialized()) { + // Initialize static PropertyListHelper if it wasn't yet. This has to be done here, + // because creating TileMapLayer in a static context is not always safe. + TileMapLayer *defaults = memnew(TileMapLayer); + + base_property_helper.set_prefix("layer_"); + base_property_helper.set_array_length_getter(&TileMap::get_layers_count); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults->get_name(), &TileMap::set_layer_name, &TileMap::get_layer_name); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "enabled"), defaults->is_enabled(), &TileMap::set_layer_enabled, &TileMap::is_layer_enabled); + base_property_helper.register_property(PropertyInfo(Variant::COLOR, "modulate"), defaults->get_modulate(), &TileMap::set_layer_modulate, &TileMap::get_layer_modulate); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "y_sort_enabled"), defaults->is_y_sort_enabled(), &TileMap::set_layer_y_sort_enabled, &TileMap::is_layer_y_sort_enabled); + base_property_helper.register_property(PropertyInfo(Variant::INT, "y_sort_origin", PROPERTY_HINT_NONE, "suffix:px"), defaults->get_y_sort_origin(), &TileMap::set_layer_y_sort_origin, &TileMap::get_layer_y_sort_origin); + base_property_helper.register_property(PropertyInfo(Variant::INT, "z_index"), defaults->get_z_index(), &TileMap::set_layer_z_index, &TileMap::get_layer_z_index); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "navigation_enabled"), defaults->is_navigation_enabled(), &TileMap::set_layer_navigation_enabled, &TileMap::is_layer_navigation_enabled); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_INT32_ARRAY, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Vector<int>(), &TileMap::_set_layer_tile_data, &TileMap::_get_tile_map_data_using_compatibility_format); + + memdelete(defaults); + } + + property_helper.setup_for_instance(base_property_helper, this); } #undef TILEMAP_CALL_FOR_LAYER diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index edea90fa95..690102f730 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -31,7 +31,8 @@ #ifndef TILE_MAP_H #define TILE_MAP_H -#include "scene/2d/tile_map_layer_group.h" +#include "scene/2d/tile_map_layer.h" +#include "scene/property_list_helper.h" #include "scene/resources/2d/tile_set.h" class Control; @@ -39,14 +40,14 @@ class TileMapLayer; class TerrainConstraint; enum TileMapDataFormat { - FORMAT_1 = 0, - FORMAT_2, - FORMAT_3, - FORMAT_MAX, + TILE_MAP_DATA_FORMAT_1 = 0, + TILE_MAP_DATA_FORMAT_2, + TILE_MAP_DATA_FORMAT_3, + TILE_MAP_DATA_FORMAT_MAX, }; -class TileMap : public TileMapLayerGroup { - GDCLASS(TileMap, TileMapLayerGroup) +class TileMap : public Node2D { + GDCLASS(TileMap, Node2D) public: // Kept for compatibility, but should use TileMapLayer::VisibilityMode instead. @@ -60,11 +61,10 @@ private: friend class TileSetPlugin; // A compatibility enum to specify how is the data if formatted. - mutable TileMapDataFormat format = TileMapDataFormat::FORMAT_3; - - static constexpr float FP_ADJUST = 0.00001; + mutable TileMapDataFormat format = TileMapDataFormat::TILE_MAP_DATA_FORMAT_3; // Properties. + Ref<TileSet> tile_set; int rendering_quadrant_size = 16; bool collision_animatable = false; VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; @@ -72,20 +72,29 @@ private: // Layers. LocalVector<TileMapLayer *> layers; - TileMapLayer *default_layer; // Dummy layer to fetch default values. + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; // Transforms for collision_animatable. Transform2D last_valid_transform; Transform2D new_transform; + void _tile_set_changed(); + void _emit_changed(); + // Kept for compatibility with TileMap. With TileMapLayers as individual nodes, the format is stored directly in the array. + void _set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data); + Vector<int> _get_tile_map_data_using_compatibility_format(int p_layer) const; + void _set_layer_tile_data(int p_layer, const PackedInt32Array &p_data); + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - bool _property_can_revert(const StringName &p_name) const; - bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); @@ -112,7 +121,9 @@ public: void set_rendering_quadrant_size(int p_size); int get_rendering_quadrant_size() const; - static void draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr, real_t p_normalized_animation_offset = 0.0); + // Accessors. + void set_tileset(const Ref<TileSet> &p_tileset); + Ref<TileSet> get_tileset() const; // Layers management. int get_layers_count() const; @@ -200,6 +211,11 @@ public: // Fixing and clearing methods. void fix_invalid_tiles(); +#ifdef TOOLS_ENABLED + // Moving layers outside of TileMap. + TileMapLayer *duplicate_layer_from_internal(int p_layer); +#endif // TOOLS_ENABLED + // Clears tiles from a given layer. void clear_layer(int p_layer); void clear(); @@ -219,7 +235,6 @@ public: PackedStringArray get_configuration_warnings() const override; TileMap(); - ~TileMap(); }; VARIANT_ENUM_CAST(TileMap::VisibilityMode); diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index c7aa4c2be4..0ac236eaa7 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -30,8 +30,8 @@ #include "tile_map_layer.h" -#include "core/core_string_names.h" #include "core/io/marshalls.h" +#include "scene/2d/tile_map.h" #include "scene/gui/control.h" #include "scene/resources/world_2d.h" #include "servers/navigation_server_2d.h" @@ -50,12 +50,11 @@ Vector2i TileMapLayer::_coords_to_debug_quadrant_coords(const Vector2i &p_coords p_coords.y > 0 ? p_coords.y / TILE_MAP_DEBUG_QUADRANT_SIZE : (p_coords.y - (TILE_MAP_DEBUG_QUADRANT_SIZE - 1)) / TILE_MAP_DEBUG_QUADRANT_SIZE); } -void TileMapLayer::_debug_update() { - const Ref<TileSet> &tile_set = get_effective_tile_set(); +void TileMapLayer::_debug_update(bool p_force_cleanup) { RenderingServer *rs = RenderingServer::get_singleton(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree(); + bool forced_cleanup = p_force_cleanup || !enabled || tile_set.is_null() || !is_visible_in_tree(); if (forced_cleanup) { for (KeyValue<Vector2i, Ref<DebugQuadrant>> &kv : debug_quadrant_map) { @@ -84,7 +83,7 @@ void TileMapLayer::_debug_update() { if (_debug_was_cleaned_up || anything_changed) { // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { CellData &cell_data = kv.value; _debug_quadrants_update_cell(cell_data, dirty_debug_quadrant_list); } @@ -178,34 +177,22 @@ void TileMapLayer::_debug_quadrants_update_cell(CellData &r_cell_data, SelfList< #endif // DEBUG_ENABLED /////////////////////////////// Rendering ////////////////////////////////////// -void TileMapLayer::_rendering_update() { - const Ref<TileSet> &tile_set = get_effective_tile_set(); +void TileMapLayer::_rendering_update(bool p_force_cleanup) { RenderingServer *rs = RenderingServer::get_singleton(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree(); + bool forced_cleanup = p_force_cleanup || !enabled || tile_set.is_null() || !is_visible_in_tree(); // ----------- Layer level processing ----------- if (!forced_cleanup) { // Modulate the layer. Color layer_modulate = get_modulate(); #ifdef TOOLS_ENABLED - const TileMapLayerGroup *tile_map_layer_group = Object::cast_to<TileMapLayerGroup>(get_parent()); - if (tile_map_layer_group) { - const Vector<StringName> selected_layers = tile_map_layer_group->get_selected_layers(); - if (tile_map_layer_group->is_highlighting_selected_layer() && selected_layers.size() == 1 && get_name() != selected_layers[0]) { - TileMapLayer *selected_layer = Object::cast_to<TileMapLayer>(tile_map_layer_group->get_node_or_null(String(selected_layers[0]))); - if (selected_layer) { - int z_selected = selected_layer->get_z_index(); - int layer_z_index = get_z_index(); - if (layer_z_index < z_selected || (layer_z_index == z_selected && get_index() < selected_layer->get_index())) { - layer_modulate = layer_modulate.darkened(0.5); - } else if (layer_z_index > z_selected || (layer_z_index == z_selected && get_index() > selected_layer->get_index())) { - layer_modulate = layer_modulate.darkened(0.5); - layer_modulate.a *= 0.3; - } - } - } + if (highlight_mode == HIGHLIGHT_MODE_BELOW) { + layer_modulate = layer_modulate.darkened(0.5); + } else if (highlight_mode == HIGHLIGHT_MODE_ABOVE) { + layer_modulate = layer_modulate.darkened(0.5); + layer_modulate.a *= 0.3; } #endif // TOOLS_ENABLED rs->canvas_item_set_modulate(get_canvas_item(), layer_modulate); @@ -219,13 +206,12 @@ void TileMapLayer::_rendering_update() { // Check if anything changed that might change the quadrant shape. // If so, recreate everything. bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] || - (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET])); + (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_TILE_SET])); // Free all quadrants. if (forced_cleanup || quandrant_shape_changed) { for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { - for (int i = 0; i < kv.value->canvas_items.size(); i++) { - const RID &ci = kv.value->canvas_items[i]; + for (const RID &ci : kv.value->canvas_items) { if (ci.is_valid()) { rs->free(ci); } @@ -238,9 +224,9 @@ void TileMapLayer::_rendering_update() { if (!forced_cleanup) { // List all quadrants to update, recreating them if needed. - if (dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || _rendering_was_cleaned_up) { + if (dirty.flags[DIRTY_FLAGS_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || _rendering_was_cleaned_up) { // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { CellData &cell_data = kv.value; _rendering_quadrants_update_cell(cell_data, dirty_rendering_quadrant_list); } @@ -354,12 +340,19 @@ void TileMapLayer::_rendering_update() { } // Drawing the tile in the canvas item. - TileMap::draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset); + draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset); + } + + // Reset physics interpolation for any recreated canvas items. + if (is_physics_interpolated_and_enabled() && is_visible_in_tree()) { + for (const RID &ci : rendering_quadrant->canvas_items) { + rs->canvas_item_reset_physics_interpolation(ci); + } } + } else { // Free the quadrant. - for (int i = 0; i < rendering_quadrant->canvas_items.size(); i++) { - const RID &ci = rendering_quadrant->canvas_items[i]; + for (const RID &ci : rendering_quadrant->canvas_items) { if (ci.is_valid()) { rs->free(ci); } @@ -412,13 +405,13 @@ void TileMapLayer::_rendering_update() { // ----------- Occluders processing ----------- if (forced_cleanup) { // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _rendering_occluders_clear_cell(kv.value); } } else { - if (_rendering_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET]) { + if (_rendering_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_SET]) { // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _rendering_occluders_update_cell(kv.value); } } else { @@ -437,11 +430,10 @@ void TileMapLayer::_rendering_update() { void TileMapLayer::_rendering_notification(int p_what) { RenderingServer *rs = RenderingServer::get_singleton(); - const Ref<TileSet> &tile_set = get_effective_tile_set(); if (p_what == NOTIFICATION_TRANSFORM_CHANGED || p_what == NOTIFICATION_ENTER_CANVAS || p_what == NOTIFICATION_VISIBILITY_CHANGED) { if (tile_set.is_valid()) { Transform2D tilemap_xform = get_global_transform(); - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { const CellData &cell_data = kv.value; for (const RID &occluder : cell_data.occluders) { if (occluder.is_null()) { @@ -453,12 +445,19 @@ void TileMapLayer::_rendering_notification(int p_what) { } } } + } else if (p_what == NOTIFICATION_RESET_PHYSICS_INTERPOLATION) { + for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { + for (const RID &ci : kv.value->canvas_items) { + if (ci.is_null()) { + continue; + } + rs->canvas_item_reset_physics_interpolation(ci); + } + } } } void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - // Check if the cell is valid and retrieve its y_sort_origin. bool is_valid = false; int tile_y_sort_origin = 0; @@ -556,7 +555,6 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); RenderingServer *rs = RenderingServer::get_singleton(); // Free unused occluders then resize the occluders array. @@ -605,6 +603,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid()); rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index)); + rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index)); } else { // Clear occluder. if (occluder.is_valid()) { @@ -625,8 +624,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_COND(tile_set.is_null()); if (!Engine::get_singleton()->is_editor_hint()) { return; @@ -673,20 +671,18 @@ void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Ve /////////////////////////////// Physics ////////////////////////////////////// -void TileMapLayer::_physics_update() { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - +void TileMapLayer::_physics_update(bool p_force_cleanup) { // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !is_inside_tree() || !tile_set.is_valid(); + bool forced_cleanup = p_force_cleanup || !enabled || !collision_enabled || !is_inside_tree() || tile_set.is_null(); if (forced_cleanup) { // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _physics_clear_cell(kv.value); } } else { - if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { + if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _physics_update_cell(kv.value); } } else { @@ -704,7 +700,6 @@ void TileMapLayer::_physics_update() { } void TileMapLayer::_physics_notification(int p_what) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); Transform2D gl_transform = get_global_transform(); PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); @@ -712,7 +707,7 @@ void TileMapLayer::_physics_notification(int p_what) { case NOTIFICATION_TRANSFORM_CHANGED: // Move the collisison shapes along with the TileMap. if (is_inside_tree() && tile_set.is_valid()) { - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { const CellData &cell_data = kv.value; for (RID body : cell_data.bodies) { @@ -730,7 +725,7 @@ void TileMapLayer::_physics_notification(int p_what) { if (is_inside_tree()) { RID space = get_world_2d()->get_space(); - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { const CellData &cell_data = kv.value; for (RID body : cell_data.bodies) { @@ -757,7 +752,6 @@ void TileMapLayer::_physics_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); Transform2D gl_transform = get_global_transform(); RID space = get_world_2d()->get_space(); PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); @@ -874,8 +868,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { // Draw the debug collision shapes. - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_COND(tile_set.is_null()); if (!get_tree()) { return; @@ -883,13 +876,13 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect bool show_collision = false; switch (collision_visibility_mode) { - case TileMapLayer::VISIBILITY_MODE_DEFAULT: + case TileMapLayer::DEBUG_VISIBILITY_MODE_DEFAULT: show_collision = !Engine::get_singleton()->is_editor_hint() && get_tree()->is_debugging_collisions_hint(); break; - case TileMapLayer::VISIBILITY_MODE_FORCE_HIDE: + case TileMapLayer::DEBUG_VISIBILITY_MODE_FORCE_HIDE: show_collision = false; break; - case TileMapLayer::VISIBILITY_MODE_FORCE_SHOW: + case TileMapLayer::DEBUG_VISIBILITY_MODE_FORCE_SHOW: show_collision = true; break; } @@ -928,13 +921,12 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect /////////////////////////////// Navigation ////////////////////////////////////// -void TileMapLayer::_navigation_update() { +void TileMapLayer::_navigation_update(bool p_force_cleanup) { ERR_FAIL_NULL(NavigationServer2D::get_singleton()); NavigationServer2D *ns = NavigationServer2D::get_singleton(); - const Ref<TileSet> &tile_set = get_effective_tile_set(); // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !is_inside_tree() || !tile_set.is_valid(); + bool forced_cleanup = p_force_cleanup || !enabled || !navigation_enabled || !is_inside_tree() || tile_set.is_null(); // ----------- Layer level processing ----------- // All this processing is kept for compatibility with the TileMap node. @@ -963,13 +955,13 @@ void TileMapLayer::_navigation_update() { // ----------- Navigation regions processing ----------- if (forced_cleanup) { // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _navigation_clear_cell(kv.value); } } else { - if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_MAP]) { + if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_MAP]) { // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _navigation_update_cell(kv.value); } } else { @@ -987,11 +979,10 @@ void TileMapLayer::_navigation_update() { } void TileMapLayer::_navigation_notification(int p_what) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { if (tile_set.is_valid()) { Transform2D tilemap_xform = get_global_transform(); - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { const CellData &cell_data = kv.value; // Update navigation regions transform. for (const RID ®ion : cell_data.navigation_regions) { @@ -1021,7 +1012,6 @@ void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); NavigationServer2D *ns = NavigationServer2D::get_singleton(); Transform2D gl_xform = get_global_transform(); RID navigation_map = navigation_map_override.is_valid() ? navigation_map_override : get_world_2d()->get_navigation_map(); @@ -1102,13 +1092,13 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V // Draw the debug collision shapes. bool show_navigation = false; switch (navigation_visibility_mode) { - case TileMapLayer::VISIBILITY_MODE_DEFAULT: + case TileMapLayer::DEBUG_VISIBILITY_MODE_DEFAULT: show_navigation = !Engine::get_singleton()->is_editor_hint() && get_tree()->is_debugging_navigation_hint(); break; - case TileMapLayer::VISIBILITY_MODE_FORCE_HIDE: + case TileMapLayer::DEBUG_VISIBILITY_MODE_FORCE_HIDE: show_navigation = false; break; - case TileMapLayer::VISIBILITY_MODE_FORCE_SHOW: + case TileMapLayer::DEBUG_VISIBILITY_MODE_FORCE_SHOW: show_navigation = true; break; } @@ -1121,8 +1111,6 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V return; } - const Ref<TileSet> &tile_set = get_effective_tile_set(); - RenderingServer *rs = RenderingServer::get_singleton(); const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); @@ -1206,21 +1194,19 @@ void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const V /////////////////////////////// Scenes ////////////////////////////////////// -void TileMapLayer::_scenes_update() { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - +void TileMapLayer::_scenes_update(bool p_force_cleanup) { // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !is_inside_tree() || !tile_set.is_valid(); + bool forced_cleanup = p_force_cleanup || !enabled || !is_inside_tree() || tile_set.is_null(); if (forced_cleanup) { // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _scenes_clear_cell(kv.value); } } else { - if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { + if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _scenes_update_cell(kv.value); } } else { @@ -1253,8 +1239,6 @@ void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - // Clear the scene in any case. _scenes_clear_cell(r_cell_data); @@ -1295,8 +1279,7 @@ void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_COND(tile_set.is_null()); if (!Engine::get_singleton()->is_editor_hint()) { return; @@ -1344,23 +1327,21 @@ void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vecto ///////////////////////////////////////////////////////////////////// -void TileMapLayer::_build_runtime_update_tile_data() { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - +void TileMapLayer::_build_runtime_update_tile_data(bool p_force_cleanup) { // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree(); + bool forced_cleanup = p_force_cleanup || !enabled || tile_set.is_null() || !is_visible_in_tree(); if (!forced_cleanup) { bool valid_runtime_update = GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update); bool valid_runtime_update_for_tilemap = tile_map_node && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update); // For keeping compatibility. if (valid_runtime_update || valid_runtime_update_for_tilemap) { bool use_tilemap_for_runtime = valid_runtime_update_for_tilemap && !valid_runtime_update; - if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET]) { + if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_SET]) { _runtime_update_needs_all_cells_cleaned_up = true; - for (KeyValue<Vector2i, CellData> &E : tile_map) { + for (KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { _build_runtime_update_tile_data_for_cell(E.value, use_tilemap_for_runtime); } } else if (dirty.flags[DIRTY_FLAGS_LAYER_RUNTIME_UPDATE]) { - for (KeyValue<Vector2i, CellData> &E : tile_map) { + for (KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { _build_runtime_update_tile_data_for_cell(E.value, use_tilemap_for_runtime, true); } } else { @@ -1378,8 +1359,6 @@ void TileMapLayer::_build_runtime_update_tile_data() { } void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_use_tilemap_for_runtime, bool p_auto_add_to_dirty_list) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - TileMapCell &c = r_cell_data.cell; TileSetSource *source; if (tile_set->has_source(c.source_id)) { @@ -1429,7 +1408,7 @@ void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_dat void TileMapLayer::_clear_runtime_update_tile_data() { if (_runtime_update_needs_all_cells_cleaned_up) { - for (KeyValue<Vector2i, CellData> &E : tile_map) { + for (KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { _clear_runtime_update_tile_data_for_cell(E.value); } _runtime_update_needs_all_cells_cleaned_up = false; @@ -1450,8 +1429,7 @@ void TileMapLayer::_clear_runtime_update_tile_data_for_cell(CellData &r_cell_dat } TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) const { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (!tile_set.is_valid()) { + if (tile_set.is_null()) { return TileSet::TerrainsPattern(); } // Returns all tiles compatible with the given constraints. @@ -1511,8 +1489,7 @@ TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints } RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (!tile_set.is_valid()) { + if (tile_set.is_null()) { return RBSet<TerrainConstraint>(); } @@ -1532,8 +1509,7 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_patte } RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (!tile_set.is_valid()) { + if (tile_set.is_null()) { return RBSet<TerrainConstraint>(); } @@ -1619,8 +1595,14 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cel return constraints; } +void TileMapLayer::_tile_set_changed() { + dirty.flags[DIRTY_FLAGS_TILE_SET] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); +} + void TileMapLayer::_renamed() { - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } void TileMapLayer::_update_notify_local_transform() { @@ -1651,23 +1633,21 @@ void TileMapLayer::_deferred_internal_update() { } // Update dirty quadrants on layers. - _internal_update(); - - pending_update = false; + _internal_update(false); } -void TileMapLayer::_internal_update() { +void TileMapLayer::_internal_update(bool p_force_cleanup) { // Find TileData that need a runtime modification. // This may add cells to the dirty list if a runtime modification has been notified. - _build_runtime_update_tile_data(); + _build_runtime_update_tile_data(p_force_cleanup); // Update all subsystems. - _rendering_update(); - _physics_update(); - _navigation_update(); - _scenes_update(); + _rendering_update(p_force_cleanup); + _physics_update(p_force_cleanup); + _navigation_update(p_force_cleanup); + _scenes_update(p_force_cleanup); #ifdef DEBUG_ENABLED - _debug_update(); + _debug_update(p_force_cleanup); #endif // DEBUG_ENABLED _clear_runtime_update_tile_data(); @@ -1689,11 +1669,13 @@ void TileMapLayer::_internal_update() { // Remove cells that are empty after the cleanup. for (const Vector2i &coords : to_delete) { - tile_map.erase(coords); + tile_map_layer_data.erase(coords); } // Clear the dirty cells list. dirty.cell_list.clear(); + + pending_update = false; } void TileMapLayer::_notification(int p_what) { @@ -1710,22 +1692,22 @@ void TileMapLayer::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] = true; - // Update immediately on exiting. - update_internals(); + // Update immediately on exiting, and force cleanup. + _internal_update(true); } break; - case TileMap::NOTIFICATION_ENTER_CANVAS: { + case NOTIFICATION_ENTER_CANVAS: { dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true; _queue_internal_update(); } break; - case TileMap::NOTIFICATION_EXIT_CANVAS: { + case NOTIFICATION_EXIT_CANVAS: { dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true; - // Update immediately on exiting. - update_internals(); + // Update immediately on exiting, and force cleanup. + _internal_update(true); } break; - case TileMap::NOTIFICATION_VISIBILITY_CHANGED: { + case NOTIFICATION_VISIBILITY_CHANGED: { dirty.flags[DIRTY_FLAGS_LAYER_VISIBILITY] = true; _queue_internal_update(); } break; @@ -1737,12 +1719,115 @@ void TileMapLayer::_notification(int p_what) { } void TileMapLayer::_bind_methods() { + // --- Cells manipulation --- + // Generic cells manipulations and access. ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapLayer::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("erase_cell", "coords"), &TileMapLayer::erase_cell); + ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMapLayer::fix_invalid_tiles); + ClassDB::bind_method(D_METHOD("clear"), &TileMapLayer::clear); + + ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapLayer::get_cell_source_id); + ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapLayer::get_cell_atlas_coords); + ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapLayer::get_cell_alternative_tile); + ClassDB::bind_method(D_METHOD("get_cell_tile_data", "coords"), &TileMapLayer::get_cell_tile_data); + + ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapLayer::get_used_cells); + ClassDB::bind_method(D_METHOD("get_used_cells_by_id", "source_id", "atlas_coords", "alternative_tile"), &TileMapLayer::get_used_cells_by_id, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMapLayer::get_used_rect); + + // Patterns. + ClassDB::bind_method(D_METHOD("get_pattern", "coords_array"), &TileMapLayer::get_pattern); + ClassDB::bind_method(D_METHOD("set_pattern", "position", "pattern"), &TileMapLayer::set_pattern); + + // Terrains. + ClassDB::bind_method(D_METHOD("set_cells_terrain_connect", "cells", "terrain_set", "terrain", "ignore_empty_terrains"), &TileMapLayer::set_cells_terrain_connect, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("set_cells_terrain_path", "path", "terrain_set", "terrain", "ignore_empty_terrains"), &TileMapLayer::set_cells_terrain_path, DEFVAL(true)); + + // --- Physics helpers --- + ClassDB::bind_method(D_METHOD("has_body_rid", "body"), &TileMapLayer::has_body_rid); + ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMapLayer::get_coords_for_body_rid); + + // --- Runtime --- + ClassDB::bind_method(D_METHOD("update_internals"), &TileMapLayer::update_internals); + ClassDB::bind_method(D_METHOD("notify_runtime_tile_data_update"), &TileMapLayer::notify_runtime_tile_data_update, DEFVAL(-1)); + + // --- Shortcuts to methods defined in TileSet --- + ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMapLayer::map_pattern); + ClassDB::bind_method(D_METHOD("get_surrounding_cells", "coords"), &TileMapLayer::get_surrounding_cells); + ClassDB::bind_method(D_METHOD("get_neighbor_cell", "coords", "neighbor"), &TileMapLayer::get_neighbor_cell); + ClassDB::bind_method(D_METHOD("map_to_local", "map_position"), &TileMapLayer::map_to_local); + ClassDB::bind_method(D_METHOD("local_to_map", "local_position"), &TileMapLayer::local_to_map); + + // --- Accessors --- + ClassDB::bind_method(D_METHOD("set_tile_map_data_from_array", "tile_map_layer_data"), &TileMapLayer::set_tile_map_data_from_array); + ClassDB::bind_method(D_METHOD("get_tile_map_data_as_array"), &TileMapLayer::get_tile_map_data_as_array); + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &TileMapLayer::set_enabled); + ClassDB::bind_method(D_METHOD("is_enabled"), &TileMapLayer::is_enabled); + + ClassDB::bind_method(D_METHOD("set_tile_set", "tile_set"), &TileMapLayer::set_tile_set); + ClassDB::bind_method(D_METHOD("get_tile_set"), &TileMapLayer::get_tile_set); + + ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileMapLayer::set_y_sort_origin); + ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileMapLayer::get_y_sort_origin); + ClassDB::bind_method(D_METHOD("set_rendering_quadrant_size", "size"), &TileMapLayer::set_rendering_quadrant_size); + ClassDB::bind_method(D_METHOD("get_rendering_quadrant_size"), &TileMapLayer::get_rendering_quadrant_size); + + ClassDB::bind_method(D_METHOD("set_collision_enabled", "enabled"), &TileMapLayer::set_collision_enabled); + ClassDB::bind_method(D_METHOD("is_collision_enabled"), &TileMapLayer::is_collision_enabled); + ClassDB::bind_method(D_METHOD("set_use_kinematic_bodies", "use_kinematic_bodies"), &TileMapLayer::set_use_kinematic_bodies); + ClassDB::bind_method(D_METHOD("is_using_kinematic_bodies"), &TileMapLayer::is_using_kinematic_bodies); + ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "visibility_mode"), &TileMapLayer::set_collision_visibility_mode); + ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMapLayer::get_collision_visibility_mode); + + ClassDB::bind_method(D_METHOD("set_navigation_enabled", "enabled"), &TileMapLayer::set_navigation_enabled); + ClassDB::bind_method(D_METHOD("is_navigation_enabled"), &TileMapLayer::is_navigation_enabled); + ClassDB::bind_method(D_METHOD("set_navigation_map", "map"), &TileMapLayer::set_navigation_map); + ClassDB::bind_method(D_METHOD("get_navigation_map"), &TileMapLayer::get_navigation_map); + ClassDB::bind_method(D_METHOD("set_navigation_visibility_mode", "show_navigation"), &TileMapLayer::set_navigation_visibility_mode); + ClassDB::bind_method(D_METHOD("get_navigation_visibility_mode"), &TileMapLayer::get_navigation_visibility_mode); GDVIRTUAL_BIND(_use_tile_data_runtime_update, "coords"); GDVIRTUAL_BIND(_tile_data_runtime_update, "coords", "tile_data"); - ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed)); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "tile_map_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_tile_map_data_from_array", "get_tile_map_data_as_array"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tile_set", "get_tile_set"); + ADD_GROUP("Rendering", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size"), "set_rendering_quadrant_size", "get_rendering_quadrant_size"); + ADD_GROUP("Physics", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_enabled"), "set_collision_enabled", "is_collision_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_kinematic_bodies"), "set_use_kinematic_bodies", "is_using_kinematic_bodies"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); + ADD_GROUP("Navigation", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "navigation_enabled"), "set_navigation_enabled", "is_navigation_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); + + ADD_SIGNAL(MethodInfo(CoreStringName(changed))); + + ADD_PROPERTY_DEFAULT("tile_map_data_format", TileMapDataFormat::TILE_MAP_DATA_FORMAT_1); + + BIND_ENUM_CONSTANT(DEBUG_VISIBILITY_MODE_DEFAULT); + BIND_ENUM_CONSTANT(DEBUG_VISIBILITY_MODE_FORCE_HIDE); + BIND_ENUM_CONSTANT(DEBUG_VISIBILITY_MODE_FORCE_SHOW); +} + +void TileMapLayer::_update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter) { + // Set a default texture filter for the whole tilemap. + CanvasItem::_update_self_texture_filter(p_texture_filter); + dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_FILTER] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); +} + +void TileMapLayer::_update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat) { + // Set a default texture repeat for the whole tilemap. + CanvasItem::_update_self_texture_repeat(p_texture_repeat); + dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_REPEAT] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); } void TileMapLayer::set_as_tile_map_internal_node(int p_index) { @@ -1759,7 +1844,6 @@ void TileMapLayer::set_as_tile_map_internal_node(int p_index) { } Rect2 TileMapLayer::get_rect(bool &r_changed) const { - const Ref<TileSet> &tile_set = get_effective_tile_set(); if (tile_set.is_null()) { r_changed = rect_cache != Rect2(); return Rect2(); @@ -1772,7 +1856,7 @@ Rect2 TileMapLayer::get_rect(bool &r_changed) const { if (rect_cache_dirty) { Rect2 r_total; bool first = true; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { + for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { Rect2 r; r.position = tile_set->map_to_local(E.key); r.size = Size2(); @@ -1794,8 +1878,7 @@ Rect2 TileMapLayer::get_rect(bool &r_changed) const { } HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) const { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (!tile_set.is_valid()) { + if (tile_set.is_null()) { return HashMap<Vector2i, TileSet::TerrainsPattern>(); } @@ -1843,8 +1926,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constrain HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) const { HashMap<Vector2i, TileSet::TerrainsPattern> output; - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_COND_V(tile_set.is_null(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); // Build list and set of tiles that can be modified (painted and their surroundings). @@ -1949,8 +2031,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(c HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) const { HashMap<Vector2i, TileSet::TerrainsPattern> output; - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_COND_V(tile_set.is_null(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); // Make sure the path is correct and build the peering bit list while doing it. @@ -2023,8 +2104,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(cons HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) const { HashMap<Vector2i, TileSet::TerrainsPattern> output; - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_COND_V(tile_set.is_null(), output); ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); // Build list and set of tiles that can be modified (painted and their surroundings). @@ -2074,153 +2154,103 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(c return output; } -TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords, bool p_use_proxies) const { - if (!tile_map.has(p_coords)) { +TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords) const { + if (!tile_map_layer_data.has(p_coords)) { return TileMapCell(); } else { - TileMapCell c = tile_map.find(p_coords)->value.cell; - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile); - c.source_id = proxyed[0]; - c.set_atlas_coords(proxyed[1]); - c.alternative_tile = proxyed[2]; - } - return c; + return tile_map_layer_data.find(p_coords)->value.cell; } } -void TileMapLayer::set_tile_data(TileMapDataFormat p_format, const Vector<int> &p_data) { - ERR_FAIL_COND(p_format > TileMapDataFormat::FORMAT_3); - - // Set data for a given tile from raw data. +void TileMapLayer::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_normalized_animation_offset) { + ERR_FAIL_COND(p_tile_set.is_null()); + ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords)); + ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile)); + TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Check for the frame. + if (p_frame >= 0) { + ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords)); + } - int c = p_data.size(); - const int *r = p_data.ptr(); + // Get the texture. + Ref<Texture2D> tex = atlas_source->get_runtime_texture(); + if (tex.is_null()) { + return; + } - int offset = (p_format >= TileMapDataFormat::FORMAT_2) ? 3 : 2; - ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %s. Expected modulo: %s", offset)); + // Check if we are in the texture, return otherwise. + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) { + return; + } - clear(); + // Get tile data. + const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile); -#ifdef DISABLE_DEPRECATED - ERR_FAIL_COND_MSG(p_format != TileMapDataFormat::FORMAT_3, vformat("Cannot handle deprecated TileMapLayer data format version %d. This Godot version was compiled with no support for deprecated data.", p_format)); -#endif + // Get the tile modulation. + Color modulate = tile_data->get_modulate() * p_modulation; - for (int i = 0; i < c; i += offset) { - const uint8_t *ptr = (const uint8_t *)&r[i]; - uint8_t local[12]; - for (int j = 0; j < ((p_format >= TileMapDataFormat::FORMAT_2) ? 12 : 8); j++) { - local[j] = ptr[j]; - } + // Compute the offset. + Vector2 tile_offset = tile_data->get_texture_origin(); -#ifdef BIG_ENDIAN_ENABLED + // Get destination rect. + Rect2 dest_rect; + dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size; + dest_rect.size.x += FP_ADJUST; + dest_rect.size.y += FP_ADJUST; - SWAP(local[0], local[3]); - SWAP(local[1], local[2]); - SWAP(local[4], local[7]); - SWAP(local[5], local[6]); - //TODO: ask someone to check this... - if (FORMAT >= FORMAT_2) { - SWAP(local[8], local[11]); - SWAP(local[9], local[10]); - } -#endif - // Extracts position in TileMap. - int16_t x = decode_uint16(&local[0]); - int16_t y = decode_uint16(&local[2]); - - if (p_format == TileMapDataFormat::FORMAT_3) { - uint16_t source_id = decode_uint16(&local[4]); - uint16_t atlas_coords_x = decode_uint16(&local[6]); - uint16_t atlas_coords_y = decode_uint16(&local[8]); - uint16_t alternative_tile = decode_uint16(&local[10]); - set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); + bool transpose = tile_data->get_transpose() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + if (transpose) { + dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); } else { -#ifndef DISABLE_DEPRECATED - // Previous decated format. - - uint32_t v = decode_uint32(&local[4]); - // Extract the transform flags that used to be in the tilemap. - bool flip_h = v & (1UL << 29); - bool flip_v = v & (1UL << 30); - bool transpose = v & (1UL << 31); - v &= (1UL << 29) - 1; - - // Extract autotile/atlas coords. - int16_t coord_x = 0; - int16_t coord_y = 0; - if (p_format == TileMapDataFormat::FORMAT_2) { - coord_x = decode_uint16(&local[8]); - coord_y = decode_uint16(&local[10]); - } - - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (tile_set.is_valid()) { - Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); - if (a.size() == 3) { - set_cell(Vector2i(x, y), a[0], a[1], a[2]); - } else { - ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose)); - } - } else { - int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); - set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); - } -#endif + dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset); } - } -} -Vector<int> TileMapLayer::get_tile_data() const { - // Export tile data to raw format. - Vector<int> tile_data; - tile_data.resize(tile_map.size() * 3); - int *w = tile_data.ptrw(); + if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) { + dest_rect.size.x = -dest_rect.size.x; + } - // Save in highest format. + if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) { + dest_rect.size.y = -dest_rect.size.y; + } - int idx = 0; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - uint8_t *ptr = (uint8_t *)&w[idx]; - encode_uint16((int16_t)(E.key.x), &ptr[0]); - encode_uint16((int16_t)(E.key.y), &ptr[2]); - encode_uint16(E.value.cell.source_id, &ptr[4]); - encode_uint16(E.value.cell.coord_x, &ptr[6]); - encode_uint16(E.value.cell.coord_y, &ptr[8]); - encode_uint16(E.value.cell.alternative_tile, &ptr[10]); - idx += 3; - } + // Draw the tile. + if (p_frame >= 0) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) { + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); + } else { + real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords); + real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed; + real_t animation_offset = p_normalized_animation_offset * animation_duration; + // Accumulate durations unaffected by the speed to avoid accumulating floating point division errors. + // Aka do `sum(duration[i]) / speed` instead of `sum(duration[i] / speed)`. + real_t time_unscaled = 0.0; + for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) { + real_t frame_duration_unscaled = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame); + real_t slice_start = time_unscaled / speed; + real_t slice_end = (time_unscaled + frame_duration_unscaled) / speed; + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, slice_start, slice_end, animation_offset); - return tile_data; -} + Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame); + tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping()); -void TileMapLayer::notify_tile_map_layer_group_change(DirtyFlags p_what) { - if (p_what == DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS || - p_what == DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED || - p_what == DIRTY_FLAGS_LAYER_GROUP_TILE_SET) { - emit_signal(CoreStringNames::get_singleton()->changed); + time_unscaled += frame_duration_unscaled; + } + RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0); + } } - - dirty.flags[p_what] = true; - _queue_internal_update(); -} - -void TileMapLayer::update_internals() { - pending_update = true; - _deferred_internal_update(); -} - -void TileMapLayer::notify_runtime_tile_data_update() { - dirty.flags[TileMapLayer::DIRTY_FLAGS_LAYER_RUNTIME_UPDATE] = true; - _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); } -void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { +void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile) { // Set the current cell tile (using integer position). Vector2i pk(p_coords); - HashMap<Vector2i, CellData>::Iterator E = tile_map.find(pk); + HashMap<Vector2i, CellData>::Iterator E = tile_map_layer_data.find(pk); int source_id = p_source_id; Vector2i atlas_coords = p_atlas_coords; @@ -2241,7 +2271,7 @@ void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vec // Insert a new cell in the tile map. CellData new_cell_data; new_cell_data.coords = pk; - E = tile_map.insert(pk, new_cell_data); + E = tile_map_layer_data.insert(pk, new_cell_data); } else { if (E->value.cell.source_id == source_id && E->value.cell.get_atlas_coords() == atlas_coords && E->value.cell.alternative_tile == alternative_tile) { return; // Nothing changed. @@ -2266,83 +2296,139 @@ void TileMapLayer::erase_cell(const Vector2i &p_coords) { set_cell(p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } -int TileMapLayer::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies) const { +void TileMapLayer::fix_invalid_tiles() { + ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot call fix_invalid_tiles() on a TileMapLayer without a valid TileSet."); + + RBSet<Vector2i> coords; + for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { + TileSetSource *source = *tile_set->get_source(E.value.cell.source_id); + if (!source || !source->has_tile(E.value.cell.get_atlas_coords()) || !source->has_alternative_tile(E.value.cell.get_atlas_coords(), E.value.cell.alternative_tile)) { + coords.insert(E.key); + } + } + for (const Vector2i &E : coords) { + set_cell(E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + } +} + +void TileMapLayer::clear() { + // Remove all tiles. + for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { + erase_cell(kv.key); + } + used_rect_cache_dirty = true; +} + +int TileMapLayer::get_cell_source_id(const Vector2i &p_coords) const { // Get a cell source id from position. - HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); + HashMap<Vector2i, CellData>::ConstIterator E = tile_map_layer_data.find(p_coords); if (!E) { return TileSet::INVALID_SOURCE; } - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); - return proxyed[0]; - } - return E->value.cell.source_id; } -Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies) const { +Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords) const { // Get a cell source id from position. - HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); + HashMap<Vector2i, CellData>::ConstIterator E = tile_map_layer_data.find(p_coords); if (!E) { return TileSetSource::INVALID_ATLAS_COORDS; } - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); - return proxyed[1]; - } - return E->value.cell.get_atlas_coords(); } -int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies) const { +int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords) const { // Get a cell source id from position. - HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); + HashMap<Vector2i, CellData>::ConstIterator E = tile_map_layer_data.find(p_coords); if (!E) { return TileSetSource::INVALID_TILE_ALTERNATIVE; } - const Ref<TileSet> &tile_set = get_effective_tile_set(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); - return proxyed[2]; - } - return E->value.cell.alternative_tile; } -TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies) const { - int source_id = get_cell_source_id(p_coords, p_use_proxies); +TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords) const { + int source_id = get_cell_source_id(p_coords); if (source_id == TileSet::INVALID_SOURCE) { return nullptr; } - const Ref<TileSet> &tile_set = get_effective_tile_set(); Ref<TileSetAtlasSource> source = tile_set->get_source(source_id); if (source.is_valid()) { - return source->get_tile_data(get_cell_atlas_coords(p_coords, p_use_proxies), get_cell_alternative_tile(p_coords, p_use_proxies)); + return source->get_tile_data(get_cell_atlas_coords(p_coords), get_cell_alternative_tile(p_coords)); } return nullptr; } -void TileMapLayer::clear() { - // Remove all tiles. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - erase_cell(kv.key); +TypedArray<Vector2i> TileMapLayer::get_used_cells() const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { + const TileMapCell &c = E.value.cell; + if (c.source_id == TileSet::INVALID_SOURCE) { + continue; + } + a.push_back(E.key); } - used_rect_cache_dirty = true; + + return a; +} + +TypedArray<Vector2i> TileMapLayer::get_used_cells_by_id(int p_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile) const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { + const TileMapCell &c = E.value.cell; + if (c.source_id == TileSet::INVALID_SOURCE) { + continue; + } + if ((p_source_id == TileSet::INVALID_SOURCE || p_source_id == c.source_id) && + (p_atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || p_atlas_coords == c.get_atlas_coords()) && + (p_alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE || p_alternative_tile == c.alternative_tile)) { + a.push_back(E.key); + } + } + + return a; +} + +Rect2i TileMapLayer::get_used_rect() const { + // Return the rect of the currently used area. + if (used_rect_cache_dirty) { + used_rect_cache = Rect2i(); + + bool first = true; + for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { + const TileMapCell &c = E.value.cell; + if (c.source_id == TileSet::INVALID_SOURCE) { + continue; + } + if (first) { + used_rect_cache = Rect2i(E.key, Size2i()); + first = false; + } else { + used_rect_cache.expand_to(E.key); + } + } + if (!first) { + // Only if we have at least one cell. + // The cache expands to top-left coordinate, so we add one full tile. + used_rect_cache.size += Vector2i(1, 1); + } + used_rect_cache_dirty = false; + } + + return used_rect_cache; } Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); + ERR_FAIL_COND_V(tile_set.is_null(), nullptr); Ref<TileMapPattern> output; output.instantiate(); @@ -2395,7 +2481,6 @@ Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_arra } void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); ERR_FAIL_COND(tile_set.is_null()); ERR_FAIL_COND(p_pattern.is_null()); @@ -2407,8 +2492,7 @@ void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPatt } void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_COND(tile_set.is_null()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); Vector<Vector2i> cells_vector; @@ -2447,8 +2531,7 @@ void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p } void TileMapLayer::set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - const Ref<TileSet> &tile_set = get_effective_tile_set(); - ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_COND(tile_set.is_null()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); Vector<Vector2i> vector_path; @@ -2487,65 +2570,49 @@ void TileMapLayer::set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_ter } } -TypedArray<Vector2i> TileMapLayer::get_used_cells() const { - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - const TileMapCell &c = E.value.cell; - if (c.source_id == TileSet::INVALID_SOURCE) { - continue; - } - a.push_back(E.key); - } +bool TileMapLayer::has_body_rid(RID p_physics_body) const { + return bodies_coords.has(p_physics_body); +} - return a; +Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { + const Vector2i *found = bodies_coords.getptr(p_physics_body); + ERR_FAIL_NULL_V(found, Vector2i()); + return *found; } -TypedArray<Vector2i> TileMapLayer::get_used_cells_by_id(int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) const { - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - const TileMapCell &c = E.value.cell; - if (c.source_id == TileSet::INVALID_SOURCE) { - continue; - } - if ((p_source_id == TileSet::INVALID_SOURCE || p_source_id == c.source_id) && - (p_atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || p_atlas_coords == c.get_atlas_coords()) && - (p_alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE || p_alternative_tile == c.alternative_tile)) { - a.push_back(E.key); - } - } +void TileMapLayer::update_internals() { + _internal_update(false); +} - return a; +void TileMapLayer::notify_runtime_tile_data_update() { + dirty.flags[TileMapLayer::DIRTY_FLAGS_LAYER_RUNTIME_UPDATE] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); } -Rect2i TileMapLayer::get_used_rect() const { - // Return the rect of the currently used area. - if (used_rect_cache_dirty) { - used_rect_cache = Rect2i(); +Vector2i TileMapLayer::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) { + ERR_FAIL_COND_V(tile_set.is_null(), Vector2i()); + return tile_set->map_pattern(p_position_in_tilemap, p_coords_in_pattern, p_pattern); +} - bool first = true; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - const TileMapCell &c = E.value.cell; - if (c.source_id == TileSet::INVALID_SOURCE) { - continue; - } - if (first) { - used_rect_cache = Rect2i(E.key.x, E.key.y, 0, 0); - first = false; - } else { - used_rect_cache.expand_to(E.key); - } - } - if (!first) { - // Only if we have at least one cell. - // The cache expands to top-left coordinate, so we add one full tile. - used_rect_cache.size += Vector2i(1, 1); - } - used_rect_cache_dirty = false; - } +TypedArray<Vector2i> TileMapLayer::get_surrounding_cells(const Vector2i &p_coords) { + ERR_FAIL_COND_V(tile_set.is_null(), TypedArray<Vector2i>()); + return tile_set->get_surrounding_cells(p_coords); +} - return used_rect_cache; +Vector2i TileMapLayer::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const { + ERR_FAIL_COND_V(tile_set.is_null(), Vector2i()); + return tile_set->get_neighbor_cell(p_coords, p_cell_neighbor); +} + +Vector2 TileMapLayer::map_to_local(const Vector2i &p_pos) const { + ERR_FAIL_COND_V(tile_set.is_null(), Vector2()); + return tile_set->map_to_local(p_pos); +} + +Vector2i TileMapLayer::local_to_map(const Vector2 &p_pos) const { + ERR_FAIL_COND_V(tile_set.is_null(), Vector2i()); + return tile_set->local_to_map(p_pos); } void TileMapLayer::set_enabled(bool p_enabled) { @@ -2555,17 +2622,137 @@ void TileMapLayer::set_enabled(bool p_enabled) { enabled = p_enabled; dirty.flags[DIRTY_FLAGS_LAYER_ENABLED] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); - - if (tile_map_node) { - tile_map_node->update_configuration_warnings(); - } + emit_signal(CoreStringName(changed)); } bool TileMapLayer::is_enabled() const { return enabled; } +void TileMapLayer::set_tile_set(const Ref<TileSet> &p_tile_set) { + if (p_tile_set == tile_set) { + return; + } + + dirty.flags[DIRTY_FLAGS_TILE_SET] = true; + _queue_internal_update(); + + // Set the TileSet, registering to its changes. + if (tile_set.is_valid()) { + tile_set->disconnect_changed(callable_mp(this, &TileMapLayer::_tile_set_changed)); + } + + tile_set = p_tile_set; + + if (tile_set.is_valid()) { + tile_set->connect_changed(callable_mp(this, &TileMapLayer::_tile_set_changed)); + } + + emit_signal(CoreStringName(changed)); + + // Trigger updates for TileSet's read-only status. + notify_property_list_changed(); +} + +Ref<TileSet> TileMapLayer::get_tile_set() const { + return tile_set; +} + +void TileMapLayer::set_highlight_mode(HighlightMode p_highlight_mode) { + if (p_highlight_mode == highlight_mode) { + return; + } + highlight_mode = p_highlight_mode; + _queue_internal_update(); +} + +TileMapLayer::HighlightMode TileMapLayer::get_highlight_mode() const { + return highlight_mode; +} + +void TileMapLayer::set_tile_map_data_from_array(const Vector<uint8_t> &p_data) { + if (p_data.is_empty()) { + clear(); + return; + } + + const int cell_data_struct_size = 12; + + int size = p_data.size(); + const uint8_t *ptr = p_data.ptr(); + + // Index in the array. + int index = 0; + + // First extract the data version. + ERR_FAIL_COND_MSG(size < 2, "Corrupted tile map data: not enough bytes."); + uint16_t format = decode_uint16(&ptr[index]); + index += 2; + ERR_FAIL_COND_MSG(format >= TileMapLayerDataFormat::TILE_MAP_LAYER_DATA_FORMAT_MAX, vformat("Unsupported tile map data format: %s. Expected format ID lower or equal to: %s", format, TileMapLayerDataFormat::TILE_MAP_LAYER_DATA_FORMAT_MAX - 1)); + + // Clear the TileMap. + clear(); + + while (index < size) { + ERR_FAIL_COND_MSG(index + cell_data_struct_size > size, vformat("Corrupted tile map data: tiles might be missing.")); + + // Get a pointer at the start of the cell data. + const uint8_t *cell_data_ptr = &ptr[index]; + + // Extracts position in TileMap. + int16_t x = decode_uint16(&cell_data_ptr[0]); + int16_t y = decode_uint16(&cell_data_ptr[2]); + + // Extracts the tile identifiers. + uint16_t source_id = decode_uint16(&cell_data_ptr[4]); + uint16_t atlas_coords_x = decode_uint16(&cell_data_ptr[6]); + uint16_t atlas_coords_y = decode_uint16(&cell_data_ptr[8]); + uint16_t alternative_tile = decode_uint16(&cell_data_ptr[10]); + + set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); + index += cell_data_struct_size; + } +} + +Vector<uint8_t> TileMapLayer::get_tile_map_data_as_array() const { + const int cell_data_struct_size = 12; + + Vector<uint8_t> tile_map_data_array; + if (tile_map_layer_data.is_empty()) { + return tile_map_data_array; + } + + tile_map_data_array.resize(2 + tile_map_layer_data.size() * cell_data_struct_size); + uint8_t *ptr = tile_map_data_array.ptrw(); + + // Index in the array. + int index = 0; + + // Save the version. + encode_uint16(TileMapLayerDataFormat::TILE_MAP_LAYER_DATA_FORMAT_MAX - 1, &ptr[index]); + index += 2; + + // Save in highest format. + for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { + // Get a pointer at the start of the cell data. + uint8_t *cell_data_ptr = (uint8_t *)&ptr[index]; + + // Store position in TileMap. + encode_uint16((int16_t)(E.key.x), &cell_data_ptr[0]); + encode_uint16((int16_t)(E.key.y), &cell_data_ptr[2]); + + // Store the tile identifiers. + encode_uint16(E.value.cell.source_id, &cell_data_ptr[4]); + encode_uint16(E.value.cell.coord_x, &cell_data_ptr[6]); + encode_uint16(E.value.cell.coord_y, &cell_data_ptr[8]); + encode_uint16(E.value.cell.alternative_tile, &cell_data_ptr[10]); + + index += cell_data_struct_size; + } + + return tile_map_data_array; +} + void TileMapLayer::set_self_modulate(const Color &p_self_modulate) { if (get_self_modulate() == p_self_modulate) { return; @@ -2573,7 +2760,7 @@ void TileMapLayer::set_self_modulate(const Color &p_self_modulate) { CanvasItem::set_self_modulate(p_self_modulate); dirty.flags[DIRTY_FLAGS_LAYER_SELF_MODULATE] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { @@ -2583,11 +2770,8 @@ void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { CanvasItem::set_y_sort_enabled(p_y_sort_enabled); dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); - if (tile_map_node) { - tile_map_node->update_configuration_warnings(); - } _update_notify_local_transform(); } @@ -2598,7 +2782,7 @@ void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) { y_sort_origin = p_y_sort_origin; dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } int TileMapLayer::get_y_sort_origin() const { @@ -2612,11 +2796,7 @@ void TileMapLayer::set_z_index(int p_z_index) { CanvasItem::set_z_index(p_z_index); dirty.flags[DIRTY_FLAGS_LAYER_Z_INDEX] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); - - if (tile_map_node) { - tile_map_node->update_configuration_warnings(); - } + emit_signal(CoreStringName(changed)); } void TileMapLayer::set_light_mask(int p_light_mask) { @@ -2626,23 +2806,7 @@ void TileMapLayer::set_light_mask(int p_light_mask) { CanvasItem::set_light_mask(p_light_mask); dirty.flags[DIRTY_FLAGS_LAYER_LIGHT_MASK] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); -} - -void TileMapLayer::set_texture_filter(TextureFilter p_texture_filter) { - // Set a default texture filter for the whole tilemap. - CanvasItem::set_texture_filter(p_texture_filter); - dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_FILTER] = true; - _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); -} - -void TileMapLayer::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { - // Set a default texture repeat for the whole tilemap. - CanvasItem::set_texture_repeat(p_texture_repeat); - dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_REPEAT] = true; - _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } void TileMapLayer::set_rendering_quadrant_size(int p_size) { @@ -2654,35 +2818,49 @@ void TileMapLayer::set_rendering_quadrant_size(int p_size) { rendering_quadrant_size = p_size; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } int TileMapLayer::get_rendering_quadrant_size() const { return rendering_quadrant_size; } +void TileMapLayer::set_collision_enabled(bool p_enabled) { + if (collision_enabled == p_enabled) { + return; + } + collision_enabled = p_enabled; + dirty.flags[DIRTY_FLAGS_LAYER_COLLISION_ENABLED] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); +} + +bool TileMapLayer::is_collision_enabled() const { + return collision_enabled; +} + void TileMapLayer::set_use_kinematic_bodies(bool p_use_kinematic_bodies) { use_kinematic_bodies = p_use_kinematic_bodies; dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] = p_use_kinematic_bodies; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } bool TileMapLayer::is_using_kinematic_bodies() const { return use_kinematic_bodies; } -void TileMapLayer::set_collision_visibility_mode(TileMapLayer::VisibilityMode p_show_collision) { +void TileMapLayer::set_collision_visibility_mode(TileMapLayer::DebugVisibilityMode p_show_collision) { if (collision_visibility_mode == p_show_collision) { return; } collision_visibility_mode = p_show_collision; dirty.flags[DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } -TileMapLayer::VisibilityMode TileMapLayer::get_collision_visibility_mode() const { +TileMapLayer::DebugVisibilityMode TileMapLayer::get_collision_visibility_mode() const { return collision_visibility_mode; } @@ -2693,7 +2871,7 @@ void TileMapLayer::set_navigation_enabled(bool p_enabled) { navigation_enabled = p_enabled; dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } bool TileMapLayer::is_navigation_enabled() const { @@ -2707,7 +2885,7 @@ void TileMapLayer::set_navigation_map(RID p_map) { navigation_map_override = p_map; dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_MAP] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } RID TileMapLayer::get_navigation_map() const { @@ -2719,61 +2897,27 @@ RID TileMapLayer::get_navigation_map() const { return RID(); } -void TileMapLayer::set_navigation_visibility_mode(TileMapLayer::VisibilityMode p_show_navigation) { +void TileMapLayer::set_navigation_visibility_mode(TileMapLayer::DebugVisibilityMode p_show_navigation) { if (navigation_visibility_mode == p_show_navigation) { return; } navigation_visibility_mode = p_show_navigation; dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_VISIBILITY_MODE] = true; _queue_internal_update(); - emit_signal(CoreStringNames::get_singleton()->changed); + emit_signal(CoreStringName(changed)); } -TileMapLayer::VisibilityMode TileMapLayer::get_navigation_visibility_mode() const { +TileMapLayer::DebugVisibilityMode TileMapLayer::get_navigation_visibility_mode() const { return navigation_visibility_mode; } -void TileMapLayer::fix_invalid_tiles() { - Ref<TileSet> tileset = get_effective_tile_set(); - ERR_FAIL_COND_MSG(tileset.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet."); - - RBSet<Vector2i> coords; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - TileSetSource *source = *tileset->get_source(E.value.cell.source_id); - if (!source || !source->has_tile(E.value.cell.get_atlas_coords()) || !source->has_alternative_tile(E.value.cell.get_atlas_coords(), E.value.cell.alternative_tile)) { - coords.insert(E.key); - } - } - for (const Vector2i &E : coords) { - set_cell(E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - } -} - -bool TileMapLayer::has_body_rid(RID p_physics_body) const { - return bodies_coords.has(p_physics_body); -} - -Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { - return bodies_coords[p_physics_body]; -} - -Ref<TileSet> TileMapLayer::get_effective_tile_set() const { - TileMapLayerGroup *tile_map_layer_group = Object::cast_to<TileMapLayerGroup>(get_parent()); - if (tile_map_layer_group) { - return tile_map_layer_group->get_tileset(); - } else { - return Ref<TileSet>(); - } -} - TileMapLayer::TileMapLayer() { set_notify_transform(true); } TileMapLayer::~TileMapLayer() { - in_destructor = true; clear(); - _internal_update(); + _internal_update(true); } HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coords_and_peering_bits() const { diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index c58c72949c..57c83d7c4c 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -31,10 +31,15 @@ #ifndef TILE_MAP_LAYER_H #define TILE_MAP_LAYER_H -#include "scene/2d/tile_map.h" #include "scene/resources/2d/tile_set.h" class TileSetAtlasSource; +class TileMap; + +enum TileMapLayerDataFormat { + TILE_MAP_LAYER_DATA_FORMAT_0 = 0, + TILE_MAP_LAYER_DATA_FORMAT_MAX, +}; class TerrainConstraint { private: @@ -218,14 +223,21 @@ class TileMapLayer : public Node2D { GDCLASS(TileMapLayer, Node2D); public: - enum VisibilityMode { - VISIBILITY_MODE_DEFAULT, - VISIBILITY_MODE_FORCE_SHOW, - VISIBILITY_MODE_FORCE_HIDE, + enum HighlightMode { + HIGHLIGHT_MODE_DEFAULT, + HIGHLIGHT_MODE_ABOVE, + HIGHLIGHT_MODE_BELOW, + }; + + enum DebugVisibilityMode { + DEBUG_VISIBILITY_MODE_DEFAULT, + DEBUG_VISIBILITY_MODE_FORCE_SHOW, + DEBUG_VISIBILITY_MODE_FORCE_HIDE, }; enum DirtyFlags { DIRTY_FLAGS_LAYER_ENABLED = 0, + DIRTY_FLAGS_LAYER_IN_TREE, DIRTY_FLAGS_LAYER_IN_CANVAS, DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM, @@ -238,6 +250,7 @@ public: DIRTY_FLAGS_LAYER_TEXTURE_FILTER, DIRTY_FLAGS_LAYER_TEXTURE_REPEAT, DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE, + DIRTY_FLAGS_LAYER_COLLISION_ENABLED, DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES, DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE, DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED, @@ -249,26 +262,35 @@ public: DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS, DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED, - DIRTY_FLAGS_LAYER_GROUP_TILE_SET, + + DIRTY_FLAGS_TILE_SET, DIRTY_FLAGS_MAX, }; private: - // Exposed properties. + static constexpr float FP_ADJUST = 0.00001; + + // Properties. + HashMap<Vector2i, CellData> tile_map_layer_data; + bool enabled = true; + Ref<TileSet> tile_set; + + HighlightMode highlight_mode = HIGHLIGHT_MODE_DEFAULT; + int y_sort_origin = 0; int rendering_quadrant_size = 16; + bool collision_enabled = true; bool use_kinematic_bodies = false; - VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; + DebugVisibilityMode collision_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT; bool navigation_enabled = true; RID navigation_map_override; - VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; + DebugVisibilityMode navigation_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT; // Internal. - HashMap<Vector2i, CellData> tile_map; bool pending_update = false; // For keeping compatibility with TileMap. @@ -280,7 +302,6 @@ private: bool flags[DIRTY_FLAGS_MAX] = { false }; SelfList<CellData>::List cell_list; } dirty; - bool in_destructor = false; // Rect cache. mutable Rect2 rect_cache; @@ -290,7 +311,7 @@ private: // Runtime tile data. bool _runtime_update_tile_data_was_cleaned_up = false; - void _build_runtime_update_tile_data(); + void _build_runtime_update_tile_data(bool p_force_cleanup); void _build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_use_tilemap_for_runtime, bool p_auto_add_to_dirty_list = false); bool _runtime_update_needs_all_cells_cleaned_up = false; void _clear_runtime_update_tile_data(); @@ -301,13 +322,13 @@ private: HashMap<Vector2i, Ref<DebugQuadrant>> debug_quadrant_map; Vector2i _coords_to_debug_quadrant_coords(const Vector2i &p_coords) const; bool _debug_was_cleaned_up = false; - void _debug_update(); + void _debug_update(bool p_force_cleanup); void _debug_quadrants_update_cell(CellData &r_cell_data, SelfList<DebugQuadrant>::List &r_dirty_debug_quadrant_list); #endif // DEBUG_ENABLED HashMap<Vector2i, Ref<RenderingQuadrant>> rendering_quadrant_map; bool _rendering_was_cleaned_up = false; - void _rendering_update(); + void _rendering_update(bool p_force_cleanup); void _rendering_notification(int p_what); void _rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list); void _rendering_occluders_clear_cell(CellData &r_cell_data); @@ -318,7 +339,7 @@ private: HashMap<RID, Vector2i> bodies_coords; // Mapping for RID to coords. bool _physics_was_cleaned_up = false; - void _physics_update(); + void _physics_update(bool p_force_cleanup); void _physics_notification(int p_what); void _physics_clear_cell(CellData &r_cell_data); void _physics_update_cell(CellData &r_cell_data); @@ -327,7 +348,7 @@ private: #endif // DEBUG_ENABLED bool _navigation_was_cleaned_up = false; - void _navigation_update(); + void _navigation_update(bool p_force_cleanup); void _navigation_notification(int p_what); void _navigation_clear_cell(CellData &r_cell_data); void _navigation_update_cell(CellData &r_cell_data); @@ -336,7 +357,7 @@ private: #endif // DEBUG_ENABLED bool _scenes_was_cleaned_up = false; - void _scenes_update(); + void _scenes_update(bool p_force_cleanup); void _scenes_clear_cell(CellData &r_cell_data); void _scenes_update_cell(CellData &r_cell_data); #ifdef DEBUG_ENABLED @@ -348,21 +369,33 @@ private: RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; + void _tile_set_changed(); + void _renamed(); void _update_notify_local_transform(); // Internal updates. void _queue_internal_update(); void _deferred_internal_update(); - void _internal_update(); + void _internal_update(bool p_force_cleanup); protected: void _notification(int p_what); + static void _bind_methods(); + virtual void _update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter) override; + virtual void _update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat) override; + public: // TileMap node. void set_as_tile_map_internal_node(int p_index); + int get_index_in_tile_map() const { + return layer_index_in_tile_map_node; + } + const HashMap<Vector2i, CellData> &get_tile_map_layer_data() const { + return tile_map_layer_data; + } // Rect caching. Rect2 get_rect(bool &r_changed) const; @@ -374,27 +407,28 @@ public: HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true) const; // Not exposed. // Not exposed to users. - TileMapCell get_cell(const Vector2i &p_coords, bool p_use_proxies = false) const; + TileMapCell get_cell(const Vector2i &p_coords) const; - // For TileMap node's use. - void set_tile_data(TileMapDataFormat p_format, const Vector<int> &p_data); - Vector<int> get_tile_data() const; - void notify_tile_map_layer_group_change(DirtyFlags p_what); + static void draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr, real_t p_normalized_animation_offset = 0.0); - void update_internals(); - void notify_runtime_tile_data_update(); + ////////////// Exposed functions ////////////// - // --- Exposed in TileMap --- - // Cells manipulation. - void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); + // --- Cells manipulation --- + // Generic cells manipulations and data access. + void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i &p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); void erase_cell(const Vector2i &p_coords); - - int get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies = false) const; - Vector2i get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies = false) const; - int get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies = false) const; - TileData *get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies = false) const; // Helper method to make accessing the data easier. + void fix_invalid_tiles(); void clear(); + int get_cell_source_id(const Vector2i &p_coords) const; + Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const; + int get_cell_alternative_tile(const Vector2i &p_coords) const; + TileData *get_cell_tile_data(const Vector2i &p_coords) const; // Helper method to make accessing the data easier. + + TypedArray<Vector2i> get_used_cells() const; + TypedArray<Vector2i> get_used_cells_by_id(int p_source_id = TileSet::INVALID_SOURCE, const Vector2i &p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; + Rect2i get_used_rect() const; + // Patterns. Ref<TileMapPattern> get_pattern(TypedArray<Vector2i> p_coords_array); void set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); @@ -403,54 +437,62 @@ public: void set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); void set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); - // Cells usage. - TypedArray<Vector2i> get_used_cells() const; - TypedArray<Vector2i> get_used_cells_by_id(int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; - Rect2i get_used_rect() const; + // --- Physics helpers --- + bool has_body_rid(RID p_physics_body) const; + Vector2i get_coords_for_body_rid(RID p_physics_body) const; // For finding tiles from collision. + + // --- Runtime --- + void update_internals(); + void notify_runtime_tile_data_update(); + GDVIRTUAL1R(bool, _use_tile_data_runtime_update, Vector2i); + GDVIRTUAL2(_tile_data_runtime_update, Vector2i, TileData *); + + // --- Shortcuts to methods defined in TileSet --- + Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern); + TypedArray<Vector2i> get_surrounding_cells(const Vector2i &p_coords); + Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const; + Vector2 map_to_local(const Vector2i &p_pos) const; + Vector2i local_to_map(const Vector2 &p_pos) const; + + // --- Accessors --- + void set_tile_map_data_from_array(const Vector<uint8_t> &p_data); + Vector<uint8_t> get_tile_map_data_as_array() const; - // Layer properties. void set_enabled(bool p_enabled); bool is_enabled() const; + void set_tile_set(const Ref<TileSet> &p_tile_set); + Ref<TileSet> get_tile_set() const; + + void set_highlight_mode(HighlightMode p_highlight_mode); + HighlightMode get_highlight_mode() const; + virtual void set_self_modulate(const Color &p_self_modulate) override; virtual void set_y_sort_enabled(bool p_y_sort_enabled) override; void set_y_sort_origin(int p_y_sort_origin); int get_y_sort_origin() const; virtual void set_z_index(int p_z_index) override; virtual void set_light_mask(int p_light_mask) override; - virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; - virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; void set_rendering_quadrant_size(int p_size); int get_rendering_quadrant_size() const; + void set_collision_enabled(bool p_enabled); + bool is_collision_enabled() const; void set_use_kinematic_bodies(bool p_use_kinematic_bodies); bool is_using_kinematic_bodies() const; - void set_collision_visibility_mode(VisibilityMode p_show_collision); - VisibilityMode get_collision_visibility_mode() const; + void set_collision_visibility_mode(DebugVisibilityMode p_show_collision); + DebugVisibilityMode get_collision_visibility_mode() const; void set_navigation_enabled(bool p_enabled); bool is_navigation_enabled() const; void set_navigation_map(RID p_map); RID get_navigation_map() const; - void set_navigation_visibility_mode(VisibilityMode p_show_navigation); - VisibilityMode get_navigation_visibility_mode() const; - - // Fixing and clearing methods. - void fix_invalid_tiles(); - - // Find coords for body. - bool has_body_rid(RID p_physics_body) const; - Vector2i get_coords_for_body_rid(RID p_physics_body) const; // For finding tiles from collision. - - // Helper. - Ref<TileSet> get_effective_tile_set() const; - - // Virtual function to modify the TileData at runtime. - GDVIRTUAL1R(bool, _use_tile_data_runtime_update, Vector2i); - GDVIRTUAL2(_tile_data_runtime_update, Vector2i, TileData *); - // --- + void set_navigation_visibility_mode(DebugVisibilityMode p_show_navigation); + DebugVisibilityMode get_navigation_visibility_mode() const; TileMapLayer(); ~TileMapLayer(); }; +VARIANT_ENUM_CAST(TileMapLayer::DebugVisibilityMode); + #endif // TILE_MAP_LAYER_H diff --git a/scene/2d/tile_map_layer_group.cpp b/scene/2d/tile_map_layer_group.cpp deleted file mode 100644 index 132b4bbba5..0000000000 --- a/scene/2d/tile_map_layer_group.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/**************************************************************************/ -/* tile_map_layer_group.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#include "tile_map_layer_group.h" - -#include "core/core_string_names.h" -#include "scene/2d/tile_map_layer.h" -#include "scene/resources/2d/tile_set.h" - -#ifdef TOOLS_ENABLED - -void TileMapLayerGroup::_cleanup_selected_layers() { - for (int i = 0; i < selected_layers.size(); i++) { - const String name = selected_layers[i]; - TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_node_or_null(name)); - if (!layer) { - selected_layers.remove_at(i); - i--; - } - } -} - -#endif // TOOLS_ENABLED - -void TileMapLayerGroup::_tile_set_changed() { - for (int i = 0; i < get_child_count(); i++) { - TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); - if (layer) { - layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET); - } - } - - update_configuration_warnings(); -} - -#ifdef TOOLS_ENABLED - -void TileMapLayerGroup::set_selected_layers(Vector<StringName> p_layer_names) { - selected_layers = p_layer_names; - _cleanup_selected_layers(); - - // Update the layers modulation. - for (int i = 0; i < get_child_count(); i++) { - TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); - if (layer) { - layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS); - } - } -} - -Vector<StringName> TileMapLayerGroup::get_selected_layers() const { - return selected_layers; -} - -void TileMapLayerGroup::set_highlight_selected_layer(bool p_highlight_selected_layer) { - if (highlight_selected_layer == p_highlight_selected_layer) { - return; - } - - highlight_selected_layer = p_highlight_selected_layer; - - for (int i = 0; i < get_child_count(); i++) { - TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); - if (layer) { - layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED); - } - } -} - -bool TileMapLayerGroup::is_highlighting_selected_layer() const { - return highlight_selected_layer; -} - -#endif // TOOLS_ENABLED - -void TileMapLayerGroup::remove_child_notify(Node *p_child) { -#ifdef TOOLS_ENABLED - _cleanup_selected_layers(); -#endif // TOOLS_ENABLED -} - -void TileMapLayerGroup::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMapLayerGroup::set_tileset); - ClassDB::bind_method(D_METHOD("get_tileset"), &TileMapLayerGroup::get_tileset); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tileset", "get_tileset"); -} - -void TileMapLayerGroup::set_tileset(const Ref<TileSet> &p_tileset) { - if (p_tileset == tile_set) { - return; - } - - // Set the tileset, registering to its changes. - if (tile_set.is_valid()) { - tile_set->disconnect_changed(callable_mp(this, &TileMapLayerGroup::_tile_set_changed)); - } - - tile_set = p_tileset; - - if (tile_set.is_valid()) { - tile_set->connect_changed(callable_mp(this, &TileMapLayerGroup::_tile_set_changed)); - } - - for (int i = 0; i < get_child_count(); i++) { - TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); - if (layer) { - layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET); - } - } -} - -Ref<TileSet> TileMapLayerGroup::get_tileset() const { - return tile_set; -} - -TileMapLayerGroup::~TileMapLayerGroup() { - if (tile_set.is_valid()) { - tile_set->disconnect_changed(callable_mp(this, &TileMapLayerGroup::_tile_set_changed)); - } -} diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index 5ed7fadb2a..ff409272c5 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -31,18 +31,17 @@ #include "touch_screen_button.h" #include "scene/main/window.h" -#include "scene/scene_string_names.h" void TouchScreenButton::set_texture_normal(const Ref<Texture2D> &p_texture) { if (texture_normal == p_texture) { return; } if (texture_normal.is_valid()) { - texture_normal->disconnect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + texture_normal->disconnect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } texture_normal = p_texture; if (texture_normal.is_valid()) { - texture_normal->connect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED); + texture_normal->connect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED); } queue_redraw(); } @@ -56,11 +55,11 @@ void TouchScreenButton::set_texture_pressed(const Ref<Texture2D> &p_texture_pres return; } if (texture_pressed.is_valid()) { - texture_pressed->disconnect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + texture_pressed->disconnect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } texture_pressed = p_texture_pressed; if (texture_pressed.is_valid()) { - texture_pressed->connect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED); + texture_pressed->connect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED); } queue_redraw(); } @@ -306,7 +305,7 @@ void TouchScreenButton::_press(int p_finger_pressed) { get_viewport()->push_input(iea, true); } - emit_signal(SNAME("pressed")); + emit_signal(SceneStringName(pressed)); queue_redraw(); } @@ -371,10 +370,10 @@ bool TouchScreenButton::is_passby_press_enabled() const { #ifndef DISABLE_DEPRECATED bool TouchScreenButton::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == SNAME("normal")) { // Compatibility with Godot 3.x. + if (p_name == CoreStringName(normal)) { // Compatibility with Godot 3.x. set_texture_normal(p_value); return true; - } else if (p_name == SNAME("pressed")) { // Compatibility with Godot 3.x. + } else if (p_name == SceneStringName(pressed)) { // Compatibility with Godot 3.x. set_texture_pressed(p_value); return true; } diff --git a/scene/2d/visible_on_screen_notifier_2d.cpp b/scene/2d/visible_on_screen_notifier_2d.cpp index 89b2c20b20..c64507fe32 100644 --- a/scene/2d/visible_on_screen_notifier_2d.cpp +++ b/scene/2d/visible_on_screen_notifier_2d.cpp @@ -30,8 +30,6 @@ #include "visible_on_screen_notifier_2d.h" -#include "scene/scene_string_names.h" - #ifdef TOOLS_ENABLED Rect2 VisibleOnScreenNotifier2D::_edit_get_rect() const { return rect; @@ -48,7 +46,7 @@ void VisibleOnScreenNotifier2D::_visibility_enter() { } on_screen = true; - emit_signal(SceneStringNames::get_singleton()->screen_entered); + emit_signal(SceneStringName(screen_entered)); _screen_enter(); } void VisibleOnScreenNotifier2D::_visibility_exit() { @@ -57,7 +55,7 @@ void VisibleOnScreenNotifier2D::_visibility_exit() { } on_screen = false; - emit_signal(SceneStringNames::get_singleton()->screen_exited); + emit_signal(SceneStringName(screen_exited)); _screen_exit(); } diff --git a/scene/3d/audio_listener_3d.cpp b/scene/3d/audio_listener_3d.cpp index 437d840d5f..4a18447b3b 100644 --- a/scene/3d/audio_listener_3d.cpp +++ b/scene/3d/audio_listener_3d.cpp @@ -55,7 +55,7 @@ bool AudioListener3D::_set(const StringName &p_name, const Variant &p_value) { bool AudioListener3D::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "current") { - if (is_inside_tree() && get_tree()->is_node_being_edited(this)) { + if (is_part_of_edited_scene()) { r_ret = current; } else { r_ret = is_current(); @@ -81,7 +81,7 @@ void AudioListener3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { bool first_listener = get_viewport()->_audio_listener_3d_add(this); - if (!get_tree()->is_node_being_edited(this) && (current || first_listener)) { + if (!is_part_of_edited_scene() && (current || first_listener)) { make_current(); } } break; @@ -91,7 +91,7 @@ void AudioListener3D::_notification(int p_what) { } break; case NOTIFICATION_EXIT_WORLD: { - if (!get_tree()->is_node_being_edited(this)) { + if (!is_part_of_edited_scene()) { if (is_current()) { clear_current(); current = true; //keep it true @@ -133,7 +133,7 @@ void AudioListener3D::clear_current() { } bool AudioListener3D::is_current() const { - if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { + if (is_inside_tree() && !is_part_of_edited_scene()) { return get_viewport()->get_audio_listener_3d() == this; } else { return current; diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index f1f9a04ea0..0cef56dbf2 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -496,6 +496,7 @@ Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { } void AudioStreamPlayer3D::set_volume_db(float p_volume) { + ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN."); internal->volume_db = p_volume; } diff --git a/scene/3d/bone_attachment_3d.compat.inc b/scene/3d/bone_attachment_3d.compat.inc new file mode 100644 index 0000000000..1b21612d5f --- /dev/null +++ b/scene/3d/bone_attachment_3d.compat.inc @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* bone_attachment_3d.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +#include "bone_attachment_3d.h" + +void BoneAttachment3D::_on_bone_pose_update_bind_compat_90575(int p_bone_index) { + return on_skeleton_update(); +} + +void BoneAttachment3D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment3D::_on_bone_pose_update_bind_compat_90575); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 76e89f24d8..2716738684 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "bone_attachment_3d.h" +#include "bone_attachment_3d.compat.inc" void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "bone_name") { @@ -148,9 +149,9 @@ void BoneAttachment3D::_check_bind() { bone_idx = sk->find_bone(bone_name); } if (bone_idx != -1) { - sk->connect(SNAME("bone_pose_changed"), callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); + sk->connect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update)); bound = true; - callable_mp(this, &BoneAttachment3D::on_bone_pose_update).call_deferred(bone_idx); + callable_mp(this, &BoneAttachment3D::on_skeleton_update); } } } @@ -176,7 +177,7 @@ void BoneAttachment3D::_check_unbind() { Skeleton3D *sk = _get_skeleton3d(); if (sk) { - sk->disconnect(SNAME("bone_pose_changed"), callable_mp(this, &BoneAttachment3D::on_bone_pose_update)); + sk->disconnect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update)); } bound = false; } @@ -187,7 +188,7 @@ void BoneAttachment3D::_transform_changed() { return; } - if (override_pose) { + if (override_pose && !overriding) { Skeleton3D *sk = _get_skeleton3d(); ERR_FAIL_NULL_MSG(sk, "Cannot override pose: Skeleton not found!"); @@ -198,8 +199,11 @@ void BoneAttachment3D::_transform_changed() { our_trans = sk->get_global_transform().affine_inverse() * get_global_transform(); } - sk->set_bone_global_pose_override(bone_idx, our_trans, 1.0, true); + overriding = true; + sk->set_bone_global_pose(bone_idx, our_trans); + sk->force_update_all_dirty_bones(); } + overriding = false; } void BoneAttachment3D::set_bone_name(const String &p_name) { @@ -246,14 +250,6 @@ void BoneAttachment3D::set_override_pose(bool p_override) { override_pose = p_override; set_notify_transform(override_pose); set_process_internal(override_pose); - - if (!override_pose) { - Skeleton3D *sk = _get_skeleton3d(); - if (sk) { - sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); - } - _transform_changed(); - } notify_property_list_changed(); } @@ -313,8 +309,12 @@ void BoneAttachment3D::_notification(int p_what) { } } -void BoneAttachment3D::on_bone_pose_update(int p_bone_index) { - if (bone_idx == p_bone_index) { +void BoneAttachment3D::on_skeleton_update() { + if (updating) { + return; + } + updating = true; + if (bone_idx >= 0) { Skeleton3D *sk = _get_skeleton3d(); if (sk) { if (!override_pose) { @@ -331,6 +331,7 @@ void BoneAttachment3D::on_bone_pose_update(int p_bone_index) { } } } + updating = false; } #ifdef TOOLS_ENABLED void BoneAttachment3D::notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Dictionary p_rename_map) { @@ -371,7 +372,7 @@ void BoneAttachment3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bone_idx", "bone_idx"), &BoneAttachment3D::set_bone_idx); ClassDB::bind_method(D_METHOD("get_bone_idx"), &BoneAttachment3D::get_bone_idx); - ClassDB::bind_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment3D::on_bone_pose_update); + ClassDB::bind_method(D_METHOD("on_skeleton_update"), &BoneAttachment3D::on_skeleton_update); ClassDB::bind_method(D_METHOD("set_override_pose", "override_pose"), &BoneAttachment3D::set_override_pose); ClassDB::bind_method(D_METHOD("get_override_pose"), &BoneAttachment3D::get_override_pose); diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index 1bf44c2756..e19180b0ea 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -45,6 +45,7 @@ class BoneAttachment3D : public Node3D { bool override_pose = false; bool _override_dirty = false; + bool overriding = false; bool use_external_skeleton = false; NodePath external_skeleton_node; @@ -53,6 +54,7 @@ class BoneAttachment3D : public Node3D { void _check_bind(); void _check_unbind(); + bool updating = false; void _transform_changed(); void _update_external_skeleton_cache(); Skeleton3D *_get_skeleton3d(); @@ -65,6 +67,10 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + virtual void _on_bone_pose_update_bind_compat_90575(int p_bone_index); + static void _bind_compatibility_methods(); +#endif public: #ifdef TOOLS_ENABLED @@ -87,7 +93,7 @@ public: void set_external_skeleton(NodePath p_skeleton); NodePath get_external_skeleton() const; - virtual void on_bone_pose_update(int p_bone_index); + virtual void on_skeleton_update(); #ifdef TOOLS_ENABLED virtual void notify_rebind_required(); diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index e8bd498e1f..8515aacba7 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -90,7 +90,7 @@ void Camera3D::_update_camera() { RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform()); - if (get_tree()->is_node_being_edited(this) || !is_current()) { + if (is_part_of_edited_scene() || !is_current()) { return; } @@ -126,7 +126,7 @@ void Camera3D::_notification(int p_what) { } break; case NOTIFICATION_EXIT_WORLD: { - if (!get_tree()->is_node_being_edited(this)) { + if (!is_part_of_edited_scene()) { if (is_current()) { clear_current(); current = true; //keep it true @@ -286,7 +286,7 @@ void Camera3D::set_current(bool p_enabled) { } bool Camera3D::is_current() const { - if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) { + if (is_inside_tree() && !is_part_of_edited_scene()) { return get_viewport()->get_camera_3d() == this; } else { return current; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 8725ff19c9..03fe5e1fad 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -37,7 +37,6 @@ #include "scene/resources/gradient_texture.h" #include "scene/resources/image_texture.h" #include "scene/resources/particle_process_material.h" -#include "scene/scene_string_names.h" AABB CPUParticles3D::get_aabb() const { return AABB(); @@ -69,7 +68,7 @@ void CPUParticles3D::set_amount(int p_amount) { for (int i = 0; i < p_amount; i++) { w[i].active = false; - w[i].custom[3] = 0.0; // Make sure w component isn't garbage data + w[i].custom[3] = 1.0; // Make sure w component isn't garbage data and doesn't break shaders with CUSTOM.y/Custom.w } } @@ -813,9 +812,10 @@ void CPUParticles3D::_particles_process(double p_delta) { p.custom[0] = Math::deg_to_rad(base_angle); //angle p.custom[1] = 0.0; //phase p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); //animation offset (0-1) + p.custom[3] = (1.0 - Math::randf() * lifetime_randomness); p.transform = Transform3D(); p.time = 0; - p.lifetime = lifetime * (1.0 - Math::randf() * lifetime_randomness); + p.lifetime = lifetime * p.custom[3]; p.base_color = Color(1, 1, 1, 1); switch (emission_shape) { @@ -879,10 +879,10 @@ void CPUParticles3D::_particles_process(double p_delta) { } break; case EMISSION_SHAPE_RING: { real_t ring_random_angle = Math::randf() * Math_TAU; - real_t ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius; - Vector3 axis = emission_ring_axis.normalized(); + real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius); + Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized(); Vector3 ortho_axis; - if (axis == Vector3(1.0, 0.0, 0.0)) { + if (axis.abs() == Vector3(1.0, 0.0, 0.0)) { ortho_axis = Vector3(0.0, 1.0, 0.0).cross(axis); } else { ortho_axis = Vector3(1.0, 0.0, 0.0).cross(axis); @@ -1124,7 +1124,7 @@ void CPUParticles3D::_particles_process(double p_delta) { //turn particle by rotation in Y if (particle_flags[PARTICLE_FLAG_ROTATE_Y]) { Basis rot_y(Vector3(0, 1, 0), p.custom[0]); - p.transform.basis = p.transform.basis * rot_y; + p.transform.basis = rot_y; } } @@ -1155,7 +1155,7 @@ void CPUParticles3D::_particles_process(double p_delta) { } if (!Math::is_equal_approx(time, 0.0) && active && !should_be_active) { active = false; - emit_signal(SceneStringNames::get_singleton()->finished); + emit_signal(SceneStringName(finished)); } } diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 6878df21d8..485599d0fb 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -31,7 +31,7 @@ #include "decal.h" void Decal::set_size(const Vector3 &p_size) { - size = Vector3(MAX(0.001, p_size.x), MAX(0.001, p_size.y), MAX(0.001, p_size.z)); + size = p_size.maxf(0.001); RS::get_singleton()->decal_set_size(decal, size); update_gizmos(); } @@ -231,7 +231,7 @@ void Decal::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "texture_emission", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_EMISSION); ADD_GROUP("Parameters", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_energy", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_emission_energy", "get_emission_energy"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_emission_energy", "get_emission_energy"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "albedo_mix", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_albedo_mix", "get_albedo_mix"); // A Normal Fade of 1.0 causes the decal to be invisible even if fully perpendicular to a surface. diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index 12ca1888c4..54631a8dff 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -73,9 +73,7 @@ bool FogVolume::_get(const StringName &p_name, Variant &r_property) const { void FogVolume::set_size(const Vector3 &p_size) { size = p_size; - size.x = MAX(0.0, size.x); - size.y = MAX(0.0, size.y); - size.z = MAX(0.0, size.z); + size = size.maxf(0); RS::get_singleton()->fog_volume_set_size(_get_volume(), size); update_gizmos(); } diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 16813b9017..3771b385e5 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -34,7 +34,6 @@ #include "scene/resources/curve_texture.h" #include "scene/resources/gradient_texture.h" #include "scene/resources/particle_process_material.h" -#include "scene/scene_string_names.h" AABB GPUParticles3D::get_aabb() const { return AABB(); @@ -478,7 +477,7 @@ void GPUParticles3D::_notification(int p_what) { } if (time > active_time) { if (active && !signal_canceled) { - emit_signal(SceneStringNames::get_singleton()->finished); + emit_signal(SceneStringName(finished)); } active = false; if (!emitting) { diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index cbc75801b0..3a05ec9c9e 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -330,7 +330,7 @@ void GPUParticlesCollisionSDF3D::_find_closest_distance(const Vector3 &p_pos, co Vector3 center = p_bvh[p_bvh_cell].bounds.position + he; Vector3 rel = (p_pos - center).abs(); - Vector3 closest(MIN(rel.x, he.x), MIN(rel.y, he.y), MIN(rel.z, he.z)); + Vector3 closest = rel.min(he); float d = rel.distance_to(closest); if (d >= r_closest_distance) { @@ -382,9 +382,7 @@ Vector3i GPUParticlesCollisionSDF3D::get_estimated_cell_size() const { float cell_size = aabb.get_longest_axis_size() / float(subdiv); Vector3i sdf_size = Vector3i(aabb.size / cell_size); - sdf_size.x = MAX(1, sdf_size.x); - sdf_size.y = MAX(1, sdf_size.y); - sdf_size.z = MAX(1, sdf_size.z); + sdf_size = sdf_size.maxi(1); return sdf_size; } @@ -397,9 +395,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { float cell_size = aabb.get_longest_axis_size() / float(subdiv); Vector3i sdf_size = Vector3i(aabb.size / cell_size); - sdf_size.x = MAX(1, sdf_size.x); - sdf_size.y = MAX(1, sdf_size.y); - sdf_size.z = MAX(1, sdf_size.z); + sdf_size = sdf_size.maxi(1); if (bake_begin_function) { bake_begin_function(100); diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index 9f71c881a9..0f2ce829eb 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -32,7 +32,6 @@ #include "scene/main/viewport.h" #include "scene/resources/theme.h" -#include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" void Label3D::_bind_methods() { @@ -636,6 +635,10 @@ void Label3D::_shape() { } void Label3D::set_text(const String &p_string) { + if (text == p_string) { + return; + } + text = p_string; xl_text = atr(p_string); dirty_text = true; diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 86ff6d15dd..ef492a6994 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -397,7 +397,10 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc const BSPSimplex &s = p_simplices[p_simplex]; for (int i = 0; i < 4; i++) { const Vector3 v = p_points[s.vertices[i]]; - if (p_plane.has_point(v)) { + // The tolerance used here comes from experiments on scenes up to + // 1000x1000x100 meters. If it's any smaller, some simplices will + // appear to self-intersect due to a lack of precision in Plane. + if (p_plane.has_point(v, 1.0 / (1 << 13))) { // Coplanar. } else if (p_plane.is_point_over(v)) { over++; @@ -419,7 +422,8 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc //#define DEBUG_BSP int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const LocalVector<Plane> &p_planes, LocalVector<int32_t> &planes_tested, const LocalVector<BSPSimplex> &p_simplices, const LocalVector<int32_t> &p_simplex_indices, LocalVector<BSPNode> &bsp_nodes) { - //if we reach here, it means there is more than one simplex + ERR_FAIL_COND_V(p_simplex_indices.size() < 2, -1); + int32_t node_index = (int32_t)bsp_nodes.size(); bsp_nodes.push_back(BSPNode()); @@ -477,13 +481,14 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc float score = 0; //by default, score is 0 (worst) if (over_count > 0) { - //give score mainly based on ratio (under / over), this means that this plane is splitting simplices a lot, but its balanced - score = float(under_count) / over_count; + // Simplices that are intersected by the plane are moved into both the over + // and under subtrees which makes the entire tree deeper, so the best plane + // will have the least intersections while separating the simplices evenly. + float balance = float(under_count) / over_count; + float separation = float(over_count + under_count) / p_simplex_indices.size(); + score = balance * separation * separation; } - //adjusting priority over least splits, probably not a great idea - //score *= Math::sqrt(float(over_count + under_count) / p_simplex_indices.size()); //also multiply score - if (score > best_plane_score) { best_plane = plane; best_plane_score = score; @@ -491,6 +496,44 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc } } + // We often end up with two (or on rare occasions, three) simplices that are + // either disjoint or share one vertex and don't have a separating plane + // among their faces. The fallback is to loop through new planes created + // with one vertex of the first simplex and two vertices of the second until + // we find a winner. + if (best_plane_score == 0) { + const BSPSimplex &simplex0 = p_simplices[p_simplex_indices[0]]; + const BSPSimplex &simplex1 = p_simplices[p_simplex_indices[1]]; + + for (uint32_t i = 0; i < 4 && !best_plane_score; i++) { + Vector3 v0 = p_points[simplex0.vertices[i]]; + for (uint32_t j = 0; j < 3 && !best_plane_score; j++) { + if (simplex0.vertices[i] == simplex1.vertices[j]) { + break; + } + Vector3 v1 = p_points[simplex1.vertices[j]]; + for (uint32_t k = j + 1; k < 4; k++) { + if (simplex0.vertices[i] == simplex1.vertices[k]) { + break; + } + Vector3 v2 = p_points[simplex1.vertices[k]]; + + Plane plane = Plane(v0, v1, v2); + if (plane == Plane()) { // When v0, v1, and v2 are collinear, they can't form a plane. + continue; + } + int32_t side0 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[0]); + int32_t side1 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[1]); + if ((side0 == 1 && side1 == -1) || (side0 == -1 && side1 == 1)) { + best_plane = plane; + best_plane_score = 1.0; + break; + } + } + } + } + } + LocalVector<int32_t> indices_over; LocalVector<int32_t> indices_under; @@ -515,8 +558,6 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc #endif if (best_plane_score < 0.0 || indices_over.size() == p_simplex_indices.size() || indices_under.size() == p_simplex_indices.size()) { - ERR_FAIL_COND_V(p_simplex_indices.size() <= 1, 0); //should not happen, this is a bug - // Failed to separate the tetrahedrons using planes // this means Delaunay broke at some point. // Luckily, because we are using tetrahedrons, we can resort to @@ -1061,7 +1102,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } } - Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization); + Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization); if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL) { return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL; @@ -1409,6 +1450,14 @@ float LightmapGI::get_denoiser_strength() const { return denoiser_strength; } +void LightmapGI::set_denoiser_range(int p_denoiser_range) { + denoiser_range = p_denoiser_range; +} + +int LightmapGI::get_denoiser_range() const { + return denoiser_range; +} + void LightmapGI::set_directional(bool p_enable) { directional = p_enable; } @@ -1552,6 +1601,9 @@ void LightmapGI::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "denoiser_strength" && !use_denoiser) { p_property.usage = PROPERTY_USAGE_NONE; } + if (p_property.name == "denoiser_range" && !use_denoiser) { + p_property.usage = PROPERTY_USAGE_NONE; + } } void LightmapGI::_bind_methods() { @@ -1597,6 +1649,9 @@ void LightmapGI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_denoiser_strength", "denoiser_strength"), &LightmapGI::set_denoiser_strength); ClassDB::bind_method(D_METHOD("get_denoiser_strength"), &LightmapGI::get_denoiser_strength); + ClassDB::bind_method(D_METHOD("set_denoiser_range", "denoiser_range"), &LightmapGI::set_denoiser_range); + ClassDB::bind_method(D_METHOD("get_denoiser_range"), &LightmapGI::get_denoiser_range); + ClassDB::bind_method(D_METHOD("set_interior", "enable"), &LightmapGI::set_interior); ClassDB::bind_method(D_METHOD("is_interior"), &LightmapGI::is_interior); @@ -1620,6 +1675,7 @@ void LightmapGI::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "denoiser_strength", PROPERTY_HINT_RANGE, "0.001,0.2,0.001,or_greater"), "set_denoiser_strength", "get_denoiser_strength"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "denoiser_range", PROPERTY_HINT_RANGE, "1,20"), "set_denoiser_range", "get_denoiser_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texel_scale", PROPERTY_HINT_RANGE, "0.01,100.0,0.01"), "set_texel_scale", "get_texel_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size", PROPERTY_HINT_RANGE, "2048,16384,1"), "set_max_texture_size", "get_max_texture_size"); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 765e4a731d..527667177b 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -156,6 +156,7 @@ private: BakeQuality bake_quality = BAKE_QUALITY_MEDIUM; bool use_denoiser = true; float denoiser_strength = 0.1f; + int denoiser_range = 10; int bounces = 3; float bounce_indirect_energy = 1.0; float bias = 0.0005; @@ -256,6 +257,9 @@ public: void set_denoiser_strength(float p_denoiser_strength); float get_denoiser_strength() const; + void set_denoiser_range(int p_denoiser_range); + int get_denoiser_range() const; + void set_directional(bool p_enable); bool is_directional() const; diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h index 446ed794ed..39181ad9a2 100644 --- a/scene/3d/lightmapper.h +++ b/scene/3d/lightmapper.h @@ -61,7 +61,7 @@ protected: static LightmapRaycaster *(*create_function)(); public: - // compatible with embree3 rays + // Compatible with embree4 rays. struct __aligned(16) Ray { const static unsigned int INVALID_GEOMETRY_ID = ((unsigned int)-1); // from rtcore_common.h @@ -180,7 +180,7 @@ public: virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0; virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0; virtual void add_probe(const Vector3 &p_position) = 0; - virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0; virtual int get_bake_texture_count() const = 0; virtual Ref<Image> get_bake_texture(int p_index) const = 0; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 8635240655..85bf8846b9 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -204,6 +204,10 @@ Ref<Skin> MeshInstance3D::get_skin() const { return skin; } +Ref<SkinReference> MeshInstance3D::get_skin_reference() const { + return skin_ref; +} + void MeshInstance3D::set_skeleton_path(const NodePath &p_skeleton) { skeleton_path = p_skeleton; if (!is_inside_tree()) { @@ -511,6 +515,162 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p return false; } +Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) { + Ref<ArrayMesh> source_mesh = get_mesh(); + ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh."); + + Ref<ArrayMesh> bake_mesh; + + if (p_existing.is_valid()) { + ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh."); + ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh."); + + bake_mesh = p_existing; + } else { + bake_mesh.instantiate(); + } + + Mesh::BlendShapeMode blend_shape_mode = source_mesh->get_blend_shape_mode(); + int mesh_surface_count = source_mesh->get_surface_count(); + + bake_mesh->clear_surfaces(); + bake_mesh->set_blend_shape_mode(blend_shape_mode); + + for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) { + uint32_t surface_format = source_mesh->surface_get_format(surface_index); + + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX)); + + const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index); + + ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>()); + + const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX]; + const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL]; + const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT]; + + Array new_mesh_arrays; + new_mesh_arrays.resize(Mesh::ARRAY_MAX); + for (int i = 0; i < source_mesh_arrays.size(); i++) { + if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT) { + continue; + } + new_mesh_arrays[i] = source_mesh_arrays[i]; + } + + bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size(); + bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size(); + + Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array; + Vector<Vector3> lerped_normal_array = source_mesh_normal_array; + Vector<float> lerped_tangent_array = source_mesh_tangent_array; + + const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr(); + const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr(); + const float *source_tangents_ptr = source_mesh_tangent_array.ptr(); + + Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw(); + Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw(); + float *lerped_tangents_ptrw = lerped_tangent_array.ptrw(); + + const Array &blendshapes_mesh_arrays = source_mesh->surface_get_blend_shape_arrays(surface_index); + int blend_shape_count = source_mesh->get_blend_shape_count(); + ERR_FAIL_COND_V(blendshapes_mesh_arrays.size() != blend_shape_count, Ref<ArrayMesh>()); + + for (int blendshape_index = 0; blendshape_index < blend_shape_count; blendshape_index++) { + float blend_weight = get_blend_shape_value(blendshape_index); + if (abs(blend_weight) <= 0.0001) { + continue; + } + + const Array &blendshape_mesh_arrays = blendshapes_mesh_arrays[blendshape_index]; + + const Vector<Vector3> &blendshape_vertex_array = blendshape_mesh_arrays[Mesh::ARRAY_VERTEX]; + const Vector<Vector3> &blendshape_normal_array = blendshape_mesh_arrays[Mesh::ARRAY_NORMAL]; + const Vector<float> &blendshape_tangent_array = blendshape_mesh_arrays[Mesh::ARRAY_TANGENT]; + + ERR_FAIL_COND_V(source_mesh_vertex_array.size() != blendshape_vertex_array.size(), Ref<ArrayMesh>()); + ERR_FAIL_COND_V(source_mesh_normal_array.size() != blendshape_normal_array.size(), Ref<ArrayMesh>()); + ERR_FAIL_COND_V(source_mesh_tangent_array.size() != blendshape_tangent_array.size(), Ref<ArrayMesh>()); + + const Vector3 *blendshape_vertices_ptr = blendshape_vertex_array.ptr(); + const Vector3 *blendshape_normals_ptr = blendshape_normal_array.ptr(); + const float *blendshape_tangents_ptr = blendshape_tangent_array.ptr(); + + if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_NORMALIZED) { + for (int i = 0; i < source_mesh_vertex_array.size(); i++) { + const Vector3 &source_vertex = source_vertices_ptr[i]; + const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i]; + Vector3 lerped_vertex = source_vertex.lerp(blendshape_vertex, blend_weight) - source_vertex; + lerped_vertices_ptrw[i] += lerped_vertex; + + if (use_normal_array) { + const Vector3 &source_normal = source_normals_ptr[i]; + const Vector3 &blendshape_normal = blendshape_normals_ptr[i]; + Vector3 lerped_normal = source_normal.lerp(blendshape_normal, blend_weight) - source_normal; + lerped_normals_ptrw[i] += lerped_normal; + } + + if (use_tangent_array) { + int tangent_index = i * 4; + const Vector4 source_tangent = Vector4( + source_tangents_ptr[tangent_index], + source_tangents_ptr[tangent_index + 1], + source_tangents_ptr[tangent_index + 2], + source_tangents_ptr[tangent_index + 3]); + const Vector4 blendshape_tangent = Vector4( + blendshape_tangents_ptr[tangent_index], + blendshape_tangents_ptr[tangent_index + 1], + blendshape_tangents_ptr[tangent_index + 2], + blendshape_tangents_ptr[tangent_index + 3]); + Vector4 lerped_tangent = source_tangent.lerp(blendshape_tangent, blend_weight); + lerped_tangents_ptrw[tangent_index] += lerped_tangent.x; + lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y; + lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z; + lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w; + } + } + } else if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_RELATIVE) { + for (int i = 0; i < source_mesh_vertex_array.size(); i++) { + const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i]; + lerped_vertices_ptrw[i] += blendshape_vertex * blend_weight; + + if (use_normal_array) { + const Vector3 &blendshape_normal = blendshape_normals_ptr[i]; + lerped_normals_ptrw[i] += blendshape_normal * blend_weight; + } + + if (use_tangent_array) { + int tangent_index = i * 4; + const Vector4 blendshape_tangent = Vector4( + blendshape_tangents_ptr[tangent_index], + blendshape_tangents_ptr[tangent_index + 1], + blendshape_tangents_ptr[tangent_index + 2], + blendshape_tangents_ptr[tangent_index + 3]); + Vector4 lerped_tangent = blendshape_tangent * blend_weight; + lerped_tangents_ptrw[tangent_index] += lerped_tangent.x; + lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y; + lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z; + lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w; + } + } + } + } + + new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array; + if (use_normal_array) { + new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array; + } + if (use_tangent_array) { + new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array; + } + + bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format); + } + + return bake_mesh; +} + void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh); ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh); @@ -518,6 +678,7 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skeleton_path"), &MeshInstance3D::get_skeleton_path); ClassDB::bind_method(D_METHOD("set_skin", "skin"), &MeshInstance3D::set_skin); ClassDB::bind_method(D_METHOD("get_skin"), &MeshInstance3D::get_skin); + ClassDB::bind_method(D_METHOD("get_skin_reference"), &MeshInstance3D::get_skin_reference); ClassDB::bind_method(D_METHOD("get_surface_override_material_count"), &MeshInstance3D::get_surface_override_material_count); ClassDB::bind_method(D_METHOD("set_surface_override_material", "surface", "material"), &MeshInstance3D::set_surface_override_material); @@ -537,6 +698,9 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value); ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents); + + ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>())); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_GROUP("Skeleton", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin"); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index add6bfe15e..8a7e03c5b3 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -75,6 +75,8 @@ public: void set_skeleton_path(const NodePath &p_skeleton); NodePath get_skeleton_path(); + Ref<SkinReference> get_skin_reference() const; + int get_blend_shape_count() const; int find_blend_shape_by_name(const StringName &p_name); float get_blend_shape_value(int p_blend_shape) const; @@ -99,6 +101,8 @@ public: virtual AABB get_aabb() const override; + Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>()); + MeshInstance3D(); ~MeshInstance3D(); }; diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index eb52d4540e..dff413f5d2 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -99,6 +99,12 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_position", "position"), &NavigationAgent3D::set_target_position); ClassDB::bind_method(D_METHOD("get_target_position"), &NavigationAgent3D::get_target_position); + ClassDB::bind_method(D_METHOD("set_simplify_path", "enabled"), &NavigationAgent3D::set_simplify_path); + ClassDB::bind_method(D_METHOD("get_simplify_path"), &NavigationAgent3D::get_simplify_path); + + ClassDB::bind_method(D_METHOD("set_simplify_epsilon", "epsilon"), &NavigationAgent3D::set_simplify_epsilon); + ClassDB::bind_method(D_METHOD("get_simplify_epsilon"), &NavigationAgent3D::get_simplify_epsilon); + ClassDB::bind_method(D_METHOD("get_next_path_position"), &NavigationAgent3D::get_next_path_position); ClassDB::bind_method(D_METHOD("set_velocity_forced", "velocity"), &NavigationAgent3D::set_velocity_forced); @@ -140,6 +146,8 @@ void NavigationAgent3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "pathfinding_algorithm", PROPERTY_HINT_ENUM, "AStar"), "set_pathfinding_algorithm", "get_pathfinding_algorithm"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_postprocessing", PROPERTY_HINT_ENUM, "Corridorfunnel,Edgecentered"), "set_path_postprocessing", "get_path_postprocessing"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_metadata_flags", PROPERTY_HINT_FLAGS, "Include Types,Include RIDs,Include Owners"), "set_path_metadata_flags", "get_path_metadata_flags"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simplify_path"), "set_simplify_path", "get_simplify_path"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "simplify_epsilon", PROPERTY_HINT_RANGE, "0.0,10.0,0.001,or_greater,suffix:m"), "set_simplify_epsilon", "get_simplify_epsilon"); ADD_GROUP("Avoidance", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); @@ -464,6 +472,24 @@ void NavigationAgent3D::set_path_postprocessing(const NavigationPathQueryParamet navigation_query->set_path_postprocessing(path_postprocessing); } +void NavigationAgent3D::set_simplify_path(bool p_enabled) { + simplify_path = p_enabled; + navigation_query->set_simplify_path(simplify_path); +} + +bool NavigationAgent3D::get_simplify_path() const { + return simplify_path; +} + +void NavigationAgent3D::set_simplify_epsilon(real_t p_epsilon) { + simplify_epsilon = MAX(0.0, p_epsilon); + navigation_query->set_simplify_epsilon(simplify_epsilon); +} + +real_t NavigationAgent3D::get_simplify_epsilon() const { + return simplify_epsilon; +} + void NavigationAgent3D::set_path_metadata_flags(BitField<NavigationPathQueryParameters3D::PathMetadataFlags> p_path_metadata_flags) { if (path_metadata_flags == p_path_metadata_flags) { return; @@ -839,7 +865,7 @@ void NavigationAgent3D::_trigger_waypoint_reached() { Dictionary details; const Vector3 waypoint = navigation_path[navigation_path_index]; - details[SNAME("position")] = waypoint; + details[CoreStringName(position)] = waypoint; int waypoint_type = -1; if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) { diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h index 4eaed83149..ade6afd445 100644 --- a/scene/3d/navigation_agent_3d.h +++ b/scene/3d/navigation_agent_3d.h @@ -66,6 +66,8 @@ class NavigationAgent3D : public Node { real_t time_horizon_obstacles = 0.0; real_t max_speed = 10.0; real_t path_max_distance = 5.0; + bool simplify_path = false; + real_t simplify_epsilon = 0.0; Vector3 target_position; @@ -200,6 +202,12 @@ public: void set_target_position(Vector3 p_position); Vector3 get_target_position() const; + void set_simplify_path(bool p_enabled); + bool get_simplify_path() const; + + void set_simplify_epsilon(real_t p_epsilon); + real_t get_simplify_epsilon() const; + Vector3 get_next_path_position(); Ref<NavigationPathQueryResult3D> get_current_navigation_result() const { return navigation_result; } diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 96f7fb137c..f2ac8f789c 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -63,12 +63,21 @@ void NavigationObstacle3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_3d_avoidance", "enabled"), &NavigationObstacle3D::set_use_3d_avoidance); ClassDB::bind_method(D_METHOD("get_use_3d_avoidance"), &NavigationObstacle3D::get_use_3d_avoidance); - ADD_GROUP("Avoidance", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle3D::set_affect_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle3D::get_affect_navigation_mesh); + + ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle3D::set_carve_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle3D::get_carve_navigation_mesh); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_GROUP("NavigationMesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh"); + ADD_GROUP("Avoidance", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_3d_avoidance"), "set_use_3d_avoidance", "get_use_3d_avoidance"); } @@ -321,6 +330,22 @@ void NavigationObstacle3D::set_velocity(const Vector3 p_velocity) { velocity_submitted = true; } +void NavigationObstacle3D::set_affect_navigation_mesh(bool p_enabled) { + affect_navigation_mesh = p_enabled; +} + +bool NavigationObstacle3D::get_affect_navigation_mesh() const { + return affect_navigation_mesh; +} + +void NavigationObstacle3D::set_carve_navigation_mesh(bool p_enabled) { + carve_navigation_mesh = p_enabled; +} + +bool NavigationObstacle3D::get_carve_navigation_mesh() const { + return carve_navigation_mesh; +} + void NavigationObstacle3D::_update_map(RID p_map) { NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map); map_current = p_map; diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index 51a84c9a54..e9a4669fa2 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -57,6 +57,9 @@ class NavigationObstacle3D : public Node3D { Vector3 previous_velocity; bool velocity_submitted = false; + bool affect_navigation_mesh = false; + bool carve_navigation_mesh = false; + #ifdef DEBUG_ENABLED RID fake_agent_radius_debug_instance; Ref<ArrayMesh> fake_agent_radius_debug_mesh; @@ -108,6 +111,12 @@ public: void _avoidance_done(Vector3 p_new_velocity); // Dummy + void set_affect_navigation_mesh(bool p_enabled); + bool get_affect_navigation_mesh() const; + + void set_carve_navigation_mesh(bool p_enabled); + bool get_carve_navigation_mesh() const; + private: void _update_map(RID p_map); void _update_position(const Vector3 p_position); diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index d8a63c60a2..40e04f0fb4 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -30,7 +30,7 @@ #include "navigation_region_3d.h" -#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" #include "servers/navigation_server_3d.h" RID NavigationRegion3D::get_rid() const { @@ -474,7 +474,7 @@ void NavigationRegion3D::_update_debug_mesh() { return; } - if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { + if (!NavigationServer3D::get_singleton()->get_debug_enabled() || !NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (debug_instance.is_valid()) { RS::get_singleton()->instance_set_visible(debug_instance, false); } @@ -640,7 +640,7 @@ void NavigationRegion3D::_update_debug_mesh() { #ifdef DEBUG_ENABLED void NavigationRegion3D::_update_debug_edge_connections_mesh() { - if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { + if (!NavigationServer3D::get_singleton()->get_debug_enabled() || !NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { if (debug_edge_connections_instance.is_valid()) { RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false); } diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 5c081a0b47..2e08afb30d 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -33,7 +33,6 @@ #include "scene/3d/visual_instance_3d.h" #include "scene/main/viewport.h" #include "scene/property_utils.h" -#include "scene/scene_string_names.h" /* @@ -193,12 +192,12 @@ void Node3D::_notification(int p_what) { ERR_FAIL_NULL(data.viewport); if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_enter_world); + get_script_instance()->call(SNAME("_enter_world")); } #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SNAME("_request_gizmo_for_id"), get_instance_id()); + if (is_part_of_edited_scene()) { + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_request_gizmo_for_id"), get_instance_id()); } #endif } break; @@ -211,7 +210,7 @@ void Node3D::_notification(int p_what) { #endif if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_exit_world); + get_script_instance()->call(SNAME("_exit_world")); } data.viewport = nullptr; @@ -293,7 +292,7 @@ Vector3 Node3D::get_global_rotation_degrees() const { void Node3D::set_global_rotation(const Vector3 &p_euler_rad) { ERR_THREAD_GUARD; Transform3D transform = get_global_transform(); - transform.basis = Basis::from_euler(p_euler_rad); + transform.basis = Basis::from_euler(p_euler_rad) * Basis::from_scale(transform.basis.get_scale()); set_global_transform(transform); } @@ -564,7 +563,7 @@ void Node3D::update_gizmos() { } if (data.gizmos.is_empty()) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SNAME("_request_gizmo_for_id"), get_instance_id()); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_request_gizmo_for_id"), get_instance_id()); return; } if (data.gizmos_dirty) { @@ -582,8 +581,8 @@ void Node3D::set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transfor return; } - if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_set_subgizmo_selection, this, p_gizmo, p_id, p_transform); + if (is_part_of_edited_scene()) { + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_set_subgizmo_selection"), this, p_gizmo, p_id, p_transform); } #endif } @@ -599,8 +598,8 @@ void Node3D::clear_subgizmo_selection() { return; } - if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { - get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_clear_subgizmo_selection, this); + if (is_part_of_edited_scene()) { + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_clear_subgizmo_selection"), this); } #endif } @@ -753,6 +752,15 @@ void Node3D::set_as_top_level(bool p_enabled) { data.top_level = p_enabled; } +void Node3D::set_as_top_level_keep_local(bool p_enabled) { + ERR_THREAD_GUARD; + if (data.top_level == p_enabled) { + return; + } + data.top_level = p_enabled; + _propagate_transform_changed(this); +} + bool Node3D::is_set_as_top_level() const { ERR_READ_THREAD_GUARD_V(false); return data.top_level; @@ -768,7 +776,7 @@ Ref<World3D> Node3D::get_world_3d() const { void Node3D::_propagate_visibility_changed() { notification(NOTIFICATION_VISIBILITY_CHANGED); - emit_signal(SceneStringNames::get_singleton()->visibility_changed); + emit_signal(SceneStringName(visibility_changed)); #ifdef TOOLS_ENABLED if (!data.gizmos.is_empty()) { diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 7f8c3e6e68..c1667221df 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -227,6 +227,7 @@ public: void clear_gizmos(); void set_as_top_level(bool p_enabled); + void set_as_top_level_keep_local(bool p_enabled); bool is_set_as_top_level() const; void set_disable_scale(bool p_enabled); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 8d995a8785..150771545b 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -192,7 +192,7 @@ void QuadOccluder3D::set_size(const Size2 &p_size) { return; } - size = p_size.max(Size2()); + size = p_size.maxf(0); _update(); } @@ -236,7 +236,7 @@ void BoxOccluder3D::set_size(const Vector3 &p_size) { return; } - size = Vector3(MAX(p_size.x, 0.0f), MAX(p_size.y, 0.0f), MAX(p_size.z, 0.0f)); + size = p_size.maxf(0); _update(); } diff --git a/scene/3d/physical_bone_simulator_3d.cpp b/scene/3d/physical_bone_simulator_3d.cpp new file mode 100644 index 0000000000..ef3c51b032 --- /dev/null +++ b/scene/3d/physical_bone_simulator_3d.cpp @@ -0,0 +1,396 @@ +/**************************************************************************/ +/* physical_bone_simulator_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "physical_bone_simulator_3d.h" + +void PhysicalBoneSimulator3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { + if (p_old) { + if (p_old->is_connected(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed))) { + p_old->disconnect(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed)); + } + if (p_old->is_connected(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) { + p_old->disconnect(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated)); + } + } + if (p_new) { + if (!p_new->is_connected(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed))) { + p_new->connect(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed)); + } + if (!p_new->is_connected(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) { + p_new->connect(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated)); + } + } + _bone_list_changed(); +} + +void PhysicalBoneSimulator3D::_bone_list_changed() { + bones.clear(); + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + for (int i = 0; i < skeleton->get_bone_count(); i++) { + SimulatedBone sb; + sb.parent = skeleton->get_bone_parent(i); + sb.child_bones = skeleton->get_bone_children(i); + bones.push_back(sb); + } + _rebuild_physical_bones_cache(); + _pose_updated(); +} + +void PhysicalBoneSimulator3D::_pose_updated() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton || simulating) { + return; + } + ERR_FAIL_COND(skeleton->get_bone_count() != bones.size()); + for (int i = 0; i < skeleton->get_bone_count(); i++) { + bones.write[i].global_pose = skeleton->get_bone_global_pose(i); + } +} + +void PhysicalBoneSimulator3D::_set_active(bool p_active) { + if (!Engine::get_singleton()->is_editor_hint()) { + _reset_physical_bones_state(); + } +} + +void PhysicalBoneSimulator3D::_reset_physical_bones_state() { + for (int i = 0; i < bones.size(); i += 1) { + if (bones[i].physical_bone) { + bones[i].physical_bone->reset_physics_simulation_state(); + } + } +} + +bool PhysicalBoneSimulator3D::is_simulating_physics() const { + return simulating; +} + +int PhysicalBoneSimulator3D::find_bone(const String &p_name) const { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return -1; + } + return skeleton->find_bone(p_name); +} + +String PhysicalBoneSimulator3D::get_bone_name(int p_bone) const { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return String(); + } + return skeleton->get_bone_name(p_bone); +} + +int PhysicalBoneSimulator3D::get_bone_count() const { + return bones.size(); +} + +bool PhysicalBoneSimulator3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return false; + } + return skeleton->is_bone_parent_of(p_bone, p_parent_bone_id); +} + +void PhysicalBoneSimulator3D::bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + ERR_FAIL_COND(bones[p_bone].physical_bone); + ERR_FAIL_NULL(p_physical_bone); + bones.write[p_bone].physical_bone = p_physical_bone; + + _rebuild_physical_bones_cache(); +} + +void PhysicalBoneSimulator3D::unbind_physical_bone_from_bone(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].physical_bone = nullptr; + + _rebuild_physical_bones_cache(); +} + +PhysicalBone3D *PhysicalBoneSimulator3D::get_physical_bone(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); + + return bones[p_bone].physical_bone; +} + +PhysicalBone3D *PhysicalBoneSimulator3D::get_physical_bone_parent(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); + + if (bones[p_bone].cache_parent_physical_bone) { + return bones[p_bone].cache_parent_physical_bone; + } + + return _get_physical_bone_parent(p_bone); +} + +PhysicalBone3D *PhysicalBoneSimulator3D::_get_physical_bone_parent(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); + + const int parent_bone = bones[p_bone].parent; + if (parent_bone < 0) { + return nullptr; + } + + PhysicalBone3D *pb = bones[parent_bone].physical_bone; + if (pb) { + return pb; + } else { + return get_physical_bone_parent(parent_bone); + } +} + +void PhysicalBoneSimulator3D::_rebuild_physical_bones_cache() { + const int b_size = bones.size(); + for (int i = 0; i < b_size; ++i) { + PhysicalBone3D *parent_pb = _get_physical_bone_parent(i); + if (parent_pb != bones[i].cache_parent_physical_bone) { + bones.write[i].cache_parent_physical_bone = parent_pb; + if (bones[i].physical_bone) { + bones[i].physical_bone->_on_bone_parent_changed(); + } + } + } +} + +#ifndef DISABLE_DEPRECATED +void _pb_stop_simulation_compat(Node *p_node) { + PhysicalBoneSimulator3D *ps = Object::cast_to<PhysicalBoneSimulator3D>(p_node); + if (ps) { + return; // Prevent conflict. + } + for (int i = p_node->get_child_count() - 1; i >= 0; --i) { + _pb_stop_simulation_compat(p_node->get_child(i)); + } + PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node); + if (pb) { + pb->set_simulate_physics(false); + } +} +#endif // _DISABLE_DEPRECATED + +void _pb_stop_simulation(Node *p_node) { + for (int i = p_node->get_child_count() - 1; i >= 0; --i) { + PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node->get_child(i)); + if (!pb) { + continue; + } + _pb_stop_simulation(pb); + } + PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node); + if (pb) { + pb->set_simulate_physics(false); + } +} + +void PhysicalBoneSimulator3D::physical_bones_stop_simulation() { + simulating = false; + _reset_physical_bones_state(); +#ifndef DISABLE_DEPRECATED + if (is_compat) { + Skeleton3D *sk = get_skeleton(); + if (sk) { + _pb_stop_simulation_compat(sk); + } + } else { + _pb_stop_simulation(this); + } +#else + _pb_stop_simulation(this); +#endif // _DISABLE_DEPRECATED +} + +#ifndef DISABLE_DEPRECATED +void _pb_start_simulation_compat(const PhysicalBoneSimulator3D *p_simulator, Node *p_node, const Vector<int> &p_sim_bones) { + PhysicalBoneSimulator3D *ps = Object::cast_to<PhysicalBoneSimulator3D>(p_node); + if (ps) { + return; // Prevent conflict. + } + for (int i = p_node->get_child_count() - 1; i >= 0; --i) { + _pb_start_simulation_compat(p_simulator, p_node->get_child(i), p_sim_bones); + } + PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node); + if (pb) { + if (p_sim_bones.is_empty()) { // If no bones are specified, activate ragdoll on full body. + pb->set_simulate_physics(true); + } else { + for (int i = p_sim_bones.size() - 1; i >= 0; --i) { + if (p_sim_bones[i] == pb->get_bone_id() || p_simulator->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) { + pb->set_simulate_physics(true); + break; + } + } + } + } +} +#endif // _DISABLE_DEPRECATED + +void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_node, const Vector<int> &p_sim_bones) { + for (int i = p_node->get_child_count() - 1; i >= 0; --i) { + PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node->get_child(i)); + if (!pb) { + continue; + } + _pb_start_simulation(p_simulator, pb, p_sim_bones); + } + PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node); + if (pb) { + if (p_sim_bones.is_empty()) { // If no bones are specified, activate ragdoll on full body. + pb->set_simulate_physics(true); + } else { + for (int i = p_sim_bones.size() - 1; i >= 0; --i) { + if (p_sim_bones[i] == pb->get_bone_id() || p_simulator->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) { + pb->set_simulate_physics(true); + break; + } + } + } + } +} + +void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) { + simulating = true; + _reset_physical_bones_state(); + + _pose_updated(); + + Vector<int> sim_bones; + if (p_bones.size() > 0) { + sim_bones.resize(p_bones.size()); + int c = 0; + for (int i = sim_bones.size() - 1; i >= 0; --i) { + int bone_id = find_bone(p_bones[i]); + if (bone_id != -1) { + sim_bones.write[c++] = bone_id; + } + } + sim_bones.resize(c); + } + +#ifndef DISABLE_DEPRECATED + if (is_compat) { + Skeleton3D *sk = get_skeleton(); + if (sk) { + _pb_start_simulation_compat(this, sk, sim_bones); + } + } else { + _pb_start_simulation(this, this, sim_bones); + } +#else + _pb_start_simulation(this, this, sim_bones); +#endif // _DISABLE_DEPRECATED +} + +void _physical_bones_add_remove_collision_exception(bool p_add, Node *p_node, RID p_exception) { + for (int i = p_node->get_child_count() - 1; i >= 0; --i) { + _physical_bones_add_remove_collision_exception(p_add, p_node->get_child(i), p_exception); + } + + CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_node); + if (co) { + if (p_add) { + PhysicsServer3D::get_singleton()->body_add_collision_exception(co->get_rid(), p_exception); + } else { + PhysicsServer3D::get_singleton()->body_remove_collision_exception(co->get_rid(), p_exception); + } + } +} + +void PhysicalBoneSimulator3D::physical_bones_add_collision_exception(RID p_exception) { + _physical_bones_add_remove_collision_exception(true, this, p_exception); +} + +void PhysicalBoneSimulator3D::physical_bones_remove_collision_exception(RID p_exception) { + _physical_bones_add_remove_collision_exception(false, this, p_exception); +} + +Transform3D PhysicalBoneSimulator3D::get_bone_global_pose(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + return bones[p_bone].global_pose; +} + +void PhysicalBoneSimulator3D::set_bone_global_pose(int p_bone, const Transform3D &p_pose) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].global_pose = p_pose; +} + +void PhysicalBoneSimulator3D::_process_modification() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + if (!enabled) { + for (int i = 0; i < bones.size(); i++) { + if (bones[i].physical_bone) { + if (bones[i].physical_bone->is_simulating_physics() == false) { + bones[i].physical_bone->reset_to_rest_position(); + } + } + } + } else { + ERR_FAIL_COND(skeleton->get_bone_count() != bones.size()); + LocalVector<Transform3D> local_poses; + for (int i = 0; i < skeleton->get_bone_count(); i++) { + Transform3D pt; + if (skeleton->get_bone_parent(i) >= 0) { + pt = get_bone_global_pose(skeleton->get_bone_parent(i)); + } + local_poses.push_back(pt.affine_inverse() * bones[i].global_pose); + } + for (int i = 0; i < skeleton->get_bone_count(); i++) { + skeleton->set_bone_pose_position(i, local_poses[i].origin); + skeleton->set_bone_pose_rotation(i, local_poses[i].basis.get_rotation_quaternion()); + skeleton->set_bone_pose_scale(i, local_poses[i].basis.get_scale()); + } + } +} + +void PhysicalBoneSimulator3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBoneSimulator3D::is_simulating_physics); + + ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &PhysicalBoneSimulator3D::physical_bones_stop_simulation); + ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &PhysicalBoneSimulator3D::physical_bones_start_simulation_on, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &PhysicalBoneSimulator3D::physical_bones_add_collision_exception); + ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &PhysicalBoneSimulator3D::physical_bones_remove_collision_exception); +} + +PhysicalBoneSimulator3D::PhysicalBoneSimulator3D() { +} diff --git a/scene/3d/physical_bone_simulator_3d.h b/scene/3d/physical_bone_simulator_3d.h new file mode 100644 index 0000000000..ee900e0e77 --- /dev/null +++ b/scene/3d/physical_bone_simulator_3d.h @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* physical_bone_simulator_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef PHYSICAL_BONE_SIMULATOR_3D_H +#define PHYSICAL_BONE_SIMULATOR_3D_H + +#include "scene/3d/skeleton_modifier_3d.h" + +#include "scene/3d/physics/physical_bone_3d.h" + +class PhysicalBone3D; + +class PhysicalBoneSimulator3D : public SkeletonModifier3D { + GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D); + + bool simulating = false; + bool enabled = true; + + struct SimulatedBone { + int parent; + Vector<int> child_bones; + + Transform3D global_pose; + + PhysicalBone3D *physical_bone = nullptr; + PhysicalBone3D *cache_parent_physical_bone = nullptr; + + SimulatedBone() { + parent = -1; + global_pose = Transform3D(); + physical_bone = nullptr; + cache_parent_physical_bone = nullptr; + } + }; + + Vector<SimulatedBone> bones; + + /// This is a slow API, so it's cached + PhysicalBone3D *_get_physical_bone_parent(int p_bone); + void _rebuild_physical_bones_cache(); + void _reset_physical_bones_state(); + +protected: + static void _bind_methods(); + + virtual void _set_active(bool p_active) override; + + void _bone_list_changed(); + void _pose_updated(); + + virtual void _process_modification() override; + + virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override; + +public: +#ifndef DISABLE_DEPRECATED + bool is_compat = false; +#endif // _DISABLE_DEPRECATED + bool is_simulating_physics() const; + + int find_bone(const String &p_name) const; + String get_bone_name(int p_bone) const; + int get_bone_count() const; + bool is_bone_parent_of(int p_bone_id, int p_parent_bone_id) const; + + Transform3D get_bone_global_pose(int p_bone) const; + void set_bone_global_pose(int p_bone, const Transform3D &p_pose); + + void bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone); + void unbind_physical_bone_from_bone(int p_bone); + + PhysicalBone3D *get_physical_bone(int p_bone); + PhysicalBone3D *get_physical_bone_parent(int p_bone); + + void physical_bones_stop_simulation(); + void physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones); + void physical_bones_add_collision_exception(RID p_exception); + void physical_bones_remove_collision_exception(RID p_exception); + + PhysicalBoneSimulator3D(); +}; + +#endif // PHYSICAL_BONE_SIMULATOR_3D_H diff --git a/scene/3d/physics/area_3d.cpp b/scene/3d/physics/area_3d.cpp index 014c33cad0..be95512bea 100644 --- a/scene/3d/physics/area_3d.cpp +++ b/scene/3d/physics/area_3d.cpp @@ -30,7 +30,6 @@ #include "area_3d.h" -#include "scene/scene_string_names.h" #include "servers/audio_server.h" void Area3D::set_gravity_space_override_mode(SpaceOverride p_mode) { @@ -199,9 +198,9 @@ void Area3D::_body_enter_tree(ObjectID p_id) { ERR_FAIL_COND(E->value.in_tree); E->value.in_tree = true; - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); + emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -213,9 +212,9 @@ void Area3D::_body_exit_tree(ObjectID p_id) { ERR_FAIL_COND(!E); ERR_FAIL_COND(!E->value.in_tree); E->value.in_tree = false; - emit_signal(SceneStringNames::get_singleton()->body_exited, node); + emit_signal(SceneStringName(body_exited), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); + emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape); } } @@ -229,9 +228,9 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i locked = true; // Emit the appropriate signals. if (body_in) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, (Node *)nullptr, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_entered), p_body, (Node *)nullptr, p_body_shape, p_area_shape); } else { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, (Node *)nullptr, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_exited), p_body, (Node *)nullptr, p_body_shape, p_area_shape); } locked = false; unlock_callback(); @@ -257,10 +256,10 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i E->value.rc = 0; E->value.in_tree = node && node->is_inside_tree(); if (node) { - node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree).bind(objid)); - node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree).bind(objid)); + node->connect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree).bind(objid)); + node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree).bind(objid)); if (E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); } } } @@ -270,7 +269,7 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i } if (!node || E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_area_shape); } } else { @@ -284,15 +283,15 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i if (E->value.rc == 0) { body_map.remove(E); if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree)); if (in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_exited, obj); + emit_signal(SceneStringName(body_exited), obj); } } } if (!node || in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape); + emit_signal(SceneStringName(body_shape_exited), p_body, obj, p_body_shape, p_area_shape); } } @@ -317,18 +316,18 @@ void Area3D::_clear_monitoring() { } //ERR_CONTINUE(!node); - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree)); if (!E.value.in_tree) { continue; } for (int i = 0; i < E.value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape); + emit_signal(SceneStringName(body_shape_exited), E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape); } - emit_signal(SceneStringNames::get_singleton()->body_exited, node); + emit_signal(SceneStringName(body_exited), node); } } @@ -346,18 +345,18 @@ void Area3D::_clear_monitoring() { } //ERR_CONTINUE(!node); - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree)); if (!E.value.in_tree) { continue; } for (int i = 0; i < E.value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape); + emit_signal(SceneStringName(area_shape_exited), E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape); } - emit_signal(SceneStringNames::get_singleton()->area_exited, obj); + emit_signal(SceneStringName(area_exited), obj); } } } @@ -405,9 +404,9 @@ void Area3D::_area_enter_tree(ObjectID p_id) { ERR_FAIL_COND(E->value.in_tree); E->value.in_tree = true; - emit_signal(SceneStringNames::get_singleton()->area_entered, node); + emit_signal(SceneStringName(area_entered), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); + emit_signal(SceneStringName(area_shape_entered), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -419,9 +418,9 @@ void Area3D::_area_exit_tree(ObjectID p_id) { ERR_FAIL_COND(!E); ERR_FAIL_COND(!E->value.in_tree); E->value.in_tree = false; - emit_signal(SceneStringNames::get_singleton()->area_exited, node); + emit_signal(SceneStringName(area_exited), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); + emit_signal(SceneStringName(area_shape_exited), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape); } } @@ -435,9 +434,9 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i locked = true; // Emit the appropriate signals. if (area_in) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, (Node *)nullptr, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_entered), p_area, (Node *)nullptr, p_area_shape, p_self_shape); } else { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, (Node *)nullptr, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_exited), p_area, (Node *)nullptr, p_area_shape, p_self_shape); } locked = false; unlock_callback(); @@ -463,10 +462,10 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i E->value.rc = 0; E->value.in_tree = node && node->is_inside_tree(); if (node) { - node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree).bind(objid)); - node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree).bind(objid)); + node->connect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree).bind(objid)); + node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree).bind(objid)); if (E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_entered, node); + emit_signal(SceneStringName(area_entered), node); } } } @@ -476,7 +475,7 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i } if (!node || E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, node, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_entered), p_area, node, p_area_shape, p_self_shape); } } else { @@ -490,15 +489,15 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i if (E->value.rc == 0) { area_map.remove(E); if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree)); if (in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_exited, obj); + emit_signal(SceneStringName(area_exited), obj); } } } if (!node || in_tree) { - emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, obj, p_area_shape, p_self_shape); + emit_signal(SceneStringName(area_shape_exited), p_area, obj, p_area_shape, p_self_shape); } } @@ -605,7 +604,7 @@ StringName Area3D::get_audio_bus_name() const { return audio_bus; } } - return SceneStringNames::get_singleton()->Master; + return SceneStringName(Master); } void Area3D::set_use_reverb_bus(bool p_enable) { @@ -626,7 +625,7 @@ StringName Area3D::get_reverb_bus_name() const { return reverb_bus; } } - return SceneStringNames::get_singleton()->Master; + return SceneStringName(Master); } void Area3D::set_reverb_amount(float p_amount) { @@ -812,6 +811,8 @@ void Area3D::_bind_methods() { Area3D::Area3D() : CollisionObject3D(PhysicsServer3D::get_singleton()->area_create(), true) { + audio_bus = SceneStringName(Master); + reverb_bus = SceneStringName(Master); set_gravity(9.8); set_gravity_direction(Vector3(0, -1, 0)); set_monitoring(true); diff --git a/scene/3d/physics/area_3d.h b/scene/3d/physics/area_3d.h index 41382b6128..8848f9c23a 100644 --- a/scene/3d/physics/area_3d.h +++ b/scene/3d/physics/area_3d.h @@ -33,7 +33,6 @@ #include "core/templates/vset.h" #include "scene/3d/physics/collision_object_3d.h" -#include "scene/scene_string_names.h" class Area3D : public CollisionObject3D { GDCLASS(Area3D, CollisionObject3D); @@ -135,10 +134,10 @@ private: void _clear_monitoring(); bool audio_bus_override = false; - StringName audio_bus = SceneStringNames::get_singleton()->Master; + StringName audio_bus; bool use_reverb_bus = false; - StringName reverb_bus = SceneStringNames::get_singleton()->Master; + StringName reverb_bus; float reverb_amount = 0.0; float reverb_uniformity = 0.0; diff --git a/scene/3d/physics/character_body_3d.cpp b/scene/3d/physics/character_body_3d.cpp index 6759033358..dda3ea9cca 100644 --- a/scene/3d/physics/character_body_3d.cpp +++ b/scene/3d/physics/character_body_3d.cpp @@ -232,7 +232,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo } else { // Travel is too high to be safely canceled, we take it into account. result.travel = result.travel.slide(up_direction); - motion = motion.normalized() * result.travel.length(); + motion = result.remainder; } set_global_transform(gt); // Determines if you are on the ground, and limits the possibility of climbing on the walls because of the approximations. @@ -704,7 +704,7 @@ Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) { // Create a new instance when the cached reference is invalid or still in use in script. if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->get_reference_count() > 1) { slide_colliders.write[p_bounce].instantiate(); - slide_colliders.write[p_bounce]->owner = this; + slide_colliders.write[p_bounce]->owner_id = get_instance_id(); } slide_colliders.write[p_bounce]->result = motion_results[p_bounce]; @@ -936,11 +936,3 @@ void CharacterBody3D::_validate_property(PropertyInfo &p_property) const { CharacterBody3D::CharacterBody3D() : PhysicsBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) { } - -CharacterBody3D::~CharacterBody3D() { - for (int i = 0; i < slide_colliders.size(); i++) { - if (slide_colliders[i].is_valid()) { - slide_colliders.write[i]->owner = nullptr; - } - } -} diff --git a/scene/3d/physics/character_body_3d.h b/scene/3d/physics/character_body_3d.h index cffc0f8e63..430d9c35cb 100644 --- a/scene/3d/physics/character_body_3d.h +++ b/scene/3d/physics/character_body_3d.h @@ -113,7 +113,6 @@ public: PlatformOnLeave get_platform_on_leave() const; CharacterBody3D(); - ~CharacterBody3D(); private: real_t margin = 0.001; diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp index bbd2ef2fb8..dddaf7eb4a 100644 --- a/scene/3d/physics/collision_object_3d.cpp +++ b/scene/3d/physics/collision_object_3d.cpp @@ -31,7 +31,6 @@ #include "collision_object_3d.h" #include "scene/resources/3d/shape_3d.h" -#include "scene/scene_string_names.h" void CollisionObject3D::_notification(int p_what) { switch (p_what) { @@ -291,17 +290,17 @@ void CollisionObject3D::_apply_enabled() { void CollisionObject3D::_input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { GDVIRTUAL_CALL(_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); - emit_signal(SceneStringNames::get_singleton()->input_event, p_camera, p_input_event, p_pos, p_normal, p_shape); + emit_signal(SceneStringName(input_event), p_camera, p_input_event, p_pos, p_normal, p_shape); } void CollisionObject3D::_mouse_enter() { GDVIRTUAL_CALL(_mouse_enter); - emit_signal(SceneStringNames::get_singleton()->mouse_entered); + emit_signal(SceneStringName(mouse_entered)); } void CollisionObject3D::_mouse_exit() { GDVIRTUAL_CALL(_mouse_exit); - emit_signal(SceneStringNames::get_singleton()->mouse_exited); + emit_signal(SceneStringName(mouse_exited)); } void CollisionObject3D::set_body_mode(PhysicsServer3D::BodyMode p_mode) { @@ -489,11 +488,11 @@ void CollisionObject3D::_bind_methods() { ClassDB::bind_method(D_METHOD("shape_owner_clear_shapes", "owner_id"), &CollisionObject3D::shape_owner_clear_shapes); ClassDB::bind_method(D_METHOD("shape_find_owner", "shape_index"), &CollisionObject3D::shape_find_owner); - GDVIRTUAL_BIND(_input_event, "camera", "event", "position", "normal", "shape_idx"); + GDVIRTUAL_BIND(_input_event, "camera", "event", "event_position", "normal", "shape_idx"); GDVIRTUAL_BIND(_mouse_enter); GDVIRTUAL_BIND(_mouse_exit); - ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "position"), PropertyInfo(Variant::VECTOR3, "normal"), PropertyInfo(Variant::INT, "shape_idx"))); + ADD_SIGNAL(MethodInfo("input_event", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), PropertyInfo(Variant::VECTOR3, "event_position"), PropertyInfo(Variant::VECTOR3, "normal"), PropertyInfo(Variant::INT, "shape_idx"))); ADD_SIGNAL(MethodInfo("mouse_entered")); ADD_SIGNAL(MethodInfo("mouse_exited")); diff --git a/scene/3d/physics/joints/cone_twist_joint_3d.cpp b/scene/3d/physics/joints/cone_twist_joint_3d.cpp index 404c074911..3da0cbee71 100644 --- a/scene/3d/physics/joints/cone_twist_joint_3d.cpp +++ b/scene/3d/physics/joints/cone_twist_joint_3d.cpp @@ -30,8 +30,6 @@ #include "cone_twist_joint_3d.h" -#include "scene/scene_string_names.h" - void ConeTwistJoint3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &ConeTwistJoint3D::set_param); ClassDB::bind_method(D_METHOD("get_param", "param"), &ConeTwistJoint3D::get_param); diff --git a/scene/3d/physics/joints/joint_3d.cpp b/scene/3d/physics/joints/joint_3d.cpp index a9c2526bd0..47c89f37e2 100644 --- a/scene/3d/physics/joints/joint_3d.cpp +++ b/scene/3d/physics/joints/joint_3d.cpp @@ -30,19 +30,17 @@ #include "joint_3d.h" -#include "scene/scene_string_names.h" - void Joint3D::_disconnect_signals() { Node *node_a = get_node_or_null(a); PhysicsBody3D *body_a = Object::cast_to<PhysicsBody3D>(node_a); if (body_a) { - body_a->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree)); + body_a->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree)); } Node *node_b = get_node_or_null(b); PhysicsBody3D *body_b = Object::cast_to<PhysicsBody3D>(node_b); if (body_b) { - body_b->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree)); + body_b->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree)); } } @@ -108,12 +106,16 @@ void Joint3D::_update_joint(bool p_only_free) { if (body_a) { ba = body_a->get_rid(); - body_a->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree)); + if (!body_a->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree))) { + body_a->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree)); + } } if (body_b) { bb = body_b->get_rid(); - body_b->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree)); + if (!body_b->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree))) { + body_b->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree)); + } } PhysicsServer3D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision); diff --git a/scene/3d/physics/joints/slider_joint_3d.cpp b/scene/3d/physics/joints/slider_joint_3d.cpp index 2e87ae1e83..df6b1cc045 100644 --- a/scene/3d/physics/joints/slider_joint_3d.cpp +++ b/scene/3d/physics/joints/slider_joint_3d.cpp @@ -30,8 +30,6 @@ #include "slider_joint_3d.h" -#include "scene/scene_string_names.h" - void SliderJoint3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &SliderJoint3D::set_param); ClassDB::bind_method(D_METHOD("get_param", "param"), &SliderJoint3D::get_param); diff --git a/scene/3d/physics/kinematic_collision_3d.cpp b/scene/3d/physics/kinematic_collision_3d.cpp index 54371425bc..de13831ac3 100644 --- a/scene/3d/physics/kinematic_collision_3d.cpp +++ b/scene/3d/physics/kinematic_collision_3d.cpp @@ -67,6 +67,7 @@ real_t KinematicCollision3D::get_angle(int p_collision_index, const Vector3 &p_u Object *KinematicCollision3D::get_local_shape(int p_collision_index) const { ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr); + PhysicsBody3D *owner = Object::cast_to<PhysicsBody3D>(ObjectDB::get_instance(owner_id)); if (!owner) { return nullptr; } diff --git a/scene/3d/physics/kinematic_collision_3d.h b/scene/3d/physics/kinematic_collision_3d.h index 656531c82b..0573af0c21 100644 --- a/scene/3d/physics/kinematic_collision_3d.h +++ b/scene/3d/physics/kinematic_collision_3d.h @@ -40,7 +40,7 @@ class PhysicsBody3D; class KinematicCollision3D : public RefCounted { GDCLASS(KinematicCollision3D, RefCounted); - PhysicsBody3D *owner = nullptr; + ObjectID owner_id; friend class PhysicsBody3D; friend class CharacterBody3D; PhysicsServer3D::MotionResult result; diff --git a/scene/3d/physics/physical_bone_3d.cpp b/scene/3d/physics/physical_bone_3d.cpp index 10c1fceb94..c6be2a9da8 100644 --- a/scene/3d/physics/physical_bone_3d.cpp +++ b/scene/3d/physics/physical_bone_3d.cpp @@ -29,6 +29,9 @@ /**************************************************************************/ #include "physical_bone_3d.h" +#ifndef DISABLE_DEPRECATED +#include "scene/3d/skeleton_3d.h" +#endif //_DISABLE_DEPRECATED bool PhysicalBone3D::JointData::_set(const StringName &p_name, const Variant &p_value, RID j) { return false; @@ -89,15 +92,14 @@ void PhysicalBone3D::reset_physics_simulation_state() { } void PhysicalBone3D::reset_to_rest_position() { - if (parent_skeleton) { - Transform3D new_transform = parent_skeleton->get_global_transform(); + PhysicalBoneSimulator3D *simulator = get_simulator(); + Skeleton3D *skeleton = get_skeleton(); + if (simulator && skeleton) { if (bone_id == -1) { - new_transform *= body_offset; + set_global_transform((skeleton->get_global_transform() * body_offset).orthonormalized()); } else { - new_transform *= parent_skeleton->get_bone_global_pose(bone_id) * body_offset; + set_global_transform((skeleton->get_global_transform() * simulator->get_bone_global_pose(bone_id) * body_offset).orthonormalized()); } - new_transform.orthonormalize(); - set_global_transform(new_transform); } } @@ -734,15 +736,14 @@ bool PhysicalBone3D::_get(const StringName &p_name, Variant &r_ret) const { } void PhysicalBone3D::_get_property_list(List<PropertyInfo> *p_list) const { - Skeleton3D *parent = find_skeleton_parent(get_parent()); - - if (parent) { + Skeleton3D *skeleton = get_skeleton(); + if (skeleton) { String names; - for (int i = 0; i < parent->get_bone_count(); i++) { + for (int i = 0; i < skeleton->get_bone_count(); i++) { if (i > 0) { names += ","; } - names += parent->get_bone_name(i); + names += skeleton->get_bone_name(i); } p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"), PROPERTY_HINT_ENUM, names)); @@ -758,7 +759,8 @@ void PhysicalBone3D::_get_property_list(List<PropertyInfo> *p_list) const { void PhysicalBone3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - parent_skeleton = find_skeleton_parent(get_parent()); + case NOTIFICATION_PARENTED: + _update_simulator_path(); update_bone_id(); reset_to_rest_position(); reset_physics_simulation_state(); @@ -768,13 +770,13 @@ void PhysicalBone3D::_notification(int p_what) { break; case NOTIFICATION_EXIT_TREE: { - if (parent_skeleton) { + PhysicalBoneSimulator3D *simulator = get_simulator(); + if (simulator) { if (-1 != bone_id) { - parent_skeleton->unbind_physical_bone_from_bone(bone_id); + simulator->unbind_physical_bone_from_bone(bone_id); bone_id = -1; } } - parent_skeleton = nullptr; PhysicsServer3D::get_singleton()->joint_clear(joint); } break; @@ -818,10 +820,12 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { Transform3D global_transform(p_state->get_transform()); - // Update skeleton - if (parent_skeleton) { + // Update simulator + PhysicalBoneSimulator3D *simulator = get_simulator(); + Skeleton3D *skeleton = get_skeleton(); + if (simulator && skeleton) { if (-1 != bone_id) { - parent_skeleton->set_bone_global_pose_override(bone_id, parent_skeleton->get_global_transform().affine_inverse() * (global_transform * body_offset_inverse), 1.0, true); + simulator->set_bone_global_pose(bone_id, skeleton->get_global_transform().affine_inverse() * (global_transform * body_offset_inverse)); } } } @@ -916,14 +920,6 @@ void PhysicalBone3D::_bind_methods() { BIND_ENUM_CONSTANT(JOINT_TYPE_6DOF); } -Skeleton3D *PhysicalBone3D::find_skeleton_parent(Node *p_parent) { - if (!p_parent) { - return nullptr; - } - Skeleton3D *s = Object::cast_to<Skeleton3D>(p_parent); - return s ? s : find_skeleton_parent(p_parent->get_parent()); -} - void PhysicalBone3D::_update_joint_offset() { _fix_joint_offset(); @@ -938,18 +934,20 @@ void PhysicalBone3D::_update_joint_offset() { void PhysicalBone3D::_fix_joint_offset() { // Clamp joint origin to bone origin - if (parent_skeleton) { + PhysicalBoneSimulator3D *simulator = get_simulator(); + if (simulator) { joint_offset.origin = body_offset.affine_inverse().origin; } } void PhysicalBone3D::_reload_joint() { - if (!parent_skeleton) { + PhysicalBoneSimulator3D *simulator = get_simulator(); + if (!simulator || !simulator->get_skeleton()) { PhysicsServer3D::get_singleton()->joint_clear(joint); return; } - PhysicalBone3D *body_a = parent_skeleton->get_physical_bone_parent(bone_id); + PhysicalBone3D *body_a = simulator->get_physical_bone_parent(bone_id); if (!body_a) { PhysicsServer3D::get_singleton()->joint_clear(joint); return; @@ -1041,6 +1039,36 @@ void PhysicalBone3D::_on_bone_parent_changed() { _reload_joint(); } +void PhysicalBone3D::_update_simulator_path() { + simulator_id = ObjectID(); + PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(get_parent()); + if (sim) { + simulator_id = sim->get_instance_id(); + return; + } +#ifndef DISABLE_DEPRECATED + Skeleton3D *sk = cast_to<Skeleton3D>(get_parent()); + if (sk) { + PhysicalBoneSimulator3D *ssim = cast_to<PhysicalBoneSimulator3D>(sk->get_simulator()); + if (ssim) { + simulator_id = ssim->get_instance_id(); + } + } +#endif // _DISABLE_DEPRECATED +} + +PhysicalBoneSimulator3D *PhysicalBone3D::get_simulator() const { + return Object::cast_to<PhysicalBoneSimulator3D>(ObjectDB::get_instance(simulator_id)); +} + +Skeleton3D *PhysicalBone3D::get_skeleton() const { + PhysicalBoneSimulator3D *simulator = get_simulator(); + if (simulator) { + return simulator->get_skeleton(); + } + return nullptr; +} + #ifdef TOOLS_ENABLED void PhysicalBone3D::_set_gizmo_move_joint(bool p_move_joint) { gizmo_move_joint = p_move_joint; @@ -1059,10 +1087,6 @@ const PhysicalBone3D::JointData *PhysicalBone3D::get_joint_data() const { return joint_data; } -Skeleton3D *PhysicalBone3D::find_skeleton_parent() { - return find_skeleton_parent(this); -} - void PhysicalBone3D::set_joint_type(JointType p_joint_type) { if (p_joint_type == get_joint_type()) { return; @@ -1269,21 +1293,22 @@ PhysicalBone3D::~PhysicalBone3D() { } void PhysicalBone3D::update_bone_id() { - if (!parent_skeleton) { + PhysicalBoneSimulator3D *simulator = get_simulator(); + if (!simulator) { return; } - const int new_bone_id = parent_skeleton->find_bone(bone_name); + const int new_bone_id = simulator->find_bone(bone_name); if (new_bone_id != bone_id) { if (-1 != bone_id) { // Assert the unbind from old node - parent_skeleton->unbind_physical_bone_from_bone(bone_id); + simulator->unbind_physical_bone_from_bone(bone_id); } bone_id = new_bone_id; - parent_skeleton->bind_physical_bone_to_bone(bone_id, this); + simulator->bind_physical_bone_to_bone(bone_id, this); _fix_joint_offset(); reset_physics_simulation_state(); @@ -1292,10 +1317,12 @@ void PhysicalBone3D::update_bone_id() { void PhysicalBone3D::update_offset() { #ifdef TOOLS_ENABLED - if (parent_skeleton) { - Transform3D bone_transform(parent_skeleton->get_global_transform()); + PhysicalBoneSimulator3D *simulator = get_simulator(); + Skeleton3D *skeleton = get_skeleton(); + if (simulator && skeleton) { + Transform3D bone_transform(skeleton->get_global_transform()); if (-1 != bone_id) { - bone_transform *= parent_skeleton->get_bone_global_pose(bone_id); + bone_transform *= simulator->get_bone_global_pose(bone_id); } if (gizmo_move_joint) { @@ -1309,7 +1336,7 @@ void PhysicalBone3D::update_offset() { } void PhysicalBone3D::_start_physics_simulation() { - if (_internal_simulate_physics || !parent_skeleton) { + if (_internal_simulate_physics || !simulator_id.is_valid()) { return; } reset_to_rest_position(); @@ -1323,23 +1350,22 @@ void PhysicalBone3D::_start_physics_simulation() { } void PhysicalBone3D::_stop_physics_simulation() { - if (!parent_skeleton) { - return; - } - if (parent_skeleton->get_animate_physical_bones()) { - set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC); - PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); - PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); - PhysicsServer3D::get_singleton()->body_set_collision_priority(get_rid(), get_collision_priority()); - } else { - set_body_mode(PhysicsServer3D::BODY_MODE_STATIC); - PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), 0); - PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), 0); - PhysicsServer3D::get_singleton()->body_set_collision_priority(get_rid(), 1.0); + PhysicalBoneSimulator3D *simulator = get_simulator(); + if (simulator) { + if (simulator->is_simulating_physics()) { + set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC); + PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); + PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); + PhysicsServer3D::get_singleton()->body_set_collision_priority(get_rid(), get_collision_priority()); + } else { + set_body_mode(PhysicsServer3D::BODY_MODE_STATIC); + PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), 0); + PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), 0); + PhysicsServer3D::get_singleton()->body_set_collision_priority(get_rid(), 1.0); + } } if (_internal_simulate_physics) { PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), Callable()); - parent_skeleton->set_bone_global_pose_override(bone_id, Transform3D(), 0.0, false); set_as_top_level(false); _internal_simulate_physics = false; } diff --git a/scene/3d/physics/physical_bone_3d.h b/scene/3d/physics/physical_bone_3d.h index 953400da2f..4765e41572 100644 --- a/scene/3d/physics/physical_bone_3d.h +++ b/scene/3d/physics/physical_bone_3d.h @@ -31,8 +31,10 @@ #ifndef PHYSICAL_BONE_3D_H #define PHYSICAL_BONE_3D_H +#include "scene/3d/physical_bone_simulator_3d.h" #include "scene/3d/physics/physics_body_3d.h" -#include "scene/3d/skeleton_3d.h" + +class PhysicalBoneSimulator3D; class PhysicalBone3D : public PhysicsBody3D { GDCLASS(PhysicalBone3D, PhysicsBody3D); @@ -169,7 +171,7 @@ private: Transform3D joint_offset; RID joint; - Skeleton3D *parent_skeleton = nullptr; + ObjectID simulator_id; Transform3D body_offset; Transform3D body_offset_inverse; bool simulate_physics = false; @@ -206,15 +208,19 @@ protected: private: void _sync_body_state(PhysicsDirectBodyState3D *p_state); - static Skeleton3D *find_skeleton_parent(Node *p_parent); void _update_joint_offset(); void _fix_joint_offset(); void _reload_joint(); + void _update_simulator_path(); + public: void _on_bone_parent_changed(); + PhysicalBoneSimulator3D *get_simulator() const; + Skeleton3D *get_skeleton() const; + void set_linear_velocity(const Vector3 &p_velocity); Vector3 get_linear_velocity() const override; @@ -231,7 +237,6 @@ public: #endif const JointData *get_joint_data() const; - Skeleton3D *find_skeleton_parent(); int get_bone_id() const { return bone_id; diff --git a/scene/3d/physics/physics_body_3d.cpp b/scene/3d/physics/physics_body_3d.cpp index 711ca60a81..b723b452c1 100644 --- a/scene/3d/physics/physics_body_3d.cpp +++ b/scene/3d/physics/physics_body_3d.cpp @@ -30,8 +30,6 @@ #include "physics_body_3d.h" -#include "scene/scene_string_names.h" - void PhysicsBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_collide", "motion", "test_only", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(false), DEFVAL(1)); ClassDB::bind_method(D_METHOD("test_move", "from", "motion", "collision", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(false), DEFVAL(1)); @@ -58,12 +56,6 @@ PhysicsBody3D::PhysicsBody3D(PhysicsServer3D::BodyMode p_mode) : set_body_mode(p_mode); } -PhysicsBody3D::~PhysicsBody3D() { - if (motion_cache.is_valid()) { - motion_cache->owner = nullptr; - } -} - TypedArray<PhysicsBody3D> PhysicsBody3D::get_collision_exceptions() { List<RID> exceptions; PhysicsServer3D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions); @@ -102,7 +94,7 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_t // Create a new instance when the cached reference is invalid or still in use in script. if (motion_cache.is_null() || motion_cache->get_reference_count() > 1) { motion_cache.instantiate(); - motion_cache->owner = this; + motion_cache->owner_id = get_instance_id(); } motion_cache->result = result; diff --git a/scene/3d/physics/physics_body_3d.h b/scene/3d/physics/physics_body_3d.h index 92b3850085..71253be0b8 100644 --- a/scene/3d/physics/physics_body_3d.h +++ b/scene/3d/physics/physics_body_3d.h @@ -65,8 +65,6 @@ public: TypedArray<PhysicsBody3D> get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody void remove_collision_exception_with(Node *p_node); - - virtual ~PhysicsBody3D(); }; #endif // PHYSICS_BODY_3D_H diff --git a/scene/3d/physics/ray_cast_3d.cpp b/scene/3d/physics/ray_cast_3d.cpp index 0cb722a77e..a9272388c1 100644 --- a/scene/3d/physics/ray_cast_3d.cpp +++ b/scene/3d/physics/ray_cast_3d.cpp @@ -204,8 +204,10 @@ void RayCast3D::_notification(int p_what) { bool prev_collision_state = collided; _update_raycast_state(); - if (prev_collision_state != collided && get_tree()->is_debugging_collisions_hint()) { - _update_debug_shape_material(true); + if (get_tree()->is_debugging_collisions_hint()) { + if (prev_collision_state != collided) { + _update_debug_shape_material(true); + } if (is_inside_tree() && debug_instance.is_valid()) { RenderingServer::get_singleton()->instance_set_transform(debug_instance, get_global_transform()); } diff --git a/scene/3d/physics/rigid_body_3d.cpp b/scene/3d/physics/rigid_body_3d.cpp index 6cd621c1c7..5ea413f2c4 100644 --- a/scene/3d/physics/rigid_body_3d.cpp +++ b/scene/3d/physics/rigid_body_3d.cpp @@ -30,8 +30,6 @@ #include "rigid_body_3d.h" -#include "scene/scene_string_names.h" - void RigidBody3D::_body_enter_tree(ObjectID p_id) { Object *obj = ObjectDB::get_instance(p_id); Node *node = Object::cast_to<Node>(obj); @@ -45,10 +43,10 @@ void RigidBody3D::_body_enter_tree(ObjectID p_id) { contact_monitor->locked = true; - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); + emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -66,10 +64,10 @@ void RigidBody3D::_body_exit_tree(ObjectID p_id) { contact_monitor->locked = true; - emit_signal(SceneStringNames::get_singleton()->body_exited, node); + emit_signal(SceneStringName(body_exited), node); for (int i = 0; i < E->value.shapes.size(); i++) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); + emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape); } contact_monitor->locked = false; @@ -94,10 +92,10 @@ void RigidBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan //E->value.rc=0; E->value.in_tree = node && node->is_inside_tree(); if (node) { - node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody3D::_body_enter_tree).bind(objid)); - node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody3D::_body_exit_tree).bind(objid)); + node->connect(SceneStringName(tree_entered), callable_mp(this, &RigidBody3D::_body_enter_tree).bind(objid)); + node->connect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody3D::_body_exit_tree).bind(objid)); if (E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_entered, node); + emit_signal(SceneStringName(body_entered), node); } } } @@ -107,7 +105,7 @@ void RigidBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan } if (E->value.in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_local_shape); + emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_local_shape); } } else { @@ -121,17 +119,17 @@ void RigidBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan if (E->value.shapes.is_empty()) { if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody3D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody3D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody3D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody3D::_body_exit_tree)); if (in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_exited, node); + emit_signal(SceneStringName(body_exited), node); } } contact_monitor->body_map.remove(E); } if (node && in_tree) { - emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_local_shape); + emit_signal(SceneStringName(body_shape_exited), p_body, obj, p_body_shape, p_local_shape); } } } @@ -157,7 +155,7 @@ void RigidBody3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) { if (sleeping != p_state->is_sleeping()) { sleeping = p_state->is_sleeping(); - emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed); + emit_signal(SceneStringName(sleeping_state_changed)); } } @@ -613,8 +611,8 @@ void RigidBody3D::set_contact_monitor(bool p_enabled) { Node *node = Object::cast_to<Node>(obj); if (node) { - node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody3D::_body_enter_tree)); - node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody3D::_body_exit_tree)); + node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody3D::_body_enter_tree)); + node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody3D::_body_exit_tree)); } } diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index b4dd6d09be..b6ec55286d 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -190,17 +190,6 @@ AABB ReflectionProbe::get_aabb() const { return aabb; } -PackedStringArray ReflectionProbe::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); - - if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { - warnings.push_back(RTR("ReflectionProbes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); - return warnings; - } - - return warnings; -} - void ReflectionProbe::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "ambient_color" || p_property.name == "ambient_color_energy") { if (ambient_mode != AMBIENT_COLOR) { diff --git a/scene/3d/reflection_probe.h b/scene/3d/reflection_probe.h index 425fbb5bc2..7221294228 100644 --- a/scene/3d/reflection_probe.h +++ b/scene/3d/reflection_probe.h @@ -122,8 +122,6 @@ public: virtual AABB get_aabb() const override; - virtual PackedStringArray get_configuration_warnings() const override; - ReflectionProbe(); ~ReflectionProbe(); }; diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index f0ffb7b2d5..a4804e928a 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -32,10 +32,11 @@ #include "skeleton_3d.compat.inc" #include "core/variant/type_info.h" -#include "scene/3d/physics/physical_bone_3d.h" -#include "scene/3d/physics/physics_body_3d.h" +#include "scene/3d/skeleton_modifier_3d.h" #include "scene/resources/surface_tool.h" -#include "scene/scene_string_names.h" +#ifndef DISABLE_DEPRECATED +#include "scene/3d/physical_bone_simulator_3d.h" +#endif // _DISABLE_DEPRECATED void SkinReference::_skin_changed() { if (skeleton_node) { @@ -70,6 +71,12 @@ SkinReference::~SkinReference() { bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; +#ifndef DISABLE_DEPRECATED + if (path.begins_with("animate_physical_bones")) { + set_animate_physical_bones(p_value); + } +#endif + if (!path.begins_with("bones/")) { return false; } @@ -134,6 +141,12 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; +#ifndef DISABLE_DEPRECATED + if (path.begins_with("animate_physical_bones")) { + r_ret = get_animate_physical_bones(); + } +#endif + if (!path.begins_with("bones/")) { return false; } @@ -239,7 +252,7 @@ void Skeleton3D::_update_process_order() { int parent_bone_idx = bonesptr[i].parent; // Check to see if this node is already added to the parent. - if (bonesptr[parent_bone_idx].child_bones.find(i) < 0) { + if (!bonesptr[parent_bone_idx].child_bones.has(i)) { // Add the child node. bonesptr[parent_bone_idx].child_bones.push_back(i); } else { @@ -250,27 +263,60 @@ void Skeleton3D::_update_process_order() { } } + bones_backup.resize(bones.size()); + process_order_dirty = false; + + emit_signal("bone_list_changed"); +} + +#ifndef DISABLE_DEPRECATED +void Skeleton3D::setup_simulator() { + if (simulator && simulator->get_parent() == this) { + remove_child(simulator); + simulator->queue_free(); + } + PhysicalBoneSimulator3D *sim = memnew(PhysicalBoneSimulator3D); + simulator = sim; + sim->is_compat = true; + sim->set_active(false); // Don't run unneeded process. + add_child(simulator); } +#endif // _DISABLE_DEPRECATED void Skeleton3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - if (dirty) { - notification(NOTIFICATION_UPDATE_SKELETON); - } + _process_changed(); + _make_modifiers_dirty(); + force_update_all_dirty_bones(); +#ifndef DISABLE_DEPRECATED + setup_simulator(); +#endif // _DISABLE_DEPRECATED } break; case NOTIFICATION_UPDATE_SKELETON: { - RenderingServer *rs = RenderingServer::get_singleton(); - Bone *bonesptr = bones.ptrw(); + // Update bone transforms to apply unprocessed poses. + force_update_all_dirty_bones(); + updating = true; + + Bone *bonesptr = bones.ptrw(); int len = bones.size(); - dirty = false; - // Update bone transforms. - force_update_all_bone_transforms(); + // Process modifiers. + _find_modifiers(); + if (!modifiers.is_empty()) { + // Store unmodified bone poses. + for (int i = 0; i < bones.size(); i++) { + bones_backup[i].save(bones[i]); + } + _process_modifiers(); + } + + emit_signal(SceneStringName(skeleton_updated)); // Update skins. + RenderingServer *rs = RenderingServer::get_singleton(); for (SkinReference *E : skin_bindings) { const Skin *skin = E->skin.operator->(); RID skeleton = E->skeleton; @@ -322,74 +368,74 @@ void Skeleton3D::_notification(int p_what) { for (uint32_t i = 0; i < bind_count; i++) { uint32_t bone_index = E->skin_bone_indices_ptrs[i]; ERR_CONTINUE(bone_index >= (uint32_t)len); - rs->skeleton_bone_set_transform(skeleton, i, bonesptr[bone_index].pose_global * skin->get_bind_pose(i)); + rs->skeleton_bone_set_transform(skeleton, i, bonesptr[bone_index].global_pose * skin->get_bind_pose(i)); } } - emit_signal(SceneStringNames::get_singleton()->pose_updated); - } break; -#ifndef _3D_DISABLED - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - // This is active only if the skeleton animates the physical bones - // and the state of the bone is not active. - if (animate_physical_bones) { - for (int i = 0; i < bones.size(); i += 1) { - if (bones[i].physical_bone) { - if (bones[i].physical_bone->is_simulating_physics() == false) { - bones[i].physical_bone->reset_to_rest_position(); - } - } + if (!modifiers.is_empty()) { + // Restore unmodified bone poses. + for (int i = 0; i < bones.size(); i++) { + bones_backup[i].restore(bones.write[i]); } } + + updating = false; + is_update_needed = false; } break; - case NOTIFICATION_READY: { - if (Engine::get_singleton()->is_editor_hint()) { - set_physics_process_internal(true); + case NOTIFICATION_INTERNAL_PROCESS: + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + _find_modifiers(); + if (!modifiers.is_empty()) { + _update_deferred(); } } break; -#endif // _3D_DISABLED } } -void Skeleton3D::clear_bones_global_pose_override() { - for (int i = 0; i < bones.size(); i += 1) { - bones.write[i].global_pose_override_amount = 0; - bones.write[i].global_pose_override_reset = true; +void Skeleton3D::set_modifier_callback_mode_process(Skeleton3D::ModifierCallbackModeProcess p_mode) { + if (modifier_callback_mode_process == p_mode) { + return; } - _make_dirty(); + modifier_callback_mode_process = p_mode; + _process_changed(); } -void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].global_pose_override_amount = p_amount; - bones.write[p_bone].global_pose_override = p_pose; - bones.write[p_bone].global_pose_override_reset = !p_persistent; - _make_dirty(); +Skeleton3D::ModifierCallbackModeProcess Skeleton3D::get_modifier_callback_mode_process() const { + return modifier_callback_mode_process; } -Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - return bones[p_bone].global_pose_override; +void Skeleton3D::_process_changed() { + if (modifier_callback_mode_process == MODIFIER_CALLBACK_MODE_PROCESS_IDLE) { + set_process_internal(true); + set_physics_process_internal(false); + } else if (modifier_callback_mode_process == MODIFIER_CALLBACK_MODE_PROCESS_PHYSICS) { + set_process_internal(false); + set_physics_process_internal(true); + } +} + +void Skeleton3D::_make_modifiers_dirty() { + modifiers_dirty = true; + _update_deferred(); } Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const { const int bone_size = bones.size(); ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - if (dirty) { - const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); - } - return bones[p_bone].pose_global; + const_cast<Skeleton3D *>(this)->force_update_all_dirty_bones(); + return bones[p_bone].global_pose; } -Transform3D Skeleton3D::get_bone_global_pose_no_override(int p_bone) const { +void Skeleton3D::set_bone_global_pose(int p_bone, const Transform3D &p_pose) { const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - if (dirty) { - const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); + ERR_FAIL_INDEX(p_bone, bone_size); + + Transform3D pt; + if (bones[p_bone].parent >= 0) { + pt = get_bone_global_pose(bones[p_bone].parent); } - return bones[p_bone].pose_global_no_override; + Transform3D t = pt.affine_inverse() * p_pose; + set_bone_pose(p_bone, t); } void Skeleton3D::set_motion_scale(float p_motion_scale) { @@ -548,7 +594,7 @@ Transform3D Skeleton3D::get_bone_global_rest(int p_bone) const { const int bone_size = bones.size(); ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); if (rest_dirty) { - const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); + const_cast<Skeleton3D *>(this)->force_update_all_bone_transforms(); } return bones[p_bone].global_rest; } @@ -558,7 +604,7 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { ERR_FAIL_INDEX(p_bone, bone_size); bones.write[p_bone].enabled = p_enabled; - emit_signal(SceneStringNames::get_singleton()->bone_enabled_changed, p_bone); + emit_signal(SceneStringName(bone_enabled_changed), p_bone); _make_dirty(); } @@ -570,7 +616,7 @@ bool Skeleton3D::is_bone_enabled(int p_bone) const { void Skeleton3D::set_show_rest_only(bool p_enabled) { show_rest_only = p_enabled; - emit_signal(SceneStringNames::get_singleton()->show_rest_only_changed); + emit_signal(SceneStringName(show_rest_only_changed)); _make_dirty(); } @@ -588,6 +634,19 @@ void Skeleton3D::clear_bones() { // Posing api +void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + + bones.write[p_bone].pose_position = p_pose.origin; + bones.write[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion(); + bones.write[p_bone].pose_scale = p_pose.basis.get_scale(); + bones.write[p_bone].pose_cache_dirty = true; + if (is_inside_tree()) { + _make_dirty(); + } +} + void Skeleton3D::set_bone_pose_position(int p_bone, const Vector3 &p_position) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); @@ -654,7 +713,7 @@ void Skeleton3D::reset_bone_poses() { Transform3D Skeleton3D::get_bone_pose(int p_bone) const { const int bone_size = bones.size(); ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - ((Skeleton3D *)this)->bones.write[p_bone].update_pose_cache(); + const_cast<Skeleton3D *>(this)->bones.write[p_bone].update_pose_cache(); return bones[p_bone].pose_cache; } @@ -662,11 +721,15 @@ void Skeleton3D::_make_dirty() { if (dirty) { return; } + dirty = true; + _update_deferred(); +} - if (is_inside_tree()) { - notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); +void Skeleton3D::_update_deferred() { + if (!is_update_needed && !updating && is_inside_tree()) { + is_update_needed = true; + notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); // It must never be called more than once in a single frame. } - dirty = true; } void Skeleton3D::localize_rests() { @@ -687,173 +750,6 @@ void Skeleton3D::localize_rests() { } } -void Skeleton3D::set_animate_physical_bones(bool p_enabled) { - animate_physical_bones = p_enabled; - - if (Engine::get_singleton()->is_editor_hint() == false) { - bool sim = false; - for (int i = 0; i < bones.size(); i += 1) { - if (bones[i].physical_bone) { - bones[i].physical_bone->reset_physics_simulation_state(); - if (bones[i].physical_bone->is_simulating_physics()) { - sim = true; - } - } - } - set_physics_process_internal(sim == false && p_enabled); - } -} - -bool Skeleton3D::get_animate_physical_bones() const { - return animate_physical_bones; -} - -void Skeleton3D::bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX(p_bone, bone_size); - ERR_FAIL_COND(bones[p_bone].physical_bone); - ERR_FAIL_NULL(p_physical_bone); - bones.write[p_bone].physical_bone = p_physical_bone; - - _rebuild_physical_bones_cache(); -} - -void Skeleton3D::unbind_physical_bone_from_bone(int p_bone) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].physical_bone = nullptr; - - _rebuild_physical_bones_cache(); -} - -PhysicalBone3D *Skeleton3D::get_physical_bone(int p_bone) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); - - return bones[p_bone].physical_bone; -} - -PhysicalBone3D *Skeleton3D::get_physical_bone_parent(int p_bone) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); - - if (bones[p_bone].cache_parent_physical_bone) { - return bones[p_bone].cache_parent_physical_bone; - } - - return _get_physical_bone_parent(p_bone); -} - -PhysicalBone3D *Skeleton3D::_get_physical_bone_parent(int p_bone) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, nullptr); - - const int parent_bone = bones[p_bone].parent; - if (0 > parent_bone) { - return nullptr; - } - - PhysicalBone3D *pb = bones[parent_bone].physical_bone; - if (pb) { - return pb; - } else { - return get_physical_bone_parent(parent_bone); - } -} - -void Skeleton3D::_rebuild_physical_bones_cache() { - const int b_size = bones.size(); - for (int i = 0; i < b_size; ++i) { - PhysicalBone3D *parent_pb = _get_physical_bone_parent(i); - if (parent_pb != bones[i].cache_parent_physical_bone) { - bones.write[i].cache_parent_physical_bone = parent_pb; - if (bones[i].physical_bone) { - bones[i].physical_bone->_on_bone_parent_changed(); - } - } - } -} - -void _pb_stop_simulation(Node *p_node) { - for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { - _pb_stop_simulation(p_node->get_child(i)); - } - - PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node); - if (pb) { - pb->set_simulate_physics(false); - } -} - -void Skeleton3D::physical_bones_stop_simulation() { - _pb_stop_simulation(this); - if (Engine::get_singleton()->is_editor_hint() == false && animate_physical_bones) { - set_physics_process_internal(true); - } -} - -void _pb_start_simulation(const Skeleton3D *p_skeleton, Node *p_node, const Vector<int> &p_sim_bones) { - for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { - _pb_start_simulation(p_skeleton, p_node->get_child(i), p_sim_bones); - } - - PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node); - if (pb) { - if (p_sim_bones.is_empty()) { // If no bones is specified, activate ragdoll on full body. - pb->set_simulate_physics(true); - } else { - for (int i = p_sim_bones.size() - 1; 0 <= i; --i) { - if (p_sim_bones[i] == pb->get_bone_id() || p_skeleton->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) { - pb->set_simulate_physics(true); - break; - } - } - } - } -} - -void Skeleton3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) { - set_physics_process_internal(false); - - Vector<int> sim_bones; - if (p_bones.size() > 0) { - sim_bones.resize(p_bones.size()); - int c = 0; - for (int i = sim_bones.size() - 1; 0 <= i; --i) { - int bone_id = find_bone(p_bones[i]); - if (bone_id != -1) { - sim_bones.write[c++] = bone_id; - } - } - sim_bones.resize(c); - } - - _pb_start_simulation(this, this, sim_bones); -} - -void _physical_bones_add_remove_collision_exception(bool p_add, Node *p_node, RID p_exception) { - for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { - _physical_bones_add_remove_collision_exception(p_add, p_node->get_child(i), p_exception); - } - - CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_node); - if (co) { - if (p_add) { - PhysicsServer3D::get_singleton()->body_add_collision_exception(co->get_rid(), p_exception); - } else { - PhysicsServer3D::get_singleton()->body_remove_collision_exception(co->get_rid(), p_exception); - } - } -} - -void Skeleton3D::physical_bones_add_collision_exception(RID p_exception) { - _physical_bones_add_remove_collision_exception(true, this, p_exception); -} - -void Skeleton3D::physical_bones_remove_collision_exception(RID p_exception) { - _physical_bones_add_remove_collision_exception(false, this, p_exception); -} - void Skeleton3D::_skin_changed() { _make_dirty(); } @@ -927,18 +823,23 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { } void Skeleton3D::force_update_all_dirty_bones() { - if (dirty) { - const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON); + if (!dirty) { + return; } + force_update_all_bone_transforms(); } void Skeleton3D::force_update_all_bone_transforms() { _update_process_order(); - for (int i = 0; i < parentless_bones.size(); i++) { force_update_bone_children_transforms(parentless_bones[i]); } rest_dirty = false; + dirty = false; + if (updating) { + return; + } + emit_signal(SceneStringName(pose_updated)); } void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { @@ -946,12 +847,13 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { ERR_FAIL_INDEX(p_bone_idx, bone_size); Bone *bonesptr = bones.ptrw(); - List<int> bones_to_process = List<int>(); + thread_local LocalVector<int> bones_to_process; + bones_to_process.clear(); bones_to_process.push_back(p_bone_idx); - while (bones_to_process.size() > 0) { - int current_bone_idx = bones_to_process[0]; - bones_to_process.erase(current_bone_idx); + uint32_t index = 0; + while (index < bones_to_process.size()) { + int current_bone_idx = bones_to_process[index]; Bone &b = bonesptr[current_bone_idx]; bool bone_enabled = b.enabled && !show_rest_only; @@ -961,32 +863,43 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { Transform3D pose = b.pose_cache; if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * pose; - b.pose_global_no_override = bonesptr[b.parent].pose_global_no_override * pose; + b.global_pose = bonesptr[b.parent].global_pose * pose; } else { - b.pose_global = pose; - b.pose_global_no_override = pose; + b.global_pose = pose; } } else { if (b.parent >= 0) { - b.pose_global = bonesptr[b.parent].pose_global * b.rest; - b.pose_global_no_override = bonesptr[b.parent].pose_global_no_override * b.rest; + b.global_pose = bonesptr[b.parent].global_pose * b.rest; } else { - b.pose_global = b.rest; - b.pose_global_no_override = b.rest; + b.global_pose = b.rest; } } if (rest_dirty) { b.global_rest = b.parent >= 0 ? bonesptr[b.parent].global_rest * b.rest : b.rest; } +#ifndef DISABLE_DEPRECATED + if (bone_enabled) { + Transform3D pose = b.pose_cache; + if (b.parent >= 0) { + b.pose_global_no_override = bonesptr[b.parent].pose_global_no_override * pose; + } else { + b.pose_global_no_override = pose; + } + } else { + if (b.parent >= 0) { + b.pose_global_no_override = bonesptr[b.parent].pose_global_no_override * b.rest; + } else { + b.pose_global_no_override = b.rest; + } + } if (b.global_pose_override_amount >= CMP_EPSILON) { - b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount); + b.global_pose = b.global_pose.interpolate_with(b.global_pose_override, b.global_pose_override_amount); } - if (b.global_pose_override_reset) { b.global_pose_override_amount = 0.0; } +#endif // _DISABLE_DEPRECATED // Add the bone's children to the list of bones to be processed. int child_bone_size = b.child_bones.size(); @@ -994,7 +907,73 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { bones_to_process.push_back(b.child_bones[i]); } - emit_signal(SceneStringNames::get_singleton()->bone_pose_changed, current_bone_idx); + index++; + } +} + +void Skeleton3D::_find_modifiers() { + if (!modifiers_dirty) { + return; + } + modifiers.clear(); + for (int i = 0; i < get_child_count(); i++) { + SkeletonModifier3D *c = Object::cast_to<SkeletonModifier3D>(get_child(i)); + if (c) { + modifiers.push_back(c->get_instance_id()); + } + } + modifiers_dirty = false; +} + +void Skeleton3D::_process_modifiers() { + for (const ObjectID &oid : modifiers) { + Object *t_obj = ObjectDB::get_instance(oid); + if (!t_obj) { + continue; + } + SkeletonModifier3D *mod = cast_to<SkeletonModifier3D>(t_obj); + if (!mod) { + continue; + } + real_t influence = mod->get_influence(); + if (influence < 1.0) { + LocalVector<Transform3D> old_poses; + for (int i = 0; i < get_bone_count(); i++) { + old_poses.push_back(get_bone_pose(i)); + } + mod->process_modification(); + LocalVector<Transform3D> new_poses; + for (int i = 0; i < get_bone_count(); i++) { + new_poses.push_back(get_bone_pose(i)); + } + for (int i = 0; i < get_bone_count(); i++) { + if (old_poses[i] == new_poses[i]) { + continue; // Avoid unneeded calculation. + } + set_bone_pose(i, old_poses[i].interpolate_with(new_poses[i], influence)); + } + } else { + mod->process_modification(); + } + force_update_all_dirty_bones(); + } +} + +void Skeleton3D::add_child_notify(Node *p_child) { + if (Object::cast_to<SkeletonModifier3D>(p_child)) { + _make_modifiers_dirty(); + } +} + +void Skeleton3D::move_child_notify(Node *p_child) { + if (Object::cast_to<SkeletonModifier3D>(p_child)) { + _make_modifiers_dirty(); + } +} + +void Skeleton3D::remove_child_notify(Node *p_child) { + if (Object::cast_to<SkeletonModifier3D>(p_child)) { + _make_modifiers_dirty(); } } @@ -1028,6 +1007,7 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_bones"), &Skeleton3D::clear_bones); ClassDB::bind_method(D_METHOD("get_bone_pose", "bone_idx"), &Skeleton3D::get_bone_pose); + ClassDB::bind_method(D_METHOD("set_bone_pose", "bone_idx", "pose"), &Skeleton3D::set_bone_pose); ClassDB::bind_method(D_METHOD("set_bone_pose_position", "bone_idx", "position"), &Skeleton3D::set_bone_pose_position); ClassDB::bind_method(D_METHOD("set_bone_pose_rotation", "bone_idx", "rotation"), &Skeleton3D::set_bone_pose_rotation); ClassDB::bind_method(D_METHOD("set_bone_pose_scale", "bone_idx", "scale"), &Skeleton3D::set_bone_pose_scale); @@ -1042,11 +1022,8 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_bone_enabled", "bone_idx"), &Skeleton3D::is_bone_enabled); ClassDB::bind_method(D_METHOD("set_bone_enabled", "bone_idx", "enabled"), &Skeleton3D::set_bone_enabled, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override); - ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_bone_global_pose_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_override); ClassDB::bind_method(D_METHOD("get_bone_global_pose", "bone_idx"), &Skeleton3D::get_bone_global_pose); - ClassDB::bind_method(D_METHOD("get_bone_global_pose_no_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_no_override); + ClassDB::bind_method(D_METHOD("set_bone_global_pose", "bone_idx", "pose"), &Skeleton3D::set_bone_global_pose); ClassDB::bind_method(D_METHOD("force_update_all_bone_transforms"), &Skeleton3D::force_update_all_bone_transforms); ClassDB::bind_method(D_METHOD("force_update_bone_child_transform", "bone_idx"), &Skeleton3D::force_update_bone_children_transforms); @@ -1057,27 +1034,125 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_show_rest_only", "enabled"), &Skeleton3D::set_show_rest_only); ClassDB::bind_method(D_METHOD("is_show_rest_only"), &Skeleton3D::is_show_rest_only); - ClassDB::bind_method(D_METHOD("set_animate_physical_bones", "enabled"), &Skeleton3D::set_animate_physical_bones); - ClassDB::bind_method(D_METHOD("get_animate_physical_bones"), &Skeleton3D::get_animate_physical_bones); - - ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &Skeleton3D::physical_bones_stop_simulation); - ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &Skeleton3D::physical_bones_start_simulation_on, DEFVAL(Array())); - ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton3D::physical_bones_add_collision_exception); - ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton3D::physical_bones_remove_collision_exception); + ClassDB::bind_method(D_METHOD("set_modifier_callback_mode_process", "mode"), &Skeleton3D::set_modifier_callback_mode_process); + ClassDB::bind_method(D_METHOD("get_modifier_callback_mode_process"), &Skeleton3D::get_modifier_callback_mode_process); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_scale", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater"), "set_motion_scale", "get_motion_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_rest_only"), "set_show_rest_only", "is_show_rest_only"); -#ifndef _3D_DISABLED - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones"); -#endif // _3D_DISABLED + + ADD_GROUP("Modifier", "modifier_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "modifier_callback_mode_process", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_modifier_callback_mode_process", "get_modifier_callback_mode_process"); ADD_SIGNAL(MethodInfo("pose_updated")); - ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx"))); + ADD_SIGNAL(MethodInfo("skeleton_updated")); ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx"))); + ADD_SIGNAL(MethodInfo("bone_list_changed")); ADD_SIGNAL(MethodInfo("show_rest_only_changed")); BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); + BIND_ENUM_CONSTANT(MODIFIER_CALLBACK_MODE_PROCESS_PHYSICS); + BIND_ENUM_CONSTANT(MODIFIER_CALLBACK_MODE_PROCESS_IDLE); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override); + ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_bone_global_pose_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_override); + ClassDB::bind_method(D_METHOD("get_bone_global_pose_no_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_no_override); + + ClassDB::bind_method(D_METHOD("set_animate_physical_bones", "enabled"), &Skeleton3D::set_animate_physical_bones); + ClassDB::bind_method(D_METHOD("get_animate_physical_bones"), &Skeleton3D::get_animate_physical_bones); + ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &Skeleton3D::physical_bones_stop_simulation); + ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &Skeleton3D::physical_bones_start_simulation_on, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton3D::physical_bones_add_collision_exception); + ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton3D::physical_bones_remove_collision_exception); +#endif // _DISABLE_DEPRECATED +} + +#ifndef DISABLE_DEPRECATED +void Skeleton3D::clear_bones_global_pose_override() { + for (int i = 0; i < bones.size(); i += 1) { + bones.write[i].global_pose_override_amount = 0; + bones.write[i].global_pose_override_reset = true; + } + _make_dirty(); +} + +void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + bones.write[p_bone].global_pose_override_amount = p_amount; + bones.write[p_bone].global_pose_override = p_pose; + bones.write[p_bone].global_pose_override_reset = !p_persistent; + _make_dirty(); +} + +Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + return bones[p_bone].global_pose_override; +} + +Transform3D Skeleton3D::get_bone_global_pose_no_override(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); + const_cast<Skeleton3D *>(this)->force_update_all_dirty_bones(); + return bones[p_bone].pose_global_no_override; +} + +Node *Skeleton3D::get_simulator() { + return simulator; +} + +void Skeleton3D::set_animate_physical_bones(bool p_enabled) { + PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); + if (!sim) { + return; + } + sim->set_active(p_enabled); +} + +bool Skeleton3D::get_animate_physical_bones() const { + PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); + if (!sim) { + return false; + } + return sim->is_active(); +} + +void Skeleton3D::physical_bones_stop_simulation() { + PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); + if (!sim) { + return; + } + sim->physical_bones_stop_simulation(); + sim->set_active(false); +} + +void Skeleton3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) { + PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); + if (!sim) { + return; + } + sim->set_active(true); + sim->physical_bones_start_simulation_on(p_bones); +} + +void Skeleton3D::physical_bones_add_collision_exception(RID p_exception) { + PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); + if (!sim) { + return; + } + sim->physical_bones_add_collision_exception(p_exception); +} + +void Skeleton3D::physical_bones_remove_collision_exception(RID p_exception) { + PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); + if (!sim) { + return; + } + sim->physical_bones_remove_collision_exception(p_exception); } +#endif // _DISABLE_DEPRECATED Skeleton3D::Skeleton3D() { } diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 979e2e52b7..23b9423993 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -36,8 +36,8 @@ typedef int BoneId; -class PhysicalBone3D; class Skeleton3D; +class SkeletonModifier3D; class SkinReference : public RefCounted { GDCLASS(SkinReference, RefCounted) @@ -66,61 +66,84 @@ public: class Skeleton3D : public Node3D { GDCLASS(Skeleton3D, Node3D); +#ifndef DISABLE_DEPRECATED + Node *simulator = nullptr; + void setup_simulator(); +#endif // _DISABLE_DEPRECATED + +public: + enum ModifierCallbackModeProcess { + MODIFIER_CALLBACK_MODE_PROCESS_PHYSICS, + MODIFIER_CALLBACK_MODE_PROCESS_IDLE, + }; + private: friend class SkinReference; + void _update_deferred(); + bool is_update_needed = false; // Is updating reserved? + bool updating = false; // Is updating now? + struct Bone { String name; - bool enabled; - int parent; + int parent = -1; + Vector<int> child_bones; Transform3D rest; Transform3D global_rest; - _FORCE_INLINE_ void update_pose_cache() { + bool enabled = true; + bool pose_cache_dirty = true; + Transform3D pose_cache; + Vector3 pose_position; + Quaternion pose_rotation; + Vector3 pose_scale = Vector3(1, 1, 1); + Transform3D global_pose; + + void update_pose_cache() { if (pose_cache_dirty) { pose_cache.basis.set_quaternion_scale(pose_rotation, pose_scale); pose_cache.origin = pose_position; pose_cache_dirty = false; } } - bool pose_cache_dirty = true; - Transform3D pose_cache; - Vector3 pose_position; - Quaternion pose_rotation; - Vector3 pose_scale = Vector3(1, 1, 1); - Transform3D pose_global; +#ifndef DISABLE_DEPRECATED Transform3D pose_global_no_override; - real_t global_pose_override_amount = 0.0; bool global_pose_override_reset = false; Transform3D global_pose_override; +#endif // _DISABLE_DEPRECATED + }; - PhysicalBone3D *physical_bone = nullptr; - PhysicalBone3D *cache_parent_physical_bone = nullptr; - - Vector<int> child_bones; + struct BonePoseBackup { + Transform3D pose_cache; + Vector3 pose_position; + Quaternion pose_rotation; + Vector3 pose_scale = Vector3(1, 1, 1); + Transform3D global_pose; + + void save(const Bone &p_bone) { + pose_cache = p_bone.pose_cache; + pose_position = p_bone.pose_position; + pose_rotation = p_bone.pose_rotation; + pose_scale = p_bone.pose_scale; + global_pose = p_bone.global_pose; + } - Bone() { - parent = -1; - enabled = true; - global_pose_override_amount = 0; - global_pose_override_reset = false; -#ifndef _3D_DISABLED - physical_bone = nullptr; - cache_parent_physical_bone = nullptr; -#endif // _3D_DISABLED - child_bones = Vector<int>(); + void restore(Bone &r_bone) { + r_bone.pose_cache = pose_cache; + r_bone.pose_position = pose_position; + r_bone.pose_rotation = pose_rotation; + r_bone.pose_scale = pose_scale; + r_bone.global_pose = global_pose; } }; HashSet<SkinReference *> skin_bindings; - void _skin_changed(); - bool animate_physical_bones = true; Vector<Bone> bones; bool process_order_dirty = false; @@ -138,6 +161,16 @@ private: void _update_process_order(); + // To process modifiers. + ModifierCallbackModeProcess modifier_callback_mode_process = MODIFIER_CALLBACK_MODE_PROCESS_IDLE; + LocalVector<ObjectID> modifiers; + bool modifiers_dirty = false; + void _find_modifiers(); + void _process_modifiers(); + void _process_changed(); + void _make_modifiers_dirty(); + LocalVector<BonePoseBackup> bones_backup; + #ifndef DISABLE_DEPRECATED void _add_bone_bind_compat_88791(const String &p_name); @@ -152,12 +185,16 @@ protected: void _notification(int p_what); static void _bind_methods(); + virtual void add_child_notify(Node *p_child) override; + virtual void move_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + public: enum { NOTIFICATION_UPDATE_SKELETON = 50 }; - // skeleton creation api + // Skeleton creation API uint64_t get_version() const; int add_bone(const String &p_name); int find_bone(const String &p_name) const; @@ -179,8 +216,6 @@ public: void set_bone_rest(int p_bone, const Transform3D &p_rest); Transform3D get_bone_rest(int p_bone) const; Transform3D get_bone_global_rest(int p_bone) const; - Transform3D get_bone_global_pose(int p_bone) const; - Transform3D get_bone_global_pose_no_override(int p_bone) const; void set_bone_enabled(int p_bone, bool p_enabled); bool is_bone_enabled(int p_bone) const; @@ -192,26 +227,23 @@ public: void set_motion_scale(float p_motion_scale); float get_motion_scale() const; - // posing api - - void set_bone_pose_position(int p_bone, const Vector3 &p_position); - void set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation); - void set_bone_pose_scale(int p_bone, const Vector3 &p_scale); - + // Posing API Transform3D get_bone_pose(int p_bone) const; - Vector3 get_bone_pose_position(int p_bone) const; Quaternion get_bone_pose_rotation(int p_bone) const; Vector3 get_bone_pose_scale(int p_bone) const; + void set_bone_pose(int p_bone, const Transform3D &p_pose); + void set_bone_pose_position(int p_bone, const Vector3 &p_position); + void set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation); + void set_bone_pose_scale(int p_bone, const Vector3 &p_scale); + + Transform3D get_bone_global_pose(int p_bone) const; + void set_bone_global_pose(int p_bone, const Transform3D &p_pose); void reset_bone_pose(int p_bone); void reset_bone_poses(); - void clear_bones_global_pose_override(); - Transform3D get_bone_global_pose_override(int p_bone) const; - void set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); - - void localize_rests(); // used for loaders and tools + void localize_rests(); // Used for loaders and tools. Ref<Skin> create_skin_from_rest_transforms(); @@ -221,31 +253,29 @@ public: void force_update_all_bone_transforms(); void force_update_bone_children_transforms(int bone_idx); - // Physical bone API + void set_modifier_callback_mode_process(ModifierCallbackModeProcess p_mode); + ModifierCallbackModeProcess get_modifier_callback_mode_process() const; + +#ifndef DISABLE_DEPRECATED + Transform3D get_bone_global_pose_no_override(int p_bone) const; + void clear_bones_global_pose_override(); + Transform3D get_bone_global_pose_override(int p_bone) const; + void set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); + Node *get_simulator(); void set_animate_physical_bones(bool p_enabled); bool get_animate_physical_bones() const; - - void bind_physical_bone_to_bone(int p_bone, PhysicalBone3D *p_physical_bone); - void unbind_physical_bone_from_bone(int p_bone); - - PhysicalBone3D *get_physical_bone(int p_bone); - PhysicalBone3D *get_physical_bone_parent(int p_bone); - -private: - /// This is a slow API, so it's cached - PhysicalBone3D *_get_physical_bone_parent(int p_bone); - void _rebuild_physical_bones_cache(); - -public: void physical_bones_stop_simulation(); void physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones); void physical_bones_add_collision_exception(RID p_exception); void physical_bones_remove_collision_exception(RID p_exception); +#endif // _DISABLE_DEPRECATED public: Skeleton3D(); ~Skeleton3D(); }; +VARIANT_ENUM_CAST(Skeleton3D::ModifierCallbackModeProcess); + #endif // SKELETON_3D_H diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 286268b4a2..78a21ba9e1 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -30,8 +30,6 @@ #include "skeleton_ik_3d.h" -#ifndef _3D_DISABLED - FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::find_child(const BoneId p_bone_id) { for (int i = children.size() - 1; 0 <= i; --i) { if (p_bone_id == children[i].bone) { @@ -236,46 +234,21 @@ void FabrikInverseKinematic::set_goal(Task *p_task, const Transform3D &p_goal) { p_task->goal_global_transform = p_goal; } -void FabrikInverseKinematic::make_goal(Task *p_task, const Transform3D &p_inverse_transf, real_t blending_delta) { - if (blending_delta >= 0.99f) { - // Update the end_effector (local transform) without blending - p_task->end_effectors.write[0].goal_transform = p_inverse_transf * p_task->goal_global_transform; - } else { - // End effector in local transform - const Transform3D end_effector_pose(p_task->skeleton->get_bone_global_pose_no_override(p_task->end_effectors[0].tip_bone)); - - // Update the end_effector (local transform) by blending with current pose - p_task->end_effectors.write[0].goal_transform = end_effector_pose.interpolate_with(p_inverse_transf * p_task->goal_global_transform, blending_delta); - } +void FabrikInverseKinematic::make_goal(Task *p_task, const Transform3D &p_inverse_transf) { + // Update the end_effector (local transform) by blending with current pose + p_task->end_effectors.write[0].goal_transform = p_inverse_transf * p_task->goal_global_transform; } -void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool override_tip_basis, bool p_use_magnet, const Vector3 &p_magnet_position) { - if (blending_delta <= 0.01f) { - // Before skipping, make sure we undo the global pose overrides - ChainItem *ci(&p_task->chain.chain_root); - while (ci) { - p_task->skeleton->set_bone_global_pose_override(ci->bone, ci->initial_transform, 0.0, false); - - if (!ci->children.is_empty()) { - ci = &ci->children.write[0]; - } else { - ci = nullptr; - } - } - - return; // Skip solving - } - +void FabrikInverseKinematic::solve(Task *p_task, bool override_tip_basis, bool p_use_magnet, const Vector3 &p_magnet_position) { // Update the initial root transform so its synced with any animation changes _update_chain(p_task->skeleton, &p_task->chain.chain_root); - p_task->skeleton->set_bone_global_pose_override(p_task->chain.chain_root.bone, Transform3D(), 0.0, false); Vector3 origin_pos = p_task->skeleton->get_bone_global_pose(p_task->chain.chain_root.bone).origin; - make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse(), blending_delta); + make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse()); if (p_use_magnet && p_task->chain.middle_chain_item) { - p_task->chain.magnet_position = p_task->chain.middle_chain_item->initial_transform.origin.lerp(p_magnet_position, blending_delta); + p_task->chain.magnet_position = p_magnet_position; solve_simple(p_task, true, origin_pos); } solve_simple(p_task, false, origin_pos); @@ -303,8 +276,7 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove // IK should not affect scale, so undo any scaling new_bone_pose.basis.orthonormalize(); new_bone_pose.basis.scale(p_task->skeleton->get_bone_global_pose(ci->bone).basis.get_scale()); - - p_task->skeleton->set_bone_global_pose_override(ci->bone, new_bone_pose, 1.0, true); + p_task->skeleton->set_bone_global_pose(ci->bone, Transform3D(new_bone_pose.basis, p_task->skeleton->get_bone_global_pose(ci->bone).origin)); if (!ci->children.is_empty()) { ci = &ci->children.write[0]; @@ -319,7 +291,7 @@ void FabrikInverseKinematic::_update_chain(const Skeleton3D *p_sk, ChainItem *p_ return; } - p_chain_item->initial_transform = p_sk->get_bone_global_pose_no_override(p_chain_item->bone); + p_chain_item->initial_transform = p_sk->get_bone_global_pose(p_chain_item->bone); p_chain_item->current_pos = p_chain_item->initial_transform.origin; ChainItem *items = p_chain_item->children.ptrw(); @@ -329,8 +301,10 @@ void FabrikInverseKinematic::_update_chain(const Skeleton3D *p_sk, ChainItem *p_ } void SkeletonIK3D::_validate_property(PropertyInfo &p_property) const { + SkeletonModifier3D::_validate_property(p_property); + if (p_property.name == "root_bone" || p_property.name == "tip_bone") { - Skeleton3D *skeleton = get_parent_skeleton(); + Skeleton3D *skeleton = get_skeleton(); if (skeleton) { String names("--,"); for (int i = 0; i < skeleton->get_bone_count(); i++) { @@ -356,9 +330,6 @@ void SkeletonIK3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tip_bone", "tip_bone"), &SkeletonIK3D::set_tip_bone); ClassDB::bind_method(D_METHOD("get_tip_bone"), &SkeletonIK3D::get_tip_bone); - ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK3D::set_interpolation); - ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK3D::get_interpolation); - ClassDB::bind_method(D_METHOD("set_target_transform", "target"), &SkeletonIK3D::set_target_transform); ClassDB::bind_method(D_METHOD("get_target_transform"), &SkeletonIK3D::get_target_transform); @@ -388,7 +359,6 @@ void SkeletonIK3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "root_bone"), "set_root_bone", "get_root_bone"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "tip_bone"), "set_tip_bone", "get_tip_bone"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_interpolation", "get_interpolation"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "target", PROPERTY_HINT_NONE, "suffix:m"), "set_target_transform", "get_target_transform"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_tip_basis"), "set_override_tip_basis", "is_override_tip_basis"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_magnet"), "set_use_magnet", "is_using_magnet"); @@ -396,23 +366,29 @@ void SkeletonIK3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_min_distance", "get_min_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations"); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK3D::_set_interpolation); + ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK3D::_get_interpolation); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001", PROPERTY_USAGE_NONE), "set_interpolation", "get_interpolation"); +#endif +} + +void SkeletonIK3D::_process_modification() { + if (!internal_active) { + return; + } + if (target_node_override_ref) { + reload_goal(); + } + _solve_chain(); } void SkeletonIK3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - skeleton_ref = Object::cast_to<Skeleton3D>(get_parent()); - set_process_priority(1); reload_chain(); } break; - - case NOTIFICATION_INTERNAL_PROCESS: { - if (target_node_override_ref) { - reload_goal(); - } - _solve_chain(); - } break; - case NOTIFICATION_EXIT_TREE: { stop(); } break; @@ -445,13 +421,15 @@ StringName SkeletonIK3D::get_tip_bone() const { return tip_bone; } -void SkeletonIK3D::set_interpolation(real_t p_interpolation) { - interpolation = p_interpolation; +#ifndef DISABLE_DEPRECATED +void SkeletonIK3D::_set_interpolation(real_t p_interpolation) { + set_influence(p_interpolation); } -real_t SkeletonIK3D::get_interpolation() const { - return interpolation; +real_t SkeletonIK3D::_get_interpolation() const { + return get_influence(); } +#endif void SkeletonIK3D::set_target_transform(const Transform3D &p_target) { target = p_target; @@ -505,33 +483,25 @@ void SkeletonIK3D::set_max_iterations(int p_iterations) { } Skeleton3D *SkeletonIK3D::get_parent_skeleton() const { - return cast_to<Skeleton3D>(skeleton_ref.get_validated_object()); + return get_skeleton(); } bool SkeletonIK3D::is_running() { - return is_processing_internal(); + return internal_active; } void SkeletonIK3D::start(bool p_one_time) { if (p_one_time) { - set_process_internal(false); - - if (target_node_override_ref) { - reload_goal(); - } - - _solve_chain(); + internal_active = true; + SkeletonModifier3D::process_modification(); + internal_active = false; } else { - set_process_internal(true); + internal_active = true; } } void SkeletonIK3D::stop() { - set_process_internal(false); - Skeleton3D *skeleton = get_parent_skeleton(); - if (skeleton) { - skeleton->clear_bones_global_pose_override(); - } + internal_active = false; } Transform3D SkeletonIK3D::_get_target_transform() { @@ -551,7 +521,7 @@ void SkeletonIK3D::reload_chain() { FabrikInverseKinematic::free_task(task); task = nullptr; - Skeleton3D *skeleton = get_parent_skeleton(); + Skeleton3D *skeleton = get_skeleton(); if (!skeleton) { return; } @@ -575,7 +545,5 @@ void SkeletonIK3D::_solve_chain() { if (!task) { return; } - FabrikInverseKinematic::solve(task, interpolation, override_tip_basis, use_magnet, magnet_position); + FabrikInverseKinematic::solve(task, override_tip_basis, use_magnet, magnet_position); } - -#endif // _3D_DISABLED diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h index 0a03e96905..5d6020194e 100644 --- a/scene/3d/skeleton_ik_3d.h +++ b/scene/3d/skeleton_ik_3d.h @@ -31,9 +31,7 @@ #ifndef SKELETON_IK_3D_H #define SKELETON_IK_3D_H -#ifndef _3D_DISABLED - -#include "scene/3d/skeleton_3d.h" +#include "scene/3d/skeleton_modifier_3d.h" class FabrikInverseKinematic { struct EndEffector { @@ -111,18 +109,19 @@ public: static void free_task(Task *p_task); // The goal of chain should be always in local space static void set_goal(Task *p_task, const Transform3D &p_goal); - static void make_goal(Task *p_task, const Transform3D &p_inverse_transf, real_t blending_delta); - static void solve(Task *p_task, real_t blending_delta, bool override_tip_basis, bool p_use_magnet, const Vector3 &p_magnet_position); + static void make_goal(Task *p_task, const Transform3D &p_inverse_transf); + static void solve(Task *p_task, bool override_tip_basis, bool p_use_magnet, const Vector3 &p_magnet_position); static void _update_chain(const Skeleton3D *p_skeleton, ChainItem *p_chain_item); }; -class SkeletonIK3D : public Node { - GDCLASS(SkeletonIK3D, Node); +class SkeletonIK3D : public SkeletonModifier3D { + GDCLASS(SkeletonIK3D, SkeletonModifier3D); + + bool internal_active = false; StringName root_bone; StringName tip_bone; - real_t interpolation = 1.0; Transform3D target; NodePath target_node_path_override; bool override_tip_basis = true; @@ -132,16 +131,22 @@ class SkeletonIK3D : public Node { real_t min_distance = 0.01; int max_iterations = 10; - Variant skeleton_ref = Variant(); Variant target_node_override_ref = Variant(); FabrikInverseKinematic::Task *task = nullptr; +#ifndef DISABLE_DEPRECATED + void _set_interpolation(real_t p_interpolation); + real_t _get_interpolation() const; +#endif // DISABLE_DEPRECATED + protected: void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); virtual void _notification(int p_what); + virtual void _process_modification() override; + public: SkeletonIK3D(); virtual ~SkeletonIK3D(); @@ -152,9 +157,6 @@ public: void set_tip_bone(const StringName &p_tip_bone); StringName get_tip_bone() const; - void set_interpolation(real_t p_interpolation); - real_t get_interpolation() const; - void set_target_transform(const Transform3D &p_target); const Transform3D &get_target_transform() const; @@ -190,6 +192,4 @@ private: void _solve_chain(); }; -#endif // _3D_DISABLED - #endif // SKELETON_IK_3D_H diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp new file mode 100644 index 0000000000..8d806ef5fc --- /dev/null +++ b/scene/3d/skeleton_modifier_3d.cpp @@ -0,0 +1,140 @@ +/**************************************************************************/ +/* skeleton_modifier_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "skeleton_modifier_3d.h" + +void SkeletonModifier3D::_validate_property(PropertyInfo &p_property) const { + // +} + +PackedStringArray SkeletonModifier3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); + if (skeleton_id.is_null()) { + warnings.push_back(RTR("Skeleton3D node not set! SkeletonModifier3D must be child of Skeleton3D or set a path to an external skeleton.")); + } + return warnings; +} + +/* Skeleton3D */ + +Skeleton3D *SkeletonModifier3D::get_skeleton() const { + return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(skeleton_id)); +} + +void SkeletonModifier3D::_update_skeleton_path() { + skeleton_id = ObjectID(); + + // Make sure parent is a Skeleton3D. + Skeleton3D *sk = Object::cast_to<Skeleton3D>(get_parent()); + if (sk) { + skeleton_id = sk->get_instance_id(); + } +} + +void SkeletonModifier3D::_update_skeleton() { + if (!is_inside_tree()) { + return; + } + Skeleton3D *old_sk = get_skeleton(); + _update_skeleton_path(); + Skeleton3D *new_sk = get_skeleton(); + if (old_sk != new_sk) { + _skeleton_changed(old_sk, new_sk); + } + update_configuration_warnings(); +} + +void SkeletonModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { + // +} + +/* Process */ + +void SkeletonModifier3D::set_active(bool p_active) { + if (active == p_active) { + return; + } + active = p_active; + _set_active(active); +} + +bool SkeletonModifier3D::is_active() const { + return active; +} + +void SkeletonModifier3D::_set_active(bool p_active) { + // +} + +void SkeletonModifier3D::set_influence(real_t p_influence) { + influence = p_influence; +} + +real_t SkeletonModifier3D::get_influence() const { + return influence; +} + +void SkeletonModifier3D::process_modification() { + if (!active) { + return; + } + _process_modification(); + emit_signal(SNAME("modification_processed")); +} + +void SkeletonModifier3D::_process_modification() { + GDVIRTUAL_CALL(_process_modification); +} + +void SkeletonModifier3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_PARENTED: { + _update_skeleton(); + } break; + } +} + +void SkeletonModifier3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_active", "active"), &SkeletonModifier3D::set_active); + ClassDB::bind_method(D_METHOD("is_active"), &SkeletonModifier3D::is_active); + + ClassDB::bind_method(D_METHOD("set_influence", "influence"), &SkeletonModifier3D::set_influence); + ClassDB::bind_method(D_METHOD("get_influence"), &SkeletonModifier3D::get_influence); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "influence", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_influence", "get_influence"); + + ADD_SIGNAL(MethodInfo("modification_processed")); + GDVIRTUAL_BIND(_process_modification); +} + +SkeletonModifier3D::SkeletonModifier3D() { +} diff --git a/scene/3d/skeleton_modifier_3d.h b/scene/3d/skeleton_modifier_3d.h new file mode 100644 index 0000000000..d00a1e94a9 --- /dev/null +++ b/scene/3d/skeleton_modifier_3d.h @@ -0,0 +1,82 @@ +/**************************************************************************/ +/* skeleton_modifier_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SKELETON_MODIFIER_3D_H +#define SKELETON_MODIFIER_3D_H + +#include "scene/3d/node_3d.h" + +#include "scene/3d/skeleton_3d.h" +#include "scene/animation/animation_mixer.h" + +class SkeletonModifier3D : public Node3D { + GDCLASS(SkeletonModifier3D, Node3D); + + void rebind(); + +protected: + bool active = true; + real_t influence = 1.0; + + // Cache them for the performance reason since finding node with NodePath is slow. + ObjectID skeleton_id; + + void _update_skeleton(); + void _update_skeleton_path(); + + virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new); + + void _validate_property(PropertyInfo &p_property) const; + void _notification(int p_what); + static void _bind_methods(); + + virtual void _set_active(bool p_active); + + virtual void _process_modification(); + GDVIRTUAL0(_process_modification); + +public: + virtual PackedStringArray get_configuration_warnings() const override; + virtual bool has_process() const { return false; } // Return true if modifier needs to modify bone pose without external animation such as physics, jiggle and etc. + + void set_active(bool p_active); + bool is_active() const; + + void set_influence(real_t p_influence); + real_t get_influence() const; + + Skeleton3D *get_skeleton() const; + + void process_modification(); + + SkeletonModifier3D(); +}; + +#endif // SKELETON_MODIFIER_3D_H diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index a7ac278bc1..ba3b32a031 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -31,7 +31,6 @@ #include "sprite_3d.h" #include "scene/resources/atlas_texture.h" -#include "scene/scene_string_names.h" Color SpriteBase3D::_get_color_accum() { if (!color_dirty) { @@ -796,15 +795,15 @@ void Sprite3D::set_texture(const Ref<Texture2D> &p_texture) { return; } if (texture.is_valid()) { - texture->disconnect(SceneStringNames::get_singleton()->changed, callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw)); + texture->disconnect(CoreStringName(changed), callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw)); } texture = p_texture; if (texture.is_valid()) { - texture->connect(SceneStringNames::get_singleton()->changed, callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw)); + texture->connect(CoreStringName(changed), callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw)); } _queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->texture_changed); + emit_signal(SceneStringName(texture_changed)); } Ref<Texture2D> Sprite3D::get_texture() const { @@ -849,7 +848,7 @@ void Sprite3D::set_frame(int p_frame) { frame = p_frame; _queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } int Sprite3D::get_frame() const { @@ -1122,7 +1121,7 @@ void AnimatedSprite3D::_notification(int p_what) { } else { frame = last_frame; pause(); - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal(SceneStringName(animation_finished)); return; } } else { @@ -1131,7 +1130,7 @@ void AnimatedSprite3D::_notification(int p_what) { _calc_frame_speed_scale(); frame_progress = 0.0; _queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } double to_process = MIN((1.0 - frame_progress) / abs_speed, remaining); frame_progress += to_process * abs_speed; @@ -1146,7 +1145,7 @@ void AnimatedSprite3D::_notification(int p_what) { } else { frame = 0; pause(); - emit_signal(SceneStringNames::get_singleton()->animation_finished); + emit_signal(SceneStringName(animation_finished)); return; } } else { @@ -1155,7 +1154,7 @@ void AnimatedSprite3D::_notification(int p_what) { _calc_frame_speed_scale(); frame_progress = 1.0; _queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } double to_process = MIN(frame_progress / abs_speed, remaining); frame_progress -= to_process * abs_speed; @@ -1177,12 +1176,12 @@ void AnimatedSprite3D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { } if (frames.is_valid()) { - frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed)); + frames->disconnect(CoreStringName(changed), callable_mp(this, &AnimatedSprite3D::_res_changed)); } stop(); frames = p_frames; if (frames.is_valid()) { - frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed)); + frames->connect(CoreStringName(changed), callable_mp(this, &AnimatedSprite3D::_res_changed)); List<StringName> al; frames->get_animation_list(&al); @@ -1191,7 +1190,7 @@ void AnimatedSprite3D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { autoplay = String(); } else { if (!frames->has_animation(animation)) { - set_animation(al[0]); + set_animation(al.front()->get()); } if (!frames->has_animation(autoplay)) { autoplay = String(); @@ -1249,7 +1248,7 @@ void AnimatedSprite3D::set_frame_and_progress(int p_frame, real_t p_progress) { return; // No change, don't redraw. } _queue_redraw(); - emit_signal(SceneStringNames::get_singleton()->frame_changed); + emit_signal(SceneStringName(frame_changed)); } void AnimatedSprite3D::set_speed_scale(float p_speed_scale) { @@ -1343,7 +1342,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool } else { set_frame_and_progress(0, 0.0); } - emit_signal("animation_changed"); + emit_signal(SceneStringName(animation_changed)); } else { bool is_backward = signbit(speed_scale * custom_speed_scale); if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) { @@ -1398,7 +1397,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) { animation = p_name; - emit_signal("animation_changed"); + emit_signal(SceneStringName(animation_changed)); if (frames == nullptr) { animation = StringName(); diff --git a/scene/3d/visible_on_screen_notifier_3d.cpp b/scene/3d/visible_on_screen_notifier_3d.cpp index 272852e8fa..a510540e4e 100644 --- a/scene/3d/visible_on_screen_notifier_3d.cpp +++ b/scene/3d/visible_on_screen_notifier_3d.cpp @@ -30,15 +30,13 @@ #include "visible_on_screen_notifier_3d.h" -#include "scene/scene_string_names.h" - void VisibleOnScreenNotifier3D::_visibility_enter() { if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) { return; } on_screen = true; - emit_signal(SceneStringNames::get_singleton()->screen_entered); + emit_signal(SceneStringName(screen_entered)); _screen_enter(); } void VisibleOnScreenNotifier3D::_visibility_exit() { @@ -47,7 +45,7 @@ void VisibleOnScreenNotifier3D::_visibility_exit() { } on_screen = false; - emit_signal(SceneStringNames::get_singleton()->screen_exited); + emit_signal(SceneStringName(screen_exited)); _screen_exit(); } diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 503c39ae3e..f14ae3a285 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -30,9 +30,6 @@ #include "visual_instance_3d.h" -#include "core/core_string_names.h" -#include "scene/scene_string_names.h" - AABB VisualInstance3D::get_aabb() const { AABB ret; GDVIRTUAL_CALL(_get_aabb, ret); @@ -169,11 +166,11 @@ VisualInstance3D::~VisualInstance3D() { void GeometryInstance3D::set_material_override(const Ref<Material> &p_material) { if (material_override.is_valid()) { - material_override->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + material_override->disconnect(CoreStringName(property_list_changed), callable_mp((Object *)this, &Object::notify_property_list_changed)); } material_override = p_material; if (material_override.is_valid()) { - material_override->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + material_override->connect(CoreStringName(property_list_changed), callable_mp((Object *)this, &Object::notify_property_list_changed)); } RS::get_singleton()->instance_geometry_set_material_override(get_instance(), p_material.is_valid() ? p_material->get_rid() : RID()); } @@ -194,6 +191,7 @@ Ref<Material> GeometryInstance3D::get_material_overlay() const { void GeometryInstance3D::set_transparency(float p_transparency) { transparency = CLAMP(p_transparency, 0.0f, 1.0f); RS::get_singleton()->instance_geometry_set_transparency(get_instance(), transparency); + update_configuration_warnings(); } float GeometryInstance3D::get_transparency() const { @@ -278,12 +276,12 @@ bool GeometryInstance3D::_set(const StringName &p_name, const Variant &p_value) return true; } #ifndef DISABLE_DEPRECATED - if (p_name == SceneStringNames::get_singleton()->use_in_baked_light && bool(p_value)) { + if (p_name == SNAME("use_in_baked_light") && bool(p_value)) { set_gi_mode(GI_MODE_STATIC); return true; } - if (p_name == SceneStringNames::get_singleton()->use_dynamic_gi && bool(p_value)) { + if (p_name == SNAME("use_dynamic_gi") && bool(p_value)) { set_gi_mode(GI_MODE_DYNAMIC); return true; } @@ -377,6 +375,7 @@ void GeometryInstance3D::set_custom_aabb(AABB p_aabb) { } custom_aabb = p_aabb; RS::get_singleton()->instance_set_custom_aabb(get_instance(), custom_aabb); + update_gizmos(); } AABB GeometryInstance3D::get_custom_aabb() const { @@ -440,6 +439,14 @@ PackedStringArray GeometryInstance3D::get_configuration_warnings() const { warnings.push_back(RTR("The GeometryInstance3D is configured to fade out smoothly over distance, but the fade transition distance is set to 0.\nTo resolve this, increase Visibility Range End Margin above 0.")); } + if (!Math::is_zero_approx(transparency) && OS::get_singleton()->get_current_rendering_method() != "forward_plus") { + warnings.push_back(RTR("GeometryInstance3D transparency is only available when using the Forward+ rendering method.")); + } + + if ((visibility_range_fade_mode == VISIBILITY_RANGE_FADE_SELF || visibility_range_fade_mode == VISIBILITY_RANGE_FADE_DEPENDENCIES) && OS::get_singleton()->get_current_rendering_method() != "forward_plus") { + warnings.push_back(RTR("GeometryInstance3D visibility range transparency fade is only available when using the Forward+ rendering method.")); + } + return warnings; } diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index eb8569fa30..ffca856fba 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -31,7 +31,6 @@ #include "voxel_gi.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "mesh_instance_3d.h" #include "multimesh_instance_3d.h" #include "scene/resources/camera_attributes.h" @@ -294,7 +293,7 @@ VoxelGI::Subdiv VoxelGI::get_subdiv() const { void VoxelGI::set_size(const Vector3 &p_size) { // Prevent very small size dimensions as these breaks baking if other size dimensions are set very high. - size = Vector3(MAX(1.0, p_size.x), MAX(1.0, p_size.y), MAX(1.0, p_size.z)); + size = p_size.maxf(1.0); update_gizmos(); } diff --git a/scene/3d/xr_body_modifier_3d.cpp b/scene/3d/xr_body_modifier_3d.cpp index 477241947a..cf73882a7b 100644 --- a/scene/3d/xr_body_modifier_3d.cpp +++ b/scene/3d/xr_body_modifier_3d.cpp @@ -38,23 +38,15 @@ void XRBodyModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_body_tracker", "tracker_name"), &XRBodyModifier3D::set_body_tracker); ClassDB::bind_method(D_METHOD("get_body_tracker"), &XRBodyModifier3D::get_body_tracker); - ClassDB::bind_method(D_METHOD("set_target", "target"), &XRBodyModifier3D::set_target); - ClassDB::bind_method(D_METHOD("get_target"), &XRBodyModifier3D::get_target); - ClassDB::bind_method(D_METHOD("set_body_update", "body_update"), &XRBodyModifier3D::set_body_update); ClassDB::bind_method(D_METHOD("get_body_update"), &XRBodyModifier3D::get_body_update); ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRBodyModifier3D::set_bone_update); ClassDB::bind_method(D_METHOD("get_bone_update"), &XRBodyModifier3D::get_bone_update); - ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRBodyModifier3D::set_show_when_tracked); - ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRBodyModifier3D::get_show_when_tracked); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body"), "set_body_tracker", "get_body_tracker"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_target", "get_target"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body_tracker"), "set_body_tracker", "get_body_tracker"); ADD_PROPERTY(PropertyInfo(Variant::INT, "body_update", PROPERTY_HINT_FLAGS, "Upper Body,Lower Body,Hands"), "set_body_update", "get_body_update"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked"); BIND_BITFIELD_FLAG(BODY_UPDATE_UPPER_BODY); BIND_BITFIELD_FLAG(BODY_UPDATE_LOWER_BODY); @@ -73,18 +65,6 @@ StringName XRBodyModifier3D::get_body_tracker() const { return tracker_name; } -void XRBodyModifier3D::set_target(const NodePath &p_target) { - target = p_target; - - if (is_inside_tree()) { - _get_joint_data(); - } -} - -NodePath XRBodyModifier3D::get_target() const { - return target; -} - void XRBodyModifier3D::set_body_update(BitField<BodyUpdate> p_body_update) { body_update = p_body_update; } @@ -102,28 +82,6 @@ XRBodyModifier3D::BoneUpdate XRBodyModifier3D::get_bone_update() const { return bone_update; } -void XRBodyModifier3D::set_show_when_tracked(bool p_show_when_tracked) { - show_when_tracked = p_show_when_tracked; -} - -bool XRBodyModifier3D::get_show_when_tracked() const { - return show_when_tracked; -} - -Skeleton3D *XRBodyModifier3D::get_skeleton() { - if (!has_node(target)) { - return nullptr; - } - - Node *node = get_node(target); - if (!node) { - return nullptr; - } - - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); - return skeleton; -} - void XRBodyModifier3D::_get_joint_data() { // Table of Godot Humanoid bone names. static const String bone_names[XRBodyTracker::JOINT_MAX] = { @@ -219,7 +177,7 @@ void XRBodyModifier3D::_get_joint_data() { joints[i].parent_joint = -1; } - Skeleton3D *skeleton = get_skeleton(); + const Skeleton3D *skeleton = get_skeleton(); if (!skeleton) { return; } @@ -252,11 +210,6 @@ void XRBodyModifier3D::_get_joint_data() { } } - // If the root bone is not found then use the skeleton root bone. - if (bones[XRBodyTracker::JOINT_ROOT] == -1) { - bones[XRBodyTracker::JOINT_ROOT] = 0; - } - // Assemble the joint relationship to the available skeleton bones. for (int i = 0; i < XRBodyTracker::JOINT_MAX; i++) { // Get the skeleton bone (skip if not found). @@ -286,33 +239,28 @@ void XRBodyModifier3D::_get_joint_data() { } } -void XRBodyModifier3D::_update_skeleton() { +void XRBodyModifier3D::_process_modification() { Skeleton3D *skeleton = get_skeleton(); if (!skeleton) { return; } - XRServer *xr_server = XRServer::get_singleton(); + const XRServer *xr_server = XRServer::get_singleton(); if (!xr_server) { return; } - Ref<XRBodyTracker> tracker = xr_server->get_body_tracker(tracker_name); - if (tracker.is_null()) { + const Ref<XRBodyTracker> tracker = xr_server->get_tracker(tracker_name); + if (!tracker.is_valid()) { return; } - // Handle no tracking data. + // Skip if no tracking data. if (!tracker->get_has_tracking_data()) { - // If tracking-state determines visibility then hide the node. - if (show_when_tracked) { - set_visible(false); - } return; } // Get the world and skeleton scale. - const float ws = xr_server->get_world_scale(); const float ss = skeleton->get_motion_scale(); // Read the relevant tracking data. This applies the skeleton motion scale to @@ -331,12 +279,8 @@ void XRBodyModifier3D::_update_skeleton() { } } - // Handle root joint not tracked. + // Skip if root joint not tracked. if (!has_valid_data[XRBodyTracker::JOINT_ROOT]) { - // If tracking-state determines visibility then hide the node. - if (show_when_tracked) { - set_visible(false); - } return; } @@ -366,56 +310,41 @@ void XRBodyModifier3D::_update_skeleton() { // Always update the bone rotation. skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis)); } - - // Transform to the tracking data root pose. This also applies the XR world-scale to allow - // scaling the avatars mesh and skeleton appropriately (if they are child nodes). - set_transform( - transforms[XRBodyTracker::JOINT_ROOT] * ws); - - // If tracking-state determines visibility then show the node. - if (show_when_tracked) { - set_visible(true); - } } -void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker) { +void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type) { if (tracker_name == p_tracker_name) { _get_joint_data(); } } +void XRBodyModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { + _get_joint_data(); +} + void XRBodyModifier3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { - xr_server->connect("body_tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); - xr_server->connect("body_tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); - xr_server->connect("body_tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed).bind(Ref<XRBodyTracker>())); + xr_server->connect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); + xr_server->connect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); + xr_server->connect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); } - _get_joint_data(); - - set_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { - xr_server->disconnect("body_tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); - xr_server->disconnect("body_tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); - xr_server->disconnect("body_tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed).bind(Ref<XRBodyTracker>())); + xr_server->disconnect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); + xr_server->disconnect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); + xr_server->disconnect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed)); } - - set_process_internal(false); - for (int i = 0; i < XRBodyTracker::JOINT_MAX; i++) { joints[i].bone = -1; joints[i].parent_joint = -1; } } break; - case NOTIFICATION_INTERNAL_PROCESS: { - _update_skeleton(); - } break; default: { } break; } diff --git a/scene/3d/xr_body_modifier_3d.h b/scene/3d/xr_body_modifier_3d.h index 89ac69b6b0..78d70146ee 100644 --- a/scene/3d/xr_body_modifier_3d.h +++ b/scene/3d/xr_body_modifier_3d.h @@ -31,7 +31,7 @@ #ifndef XR_BODY_MODIFIER_3D_H #define XR_BODY_MODIFIER_3D_H -#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_modifier_3d.h" #include "servers/xr/xr_body_tracker.h" class Skeleton3D; @@ -41,8 +41,8 @@ class Skeleton3D; data from an XRBodyTracker instance. */ -class XRBodyModifier3D : public Node3D { - GDCLASS(XRBodyModifier3D, Node3D); +class XRBodyModifier3D : public SkeletonModifier3D { + GDCLASS(XRBodyModifier3D, SkeletonModifier3D); public: enum BodyUpdate { @@ -60,40 +60,33 @@ public: void set_body_tracker(const StringName &p_tracker_name); StringName get_body_tracker() const; - void set_target(const NodePath &p_target); - NodePath get_target() const; - void set_body_update(BitField<BodyUpdate> p_body_update); BitField<BodyUpdate> get_body_update() const; void set_bone_update(BoneUpdate p_bone_update); BoneUpdate get_bone_update() const; - void set_show_when_tracked(bool p_show_when_tracked); - bool get_show_when_tracked() const; - void _notification(int p_what); protected: static void _bind_methods(); + virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override; + virtual void _process_modification() override; + private: struct JointData { int bone = -1; int parent_joint = -1; }; - StringName tracker_name = "/user/body"; - NodePath target; + StringName tracker_name = "/user/body_tracker"; BitField<BodyUpdate> body_update = BODY_UPDATE_UPPER_BODY | BODY_UPDATE_LOWER_BODY | BODY_UPDATE_HANDS; BoneUpdate bone_update = BONE_UPDATE_FULL; - bool show_when_tracked = true; JointData joints[XRBodyTracker::JOINT_MAX]; - Skeleton3D *get_skeleton(); void _get_joint_data(); - void _update_skeleton(); - void _tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker); + void _tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type); }; VARIANT_BITFIELD_CAST(XRBodyModifier3D::BodyUpdate) diff --git a/scene/3d/xr_face_modifier_3d.cpp b/scene/3d/xr_face_modifier_3d.cpp index be92a587b0..43cef95fb9 100644 --- a/scene/3d/xr_face_modifier_3d.cpp +++ b/scene/3d/xr_face_modifier_3d.cpp @@ -495,7 +495,7 @@ static void remove_driven_unified_blend_shapes(RBMap<int, int> &p_blend_mapping) void XRFaceModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_face_tracker", "tracker_name"), &XRFaceModifier3D::set_face_tracker); ClassDB::bind_method(D_METHOD("get_face_tracker"), &XRFaceModifier3D::get_face_tracker); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/head"), "set_face_tracker", "get_face_tracker"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/face_tracker"), "set_face_tracker", "get_face_tracker"); ClassDB::bind_method(D_METHOD("set_target", "target"), &XRFaceModifier3D::set_target); ClassDB::bind_method(D_METHOD("get_target"), &XRFaceModifier3D::get_target); @@ -576,8 +576,8 @@ void XRFaceModifier3D::_update_face_blends() const { } // Get the face tracker. - const Ref<XRFaceTracker> p = xr_server->get_face_tracker(tracker_name); - if (!p.is_valid()) { + const Ref<XRFaceTracker> tracker = xr_server->get_tracker(tracker_name); + if (!tracker.is_valid()) { return; } @@ -588,7 +588,7 @@ void XRFaceModifier3D::_update_face_blends() const { } // Get the blend weights. - const PackedFloat32Array weights = p->get_blend_shapes(); + const PackedFloat32Array weights = tracker->get_blend_shapes(); // Apply all the face blend weights to the mesh. for (const KeyValue<int, int> &it : blend_mapping) { diff --git a/scene/3d/xr_face_modifier_3d.h b/scene/3d/xr_face_modifier_3d.h index 147c374e95..e5e59afe1d 100644 --- a/scene/3d/xr_face_modifier_3d.h +++ b/scene/3d/xr_face_modifier_3d.h @@ -47,7 +47,7 @@ class XRFaceModifier3D : public Node3D { GDCLASS(XRFaceModifier3D, Node3D); private: - StringName tracker_name = "/user/head"; + StringName tracker_name = "/user/face_tracker"; NodePath target; // Map from XRFaceTracker blend shape index to mesh blend shape index. diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp index 1e1449b54b..baaa9eee48 100644 --- a/scene/3d/xr_hand_modifier_3d.cpp +++ b/scene/3d/xr_hand_modifier_3d.cpp @@ -30,7 +30,6 @@ #include "xr_hand_modifier_3d.h" -#include "scene/3d/skeleton_3d.h" #include "servers/xr/xr_pose.h" #include "servers/xr_server.h" @@ -38,14 +37,10 @@ void XRHandModifier3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hand_tracker", "tracker_name"), &XRHandModifier3D::set_hand_tracker); ClassDB::bind_method(D_METHOD("get_hand_tracker"), &XRHandModifier3D::get_hand_tracker); - ClassDB::bind_method(D_METHOD("set_target", "target"), &XRHandModifier3D::set_target); - ClassDB::bind_method(D_METHOD("get_target"), &XRHandModifier3D::get_target); - ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRHandModifier3D::set_bone_update); ClassDB::bind_method(D_METHOD("get_bone_update"), &XRHandModifier3D::get_bone_update); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/left,/user/right"), "set_hand_tracker", "get_hand_tracker"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_target", "get_target"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/hand_tracker/left,/user/hand_tracker/right"), "set_hand_tracker", "get_hand_tracker"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); BIND_ENUM_CONSTANT(BONE_UPDATE_FULL); @@ -61,18 +56,6 @@ StringName XRHandModifier3D::get_hand_tracker() const { return tracker_name; } -void XRHandModifier3D::set_target(const NodePath &p_target) { - target = p_target; - - if (is_inside_tree()) { - _get_joint_data(); - } -} - -NodePath XRHandModifier3D::get_target() const { - return target; -} - void XRHandModifier3D::set_bone_update(BoneUpdate p_bone_update) { ERR_FAIL_INDEX(p_bone_update, BONE_UPDATE_MAX); bone_update = p_bone_update; @@ -82,21 +65,16 @@ XRHandModifier3D::BoneUpdate XRHandModifier3D::get_bone_update() const { return bone_update; } -Skeleton3D *XRHandModifier3D::get_skeleton() { - if (!has_node(target)) { - return nullptr; +void XRHandModifier3D::_get_joint_data() { + if (!is_inside_tree()) { + return; } - Node *node = get_node(target); - if (!node) { - return nullptr; + if (has_stored_previous_transforms) { + previous_relative_transforms.clear(); + has_stored_previous_transforms = false; } - Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); - return skeleton; -} - -void XRHandModifier3D::_get_joint_data() { // Table of bone names for different rig types. static const String bone_names[XRHandTracker::HAND_JOINT_MAX] = { "Palm", @@ -138,22 +116,30 @@ void XRHandModifier3D::_get_joint_data() { joints[i].parent_joint = -1; } - Skeleton3D *skeleton = get_skeleton(); + const Skeleton3D *skeleton = get_skeleton(); if (!skeleton) { return; } - XRServer *xr_server = XRServer::get_singleton(); + const XRServer *xr_server = XRServer::get_singleton(); if (!xr_server) { return; } - Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name); + const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name); if (tracker.is_null()) { return; } - XRHandTracker::Hand hand = tracker->get_hand(); + // Verify we have a left or right hand tracker. + const XRPositionalTracker::TrackerHand tracker_hand = tracker->get_tracker_hand(); + if (tracker_hand != XRPositionalTracker::TRACKER_HAND_LEFT && + tracker_hand != XRPositionalTracker::TRACKER_HAND_RIGHT) { + return; + } + + // Get the hand index (0 = left, 1 = right). + const int hand = tracker_hand == XRPositionalTracker::TRACKER_HAND_LEFT ? 0 : 1; // Find the skeleton-bones associated with each joint. int bones[XRHandTracker::HAND_JOINT_MAX]; @@ -197,24 +183,40 @@ void XRHandModifier3D::_get_joint_data() { } } -void XRHandModifier3D::_update_skeleton() { +void XRHandModifier3D::_process_modification() { Skeleton3D *skeleton = get_skeleton(); if (!skeleton) { return; } - XRServer *xr_server = XRServer::get_singleton(); + const XRServer *xr_server = XRServer::get_singleton(); if (!xr_server) { return; } - Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name); + const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name); if (tracker.is_null()) { return; } + // Skip if no tracking data + if (!tracker->get_has_tracking_data()) { + if (!has_stored_previous_transforms) { + return; + } + + // Apply previous relative transforms if they are stored. + for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + if (bone_update == BONE_UPDATE_FULL) { + skeleton->set_bone_pose_position(joints[joint].bone, previous_relative_transforms[joint].origin); + } + + skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(previous_relative_transforms[joint].basis)); + } + return; + } + // Get the world and skeleton scale. - const float ws = xr_server->get_world_scale(); const float ss = skeleton->get_motion_scale(); // We cache our transforms so we can quickly calculate local transforms. @@ -222,92 +224,85 @@ void XRHandModifier3D::_update_skeleton() { Transform3D transforms[XRHandTracker::HAND_JOINT_MAX]; Transform3D inv_transforms[XRHandTracker::HAND_JOINT_MAX]; - if (tracker->get_has_tracking_data()) { - for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { - BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint); - has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID); + for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint); + has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID); - if (has_valid_data[joint]) { - transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint); - transforms[joint].origin *= ss; - inv_transforms[joint] = transforms[joint].inverse(); - } + if (has_valid_data[joint]) { + transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint); + transforms[joint].origin *= ss; + inv_transforms[joint] = transforms[joint].inverse(); } + } - if (has_valid_data[XRHandTracker::HAND_JOINT_PALM]) { - for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { - // Get the skeleton bone (skip if none). - const int bone = joints[joint].bone; - if (bone == -1) { - continue; - } - - // Calculate the relative relationship to the parent bone joint. - const int parent_joint = joints[joint].parent_joint; - const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint]; - - // Update the bone position if enabled by update mode. - if (bone_update == BONE_UPDATE_FULL) { - skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin); - } - - // Always update the bone rotation. - skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis)); - } + // Skip if palm has no tracking data + if (!has_valid_data[XRHandTracker::HAND_JOINT_PALM]) { + return; + } + + if (!has_stored_previous_transforms) { + previous_relative_transforms.resize(XRHandTracker::HAND_JOINT_MAX); + has_stored_previous_transforms = true; + } + Transform3D *previous_relative_transforms_ptr = previous_relative_transforms.ptrw(); + + for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + // Get the skeleton bone (skip if none). + const int bone = joints[joint].bone; + if (bone == -1) { + continue; + } - // Transform to the skeleton pose. This uses the HAND_JOINT_PALM position without skeleton-scaling, as it - // must be positioned to match the physical hand location. It is scaled with the world space to match - // the scaling done to the camera and eyes. - set_transform( - tracker->get_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM) * ws); + // Calculate the relative relationship to the parent bone joint. + const int parent_joint = joints[joint].parent_joint; + const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint]; + previous_relative_transforms_ptr[joint] = relative_transform; - set_visible(true); - } else { - set_visible(false); + // Update the bone position if enabled by update mode. + if (bone_update == BONE_UPDATE_FULL) { + skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin); } - } else { - set_visible(false); + + // Always update the bone rotation. + skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis)); } } -void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker) { +void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type) { if (tracker_name == p_tracker_name) { _get_joint_data(); } } +void XRHandModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { + _get_joint_data(); +} + void XRHandModifier3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { - xr_server->connect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->connect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->connect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>())); + xr_server->connect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->connect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->connect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed)); } _get_joint_data(); - - set_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { - xr_server->disconnect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->disconnect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); - xr_server->disconnect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>())); + xr_server->disconnect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->disconnect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed)); + xr_server->disconnect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed)); } - set_process_internal(false); - for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) { joints[i].bone = -1; joints[i].parent_joint = -1; } } break; - case NOTIFICATION_INTERNAL_PROCESS: { - _update_skeleton(); - } break; default: { } break; } diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h index 2bc30d42d4..3d78f32b64 100644 --- a/scene/3d/xr_hand_modifier_3d.h +++ b/scene/3d/xr_hand_modifier_3d.h @@ -31,18 +31,16 @@ #ifndef XR_HAND_MODIFIER_3D_H #define XR_HAND_MODIFIER_3D_H -#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_modifier_3d.h" #include "servers/xr/xr_hand_tracker.h" -class Skeleton3D; - /** The XRHandModifier3D node drives a hand skeleton using hand tracking data from an XRHandTracking instance. */ -class XRHandModifier3D : public Node3D { - GDCLASS(XRHandModifier3D, Node3D); +class XRHandModifier3D : public SkeletonModifier3D { + GDCLASS(XRHandModifier3D, SkeletonModifier3D); public: enum BoneUpdate { @@ -54,9 +52,6 @@ public: void set_hand_tracker(const StringName &p_tracker_name); StringName get_hand_tracker() const; - void set_target(const NodePath &p_target); - NodePath get_target() const; - void set_bone_update(BoneUpdate p_bone_update); BoneUpdate get_bone_update() const; @@ -65,21 +60,24 @@ public: protected: static void _bind_methods(); + virtual void _skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) override; + virtual void _process_modification() override; + private: struct JointData { int bone = -1; int parent_joint = -1; }; - StringName tracker_name = "/user/left"; - NodePath target; + StringName tracker_name = "/user/hand_tracker/left"; BoneUpdate bone_update = BONE_UPDATE_FULL; JointData joints[XRHandTracker::HAND_JOINT_MAX]; - Skeleton3D *get_skeleton(); + bool has_stored_previous_transforms = false; + Vector<Transform3D> previous_relative_transforms; + void _get_joint_data(); - void _update_skeleton(); - void _tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker); + void _tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type); }; VARIANT_ENUM_CAST(XRHandModifier3D::BoneUpdate) diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index 12a9f50ed7..3f4b962641 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -80,10 +80,11 @@ PackedStringArray XRCamera3D::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { - // must be child node of XROrigin3D! - XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); - if (origin == nullptr) { - warnings.push_back(RTR("XRCamera3D must have an XROrigin3D node as its parent.")); + // Warn if the node has a parent which isn't an XROrigin3D! + Node *parent = get_parent(); + XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent); + if (parent && origin == nullptr) { + warnings.push_back(RTR("XRCamera3D may not function as expected without an XROrigin3D node as its parent.")); }; } @@ -229,6 +230,10 @@ void XRNode3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pose_name"), &XRNode3D::get_pose_name); ADD_PROPERTY(PropertyInfo(Variant::STRING, "pose", PROPERTY_HINT_ENUM_SUGGESTION), "set_pose_name", "get_pose_name"); + ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRNode3D::set_show_when_tracked); + ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRNode3D::get_show_when_tracked); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked"); + ClassDB::bind_method(D_METHOD("get_is_active"), &XRNode3D::get_is_active); ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRNode3D::get_has_tracking_data); ClassDB::bind_method(D_METHOD("get_pose"), &XRNode3D::get_pose); @@ -296,6 +301,14 @@ StringName XRNode3D::get_pose_name() const { return pose_name; } +void XRNode3D::set_show_when_tracked(bool p_show) { + show_when_tracked = p_show; +} + +bool XRNode3D::get_show_when_tracked() const { + return show_when_tracked; +} + bool XRNode3D::get_is_active() const { if (tracker.is_null()) { return false; @@ -402,6 +415,11 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) { // Handle change of has_tracking_data. has_tracking_data = p_has_tracking_data; emit_signal(SNAME("tracking_changed"), has_tracking_data); + + // If configured, show or hide the node based on tracking data. + if (show_when_tracked) { + set_visible(has_tracking_data); + } } XRNode3D::XRNode3D() { @@ -428,11 +446,12 @@ PackedStringArray XRNode3D::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { - // must be child node of XROrigin! - XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); - if (origin == nullptr) { - warnings.push_back(RTR("XRController3D must have an XROrigin3D node as its parent.")); - } + // Warn if the node has a parent which isn't an XROrigin3D! + Node *parent = get_parent(); + XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent); + if (parent && origin == nullptr) { + warnings.push_back(RTR("XRNode3D may not function as expected without an XROrigin3D node as its parent.")); + }; if (tracker_name == "") { warnings.push_back(RTR("No tracker name is set.")); diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index bdcccd51ea..a42f6d470f 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -79,6 +79,7 @@ private: StringName tracker_name; StringName pose_name = "default"; bool has_tracking_data = false; + bool show_when_tracked = false; protected: Ref<XRPositionalTracker> tracker; @@ -105,6 +106,9 @@ public: bool get_is_active() const; bool get_has_tracking_data() const; + void set_show_when_tracked(bool p_show); + bool get_show_when_tracked() const; + void trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0); Ref<XRPose> get_pose(); diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 4cbd9b1d76..36343edd11 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -33,12 +33,17 @@ #include "animation_blend_tree.h" void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position)); r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); - r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == closest) { return -1; } else { @@ -272,9 +277,9 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio } } -double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { if (blend_points_used == 0) { - return 0.0; + return NodeTimeInfo(); } AnimationMixer::PlaybackInfo pi = p_playback_info; @@ -287,8 +292,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ double blend_pos = get_parameter(blend_position); int cur_closest = get_parameter(closest); - double cur_length_internal = get_parameter(length_internal); - double max_time_remaining = 0.0; + NodeTimeInfo mind; if (blend_mode == BLEND_MODE_INTERPOLATED) { int point_lower = -1; @@ -341,12 +345,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ } // actually blend the animations now - + bool first = true; + double max_weight = 0.0; for (int i = 0; i < blend_points_used; i++) { if (i == point_lower || i == point_higher) { pi.weight = weights[i]; - double remaining = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); - max_time_remaining = MAX(max_time_remaining, remaining); + NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + if (first || pi.weight > max_weight) { + max_weight = pi.weight; + mind = t; + first = false; + } } else if (sync) { pi.weight = 0; blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); @@ -365,7 +374,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ } if (new_closest != cur_closest && new_closest != -1) { - double from = 0.0; + NodeTimeInfo from; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) { //for ping-pong loop Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node); @@ -376,18 +385,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ //see how much animation remains pi.seeked = false; pi.weight = 0; - from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } - pi.time = from; + pi.time = from.position; pi.seeked = true; pi.weight = 1.0; - max_time_remaining = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); - cur_length_internal = from + max_time_remaining; + mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); cur_closest = new_closest; } else { pi.weight = 1.0; - max_time_remaining = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } if (sync) { @@ -402,8 +410,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_ } set_parameter(closest, cur_closest); - set_parameter(length_internal, cur_length_internal); - return max_time_remaining; + return mind; } String AnimationNodeBlendSpace1D::get_caption() const { diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index 40679d55ef..64ae4d0505 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -68,7 +68,6 @@ protected: StringName blend_position = "blend_position"; StringName closest = "closest"; - StringName length_internal = "length_internal"; BlendMode blend_mode = BLEND_MODE_INTERPOLATED; @@ -114,7 +113,7 @@ public: void set_use_sync(bool p_sync); bool is_using_sync() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; String get_caption() const override; Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override; diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index d5c6253e9d..2634248231 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -34,16 +34,19 @@ #include "core/math/geometry_2d.h" void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::VECTOR2, blend_position)); r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); - r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == closest) { return -1; - } else if (p_parameter == length_internal) { - return 0; } else { return Vector2(); } @@ -442,19 +445,18 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect r_weights[2] = w; } -double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { _update_triangles(); Vector2 blend_pos = get_parameter(blend_position); int cur_closest = get_parameter(closest); - double cur_length_internal = get_parameter(length_internal); - double mind = 0.0; //time of min distance point + NodeTimeInfo mind; //time of min distance point AnimationMixer::PlaybackInfo pi = p_playback_info; if (blend_mode == BLEND_MODE_INTERPOLATED) { if (triangles.size() == 0) { - return 0; + return NodeTimeInfo(); } Vector2 best_point; @@ -500,7 +502,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ } } - ERR_FAIL_COND_V(blend_triangle == -1, 0); //should never reach here + ERR_FAIL_COND_V(blend_triangle == -1, NodeTimeInfo()); //should never reach here int triangle_points[3]; for (int j = 0; j < 3; j++) { @@ -509,15 +511,17 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ first = true; + bool found = false; + double max_weight = 0.0; for (int i = 0; i < blend_points_used; i++) { - bool found = false; for (int j = 0; j < 3; j++) { if (i == triangle_points[j]) { //blend with the given weight pi.weight = blend_weights[j]; - double t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); - if (first || t < mind) { + NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); + if (first || pi.weight > max_weight) { mind = t; + max_weight = pi.weight; first = false; } found = true; @@ -543,7 +547,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ } if (new_closest != cur_closest && new_closest != -1) { - double from = 0.0; + NodeTimeInfo from; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) { //for ping-pong loop Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node); @@ -554,14 +558,13 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ //see how much animation remains pi.seeked = false; pi.weight = 0; - from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); + from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } - pi.time = from; + pi.time = from.position; pi.seeked = true; pi.weight = 1.0; mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); - cur_length_internal = from + mind; cur_closest = new_closest; } else { pi.weight = 1.0; @@ -580,7 +583,6 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_ } set_parameter(closest, cur_closest); - set_parameter(length_internal, cur_length_internal); return mind; } diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index 33a821d80c..c26ff2bce0 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -65,7 +65,6 @@ protected: StringName blend_position = "blend_position"; StringName closest = "closest"; - StringName length_internal = "length_internal"; Vector2 max_space = Vector2(1, 1); Vector2 min_space = Vector2(-1, -1); Vector2 snap = Vector2(0.1, 0.1); @@ -129,7 +128,7 @@ public: void set_y_label(const String &p_label); String get_y_label() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual String get_caption() const override; Vector2 get_closest_point(const Vector2 &p_point); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 4a01b2ad65..6e33a1b27c 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -31,7 +31,6 @@ #include "animation_blend_tree.h" #include "scene/resources/animation.h" -#include "scene/scene_string_names.h" void AnimationNodeAnimation::set_animation(const StringName &p_name) { animation = p_name; @@ -44,7 +43,26 @@ StringName AnimationNodeAnimation::get_animation() const { Vector<String> (*AnimationNodeAnimation::get_editable_animation_list)() = nullptr; void AnimationNodeAnimation::get_parameter_list(List<PropertyInfo> *r_list) const { - r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + AnimationNode::get_parameter_list(r_list); +} + +AnimationNode::NodeTimeInfo AnimationNodeAnimation::get_node_time_info() const { + NodeTimeInfo nti; + if (!process_state->tree->has_animation(animation)) { + return nti; + } + + if (use_custom_timeline) { + nti.length = timeline_length; + nti.loop_mode = loop_mode; + } else { + Ref<Animation> anim = process_state->tree->get_animation(animation); + nti.length = (double)anim->get_length(); + nti.loop_mode = anim->get_loop_mode(); + } + nti.position = get_parameter(current_position); + + return nti; } void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const { @@ -62,11 +80,34 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const p_property.hint_string = anims; } } + + if (!use_custom_timeline) { + if (p_property.name == "timeline_length" || p_property.name == "start_offset" || p_property.name == "loop_mode" || p_property.name == "stretch_time_scale") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } } -double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double cur_time = get_parameter(time); +AnimationNode::NodeTimeInfo AnimationNodeAnimation::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + process_state->is_testing = p_test_only; + AnimationMixer::PlaybackInfo pi = p_playback_info; + if (p_playback_info.seeked) { + pi.delta = get_node_time_info().position - p_playback_info.time; + } else { + pi.time = get_node_time_info().position + (backward ? -p_playback_info.delta : p_playback_info.delta); + } + + NodeTimeInfo nti = _process(pi, p_test_only); + + if (!p_test_only) { + set_node_time_info(nti); + } + + return nti; +} + +AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { if (!process_state->tree->has_animation(animation)) { AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent); if (tree) { @@ -77,88 +118,130 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla make_invalid(vformat(RTR("Animation not found: '%s'"), animation)); } - return 0; + return NodeTimeInfo(); } Ref<Animation> anim = process_state->tree->get_animation(animation); double anim_size = (double)anim->get_length(); - double step = 0.0; - double prev_time = cur_time; + + NodeTimeInfo cur_nti = get_node_time_info(); + double cur_len = cur_nti.length; + double cur_time = p_playback_info.time; + double cur_delta = p_playback_info.delta; + + Animation::LoopMode cur_loop_mode = cur_nti.loop_mode; + double prev_time = cur_nti.position; + Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; bool node_backward = play_mode == PLAY_MODE_BACKWARD; - double p_time = p_playback_info.time; bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; - if (p_playback_info.seeked) { - step = p_time - cur_time; - cur_time = p_time; + bool is_just_looped = false; + + // 1. Progress for AnimationNode. + if (cur_loop_mode != Animation::LOOP_NONE) { + if (cur_loop_mode == Animation::LOOP_LINEAR) { + if (!Math::is_zero_approx(cur_len)) { + if (prev_time <= cur_len && cur_time > cur_len) { + is_just_looped = true; // Don't break with negative timescale since remain will not be 0. + } + cur_time = Math::fposmod(cur_time, cur_len); + } + backward = false; + } else { + if (!Math::is_zero_approx(cur_len)) { + if (prev_time >= 0 && cur_time < 0) { + backward = !backward; + } else if (prev_time <= cur_len && cur_time > cur_len) { + backward = !backward; + is_just_looped = true; // Don't break with negative timescale since remain will not be 0. + } + cur_time = Math::pingpong(cur_time, cur_len); + } + } } else { - p_time *= backward ? -1.0 : 1.0; - cur_time = cur_time + p_time; - step = p_time; + if (cur_time < 0) { + cur_delta += cur_time; + cur_time = 0; + } else if (cur_time > cur_len) { + cur_delta += cur_time - cur_len; + cur_time = cur_len; + } + backward = false; + // If ended, don't progress AnimationNode. So set delta to 0. + if (!Math::is_zero_approx(cur_delta)) { + if (play_mode == PLAY_MODE_FORWARD) { + if (prev_time >= cur_len) { + cur_delta = 0; + } + } else { + if (prev_time <= 0) { + cur_delta = 0; + } + } + } } - bool is_looping = false; - if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { + // 2. For return, store "AnimationNode" time info here, not "Animation" time info as below. + NodeTimeInfo nti; + nti.length = cur_len; + nti.position = cur_time; + nti.delta = cur_delta; + nti.loop_mode = cur_loop_mode; + nti.is_just_looped = is_just_looped; + + // 3. Progress for Animation. + double prev_playback_time = prev_time + start_offset; + double cur_playback_time = cur_time + start_offset; + if (stretch_time_scale) { + double mlt = anim_size / cur_len; + cur_playback_time *= mlt; + cur_delta *= mlt; + } + if (cur_loop_mode == Animation::LOOP_LINEAR) { if (!Math::is_zero_approx(anim_size)) { - if (prev_time >= 0 && cur_time < 0) { - backward = !backward; + prev_playback_time = Math::fposmod(prev_playback_time, anim_size); + cur_playback_time = Math::fposmod(cur_playback_time, anim_size); + if (prev_playback_time >= 0 && cur_playback_time < 0) { looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START; } - if (prev_time <= anim_size && cur_time > anim_size) { - backward = !backward; + if (prev_playback_time <= anim_size && cur_playback_time > anim_size) { looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END; } - cur_time = Math::pingpong(cur_time, anim_size); } - is_looping = true; - } else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { + } else if (cur_loop_mode == Animation::LOOP_PINGPONG) { if (!Math::is_zero_approx(anim_size)) { - if (prev_time >= 0 && cur_time < 0) { + if (Math::fposmod(cur_playback_time, anim_size * 2.0) >= anim_size) { + cur_delta = -cur_delta; // Needed for retrieving discrete keys correctly. + } + prev_playback_time = Math::pingpong(prev_playback_time, anim_size); + cur_playback_time = Math::pingpong(cur_playback_time, anim_size); + if (prev_playback_time >= 0 && cur_playback_time < 0) { looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START; } - if (prev_time <= anim_size && cur_time > anim_size) { + if (prev_playback_time <= anim_size && cur_playback_time > anim_size) { looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END; } - cur_time = Math::fposmod(cur_time, anim_size); } - backward = false; - is_looping = true; } else { - if (cur_time < 0) { - step += cur_time; - cur_time = 0; - } else if (cur_time > anim_size) { - step += anim_size - cur_time; - cur_time = anim_size; - } - backward = false; - - // If ended, don't progress animation. So set delta to 0. - if (p_time > 0) { - if (play_mode == PLAY_MODE_FORWARD) { - if (prev_time >= anim_size) { - step = 0; - } - } else { - if (prev_time <= 0) { - step = 0; - } - } + if (cur_playback_time < 0) { + cur_playback_time = 0; + } else if (cur_playback_time > anim_size) { + cur_playback_time = anim_size; } // Emit start & finish signal. Internally, the detections are the same for backward. // We should use call_deferred since the track keys are still being processed. if (process_state->tree && !p_test_only) { // AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection. - if (p_seek && !p_is_external_seeking && cur_time == 0) { - process_state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation); + if (p_seek && !p_is_external_seeking && cur_playback_time == 0) { + process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_started), animation); } // Finished. - if (prev_time < anim_size && cur_time >= anim_size) { - process_state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation); + if (prev_time + start_offset < anim_size && cur_playback_time >= anim_size) { + process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_finished), animation); } } } @@ -166,19 +249,18 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla if (!p_test_only) { AnimationMixer::PlaybackInfo pi = p_playback_info; if (play_mode == PLAY_MODE_FORWARD) { - pi.time = cur_time; - pi.delta = step; + pi.time = cur_playback_time; + pi.delta = cur_delta; } else { - pi.time = anim_size - cur_time; - pi.delta = -step; + pi.time = anim_size - cur_playback_time; + pi.delta = -cur_delta; } pi.weight = 1.0; pi.looped_flag = looped_flag; blend_animation(animation, pi); } - set_parameter(time, cur_time); - return is_looping ? HUGE_LENGTH : anim_size - cur_time; + return nti; } String AnimationNodeAnimation::get_caption() const { @@ -201,6 +283,48 @@ bool AnimationNodeAnimation::is_backward() const { return backward; } +void AnimationNodeAnimation::set_use_custom_timeline(bool p_use_custom_timeline) { + use_custom_timeline = p_use_custom_timeline; + notify_property_list_changed(); +} + +bool AnimationNodeAnimation::is_using_custom_timeline() const { + return use_custom_timeline; +} + +void AnimationNodeAnimation::set_timeline_length(double p_length) { + timeline_length = p_length; +} + +double AnimationNodeAnimation::get_timeline_length() const { + return timeline_length; +} + +void AnimationNodeAnimation::set_stretch_time_scale(bool p_strech_time_scale) { + stretch_time_scale = p_strech_time_scale; + notify_property_list_changed(); +} + +bool AnimationNodeAnimation::is_stretching_time_scale() const { + return stretch_time_scale; +} + +void AnimationNodeAnimation::set_start_offset(double p_offset) { + start_offset = p_offset; +} + +double AnimationNodeAnimation::get_start_offset() const { + return start_offset; +} + +void AnimationNodeAnimation::set_loop_mode(Animation::LoopMode p_loop_mode) { + loop_mode = p_loop_mode; +} + +Animation::LoopMode AnimationNodeAnimation::get_loop_mode() const { + return loop_mode; +} + void AnimationNodeAnimation::_bind_methods() { ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation); ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation); @@ -208,8 +332,28 @@ void AnimationNodeAnimation::_bind_methods() { ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode); ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode); + ClassDB::bind_method(D_METHOD("set_use_custom_timeline", "use_custom_timeline"), &AnimationNodeAnimation::set_use_custom_timeline); + ClassDB::bind_method(D_METHOD("is_using_custom_timeline"), &AnimationNodeAnimation::is_using_custom_timeline); + + ClassDB::bind_method(D_METHOD("set_timeline_length", "timeline_length"), &AnimationNodeAnimation::set_timeline_length); + ClassDB::bind_method(D_METHOD("get_timeline_length"), &AnimationNodeAnimation::get_timeline_length); + + ClassDB::bind_method(D_METHOD("set_stretch_time_scale", "stretch_time_scale"), &AnimationNodeAnimation::set_stretch_time_scale); + ClassDB::bind_method(D_METHOD("is_stretching_time_scale"), &AnimationNodeAnimation::is_stretching_time_scale); + + ClassDB::bind_method(D_METHOD("set_start_offset", "start_offset"), &AnimationNodeAnimation::set_start_offset); + ClassDB::bind_method(D_METHOD("get_start_offset"), &AnimationNodeAnimation::get_start_offset); + + ClassDB::bind_method(D_METHOD("set_loop_mode", "loop_mode"), &AnimationNodeAnimation::set_loop_mode); + ClassDB::bind_method(D_METHOD("get_loop_mode"), &AnimationNodeAnimation::get_loop_mode); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation"); ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_timeline"), "set_use_custom_timeline", "is_using_custom_timeline"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeline_length", PROPERTY_HINT_RANGE, "0.001,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_timeline_length", "get_timeline_length"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch_time_scale"), "set_stretch_time_scale", "is_stretching_time_scale"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "start_offset", PROPERTY_HINT_RANGE, "-60,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_start_offset", "get_start_offset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode"); BIND_ENUM_CONSTANT(PLAY_MODE_FORWARD); BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD); @@ -240,16 +384,21 @@ AnimationNodeSync::AnimationNodeSync() { //////////////////////////////////////////////////////// void AnimationNodeOneShot::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::BOOL, active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); r_list->push_back(PropertyInfo(Variant::BOOL, internal_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); r_list->push_back(PropertyInfo(Variant::INT, request, PROPERTY_HINT_ENUM, ",Fire,Abort,Fade Out")); - r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); - r_list->push_back(PropertyInfo(Variant::FLOAT, remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + r_list->push_back(PropertyInfo(Variant::FLOAT, fade_in_remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, fade_out_remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, time_to_restart, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == request) { return ONE_SHOT_REQUEST_NONE; } else if (p_parameter == active || p_parameter == internal_active) { @@ -262,6 +411,10 @@ Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_pa } bool AnimationNodeOneShot::is_parameter_read_only(const StringName &p_parameter) const { + if (AnimationNode::is_parameter_read_only(p_parameter)) { + return true; + } + if (p_parameter == active || p_parameter == internal_active) { return true; } @@ -332,6 +485,14 @@ AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const { return mix; } +void AnimationNodeOneShot::set_break_loop_at_end(bool p_enable) { + break_loop_at_end = p_enable; +} + +bool AnimationNodeOneShot::is_loop_broken_at_end() const { + return break_loop_at_end; +} + String AnimationNodeOneShot::get_caption() const { return "OneShot"; } @@ -340,14 +501,14 @@ bool AnimationNodeOneShot::has_filter() const { return true; } -double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { OneShotRequest cur_request = static_cast<OneShotRequest>((int)get_parameter(request)); bool cur_active = get_parameter(active); bool cur_internal_active = get_parameter(internal_active); - double cur_time = get_parameter(time); - double cur_remaining = get_parameter(remaining); - double cur_fade_out_remaining = get_parameter(fade_out_remaining); + NodeTimeInfo cur_nti = get_node_time_info(); double cur_time_to_restart = get_parameter(time_to_restart); + double cur_fade_in_remaining = get_parameter(fade_in_remaining); + double cur_fade_out_remaining = get_parameter(fade_out_remaining); set_parameter(request, ONE_SHOT_REQUEST_NONE); @@ -356,6 +517,8 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb bool is_fading_out = cur_active == true && cur_internal_active == false; double p_time = p_playback_info.time; + double p_delta = p_playback_info.delta; + double abs_delta = Math::abs(p_delta); bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; @@ -374,6 +537,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb // Request fading. is_fading_out = true; cur_fade_out_remaining = fade_out; + cur_fade_in_remaining = 0; } else { // Shot is ended, do nothing. is_shooting = false; @@ -382,7 +546,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb set_parameter(time_to_restart, -1); } else if (!do_start && !cur_active) { if (cur_time_to_restart >= 0.0 && !p_seek) { - cur_time_to_restart -= p_time; + cur_time_to_restart -= abs_delta; if (cur_time_to_restart < 0) { do_start = true; // Restart. } @@ -413,8 +577,11 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb } if (do_start) { - cur_time = 0; os_seek = true; + if (!cur_internal_active) { + cur_fade_in_remaining = fade_in; // If already active, don't fade-in again. + } + cur_internal_active = true; set_parameter(request, ONE_SHOT_REQUEST_NONE); set_parameter(internal_active, true); set_parameter(active, true); @@ -422,20 +589,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb real_t blend = 1.0; bool use_blend = sync; - if (cur_time < fade_in) { + + if (cur_fade_in_remaining > 0) { if (fade_in > 0) { use_blend = true; - blend = cur_time / fade_in; + blend = (fade_in - cur_fade_in_remaining) / fade_in; if (fade_in_curve.is_valid()) { blend = fade_in_curve->sample(blend); } } else { blend = 0; // Should not happen. } - } else if (!do_start && !is_fading_out && cur_remaining <= fade_out) { - is_fading_out = true; - cur_fade_out_remaining = cur_remaining; - set_parameter(internal_active, false); } if (is_fading_out) { @@ -451,34 +615,36 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb } AnimationMixer::PlaybackInfo pi = p_playback_info; - double main_rem = 0.0; + NodeTimeInfo main_nti; if (mix == MIX_MODE_ADD) { pi.weight = 1.0; - main_rem = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + main_nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); } else { pi.seeked &= use_blend; pi.weight = 1.0 - blend; - main_rem = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case. + main_nti = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case. } + pi = p_playback_info; - if (os_seek) { - pi.time = cur_time; + if (do_start) { + pi.time = 0; + } else if (os_seek) { + pi.time = cur_nti.position; } pi.seeked = os_seek; pi.weight = Math::is_zero_approx(blend) ? CMP_EPSILON : blend; - double os_rem = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. - if (do_start) { - cur_remaining = os_rem; + NodeTimeInfo os_nti = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + + if (cur_fade_in_remaining <= 0 && !do_start && !is_fading_out && os_nti.get_remain(break_loop_at_end) <= fade_out) { + is_fading_out = true; + cur_fade_out_remaining = os_nti.get_remain(break_loop_at_end); + cur_fade_in_remaining = 0; + set_parameter(internal_active, false); } - if (p_seek) { - cur_time = p_time; - } else { - cur_time += p_time; - cur_remaining = os_rem; - cur_fade_out_remaining -= p_time; - if (cur_remaining <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) { + if (!p_seek) { + if (os_nti.get_remain(break_loop_at_end) <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) { set_parameter(internal_active, false); set_parameter(active, false); if (auto_restart) { @@ -486,13 +652,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb set_parameter(time_to_restart, restart_sec); } } + double d = Math::abs(os_nti.delta); + if (!do_start) { + cur_fade_in_remaining = MAX(0, cur_fade_in_remaining - d); // Don't consider seeked delta by restart. + } + cur_fade_out_remaining = MAX(0, cur_fade_out_remaining - d); } - set_parameter(time, cur_time); - set_parameter(remaining, cur_remaining); + set_parameter(fade_in_remaining, cur_fade_in_remaining); set_parameter(fade_out_remaining, cur_fade_out_remaining); - return MAX(main_rem, cur_remaining); + return cur_internal_active ? os_nti : main_nti; } void AnimationNodeOneShot::_bind_methods() { @@ -508,6 +678,9 @@ void AnimationNodeOneShot::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fadeout_curve", "curve"), &AnimationNodeOneShot::set_fade_out_curve); ClassDB::bind_method(D_METHOD("get_fadeout_curve"), &AnimationNodeOneShot::get_fade_out_curve); + ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeOneShot::set_break_loop_at_end); + ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeOneShot::is_loop_broken_at_end); + ClassDB::bind_method(D_METHOD("set_autorestart", "active"), &AnimationNodeOneShot::set_auto_restart_enabled); ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::is_auto_restart_enabled); @@ -526,6 +699,7 @@ void AnimationNodeOneShot::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadein_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadein_curve", "get_fadein_curve"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_fadeout_time", "get_fadeout_time"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadeout_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadeout_curve", "get_fadeout_curve"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end"); ADD_GROUP("Auto Restart", "autorestart_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart"); @@ -550,10 +724,16 @@ AnimationNodeOneShot::AnimationNodeOneShot() { //////////////////////////////////////////////// void AnimationNodeAdd2::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater")); } Variant AnimationNodeAdd2::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; } @@ -565,16 +745,16 @@ bool AnimationNodeAdd2::has_filter() const { return true; } -double AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(add_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; - double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = amount; blend_input(1, pi, FILTER_PASS, sync, p_test_only); - return rem0; + return nti; } void AnimationNodeAdd2::_bind_methods() { @@ -588,10 +768,16 @@ AnimationNodeAdd2::AnimationNodeAdd2() { //////////////////////////////////////////////// void AnimationNodeAdd3::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater")); } Variant AnimationNodeAdd3::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; } @@ -603,18 +789,18 @@ bool AnimationNodeAdd3::has_filter() const { return true; } -double AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(add_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = MAX(0, -amount); blend_input(0, pi, FILTER_PASS, sync, p_test_only); pi.weight = 1.0; - double rem0 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = MAX(0, amount); blend_input(2, pi, FILTER_PASS, sync, p_test_only); - return rem0; + return nti; } void AnimationNodeAdd3::_bind_methods() { @@ -629,10 +815,16 @@ AnimationNodeAdd3::AnimationNodeAdd3() { ///////////////////////////////////////////// void AnimationNodeBlend2::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater")); } Variant AnimationNodeBlend2::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; // For blend amount. } @@ -640,16 +832,16 @@ String AnimationNodeBlend2::get_caption() const { return "Blend2"; } -double AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(blend_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0 - amount; - double rem0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); + NodeTimeInfo nti0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); pi.weight = amount; - double rem1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only); + NodeTimeInfo nti1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only); - return amount > 0.5 ? rem1 : rem0; // Hacky but good enough. + return amount > 0.5 ? nti1 : nti0; // Hacky but good enough. } bool AnimationNodeBlend2::has_filter() const { @@ -667,10 +859,16 @@ AnimationNodeBlend2::AnimationNodeBlend2() { ////////////////////////////////////// void AnimationNodeBlend3::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater")); } Variant AnimationNodeBlend3::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; // For blend amount. } @@ -678,18 +876,18 @@ String AnimationNodeBlend3::get_caption() const { return "Blend3"; } -double AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(blend_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = MAX(0, -amount); - double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = 1.0 - ABS(amount); - double rem1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); pi.weight = MAX(0, amount); - double rem2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only); + NodeTimeInfo nti2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only); - return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough. + return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough. } void AnimationNodeBlend3::_bind_methods() { @@ -704,10 +902,16 @@ AnimationNodeBlend3::AnimationNodeBlend3() { //////////////////////////////////////////////// void AnimationNodeSub2::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, sub_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater")); } Variant AnimationNodeSub2::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 0; } @@ -719,7 +923,7 @@ bool AnimationNodeSub2::has_filter() const { return true; } -double AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(sub_amount); AnimationMixer::PlaybackInfo pi = p_playback_info; @@ -742,10 +946,16 @@ AnimationNodeSub2::AnimationNodeSub2() { ///////////////////////////////// void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_less,or_greater")); } Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return 1.0; // Initial timescale. } @@ -753,13 +963,13 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -double AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double cur_scale = get_parameter(scale); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; if (!pi.seeked) { - pi.time *= cur_scale; + pi.delta *= cur_scale; } return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); @@ -775,10 +985,16 @@ AnimationNodeTimeScale::AnimationNodeTimeScale() { //////////////////////////////////// void AnimationNodeTimeSeek::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::FLOAT, seek_pos_request, PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater")); // It will be reset to -1 after seeking the position immediately. } Variant AnimationNodeTimeSeek::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + return -1.0; // Initial seek request. } @@ -786,7 +1002,7 @@ String AnimationNodeTimeSeek::get_caption() const { return "TimeSeek"; } -double AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double cur_seek_pos = get_parameter(seek_pos_request); AnimationMixer::PlaybackInfo pi = p_playback_info; @@ -833,6 +1049,8 @@ bool AnimationNodeTransition::_set(const StringName &p_path, const Variant &p_va set_input_name(which, p_value); } else if (what == "auto_advance") { set_input_as_auto_advance(which, p_value); + } else if (what == "break_loop_at_end") { + set_input_break_loop_at_end(which, p_value); } else if (what == "reset") { set_input_reset(which, p_value); } else { @@ -858,6 +1076,8 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con r_ret = get_input_name(which); } else if (what == "auto_advance") { r_ret = is_input_set_as_auto_advance(which); + } else if (what == "break_loop_at_end") { + r_ret = is_input_loop_broken_at_end(which); } else if (what == "reset") { r_ret = is_input_reset(which); } else { @@ -868,6 +1088,7 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con } void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); String anims; for (int i = 0; i < get_input_count(); i++) { if (i > 0) { @@ -880,12 +1101,16 @@ void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) con r_list->push_back(PropertyInfo(Variant::STRING, transition_request, PROPERTY_HINT_ENUM, anims)); // For transition request. It will be cleared after setting the value immediately. r_list->push_back(PropertyInfo(Variant::INT, current_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); // To avoid finding the index every frame, use this internally. r_list->push_back(PropertyInfo(Variant::INT, prev_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); - r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); r_list->push_back(PropertyInfo(Variant::FLOAT, prev_xfading, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p_parameter) const { - if (p_parameter == time || p_parameter == prev_xfading) { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + + if (p_parameter == prev_xfading) { return 0.0; } else if (p_parameter == prev_index || p_parameter == current_index) { return -1; @@ -895,6 +1120,10 @@ Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p } bool AnimationNodeTransition::is_parameter_read_only(const StringName &p_parameter) const { + if (AnimationNode::is_parameter_read_only(p_parameter)) { + return true; + } + if (p_parameter == current_state || p_parameter == current_index) { return true; } @@ -947,6 +1176,16 @@ bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const { return input_data[p_input].auto_advance; } +void AnimationNodeTransition::set_input_break_loop_at_end(int p_input, bool p_enable) { + ERR_FAIL_INDEX(p_input, get_input_count()); + input_data.write[p_input].break_loop_at_end = p_enable; +} + +bool AnimationNodeTransition::is_input_loop_broken_at_end(int p_input) const { + ERR_FAIL_INDEX_V(p_input, get_input_count(), false); + return input_data[p_input].break_loop_at_end; +} + void AnimationNodeTransition::set_input_reset(int p_input, bool p_enable) { ERR_FAIL_INDEX(p_input, get_input_count()); input_data.write[p_input].reset = p_enable; @@ -981,12 +1220,12 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const { return allow_transition_to_self; } -double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { String cur_transition_request = get_parameter(transition_request); int cur_current_index = get_parameter(current_index); int cur_prev_index = get_parameter(prev_index); - double cur_time = get_parameter(time); + NodeTimeInfo cur_nti = get_node_time_info(); double cur_prev_xfading = get_parameter(prev_xfading); bool switched = false; @@ -1052,7 +1291,6 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl // Special case for restart. if (restart) { - set_parameter(time, 0); pi.time = 0; pi.seeked = true; pi.weight = 1.0; @@ -1061,16 +1299,12 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl if (switched) { cur_prev_xfading = xfade_time; - cur_time = 0; } if (cur_current_index < 0 || cur_current_index >= get_input_count() || cur_prev_index >= get_input_count()) { - return 0; + return NodeTimeInfo(); } - double rem = 0.0; - double abs_time = Math::abs(p_time); - if (sync) { pi.weight = 0; for (int i = 0; i < get_input_count(); i++) { @@ -1081,20 +1315,11 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl } if (cur_prev_index < 0) { // Process current animation, check for transition. - pi.weight = 1.0; - rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); - - if (p_seek) { - cur_time = abs_time; - } else { - cur_time += abs_time; - } - - if (input_data[cur_current_index].auto_advance && rem <= xfade_time) { + cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); + if (input_data[cur_current_index].auto_advance && cur_nti.get_remain(input_data[cur_current_index].break_loop_at_end) <= xfade_time) { set_parameter(transition_request, get_input_name((cur_current_index + 1) % get_input_count())); } - } else { // Cross-fading from prev to current. real_t blend = 0.0; @@ -1117,33 +1342,30 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl pi.time = 0; pi.seeked = true; } - rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); + cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); pi = p_playback_info; pi.seeked &= use_blend; pi.weight = blend; blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only); - if (p_seek) { - cur_time = abs_time; - } else { - cur_time += abs_time; - cur_prev_xfading -= abs_time; - if (cur_prev_xfading < 0) { + if (!p_seek) { + if (cur_prev_xfading <= 0) { set_parameter(prev_index, -1); } + cur_prev_xfading -= Math::abs(p_playback_info.delta); } } - set_parameter(time, cur_time); set_parameter(prev_xfading, cur_prev_xfading); - return rem; + return cur_nti; } void AnimationNodeTransition::_get_property_list(List<PropertyInfo> *p_list) const { for (int i = 0; i < get_input_count(); i++) { p_list->push_back(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/break_loop_at_end", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/reset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL)); } } @@ -1154,6 +1376,9 @@ void AnimationNodeTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance); ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance); + ClassDB::bind_method(D_METHOD("set_input_break_loop_at_end", "input", "enable"), &AnimationNodeTransition::set_input_break_loop_at_end); + ClassDB::bind_method(D_METHOD("is_input_loop_broken_at_end", "input"), &AnimationNodeTransition::is_input_loop_broken_at_end); + ClassDB::bind_method(D_METHOD("set_input_reset", "input", "enable"), &AnimationNodeTransition::set_input_reset); ClassDB::bind_method(D_METHOD("is_input_reset", "input"), &AnimationNodeTransition::is_input_reset); @@ -1181,7 +1406,7 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -double AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); @@ -1195,7 +1420,7 @@ AnimationNodeOutput::AnimationNodeOutput() { void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position) { ERR_FAIL_COND(nodes.has(p_name)); ERR_FAIL_COND(p_node.is_null()); - ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(p_name == SceneStringName(output)); ERR_FAIL_COND(String(p_name).contains("/")); Node n; @@ -1265,7 +1490,7 @@ Vector<StringName> AnimationNodeBlendTree::get_node_connection_array(const Strin void AnimationNodeBlendTree::remove_node(const StringName &p_name) { ERR_FAIL_COND(!nodes.has(p_name)); - ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); //can't delete output + ERR_FAIL_COND(p_name == SceneStringName(output)); //can't delete output { Ref<AnimationNode> node = nodes[p_name].node; @@ -1294,8 +1519,8 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) { void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringName &p_new_name) { ERR_FAIL_COND(!nodes.has(p_name)); ERR_FAIL_COND(nodes.has(p_new_name)); - ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); - ERR_FAIL_COND(p_new_name == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(p_name == SceneStringName(output)); + ERR_FAIL_COND(p_new_name == SceneStringName(output)); nodes[p_name].node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed)); @@ -1320,7 +1545,7 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) { ERR_FAIL_COND(!nodes.has(p_output_node)); ERR_FAIL_COND(!nodes.has(p_input_node)); - ERR_FAIL_COND(p_output_node == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(p_output_node == SceneStringName(output)); ERR_FAIL_COND(p_input_node == p_output_node); Ref<AnimationNode> input = nodes[p_input_node].node; @@ -1348,7 +1573,7 @@ void AnimationNodeBlendTree::disconnect_node(const StringName &p_node, int p_inp } AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const { - if (!nodes.has(p_output_node) || p_output_node == SceneStringNames::get_singleton()->output) { + if (!nodes.has(p_output_node) || p_output_node == SceneStringName(output)) { return CONNECTION_ERROR_NO_OUTPUT; } @@ -1400,10 +1625,10 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -double AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node; - node_state.connections = nodes[SceneStringNames::get_singleton()->output].connections; - ERR_FAIL_COND_V(output.is_null(), 0); +AnimationNode::NodeTimeInfo AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + Ref<AnimationNodeOutput> output = nodes[SceneStringName(output)].node; + node_state.connections = nodes[SceneStringName(output)].connections; + ERR_FAIL_COND_V(output.is_null(), NodeTimeInfo()); AnimationMixer::PlaybackInfo pi = p_playback_info; pi.weight = 1.0; diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index cf0884b892..c7ef7ed624 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -37,7 +37,12 @@ class AnimationNodeAnimation : public AnimationRootNode { GDCLASS(AnimationNodeAnimation, AnimationRootNode); StringName animation; - StringName time = "time"; + + bool use_custom_timeline = false; + double timeline_length = 1.0; + Animation::LoopMode loop_mode = Animation::LOOP_NONE; + bool stretch_time_scale = true; + double start_offset = 0.0; uint64_t last_version = 0; bool skip = false; @@ -50,10 +55,13 @@ public: void get_parameter_list(List<PropertyInfo> *r_list) const override; + virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter(). + static Vector<String> (*get_editable_animation_list)(); virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; void set_animation(const StringName &p_name); StringName get_animation() const; @@ -64,6 +72,21 @@ public: void set_backward(bool p_backward); bool is_backward() const; + void set_use_custom_timeline(bool p_use_custom_timeline); + bool is_using_custom_timeline() const; + + void set_timeline_length(double p_length); + double get_timeline_length() const; + + void set_stretch_time_scale(bool p_strech_time_scale); + bool is_stretching_time_scale() const; + + void set_start_offset(double p_offset); + double get_start_offset() const; + + void set_loop_mode(Animation::LoopMode p_loop_mode); + Animation::LoopMode get_loop_mode() const; + AnimationNodeAnimation(); protected: @@ -118,12 +141,12 @@ private: double auto_restart_delay = 1.0; double auto_restart_random_delay = 0.0; MixMode mix = MIX_MODE_BLEND; + bool break_loop_at_end = false; StringName request = PNAME("request"); StringName active = PNAME("active"); StringName internal_active = PNAME("internal_active"); - StringName time = "time"; - StringName remaining = "remaining"; + StringName fade_in_remaining = "fade_in_remaining"; StringName fade_out_remaining = "fade_out_remaining"; StringName time_to_restart = "time_to_restart"; @@ -160,8 +183,11 @@ public: void set_mix_mode(MixMode p_mix); MixMode get_mix_mode() const; + void set_break_loop_at_end(bool p_enable); + bool is_loop_broken_at_end() const; + virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeOneShot(); }; @@ -184,7 +210,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeAdd2(); }; @@ -204,7 +230,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeAdd3(); }; @@ -222,7 +248,7 @@ public: virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual bool has_filter() const override; AnimationNodeBlend2(); @@ -242,7 +268,7 @@ public: virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeBlend3(); }; @@ -261,7 +287,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeSub2(); }; @@ -280,7 +306,7 @@ public: virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTimeScale(); }; @@ -299,7 +325,7 @@ public: virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTimeSeek(); }; @@ -309,11 +335,11 @@ class AnimationNodeTransition : public AnimationNodeSync { struct InputData { bool auto_advance = false; + bool break_loop_at_end = false; bool reset = true; }; Vector<InputData> input_data; - StringName time = "time"; StringName prev_xfading = "prev_xfading"; StringName prev_index = "prev_index"; StringName current_index = PNAME("current_index"); @@ -351,6 +377,9 @@ public: void set_input_as_auto_advance(int p_input, bool p_enable); bool is_input_set_as_auto_advance(int p_input) const; + void set_input_break_loop_at_end(int p_input, bool p_enable); + bool is_input_loop_broken_at_end(int p_input) const; + void set_input_reset(int p_input, bool p_enable); bool is_input_reset(int p_input) const; @@ -363,7 +392,7 @@ public: void set_allow_transition_to_self(bool p_enable); bool is_allow_transition_to_self() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTransition(); }; @@ -373,7 +402,7 @@ class AnimationNodeOutput : public AnimationNode { public: virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeOutput(); }; @@ -445,7 +474,7 @@ public: void get_node_connections(List<NodeConnection> *r_connections) const; virtual String get_caption() const override; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; void get_node_list(List<StringName> *r_list); diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 7080a5338b..e600de6b8b 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -35,13 +35,13 @@ #include "core/config/project_settings.h" #include "scene/animation/animation_player.h" #include "scene/resources/animation.h" -#include "scene/scene_string_names.h" #include "servers/audio/audio_stream.h" #ifndef _3D_DISABLED #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" +#include "scene/3d/skeleton_modifier_3d.h" #endif // _3D_DISABLED #ifdef TOOLS_ENABLED @@ -300,7 +300,7 @@ Error AnimationMixer::add_animation_library(const StringName &p_name, const Ref< ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added).bind(p_name)); ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed).bind(p_name)); ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed).bind(p_name)); - ald.library->connect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed)); + ald.library->connect(SceneStringName(animation_changed), callable_mp(this, &AnimationMixer::_animation_changed)); _animation_set_cache_update(); @@ -324,7 +324,7 @@ void AnimationMixer::remove_animation_library(const StringName &p_name) { animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added)); animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed)); animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed)); - animation_libraries[at_pos].library->disconnect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed)); + animation_libraries[at_pos].library->disconnect(SceneStringName(animation_changed), callable_mp(this, &AnimationMixer::_animation_changed)); animation_libraries.remove_at(at_pos); _animation_set_cache_update(); @@ -484,10 +484,6 @@ void AnimationMixer::set_callback_mode_process(AnimationCallbackModeProcess p_mo if (was_active) { set_active(true); } - -#ifdef TOOLS_ENABLED - emit_signal(SNAME("mixer_updated")); -#endif // TOOLS_ENABLED } AnimationMixer::AnimationCallbackModeProcess AnimationMixer::get_callback_mode_process() const { @@ -496,9 +492,7 @@ AnimationMixer::AnimationCallbackModeProcess AnimationMixer::get_callback_mode_p void AnimationMixer::set_callback_mode_method(AnimationCallbackModeMethod p_mode) { callback_mode_method = p_mode; -#ifdef TOOLS_ENABLED emit_signal(SNAME("mixer_updated")); -#endif // TOOLS_ENABLED } AnimationMixer::AnimationCallbackModeMethod AnimationMixer::get_callback_mode_method() const { @@ -507,9 +501,8 @@ AnimationMixer::AnimationCallbackModeMethod AnimationMixer::get_callback_mode_me void AnimationMixer::set_callback_mode_discrete(AnimationCallbackModeDiscrete p_mode) { callback_mode_discrete = p_mode; -#ifdef TOOLS_ENABLED + _clear_caches(); emit_signal(SNAME("mixer_updated")); -#endif // TOOLS_ENABLED } AnimationMixer::AnimationCallbackModeDiscrete AnimationMixer::get_callback_mode_discrete() const { @@ -635,9 +628,9 @@ bool AnimationMixer::_update_caches() { #endif Ref<Animation> reset_anim; - bool has_reset_anim = has_animation(SceneStringNames::get_singleton()->RESET); + bool has_reset_anim = has_animation(SceneStringName(RESET)); if (has_reset_anim) { - reset_anim = get_animation(SceneStringNames::get_singleton()->RESET); + reset_anim = get_animation(SceneStringName(RESET)); } for (const StringName &E : sname) { Ref<Animation> anim = get_animation(E); @@ -696,14 +689,24 @@ bool AnimationMixer::_update_caches() { track_value->init_value = anim->track_get_key_value(i, 0); track_value->init_value.zero(); + track_value->is_init = false; + // Can't interpolate them, need to convert. track_value->is_variant_interpolatable = Animation::is_variant_interpolatable(track_value->init_value); // If there is a Reset Animation, it takes precedence by overwriting. if (has_reset_anim) { int rt = reset_anim->find_track(path, track_src_type); - if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { - track_value->init_value = track_src_type == Animation::TYPE_VALUE ? reset_anim->track_get_key_value(rt, 0) : (reset_anim->track_get_key_value(rt, 0).operator Array())[0]; + if (rt >= 0) { + if (track_src_type == Animation::TYPE_VALUE) { + if (reset_anim->track_get_key_count(rt) > 0) { + track_value->init_value = reset_anim->track_get_key_value(rt, 0); + } + } else { + if (reset_anim->track_get_key_count(rt) > 0) { + track_value->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0]; + } + } } } @@ -877,7 +880,7 @@ bool AnimationMixer::_update_caches() { if (track_value->init_value.is_string() && anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE) { WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm."); } - track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; + track_value->is_using_angle = track_value->is_using_angle || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; } if (check_angle_interpolation && (was_using_angle != track_value->is_using_angle)) { WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); @@ -930,6 +933,7 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) { _blend_process(p_delta, p_update_only); _blend_apply(); _blend_post_process(); + emit_signal(SNAME("mixer_applied")); }; clear_animation_instances(); } @@ -1002,6 +1006,7 @@ void AnimationMixer::_blend_init() { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); t->value = Animation::cast_to_blendwise(t->init_value); t->element_size = t->init_value.is_string() ? (real_t)(t->init_value.operator String()).length() : 0; + t->use_continuous = false; t->use_discrete = false; } break; case Animation::TYPE_AUDIO: { @@ -1421,6 +1426,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { bool is_discrete = is_value && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; bool force_continuous = callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS; if (t->is_variant_interpolatable && (!is_discrete || force_continuous)) { + t->use_continuous = true; Variant value = is_value ? a->value_track_interpolate(i, time, is_discrete && force_continuous ? backward : false) : Variant(a->bezier_track_interpolate(i, time)); value = post_process_key_value(a, i, value, t->object_id); if (value == Variant()) { @@ -1456,12 +1462,12 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { t->value = Animation::blend_variant(t->value, value, blend); } } else { - t->use_discrete = true; if (seeked) { int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); if (idx < 0) { continue; } + t->use_discrete = true; Variant value = a->track_get_key_value(i, idx); value = post_process_key_value(a, i, value, t->object_id); Object *t_obj = ObjectDB::get_instance(t->object_id); @@ -1472,6 +1478,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { List<int> indices; a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { + t->use_discrete = true; Variant value = a->track_get_key_value(i, F); value = post_process_key_value(a, i, value, t->object_id); Object *t_obj = ObjectDB::get_instance(t->object_id); @@ -1596,7 +1603,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } break; case Animation::TYPE_ANIMATION: { - if (p_update_only || Math::is_zero_approx(blend)) { + if (Math::is_zero_approx(blend)) { continue; } TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track); @@ -1610,7 +1617,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } if (seeked) { // Seek. - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); + int idx = a->track_find_key(i, time, Animation::FIND_MODE_NEAREST, true); if (idx < 0) { continue; } @@ -1623,10 +1630,13 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { double at_anim_pos = 0.0; switch (anim->get_loop_mode()) { case Animation::LOOP_NONE: { - at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end + if (!is_external_seeking && ((!backward && time >= pos + (double)anim->get_length()) || (backward && time <= pos))) { + continue; // Do nothing if current time is outside of length when started. + } + at_anim_pos = MIN((double)anim->get_length(), time - pos); // Seek to end. } break; case Animation::LOOP_LINEAR: { - at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop + at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop. } break; case Animation::LOOP_PINGPONG: { at_anim_pos = Math::pingpong(time - pos, (double)a->get_length()); @@ -1634,14 +1644,14 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { default: break; } - if (player2->is_playing() || seeked) { - player2->seek(at_anim_pos); + if (player2->is_playing() || !is_external_seeking) { + player2->seek(at_anim_pos, false, p_update_only); player2->play(anim_name); t->playing = true; playing_caches.insert(t); } else { player2->set_assigned_animation(anim_name); - player2->seek(at_anim_pos, true); + player2->seek(at_anim_pos, true, p_update_only); } } else { // Find stuff to play. @@ -1673,7 +1683,8 @@ void AnimationMixer::_blend_apply() { // Finally, set the tracks. for (const KeyValue<Animation::TypeHash, TrackCache *> &K : track_cache) { TrackCache *track = K.value; - if (!deterministic && Math::is_zero_approx(track->total_weight)) { + bool is_zero_amount = Math::is_zero_approx(track->total_weight); + if (!deterministic && is_zero_amount) { continue; } switch (track->type) { @@ -1733,10 +1744,24 @@ void AnimationMixer::_blend_apply() { case Animation::TYPE_VALUE: { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); - if (!t->is_variant_interpolatable || (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT && t->use_discrete)) { + if (t->use_discrete && !t->use_continuous) { + t->is_init = true; // If only disctere value is applied, no more RESET. + } + + if ((t->is_init && (is_zero_amount || !t->use_continuous)) || + (callback_mode_discrete != ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS && + !is_zero_amount && + callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT && + t->use_discrete)) { break; // Don't overwrite the value set by UPDATE_DISCRETE. } + if (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { + t->is_init = false; // Always update in Force Continuous. + } else { + t->is_init = !t->use_continuous; // If there is no Continuous in non-Force Continuous type, it means RESET. + } + // Trim unused elements if init array/string is not blended. if (t->value.is_array()) { int actual_blended_size = (int)Math::round(Math::abs(t->element_size.operator real_t())); @@ -1916,7 +1941,7 @@ bool AnimationMixer::is_reset_on_save_enabled() const { } bool AnimationMixer::can_apply_reset() const { - return has_animation(SceneStringNames::get_singleton()->RESET); + return has_animation(SceneStringName(RESET)); } void AnimationMixer::_build_backup_track_cache() { @@ -1975,6 +2000,7 @@ void AnimationMixer::_build_backup_track_cache() { if (t_obj) { t->value = Animation::cast_to_blendwise(t_obj->get_indexed(t->subpath)); } + t->use_continuous = true; t->use_discrete = false; if (t->init_value.is_array()) { t->element_size = MAX(t->element_size.operator int(), (t->value.operator Array()).size()); @@ -2002,7 +2028,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() { Ref<AnimatedValuesBackup> backup; backup.instantiate(); - Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation; + Ref<Animation> reset_anim = animation_set[SceneStringName(RESET)].animation; ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>()); _blend_init(); @@ -2011,7 +2037,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() { pi.delta = 0; pi.seeked = true; pi.weight = 1.0; - make_animation_instance(SceneStringNames::get_singleton()->RESET, pi); + make_animation_instance(SceneStringName(RESET), pi); _build_backup_track_cache(); backup->set_data(track_cache); @@ -2023,7 +2049,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() { void AnimationMixer::reset() { ERR_FAIL_COND(!can_apply_reset()); - Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation; + Ref<Animation> reset_anim = animation_set[SceneStringName(RESET)].animation; ERR_FAIL_COND(reset_anim.is_null()); Node *root_node_object = get_node_or_null(root_node); @@ -2033,11 +2059,11 @@ void AnimationMixer::reset() { root_node_object->add_child(aux_player); Ref<AnimationLibrary> al; al.instantiate(); - al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); + al->add_animation(SceneStringName(RESET), reset_anim); aux_player->set_reset_on_save_enabled(false); aux_player->set_root_node(aux_player->get_path_to(root_node_object)); aux_player->add_animation_library("", al); - aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET); + aux_player->set_assigned_animation(SceneStringName(RESET)); aux_player->seek(0.0f, true); aux_player->queue_free(); } @@ -2057,7 +2083,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) { } ERR_FAIL_COND_V(!can_apply_reset(), Ref<AnimatedValuesBackup>()); - Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation; + Ref<Animation> reset_anim = animation_set[SceneStringName(RESET)].animation; ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>()); Ref<AnimatedValuesBackup> backup_current = make_backup(); @@ -2082,7 +2108,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) { void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { ERR_FAIL_COND(!active); ERR_FAIL_COND(!has_animation(p_name)); - ERR_FAIL_COND(Math::is_zero_approx(p_duration)); + ERR_FAIL_COND(p_duration <= 0); Ref<Animation> reference_animation = get_animation(p_name); if (!cache_valid) { @@ -2228,19 +2254,16 @@ void AnimationMixer::_bind_methods() { ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationMixer::advance); GDVIRTUAL_BIND(_post_process_key_value, "animation", "track", "value", "object_id", "object_sub_idx"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deterministic"), "set_deterministic", "is_deterministic"); + /* ---- Capture feature ---- */ + ClassDB::bind_method(D_METHOD("capture", "name", "duration", "trans_type", "ease_type"), &AnimationMixer::capture, DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN)); /* ---- Reset on save ---- */ ClassDB::bind_method(D_METHOD("set_reset_on_save_enabled", "enabled"), &AnimationMixer::set_reset_on_save_enabled); ClassDB::bind_method(D_METHOD("is_reset_on_save_enabled"), &AnimationMixer::is_reset_on_save_enabled); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled"); - - /* ---- Capture feature ---- */ - ClassDB::bind_method(D_METHOD("capture", "name", "duration", "trans_type", "ease_type"), &AnimationMixer::capture, DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN)); - - ADD_SIGNAL(MethodInfo("mixer_updated")); // For updating dummy player. + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deterministic"), "set_deterministic", "is_deterministic"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root_node", "get_root_node"); ADD_GROUP("Root Motion", "root_motion_"); @@ -2270,13 +2293,15 @@ void AnimationMixer::_bind_methods() { ADD_SIGNAL(MethodInfo(SNAME("animation_finished"), PropertyInfo(Variant::STRING_NAME, "anim_name"))); ADD_SIGNAL(MethodInfo(SNAME("animation_started"), PropertyInfo(Variant::STRING_NAME, "anim_name"))); ADD_SIGNAL(MethodInfo(SNAME("caches_cleared"))); + ADD_SIGNAL(MethodInfo(SNAME("mixer_applied"))); + ADD_SIGNAL(MethodInfo(SNAME("mixer_updated"))); // For updating dummy player. ClassDB::bind_method(D_METHOD("_reset"), &AnimationMixer::reset); ClassDB::bind_method(D_METHOD("_restore", "backup"), &AnimationMixer::restore); } AnimationMixer::AnimationMixer() { - root_node = SceneStringNames::get_singleton()->path_pp; + root_node = SceneStringName(path_pp); } AnimationMixer::~AnimationMixer() { diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 682246f9ab..089a210193 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -126,7 +126,7 @@ protected: /* ---- General settings for animation ---- */ AnimationCallbackModeProcess callback_mode_process = ANIMATION_CALLBACK_MODE_PROCESS_IDLE; AnimationCallbackModeMethod callback_mode_method = ANIMATION_CALLBACK_MODE_METHOD_DEFERRED; - AnimationCallbackModeDiscrete callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT; + AnimationCallbackModeDiscrete callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE; int audio_max_polyphony = 32; NodePath root_node; @@ -222,9 +222,14 @@ protected: Variant init_value; Variant value; Vector<StringName> subpath; + + // TODO: There are many boolean, can be packed into one integer. + bool is_init = false; + bool use_continuous = false; bool use_discrete = false; bool is_using_angle = false; bool is_variant_interpolatable = true; + Variant element_size; TrackCacheValue(const TrackCacheValue &p_other) : @@ -232,6 +237,8 @@ protected: init_value(p_other.init_value), value(p_other.value), subpath(p_other.subpath), + is_init(p_other.is_init), + use_continuous(p_other.use_continuous), use_discrete(p_other.use_discrete), is_using_angle(p_other.is_using_angle), is_variant_interpolatable(p_other.is_variant_interpolatable), @@ -341,6 +348,8 @@ protected: /* ---- Blending processor ---- */ virtual void _process_animation(double p_delta, bool p_update_only = false); + + // For post process with retrieved key value during blending. virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1); Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1); GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, ObjectID, int); @@ -377,7 +386,6 @@ protected: #ifndef DISABLE_DEPRECATED virtual Variant _post_process_key_value_bind_compat_86687(const Ref<Animation> &p_anim, int p_track, Variant p_value, Object *p_object, int p_object_idx = -1); - static void _bind_compatibility_methods(); #endif // DISABLE_DEPRECATED @@ -436,7 +444,7 @@ public: void make_animation_instance(const StringName &p_name, const PlaybackInfo p_playback_info); void clear_animation_instances(); virtual void advance(double p_time); - virtual void clear_caches(); ///< must be called by hand if an animation was modified after added + virtual void clear_caches(); // Must be called by hand if an animation was modified after added. /* ---- Capture feature ---- */ void capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN); diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index ec44641484..87574a66ed 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -101,12 +101,22 @@ float AnimationNodeStateMachineTransition::get_xfade_time() const { void AnimationNodeStateMachineTransition::set_xfade_curve(const Ref<Curve> &p_curve) { xfade_curve = p_curve; + emit_changed(); } Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const { return xfade_curve; } +void AnimationNodeStateMachineTransition::set_break_loop_at_end(bool p_enable) { + break_loop_at_end = p_enable; + emit_changed(); +} + +bool AnimationNodeStateMachineTransition::is_loop_broken_at_end() const { + return break_loop_at_end; +} + void AnimationNodeStateMachineTransition::set_reset(bool p_reset) { reset = p_reset; emit_changed(); @@ -141,6 +151,9 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeStateMachineTransition::set_xfade_curve); ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeStateMachineTransition::get_xfade_curve); + ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeStateMachineTransition::set_break_loop_at_end); + ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeStateMachineTransition::is_loop_broken_at_end); + ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset); ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_reset); @@ -153,6 +166,7 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); @@ -310,19 +324,19 @@ TypedArray<StringName> AnimationNodeStateMachinePlayback::_get_travel_path() con } float AnimationNodeStateMachinePlayback::get_current_play_pos() const { - return pos_current; + return current_nti.position; } float AnimationNodeStateMachinePlayback::get_current_length() const { - return len_current; + return current_nti.length; } float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const { - return pos_fade_from; + return fadeing_from_nti.position; } float AnimationNodeStateMachinePlayback::get_fade_from_length() const { - return len_fade_from; + return fadeing_from_nti.length; } float AnimationNodeStateMachinePlayback::get_fading_time() const { @@ -336,18 +350,18 @@ float AnimationNodeStateMachinePlayback::get_fading_pos() const { void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { List<AnimationNode::ChildNode> child_nodes; p_state_machine->get_child_nodes(&child_nodes); - for (int i = 0; i < child_nodes.size(); i++) { - Ref<AnimationNodeStateMachine> anodesm = child_nodes[i].node; + for (const AnimationNode::ChildNode &child_node : child_nodes) { + Ref<AnimationNodeStateMachine> anodesm = child_node.node; if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { - Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + child_nodes[i].name + "/playback"); + Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + child_node.name + "/playback"); ERR_FAIL_COND(!playback.is_valid()); - playback->_set_base_path(base_path + child_nodes[i].name + "/"); + playback->_set_base_path(base_path + child_node.name + "/"); if (p_test_only) { playback = playback->duplicate(); } playback->path.clear(); playback->_clear_path_children(p_tree, anodesm.ptr(), p_test_only); - if (current != child_nodes[i].name) { + if (current != child_node.name) { playback->_start(anodesm.ptr()); // Can restart. } } @@ -665,21 +679,22 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, } } -double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double rem = _process(p_base_path, p_state_machine, p_playback_info, p_test_only); +AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + AnimationNode::NodeTimeInfo nti = _process(p_base_path, p_state_machine, p_playback_info, p_test_only); start_request = StringName(); next_request = false; stop_request = false; reset_request_on_teleport = false; - return rem; + return nti; } -double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { _set_base_path(p_base_path); AnimationTree *tree = p_state_machine->process_state->tree; double p_time = p_playback_info.time; + double p_delta = p_playback_info.delta; bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; @@ -690,8 +705,8 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) { path.clear(); _clear_path_children(tree, p_state_machine, p_test_only); - _start(p_state_machine); } + _start(p_state_machine); reset_request = true; } else { // Reset current state. @@ -705,11 +720,11 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An travel_request = StringName(); path.clear(); playing = false; - return 0; + return AnimationNode::NodeTimeInfo(); } if (!playing && start_request != StringName() && travel_request != StringName()) { - return 0; + return AnimationNode::NodeTimeInfo(); } // Process start/travel request. @@ -732,7 +747,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An _start(p_state_machine); } else { StringName node = start_request; - ERR_FAIL_V_MSG(0, "No such node: '" + node + "'"); + ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'"); } } @@ -766,7 +781,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An teleport_request = true; } } else { - ERR_FAIL_V_MSG(0, "No such node: '" + temp_travel_request + "'"); + ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'"); } } } @@ -777,16 +792,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An teleport_request = false; // Clear fadeing on teleport. fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_pos = 0; // Init current length. - pos_current = 0; // Overwritten suddenly in main process. - pi.time = 0; pi.seeked = true; pi.is_external_seeking = false; pi.weight = 0; - - len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); + current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Don't process first node if not necessary, insteads process next node. _transition_to_next_recursive(tree, p_state_machine, p_test_only); } @@ -795,7 +808,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An if (!p_state_machine->states.has(current)) { playing = false; // Current does not exist. _set_current(p_state_machine, StringName()); - return 0; + return AnimationNode::NodeTimeInfo(); } // Special case for grouped state machine Start/End to make priority with parent blend (means don't treat Start and End states as RESET animations). @@ -813,7 +826,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An fading_from = StringName(); } else { if (!p_seek) { - fading_pos += p_time; + fading_pos += Math::abs(p_delta); } fade_blend = MIN(1.0, fading_pos / fading_time); } @@ -829,18 +842,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An } // Main process. - double rem = 0.0; pi = p_playback_info; pi.weight = fade_blend; if (reset_request) { reset_request = false; pi.time = 0; pi.seeked = true; - len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); - rem = len_current; - } else { - rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. } + current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. // Cross-fade process. if (fading_from != StringName()) { @@ -852,7 +861,6 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An fade_blend_inv = 1.0; } - float fading_from_rem = 0.0; pi = p_playback_info; pi.weight = fade_blend_inv; if (_reset_request_for_fading_from) { @@ -860,57 +868,41 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An pi.time = 0; pi.seeked = true; } - fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. - - // Guess playback position. - if (fading_from_rem > len_fade_from) { /// Weird but ok. - len_fade_from = fading_from_rem; - } - pos_fade_from = len_fade_from - fading_from_rem; + fadeing_from_nti = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (fading_pos >= fading_time) { - fading_from = StringName(); // Finish fading. + // Finish fading. + fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); } } - // Guess playback position. - if (rem > len_current) { // Weird but ok. - len_current = rem; - } - pos_current = len_current - rem; - // Find next and see when to transition. _transition_to_next_recursive(tree, p_state_machine, p_test_only); // Predict remaining time. - double remain = rem; // If we can't predict the end of state machine, the time remaining must be INFINITY. - if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) { // There is no next transition. if (!p_state_machine->has_transition_from(current)) { if (fading_from != StringName()) { - remain = MAX(rem, fading_time - fading_pos); - } else { - remain = rem; + return current_nti.get_remain() > fadeing_from_nti.get_remain() ? current_nti : fadeing_from_nti; } - return remain; + return current_nti; } } if (current == p_state_machine->end_node) { - if (fading_from != StringName()) { - remain = MAX(0, fading_time - fading_pos); - } else { - remain = 0; + if (fading_from != StringName() && fadeing_from_nti.get_remain() > 0) { + return fadeing_from_nti; } - return remain; + return AnimationNode::NodeTimeInfo(); } if (!is_end()) { - return HUGE_LENGTH; + current_nti.is_infinity = true; } - return remain; + return current_nti; } bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { @@ -924,15 +916,16 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT transition_path.push_back(current); while (true) { next = _find_next(p_tree, p_state_machine); - if (transition_path.has(next.node)) { - WARN_PRINT_ONCE_ED("AnimationNodeStateMachinePlayback: " + base_path + "playback aborts the transition by detecting one or more looped transitions in the same frame to prevent to infinity loop. You may need to check the transition settings."); - break; // Maybe infinity loop, do nothing more. - } if (!_can_transition_to_next(p_tree, p_state_machine, next, p_test_only)) { break; // Finish transition. } + if (transition_path.has(next.node)) { + WARN_PRINT_ONCE_ED("AnimationNodeStateMachinePlayback: " + base_path + "playback aborts the transition by detecting one or more looped transitions in the same frame to prevent to infinity loop. You may need to check the transition settings."); + break; // Maybe infinity loop, do nothing more. + } + transition_path.push_back(next.node); is_state_changed = true; @@ -952,6 +945,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); } fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_time = 0; fading_pos = 0; } @@ -968,11 +962,10 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT _reset_request_for_fading_from = reset_request; // To avoid processing doubly, it must be reset in the fading process within _process(). reset_request = next.is_reset; - pos_fade_from = pos_current; - len_fade_from = len_current; + fadeing_from_nti = current_nti; if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { - pi.time = MIN(pos_current, len_current); + pi.time = current_nti.position; pi.seeked = true; pi.is_external_seeking = false; pi.weight = 0; @@ -980,24 +973,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT } // Just get length to find next recursive. - double rem = 0.0; pi.time = 0; pi.is_external_seeking = false; pi.weight = 0; - if (next.is_reset) { - pi.seeked = true; - len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. - rem = len_current; - } else { - pi.seeked = false; - rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. - } - - // Guess playback position. - if (rem > len_current) { // Weird but ok. - len_current = rem; - } - pos_current = len_current - rem; + pi.seeked = next.is_reset; + current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. // Fading must be processed. if (fading_time) { @@ -1028,6 +1008,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p playback->_next_main(); // Then, fadeing should be end. fading_from = StringName(); + fadeing_from_nti = AnimationNode::NodeTimeInfo(); fading_pos = 0; } else { return true; @@ -1039,7 +1020,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p } if (current != p_state_machine->start_node && p_next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) { - return pos_current >= len_current - p_next.xfade; + return current_nti.get_remain(p_next.break_loop_at_end) <= p_next.xfade; } return true; } @@ -1084,6 +1065,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ next.curve = ref_transition->get_xfade_curve(); next.switch_mode = ref_transition->get_switch_mode(); next.is_reset = ref_transition->is_reset(); + next.break_loop_at_end = ref_transition->is_loop_broken_at_end(); } } } else { @@ -1113,6 +1095,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_ next.curve = ref_transition->get_xfade_curve(); next.switch_mode = ref_transition->get_switch_mode(); next.is_reset = ref_transition->is_reset(); + next.break_loop_at_end = ref_transition->is_loop_broken_at_end(); } } @@ -1233,6 +1216,7 @@ AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() { /////////////////////////////////////////////////////// void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) const { + AnimationNode::get_parameter_list(r_list); r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); // Don't store this object in .tres, it always needs to be made as unique object. List<StringName> advance_conditions; for (int i = 0; i < transitions.size(); i++) { @@ -1249,6 +1233,11 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c } Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const { + Variant ret = AnimationNode::get_parameter_default_value(p_parameter); + if (ret != Variant()) { + return ret; + } + if (p_parameter == playback) { Ref<AnimationNodeStateMachinePlayback> p; p.instantiate(); @@ -1259,6 +1248,10 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName } bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_parameter) const { + if (AnimationNode::is_parameter_read_only(p_parameter)) { + return true; + } + if (p_parameter == playback) { return true; } @@ -1622,9 +1615,9 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { return graph_offset; } -double AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { Ref<AnimationNodeStateMachinePlayback> playback_new = get_parameter(playback); - ERR_FAIL_COND_V(playback_new.is_null(), 0.0); + ERR_FAIL_COND_V(playback_new.is_null(), AnimationNode::NodeTimeInfo()); playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED); if (p_test_only) { playback_new = playback_new->duplicate(); // Don't process original when testing. diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index fa0eca5877..8078ffb26d 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -57,6 +57,7 @@ private: StringName advance_condition_name; float xfade_time = 0.0; Ref<Curve> xfade_curve; + bool break_loop_at_end = false; bool reset = true; int priority = 1; String advance_expression; @@ -85,6 +86,9 @@ public: void set_xfade_time(float p_xfade); float get_xfade_time() const; + void set_break_loop_at_end(bool p_enable); + bool is_loop_broken_at_end() const; + void set_reset(bool p_reset); bool is_reset() const; @@ -210,7 +214,7 @@ public: void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() const; - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual String get_caption() const override; virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override; @@ -246,6 +250,7 @@ class AnimationNodeStateMachinePlayback : public Resource { Ref<Curve> curve; AnimationNodeStateMachineTransition::SwitchMode switch_mode; bool is_reset; + bool break_loop_at_end; }; struct ChildStateMachineInfo { @@ -257,18 +262,14 @@ class AnimationNodeStateMachinePlayback : public Resource { Ref<AnimationNodeStateMachineTransition> default_transition; String base_path; - double len_fade_from = 0.0; - double pos_fade_from = 0.0; - - double len_current = 0.0; - double pos_current = 0.0; - + AnimationNode::NodeTimeInfo current_nti; StringName current; Ref<Curve> current_curve; Ref<AnimationNodeStateMachineTransition> group_start_transition; Ref<AnimationNodeStateMachineTransition> group_end_transition; + AnimationNode::NodeTimeInfo fadeing_from_nti; StringName fading_from; float fading_time = 0.0; float fading_pos = 0.0; @@ -301,8 +302,8 @@ class AnimationNodeStateMachinePlayback : public Resource { bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only); void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only); - double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); - double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); + AnimationNode::NodeTimeInfo process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); + AnimationNode::NodeTimeInfo _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const; bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index d46470282f..0c24d79ad7 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -32,7 +32,6 @@ #include "animation_player.compat.inc" #include "core/config/engine.h" -#include "scene/scene_string_names.h" bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; @@ -41,7 +40,7 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { } else if (name.begins_with("next/")) { String which = name.get_slicec('/', 1); animation_set_next(which, p_value); - } else if (p_name == SceneStringNames::get_singleton()->blend_times) { + } else if (p_name == SceneStringName(blend_times)) { Array array = p_value; int len = array.size(); ERR_FAIL_COND_V(len % 3, false); @@ -77,7 +76,7 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { String which = name.get_slicec('/', 1); r_ret = animation_get_next(which); - } else if (name == "blend_times") { + } else if (p_name == SceneStringName(blend_times)) { Vector<BlendKey> keys; for (const KeyValue<BlendKey, double> &E : blend_times) { keys.ordered_insert(E.key); @@ -125,6 +124,8 @@ void AnimationPlayer::_validate_property(PropertyInfo &p_property) const { } p_property.hint_string = hint; + } else if (!auto_capture && p_property.name.begins_with("playback_auto_capture_")) { + p_property.usage = PROPERTY_USAGE_NONE; } } @@ -149,7 +150,7 @@ void AnimationPlayer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { if (!Engine::get_singleton()->is_editor_hint() && animation_set.has(autoplay)) { - set_active(true); + set_active(active); play(autoplay); _check_immediately_after_start(); } @@ -324,14 +325,14 @@ void AnimationPlayer::_blend_post_process() { String new_name = playback.assigned; playback_queue.pop_front(); if (end_notify) { - emit_signal(SceneStringNames::get_singleton()->animation_changed, old, new_name); + emit_signal(SceneStringName(animation_changed), old, new_name); } } else { _clear_caches(); playing = false; _set_process(false); if (end_notify) { - emit_signal(SceneStringNames::get_singleton()->animation_finished, playback.assigned); + emit_signal(SceneStringName(animation_finished), playback.assigned); if (movie_quit_on_finish && OS::get_singleton()->has_feature("movie")) { print_line(vformat("Movie Maker mode is enabled. Quitting on animation finish as requested by: %s", get_path())); get_tree()->quit(); @@ -370,73 +371,21 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b play(p_name, p_custom_blend, -1, true); } -void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { - StringName name = p_name; - if (name == StringName()) { - name = playback.assigned; - } - - if (signbit(p_duration)) { - double max_dur = 0; - Ref<Animation> anim = get_animation(name); - if (anim.is_valid()) { - double current_pos = playback.current.pos; - if (playback.assigned != name) { - current_pos = p_from_end ? anim->get_length() : 0; - } - for (int i = 0; i < anim->get_track_count(); i++) { - if (anim->track_get_type(i) != Animation::TYPE_VALUE) { - continue; - } - if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) { - continue; - } - if (anim->track_get_key_count(i) == 0) { - continue; - } - max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos); - } - } - p_duration = max_dur; +void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { + if (auto_capture) { + play_with_capture(p_name, auto_capture_duration, p_custom_blend, p_custom_scale, p_from_end, auto_capture_transition_type, auto_capture_ease_type); + } else { + _play(p_name, p_custom_blend, p_custom_scale, p_from_end); } - - capture(name, p_duration, p_trans_type, p_ease_type); - play(name, p_custom_blend, p_custom_scale, p_from_end); } -void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { +void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { StringName name = p_name; if (name == StringName()) { name = playback.assigned; } -#ifdef TOOLS_ENABLED - if (!Engine::get_singleton()->is_editor_hint()) { - bool warn_enabled = false; - if (capture_cache.animation.is_null()) { - Ref<Animation> anim = get_animation(name); - if (anim.is_valid()) { - for (int i = 0; i < anim->get_track_count(); i++) { - if (anim->track_get_type(i) != Animation::TYPE_VALUE) { - continue; - } - if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) { - continue; - } - if (anim->track_get_key_count(i) == 0) { - continue; - } - warn_enabled = true; - } - } - } - if (warn_enabled) { - WARN_PRINT_ONCE_ED("Capture track found. If you want to interpolate animation with captured frame, you can use play_with_capture() instead of play()."); - } - } -#endif - ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); Playback &c = playback; @@ -498,10 +447,10 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa } else { if (p_from_end && c.current.pos == 0) { // Animation reset but played backwards, set position to the end. - c.current.pos = c.current.from->animation->get_length(); + seek(c.current.from->animation->get_length(), true, true); } else if (!p_from_end && c.current.pos == c.current.from->animation->get_length()) { // Animation resumed but already ended, set position to the beginning. - c.current.pos = 0; + seek(0, true, true); } else if (playing) { return; } @@ -513,7 +462,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa _set_process(true); // Always process when starting an animation. playing = true; - emit_signal(SceneStringNames::get_singleton()->animation_started, c.assigned); + emit_signal(SceneStringName(animation_started), c.assigned); if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) { return; // No next in this case. @@ -525,6 +474,47 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa } } +void AnimationPlayer::_capture(const StringName &p_name, bool p_from_end, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { + StringName name = p_name; + if (name == StringName()) { + name = playback.assigned; + } + + Ref<Animation> anim = get_animation(name); + if (anim.is_null() || !anim->is_capture_included()) { + return; + } + if (signbit(p_duration)) { + double max_dur = 0; + double current_pos = playback.current.pos; + if (playback.assigned != name) { + current_pos = p_from_end ? anim->get_length() : 0; + } + for (int i = 0; i < anim->get_track_count(); i++) { + if (anim->track_get_type(i) != Animation::TYPE_VALUE) { + continue; + } + if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) { + continue; + } + if (anim->track_get_key_count(i) == 0) { + continue; + } + max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos); + } + p_duration = max_dur; + } + if (Math::is_zero_approx(p_duration)) { + return; + } + capture(name, p_duration, p_trans_type, p_ease_type); +} + +void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) { + _capture(p_name, p_from_end, p_duration, p_trans_type, p_ease_type); + _play(p_name, p_custom_blend, p_custom_scale, p_from_end); +} + bool AnimationPlayer::is_playing() const { return playing; } @@ -725,6 +715,39 @@ double AnimationPlayer::get_blend_time(const StringName &p_animation1, const Str } } +void AnimationPlayer::set_auto_capture(bool p_auto_capture) { + auto_capture = p_auto_capture; + notify_property_list_changed(); +} + +bool AnimationPlayer::is_auto_capture() const { + return auto_capture; +} + +void AnimationPlayer::set_auto_capture_duration(double p_auto_capture_duration) { + auto_capture_duration = p_auto_capture_duration; +} + +double AnimationPlayer::get_auto_capture_duration() const { + return auto_capture_duration; +} + +void AnimationPlayer::set_auto_capture_transition_type(Tween::TransitionType p_auto_capture_transition_type) { + auto_capture_transition_type = p_auto_capture_transition_type; +} + +Tween::TransitionType AnimationPlayer::get_auto_capture_transition_type() const { + return auto_capture_transition_type; +} + +void AnimationPlayer::set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type) { + auto_capture_ease_type = p_auto_capture_ease_type; +} + +Tween::EaseType AnimationPlayer::get_auto_capture_ease_type() const { + return auto_capture_ease_type; +} + #ifdef TOOLS_ENABLED void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { const String pf = p_function; @@ -815,9 +838,18 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_blend_time", "sec"), &AnimationPlayer::set_default_blend_time); ClassDB::bind_method(D_METHOD("get_default_blend_time"), &AnimationPlayer::get_default_blend_time); + ClassDB::bind_method(D_METHOD("set_auto_capture", "auto_capture"), &AnimationPlayer::set_auto_capture); + ClassDB::bind_method(D_METHOD("is_auto_capture"), &AnimationPlayer::is_auto_capture); + ClassDB::bind_method(D_METHOD("set_auto_capture_duration", "auto_capture_duration"), &AnimationPlayer::set_auto_capture_duration); + ClassDB::bind_method(D_METHOD("get_auto_capture_duration"), &AnimationPlayer::get_auto_capture_duration); + ClassDB::bind_method(D_METHOD("set_auto_capture_transition_type", "auto_capture_transition_type"), &AnimationPlayer::set_auto_capture_transition_type); + ClassDB::bind_method(D_METHOD("get_auto_capture_transition_type"), &AnimationPlayer::get_auto_capture_transition_type); + ClassDB::bind_method(D_METHOD("set_auto_capture_ease_type", "auto_capture_ease_type"), &AnimationPlayer::set_auto_capture_ease_type); + ClassDB::bind_method(D_METHOD("get_auto_capture_ease_type"), &AnimationPlayer::get_auto_capture_ease_type); + ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(StringName()), DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN)); + ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(StringName()), DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN)); ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause); ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false)); ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing); @@ -855,6 +887,10 @@ void AnimationPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_position"); ADD_GROUP("Playback Options", "playback_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_auto_capture"), "set_auto_capture", "is_auto_capture"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_auto_capture_duration", PROPERTY_HINT_NONE, "suffix:s"), "set_auto_capture_duration", "get_auto_capture_duration"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Expo,Elastic,Cubic,Circ,Bounce,Back,Spring"), "set_auto_capture_transition_type", "get_auto_capture_transition_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_ease_type", PROPERTY_HINT_ENUM, "In,Out,InOut,OutIn"), "set_auto_capture_ease_type", "get_auto_capture_ease_type"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-4,4,0.001,or_less,or_greater"), "set_speed_scale", "get_speed_scale"); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 13e1e37ca9..f270f32193 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -56,6 +56,12 @@ private: float speed_scale = 1.0; double default_blend_time = 0.0; + + bool auto_capture = true; + double auto_capture_duration = -1.0; + Tween::TransitionType auto_capture_transition_type = Tween::TRANS_LINEAR; + Tween::EaseType auto_capture_ease_type = Tween::EASE_IN; + bool is_stopping = false; struct PlaybackData { @@ -108,6 +114,8 @@ private: bool reset_on_save = true; bool movie_quit_on_finish = false; + void _play(const StringName &p_name, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); + void _capture(const StringName &p_name, bool p_from_end = false, double p_duration = -1.0, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN); void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current = false); void _blend_playback_data(double p_delta, bool p_started); void _stop_internal(bool p_reset, bool p_keep_state); @@ -158,9 +166,18 @@ public: void set_default_blend_time(double p_default); double get_default_blend_time() const; + void set_auto_capture(bool p_auto_capture); + bool is_auto_capture() const; + void set_auto_capture_duration(double p_auto_capture_duration); + double get_auto_capture_duration() const; + void set_auto_capture_transition_type(Tween::TransitionType p_auto_capture_transition_type); + Tween::TransitionType get_auto_capture_transition_type() const; + void set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type); + Tween::EaseType get_auto_capture_ease_type() const; + void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1); - void play_with_capture(const StringName &p_name, double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN); + void play_with_capture(const StringName &p_name = StringName(), double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN); void queue(const StringName &p_name); Vector<String> get_queue(); void clear_queue(); diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 2c2d8387f3..d4061ab167 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -34,7 +34,6 @@ #include "animation_blend_tree.h" #include "core/config/engine.h" #include "scene/animation/animation_player.h" -#include "scene/scene_string_names.h" void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { Array parameters; @@ -46,6 +45,10 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { r_list->push_back(PropertyInfo::from_dict(d)); } } + + r_list->push_back(PropertyInfo(Variant::FLOAT, current_length, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); + r_list->push_back(PropertyInfo(Variant::FLOAT, current_position, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); + r_list->push_back(PropertyInfo(Variant::FLOAT, current_delta, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); } Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { @@ -56,8 +59,15 @@ Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const { bool ret = false; - GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret); - return ret; + if (GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret) && ret) { + return true; + } + + if (p_parameter == current_length || p_parameter == current_position || p_parameter == current_delta) { + return true; + } + + return false; } void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { @@ -81,6 +91,20 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { return process_state->tree->property_map[path].first; } +void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) { + set_parameter(current_length, p_node_time_info.length); + set_parameter(current_position, p_node_time_info.position); + set_parameter(current_delta, p_node_time_info.delta); +} + +AnimationNode::NodeTimeInfo AnimationNode::get_node_time_info() const { + NodeTimeInfo nti; + nti.length = get_parameter(current_length); + nti.position = get_parameter(current_position); + nti.delta = get_parameter(current_delta); + return nti; +} + void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { Dictionary cn; if (GDVIRTUAL_CALL(_get_child_nodes, cn)) { @@ -101,11 +125,11 @@ void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixe process_state->tree->make_animation_instance(p_animation, p_playback_info); } -double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { process_state = p_process_state; - double t = process(p_playback_info, p_test_only); + NodeTimeInfo nti = process(p_playback_info, p_test_only); process_state = nullptr; - return t; + return nti; } void AnimationNode::make_invalid(const String &p_reason) { @@ -122,11 +146,11 @@ AnimationTree *AnimationNode::get_animation_tree() const { return process_state->tree; } -double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { - ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); +AnimationNode::NodeTimeInfo AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), NodeTimeInfo()); AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent); - ERR_FAIL_NULL_V(blend_tree, 0); + ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo()); // Update connections. StringName current_name = blend_tree->get_node_name(Ref<AnimationNode>(this)); @@ -136,32 +160,31 @@ double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_pl StringName node_name = node_state.connections[p_input]; if (!blend_tree->has_node(node_name)) { make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name)); - return 0; + return NodeTimeInfo(); } Ref<AnimationNode> node = blend_tree->get_node(node_name); - ERR_FAIL_COND_V(node.is_null(), 0); + ERR_FAIL_COND_V(node.is_null(), NodeTimeInfo()); real_t activity = 0.0; Vector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path); - double ret = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity); + NodeTimeInfo nti = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity); if (activity_ptr && p_input < activity_ptr->size()) { activity_ptr->write[p_input].last_pass = process_state->last_pass; activity_ptr->write[p_input].activity = activity; } - return ret; + return nti; } -double AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { - ERR_FAIL_COND_V(p_node.is_null(), 0); - +AnimationNode::NodeTimeInfo AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_COND_V(p_node.is_null(), NodeTimeInfo()); p_node->node_state.connections.clear(); return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr); } -double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) { - ERR_FAIL_NULL_V(process_state, 0); +AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) { + ERR_FAIL_NULL_V(process_state, NodeTimeInfo()); int blend_count = node_state.track_weights.size(); @@ -261,7 +284,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p new_parent = p_new_parent; new_path = String(node_state.base_path) + String(p_subpath) + "/"; } else { - ERR_FAIL_NULL_V(node_state.parent, 0); + ERR_FAIL_NULL_V(node_state.parent, NodeTimeInfo()); new_parent = node_state.parent; new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/"; } @@ -271,7 +294,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p p_node->node_state.base_path = new_path; p_node->node_state.parent = new_parent; if (!p_playback_info.seeked && !p_sync && !any_valid) { - p_playback_info.time = 0.0; + p_playback_info.delta = 0.0; return p_node->_pre_process(process_state, p_playback_info, p_test_only); } return p_node->_pre_process(process_state, p_playback_info, p_test_only); @@ -328,15 +351,31 @@ int AnimationNode::find_input(const String &p_name) const { return idx; } -double AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { +AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { process_state->is_testing = p_test_only; - return _process(p_playback_info, p_test_only); + + AnimationMixer::PlaybackInfo pi = p_playback_info; + if (p_playback_info.seeked) { + pi.delta = get_node_time_info().position - p_playback_info.time; + } else { + pi.time = get_node_time_info().position + p_playback_info.delta; + } + + NodeTimeInfo nti = _process(pi, p_test_only); + + if (!p_test_only) { + set_node_time_info(nti); + } + + return nti; } -double AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { - double ret = 0; - GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, ret); - return ret; +AnimationNode::NodeTimeInfo AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + double r_ret = 0.0; + GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, r_ret); + NodeTimeInfo nti; + nti.delta = r_ret; + return nti; } void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { @@ -355,11 +394,11 @@ bool AnimationNode::is_filter_enabled() const { return filter_enabled; } -void AnimationNode::set_closable(bool p_closable) { +void AnimationNode::set_deletable(bool p_closable) { closable = p_closable; } -bool AnimationNode::is_closable() const { +bool AnimationNode::is_deletable() const { return closable; } @@ -432,7 +471,8 @@ double AnimationNode::blend_node_ex(const StringName &p_sub_path, Ref<AnimationN info.seeked = p_seek; info.is_external_seeking = p_is_external_seeking; info.weight = p_blend; - return blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only); + NodeTimeInfo nti = blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only); + return nti.length - nti.position; } double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { @@ -441,9 +481,38 @@ double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bo info.seeked = p_seek; info.is_external_seeking = p_is_external_seeking; info.weight = p_blend; - return blend_input(p_input, info, p_filter, p_sync, p_test_only); + NodeTimeInfo nti = blend_input(p_input, info, p_filter, p_sync, p_test_only); + return nti.length - nti.position; } +#ifdef TOOLS_ENABLED +void AnimationNode::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + const String pf = p_function; + if (p_idx == 0) { + if (pf == "find_input") { + for (const AnimationNode::Input &E : inputs) { + r_options->push_back(E.name.quote()); + } + } else if (pf == "get_parameter" || pf == "set_parameter") { + bool is_setter = pf == "set_parameter"; + List<PropertyInfo> parameters; + get_parameter_list(¶meters); + for (const PropertyInfo &E : parameters) { + if (is_setter && is_parameter_read_only(E.name)) { + continue; + } + r_options->push_back(E.name.quote()); + } + } else if (pf == "set_filter_path" || pf == "is_path_filtered") { + for (const KeyValue<NodePath, bool> &E : filter) { + r_options->push_back(String(E.key).quote()); + } + } + } + Resource::get_argument_options(p_function, p_idx, r_options); +} +#endif + void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input); ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input); @@ -557,7 +626,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const for (int i = 0; i < p_track_count; i++) { src_blendsw[i] = 1.0; // By default all go to 1 for the root input. } - root_animation_node->node_state.base_path = SceneStringNames::get_singleton()->parameters_base_path; + root_animation_node->node_state.base_path = SceneStringName(parameters_base_path); root_animation_node->node_state.parent = nullptr; } @@ -568,11 +637,12 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const if (started) { // If started, seek. pi.seeked = true; + pi.delta = p_delta; root_animation_node->_pre_process(&process_state, pi, false); started = false; } else { pi.seeked = false; - pi.time = p_delta; + pi.delta = p_delta; root_animation_node->_pre_process(&process_state, pi, false); } } @@ -717,7 +787,7 @@ void AnimationTree::_update_properties() { input_activity_map_get.clear(); if (root_animation_node.is_valid()) { - _update_properties_for_node(SceneStringNames::get_singleton()->parameters_base_path, root_animation_node); + _update_properties_for_node(SceneStringName(parameters_base_path), root_animation_node); } properties_dirty = false; @@ -739,15 +809,12 @@ void AnimationTree::_notification(int p_what) { void AnimationTree::set_animation_player(const NodePath &p_path) { animation_player = p_path; if (p_path.is_empty()) { - set_root_node(SceneStringNames::get_singleton()->path_pp); + set_root_node(SceneStringName(path_pp)); while (animation_libraries.size()) { remove_animation_library(animation_libraries[0].name); } } -#ifdef TOOLS_ENABLED emit_signal(SNAME("animation_player_changed")); // Needs to unpin AnimationPlayerEditor. - emit_signal(SNAME("mixer_updated")); -#endif // TOOLS_ENABLED _setup_animation_player(); notify_property_list_changed(); } @@ -786,10 +853,10 @@ void AnimationTree::_setup_animation_player() { } List<StringName> list; player->get_animation_library_list(&list); - for (int i = 0; i < list.size(); i++) { - Ref<AnimationLibrary> lib = player->get_animation_library(list[i]); + for (const StringName &E : list) { + Ref<AnimationLibrary> lib = player->get_animation_library(E); if (lib.is_valid()) { - add_animation_library(list[i], lib); + add_animation_library(E, lib); } } } @@ -893,9 +960,7 @@ void AnimationTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); -#ifdef TOOLS_ENABLED ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed"))); -#endif // TOOLS_ENABLED } AnimationTree::AnimationTree() { diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 87928e4d20..62a01b758f 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -63,6 +63,34 @@ public: HashMap<NodePath, bool> filter; bool filter_enabled = false; + // To propagate information from upstream for use in estimation of playback progress. + // These values must be taken from the result of blend_node() or blend_input() and must be essentially read-only. + // For example, if you want to change the position, you need to change the pi.time value of PlaybackInfo passed to blend_input(pi) and get the result. + struct NodeTimeInfo { + // Retain the previous frame values. These are stored into the AnimationTree's Map and exposing them as read-only values. + double length = 0.0; + double position = 0.0; + double delta = 0.0; + + // Needs internally to estimate remain time, the previous frame values are not retained. + Animation::LoopMode loop_mode = Animation::LOOP_NONE; + bool is_just_looped = false; // For breaking loop, it is true when just looped. + bool is_infinity = false; // For unpredictable state machine's end. + + bool is_looping() { + return loop_mode != Animation::LOOP_NONE; + } + double get_remain(bool p_break_loop = false) { + if ((is_looping() && !p_break_loop) || is_infinity) { + return HUGE_LENGTH; + } + if (p_break_loop && is_just_looped) { + return 0; + } + return length - position; + } + }; + // Temporary state for blending process which needs to be stored in each AnimationNodes. struct NodeState { StringName base_path; @@ -84,16 +112,23 @@ public: Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; - double _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr); - double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); + + // The time information is passed from upstream to downstream by AnimationMixer::PlaybackInfo::p_playback_info until AnimationNodeAnimation processes it. + // Conversely, AnimationNodeAnimation returns the processed result as NodeTimeInfo from downstream to upstream. + NodeTimeInfo _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr); + NodeTimeInfo _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); protected: - virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); - double process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); + StringName current_length = "current_length"; + StringName current_position = "current_position"; + StringName current_delta = "current_delta"; + + virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // To organize time information. Virtualizing for especially AnimationNodeAnimation needs to take "backward" into account. + virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // Main process. void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info); - double blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); - double blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + NodeTimeInfo blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + NodeTimeInfo blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); // Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed. void blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE); @@ -124,6 +159,9 @@ public: void set_parameter(const StringName &p_name, const Variant &p_value); Variant get_parameter(const StringName &p_name) const; + void set_node_time_info(const NodeTimeInfo &p_node_time_info); // Wrapper of set_parameter(). + virtual NodeTimeInfo get_node_time_info() const; // Wrapper of get_parameter(). + struct ChildNode { StringName name; Ref<AnimationNode> node; @@ -146,11 +184,15 @@ public: void set_filter_enabled(bool p_enable); bool is_filter_enabled() const; - void set_closable(bool p_closable); - bool is_closable() const; + void set_deletable(bool p_closable); + bool is_deletable() const; virtual bool has_filter() const; +#ifdef TOOLS_ENABLED + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; +#endif + virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const; Ref<AnimationNode> find_node_by_path(const String &p_name) const; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 56c582e2d7..6a61e8693d 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -351,7 +351,7 @@ bool Tween::step(double p_delta) { if (loops_done == loops) { running = false; dead = true; - emit_signal(SNAME("finished")); + emit_signal(SceneStringName(finished)); break; } else { emit_signal(SNAME("loop_finished"), loops_done); @@ -614,7 +614,7 @@ bool PropertyTweener::step(double &r_delta) { target_instance->set_indexed(property, final_val); finished = true; r_delta = elapsed_time - delay - duration; - emit_signal(SNAME("finished")); + emit_signal(SceneStringName(finished)); return false; } } @@ -674,7 +674,7 @@ bool IntervalTweener::step(double &r_delta) { } else { finished = true; r_delta = elapsed_time - duration; - emit_signal(SNAME("finished")); + emit_signal(SceneStringName(finished)); return false; } } @@ -717,7 +717,7 @@ bool CallbackTweener::step(double &r_delta) { finished = true; r_delta = elapsed_time - delay; - emit_signal(SNAME("finished")); + emit_signal(SceneStringName(finished)); return false; } @@ -803,7 +803,7 @@ bool MethodTweener::step(double &r_delta) { } else { finished = true; r_delta = elapsed_time - delay - duration; - emit_signal(SNAME("finished")); + emit_signal(SceneStringName(finished)); return false; } } diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index dadcfab69f..0c2bd64e84 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -59,6 +59,7 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const { } void AudioStreamPlayer::set_volume_db(float p_volume) { + ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN."); internal->volume_db = p_volume; Vector<AudioFrame> volume_vector = _get_volume_vector(); diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp index 19b3ec481b..a7b8faaaae 100644 --- a/scene/audio/audio_stream_player_internal.cpp +++ b/scene/audio/audio_stream_player_internal.cpp @@ -31,7 +31,6 @@ #include "audio_stream_player_internal.h" #include "scene/main/node.h" -#include "scene/scene_string_names.h" #include "servers/audio/audio_stream.h" void AudioStreamPlayerInternal::_set_process(bool p_enabled) { @@ -78,7 +77,7 @@ void AudioStreamPlayerInternal::process() { _set_process(false); } if (!playbacks_to_remove.is_empty()) { - node->emit_signal(SNAME("finished")); + node->emit_signal(SceneStringName(finished)); } } @@ -307,14 +306,14 @@ StringName AudioStreamPlayerInternal::get_bus() const { return bus; } } - return SceneStringNames::get_singleton()->Master; + return SceneStringName(Master); } AudioStreamPlayerInternal::AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical) { node = p_node; play_callable = p_play_callable; physical = p_physical; - bus = SceneStringNames::get_singleton()->Master; + bus = SceneStringName(Master); AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp((Object *)node, &Object::notify_property_list_changed)); AudioServer::get_singleton()->connect("bus_renamed", callable_mp((Object *)node, &Object::notify_property_list_changed).unbind(3)); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 19e5693736..07c32eef13 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -421,9 +421,9 @@ void SceneDebuggerObject::_parse_script_properties(Script *p_script, ScriptInsta void SceneDebuggerObject::serialize(Array &r_arr, int p_max_size) { Array send_props; - for (int i = 0; i < properties.size(); i++) { - const PropertyInfo &pi = properties[i].first; - Variant &var = properties[i].second; + for (SceneDebuggerObject::SceneDebuggerProperty &property : properties) { + const PropertyInfo &pi = property.first; + Variant &var = property.second; Ref<Resource> res = var; @@ -510,7 +510,7 @@ SceneDebuggerTree::SceneDebuggerTree(Node *p_root) { const StringName &is_visible_sn = SNAME("is_visible"); const StringName &is_visible_in_tree_sn = SNAME("is_visible_in_tree"); while (stack.size()) { - Node *n = stack[0]; + Node *n = stack.front()->get(); stack.pop_front(); int count = n->get_child_count(); diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp index 94240ccead..1663a7d602 100644 --- a/scene/gui/aspect_ratio_container.cpp +++ b/scene/gui/aspect_ratio_container.cpp @@ -35,19 +35,12 @@ Size2 AspectRatioContainer::get_minimum_size() const { Size2 ms; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + Control *c = as_sortable_control(get_child(i)); if (!c) { continue; } - if (c->is_set_as_top_level()) { - continue; - } - if (!c->is_visible()) { - continue; - } Size2 minsize = c->get_combined_minimum_size(); - ms.width = MAX(ms.width, minsize.width); - ms.height = MAX(ms.height, minsize.height); + ms = ms.max(minsize); } return ms; } @@ -108,13 +101,10 @@ void AspectRatioContainer::_notification(int p_what) { bool rtl = is_layout_rtl(); Size2 size = get_size(); for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + Control *c = as_sortable_control(get_child(i)); if (!c) { continue; } - if (c->is_set_as_top_level()) { - continue; - } // Temporary fix for editor crash. TextureRect *trect = Object::cast_to<TextureRect>(c); @@ -144,8 +134,7 @@ void AspectRatioContainer::_notification(int p_what) { } break; } child_size *= scale_factor; - child_size.x = MAX(child_size.x, child_minsize.x); - child_size.y = MAX(child_size.y, child_minsize.y); + child_size = child_size.max(child_minsize); float align_x = 0.5; switch (alignment_horizontal) { diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 66b14dc967..01e3cce78b 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -33,7 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/keyboard.h" #include "scene/main/window.h" -#include "scene/scene_string_names.h" void BaseButton::_unpress_group() { if (!button_group.is_valid()) { @@ -135,7 +134,7 @@ void BaseButton::_notification(int p_what) { void BaseButton::_pressed() { GDVIRTUAL_CALL(_pressed); pressed(); - emit_signal(SNAME("pressed")); + emit_signal(SceneStringName(pressed)); } void BaseButton::_toggled(bool p_pressed) { @@ -162,7 +161,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { status.pressed = !status.pressed; _unpress_group(); if (button_group.is_valid()) { - button_group->emit_signal(SNAME("pressed"), this); + button_group->emit_signal(SceneStringName(pressed), this); } _toggled(status.pressed); _pressed(); @@ -226,7 +225,7 @@ void BaseButton::set_pressed(bool p_pressed) { if (p_pressed) { _unpress_group(); if (button_group.is_valid()) { - button_group->emit_signal(SNAME("pressed"), this); + button_group->emit_signal(SceneStringName(pressed), this); } } _toggled(status.pressed); @@ -368,7 +367,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) { _unpress_group(); if (button_group.is_valid()) { - button_group->emit_signal(SNAME("pressed"), this); + button_group->emit_signal(SceneStringName(pressed), this); } _toggled(status.pressed); diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 2728126e64..d8fcbbb883 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -55,11 +55,8 @@ void BoxContainer::_resort() { HashMap<Control *, _MinSizeCache> min_size_cache; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } @@ -109,11 +106,8 @@ void BoxContainer::_resort() { float error = 0.0; // Keep track of accumulated error in pixels for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } @@ -201,11 +195,8 @@ void BoxContainer::_resort() { } for (int i = start; i != end; i += delta) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } @@ -253,14 +244,7 @@ Size2 BoxContainer::get_minimum_size() const { for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { - continue; - } - if (c->is_set_as_top_level()) { - continue; - } - - if (!c->is_visible()) { + if (!c || !c->is_visible() || c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index b1870eea55..3bf9d79c7f 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -50,6 +50,83 @@ void Button::_set_internal_margin(Side p_side, float p_value) { void Button::_queue_update_size_cache() { } +void Button::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + const bool rtl = is_layout_rtl(); + if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) { + theme_cache.max_style_size = theme_cache.normal_mirrored->get_minimum_size(); + theme_cache.style_margin_left = theme_cache.normal_mirrored->get_margin(SIDE_LEFT); + theme_cache.style_margin_right = theme_cache.normal_mirrored->get_margin(SIDE_RIGHT); + theme_cache.style_margin_top = theme_cache.normal_mirrored->get_margin(SIDE_TOP); + theme_cache.style_margin_bottom = theme_cache.normal_mirrored->get_margin(SIDE_BOTTOM); + } else { + theme_cache.max_style_size = theme_cache.normal->get_minimum_size(); + theme_cache.style_margin_left = theme_cache.normal->get_margin(SIDE_LEFT); + theme_cache.style_margin_right = theme_cache.normal->get_margin(SIDE_RIGHT); + theme_cache.style_margin_top = theme_cache.normal->get_margin(SIDE_TOP); + theme_cache.style_margin_bottom = theme_cache.normal->get_margin(SIDE_BOTTOM); + } + if (has_theme_stylebox("hover_pressed")) { + if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_pressed_mirrored->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_pressed_mirrored->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_pressed_mirrored->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_pressed_mirrored->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_pressed_mirrored->get_margin(SIDE_BOTTOM)); + } else { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_pressed->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_pressed->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_pressed->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_pressed->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_pressed->get_margin(SIDE_BOTTOM)); + } + } + if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.pressed_mirrored->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.pressed_mirrored->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.pressed_mirrored->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.pressed_mirrored->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.pressed_mirrored->get_margin(SIDE_BOTTOM)); + } else { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.pressed->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.pressed->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.pressed->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.pressed->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.pressed->get_margin(SIDE_BOTTOM)); + } + if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_mirrored->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_mirrored->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_mirrored->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_mirrored->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_mirrored->get_margin(SIDE_BOTTOM)); + } else { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover->get_margin(SIDE_BOTTOM)); + } + if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.disabled_mirrored->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.disabled_mirrored->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.disabled_mirrored->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.disabled_mirrored->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.disabled_mirrored->get_margin(SIDE_BOTTOM)); + } else { + theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.disabled->get_minimum_size()); + theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.disabled->get_margin(SIDE_LEFT)); + theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.disabled->get_margin(SIDE_RIGHT)); + theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.disabled->get_margin(SIDE_TOP)); + theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.disabled->get_margin(SIDE_BOTTOM)); + } +} + +Size2 Button::_get_largest_stylebox_size() const { + return theme_cache.max_style_size; +} + Ref<StyleBox> Button::_get_current_stylebox() const { Ref<StyleBox> stylebox = theme_cache.normal; const bool rtl = is_layout_rtl(); @@ -137,16 +214,13 @@ void Button::_notification(int p_what) { const RID ci = get_canvas_item(); const Size2 size = get_size(); - const Ref<StyleBox> style = _get_current_stylebox(); - { // Draws the stylebox in the current state. - if (!flat) { - style->draw(ci, Rect2(Point2(), size)); - } + // Draws the stylebox in the current state. + if (!flat) { + _get_current_stylebox()->draw(ci, Rect2(Point2(), size)); + } - if (has_focus()) { - Ref<StyleBox> style2 = theme_cache.focus; - style2->draw(ci, Rect2(Point2(), size)); - } + if (has_focus()) { + theme_cache.focus->draw(ci, Rect2(Point2(), size)); } Ref<Texture2D> _icon = icon; @@ -158,16 +232,11 @@ void Button::_notification(int p_what) { break; } - const float style_margin_left = style->get_margin(SIDE_LEFT); - const float style_margin_right = style->get_margin(SIDE_RIGHT); - const float style_margin_top = style->get_margin(SIDE_TOP); - const float style_margin_bottom = style->get_margin(SIDE_BOTTOM); - Size2 drawable_size_remained = size; { // The size after the stelybox is stripped. - drawable_size_remained.width -= style_margin_left + style_margin_right; - drawable_size_remained.height -= style_margin_top + style_margin_bottom; + drawable_size_remained.width -= theme_cache.style_margin_left + theme_cache.style_margin_right; + drawable_size_remained.height -= theme_cache.style_margin_top + theme_cache.style_margin_bottom; } const int h_separation = MAX(0, theme_cache.h_separation); @@ -298,6 +367,7 @@ void Button::_notification(int p_what) { icon_size = Size2(icon_width, icon_height); } icon_size = _fit_icon_size(icon_size); + icon_size = icon_size.round(); } if (icon_size.width > 0.0f) { @@ -311,12 +381,12 @@ void Button::_notification(int p_what) { [[fallthrough]]; case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: { - icon_ofs.x += style_margin_left; + icon_ofs.x += theme_cache.style_margin_left; icon_ofs.x += left_internal_margin_with_h_separation; } break; case HORIZONTAL_ALIGNMENT_RIGHT: { - icon_ofs.x = size.x - style_margin_right; + icon_ofs.x = size.x - theme_cache.style_margin_right; icon_ofs.x -= right_internal_margin_with_h_separation; icon_ofs.x -= icon_size.width; } break; @@ -329,13 +399,14 @@ void Button::_notification(int p_what) { [[fallthrough]]; case VERTICAL_ALIGNMENT_FILL: case VERTICAL_ALIGNMENT_TOP: { - icon_ofs.y += style_margin_top; + icon_ofs.y += theme_cache.style_margin_top; } break; case VERTICAL_ALIGNMENT_BOTTOM: { - icon_ofs.y = size.y - style_margin_bottom - icon_size.height; + icon_ofs.y = size.y - theme_cache.style_margin_bottom - icon_size.height; } break; } + icon_ofs = icon_ofs.floor(); Rect2 icon_region = Rect2(icon_ofs, icon_size); draw_texture_rect(_icon, icon_region, false, icon_modulate_color); @@ -358,7 +429,7 @@ void Button::_notification(int p_what) { if (!xl_text.is_empty()) { text_buf->set_alignment(align_rtl_checked); - float text_buf_width = MAX(1.0f, drawable_size_remained.width); // The space's width filled by the text_buf. + float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf. text_buf->set_width(text_buf_width); Point2 text_ofs; @@ -371,7 +442,7 @@ void Button::_notification(int p_what) { case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: case HORIZONTAL_ALIGNMENT_RIGHT: { - text_ofs.x += style_margin_left; + text_ofs.x += theme_cache.style_margin_left; text_ofs.x += left_internal_margin_with_h_separation; if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { // Offset by the space's width that occupied by icon and h_separation together. @@ -380,7 +451,7 @@ void Button::_notification(int p_what) { } break; } - text_ofs.y = (drawable_size_remained.height - text_buf->get_size().height) / 2.0f + style_margin_top; + text_ofs.y = (drawable_size_remained.height - text_buf->get_size().height) / 2.0f + theme_cache.style_margin_top; if (vertical_icon_alignment == VERTICAL_ALIGNMENT_TOP) { text_ofs.y += custom_element_size.height - drawable_size_remained.height; // Offset by the icon's height. } @@ -450,7 +521,7 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu } } - return _get_current_stylebox()->get_minimum_size() + minsize; + return _get_largest_stylebox_size() + minsize; } void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) { diff --git a/scene/gui/button.h b/scene/gui/button.h index 86fdbd35d5..eefb690913 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -69,6 +69,12 @@ private: Ref<StyleBox> disabled_mirrored; Ref<StyleBox> focus; + Size2 max_style_size; + float style_margin_left = 0; + float style_margin_right = 0; + float style_margin_top = 0; + float style_margin_bottom = 0; + Color font_color; Color font_focus_color; Color font_pressed_color; @@ -94,16 +100,18 @@ private: int icon_max_width = 0; } theme_cache; - Size2 _fit_icon_size(const Size2 &p_size) const; - void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = ""); void _texture_changed(); protected: + virtual void _update_theme_item_cache() override; + void _set_internal_margin(Side p_side, float p_value); virtual void _queue_update_size_cache(); + Size2 _fit_icon_size(const Size2 &p_size) const; Ref<StyleBox> _get_current_stylebox() const; + Size2 _get_largest_stylebox_size() const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp index 7a860cdea7..1af33d2814 100644 --- a/scene/gui/center_container.cpp +++ b/scene/gui/center_container.cpp @@ -36,19 +36,12 @@ Size2 CenterContainer::get_minimum_size() const { } Size2 ms; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + Control *c = as_sortable_control(get_child(i)); if (!c) { continue; } - if (c->is_set_as_top_level()) { - continue; - } - if (!c->is_visible()) { - continue; - } Size2 minsize = c->get_combined_minimum_size(); - ms.width = MAX(ms.width, minsize.width); - ms.height = MAX(ms.height, minsize.height); + ms = ms.max(minsize); } return ms; @@ -82,14 +75,10 @@ void CenterContainer::_notification(int p_what) { case NOTIFICATION_SORT_CHILDREN: { Size2 size = get_size(); for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + Control *c = as_sortable_control(get_child(i)); if (!c) { continue; } - if (c->is_set_as_top_level()) { - continue; - } - Size2 minsize = c->get_combined_minimum_size(); Point2 ofs = use_top_left ? (-minsize * 0.5).floor() : ((size - minsize) / 2.0).floor(); fit_child_in_rect(c, Rect2(ofs, minsize)); diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index 46c20e4a1c..99937aaf41 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -36,37 +36,37 @@ Size2 CheckBox::get_icon_size() const { Size2 tex_size = Size2(0, 0); if (!theme_cache.checked.is_null()) { - tex_size = Size2(theme_cache.checked->get_width(), theme_cache.checked->get_height()); + tex_size = theme_cache.checked->get_size(); } if (!theme_cache.unchecked.is_null()) { - tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked->get_width()), MAX(tex_size.height, theme_cache.unchecked->get_height())); + tex_size = tex_size.max(theme_cache.unchecked->get_size()); } if (!theme_cache.radio_checked.is_null()) { - tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked->get_width()), MAX(tex_size.height, theme_cache.radio_checked->get_height())); + tex_size = tex_size.max(theme_cache.radio_checked->get_size()); } if (!theme_cache.radio_unchecked.is_null()) { - tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked->get_height())); + tex_size = tex_size.max(theme_cache.radio_unchecked->get_size()); } if (!theme_cache.checked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, theme_cache.checked_disabled->get_width()), MAX(tex_size.height, theme_cache.checked_disabled->get_height())); + tex_size = tex_size.max(theme_cache.checked_disabled->get_size()); } if (!theme_cache.unchecked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.unchecked_disabled->get_height())); + tex_size = tex_size.max(theme_cache.unchecked_disabled->get_size()); } if (!theme_cache.radio_checked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_checked_disabled->get_height())); + tex_size = tex_size.max(theme_cache.radio_checked_disabled->get_size()); } if (!theme_cache.radio_unchecked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked_disabled->get_height())); + tex_size = tex_size.max(theme_cache.radio_unchecked_disabled->get_size()); } - return tex_size; + return _fit_icon_size(tex_size); } Size2 CheckBox::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); const Size2 tex_size = get_icon_size(); if (tex_size.width > 0 || tex_size.height > 0) { - const Size2 padding = _get_current_stylebox()->get_minimum_size(); + const Size2 padding = _get_largest_stylebox_size(); Size2 content_size = minsize - padding; if (content_size.width > 0 && tex_size.width > 0) { content_size.width += MAX(0, theme_cache.h_separation); @@ -127,9 +127,9 @@ void CheckBox::_notification(int p_what) { ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_offset; if (is_pressed()) { - on_tex->draw(ci, ofs); + on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size()))); } else { - off_tex->draw(ci, ofs); + off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size()))); } } break; } diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index ca2ef220fd..29b9504776 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -57,20 +57,20 @@ Size2 CheckButton::get_icon_size() const { Size2 tex_size = Size2(0, 0); if (!on_tex.is_null()) { - tex_size = Size2(on_tex->get_width(), on_tex->get_height()); + tex_size = on_tex->get_size(); } if (!off_tex.is_null()) { - tex_size = Size2(MAX(tex_size.width, off_tex->get_width()), MAX(tex_size.height, off_tex->get_height())); + tex_size = tex_size.max(off_tex->get_size()); } - return tex_size; + return _fit_icon_size(tex_size); } Size2 CheckButton::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); const Size2 tex_size = get_icon_size(); if (tex_size.width > 0 || tex_size.height > 0) { - const Size2 padding = _get_current_stylebox()->get_minimum_size(); + const Size2 padding = _get_largest_stylebox_size(); Size2 content_size = minsize - padding; if (content_size.width > 0 && tex_size.width > 0) { content_size.width += MAX(0, theme_cache.h_separation); @@ -134,9 +134,9 @@ void CheckButton::_notification(int p_what) { ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_offset; if (is_pressed()) { - on_tex->draw(ci, ofs); + on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size()))); } else { - off_tex->draw(ci, ofs); + off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size()))); } } break; } diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 632e6af2ce..c843bb8c44 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -624,16 +624,31 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return TextEdit::get_cursor_shape(p_pos); } +void CodeEdit::_unhide_carets() { + // Unfold caret and selection origin. + for (int i = 0; i < get_caret_count(); i++) { + if (_is_line_hidden(get_caret_line(i))) { + unfold_line(get_caret_line(i)); + } + if (has_selection(i) && _is_line_hidden(get_selection_origin_line(i))) { + unfold_line(get_selection_origin_line(i)); + } + } +} + /* Text manipulation */ // Overridable actions void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) { start_action(EditAction::ACTION_TYPING); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { if (p_caret != -1 && p_caret != i) { continue; } + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } bool had_selection = has_selection(i); String selection_text = (had_selection ? get_selected_text(i) : ""); @@ -691,6 +706,7 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_ca insert_text_at_caret(chr, i); } } + end_multicaret_edit(); end_action(); } @@ -705,66 +721,80 @@ void CodeEdit::_backspace_internal(int p_caret) { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { if (p_caret != -1 && p_caret != i) { continue; } + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } - int cc = get_caret_column(i); - int cl = get_caret_line(i); + int to_line = get_caret_line(i); + int to_column = get_caret_column(i); - if (cc == 0 && cl == 0) { + if (to_column == 0 && to_line == 0) { continue; } - if (cl > 0 && _is_line_hidden(cl - 1)) { - unfold_line(get_caret_line(i) - 1); + if (to_line > 0 && _is_line_hidden(to_line - 1)) { + unfold_line(to_line - 1); } - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); + int from_line = to_column > 0 ? to_line : to_line - 1; + int from_column = to_column > 0 ? (to_column - 1) : (get_line(to_line - 1).length()); - merge_gutters(prev_line, cl); + merge_gutters(from_line, to_line); - if (auto_brace_completion_enabled && cc > 0) { - int idx = _get_auto_brace_pair_open_at_pos(cl, cc); + if (auto_brace_completion_enabled && to_column > 0) { + int idx = _get_auto_brace_pair_open_at_pos(to_line, to_column); if (idx != -1) { - prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + from_column = to_column - auto_brace_completion_pairs[idx].open_key.length(); - if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { - cc += auto_brace_completion_pairs[idx].close_key.length(); + if (_get_auto_brace_pair_close_at_pos(to_line, to_column) == idx) { + to_column += auto_brace_completion_pairs[idx].close_key.length(); } - - remove_text(prev_line, prev_column, cl, cc); - - set_caret_line(prev_line, false, true, 0, i); - set_caret_column(prev_column, i == 0, i); - - adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); - continue; } } // For space indentation we need to do a basic unindent if there are no chars to the left, acting the same way as tabs. - if (indent_using_spaces && cc != 0) { - if (get_first_non_whitespace_column(cl) >= cc) { - prev_column = cc - _calculate_spaces_till_next_left_indent(cc); - prev_line = cl; + if (indent_using_spaces && to_column != 0) { + if (get_first_non_whitespace_column(to_line) >= to_column) { + from_column = to_column - _calculate_spaces_till_next_left_indent(to_column); + from_line = to_line; } } - remove_text(prev_line, prev_column, cl, cc); - - set_caret_line(prev_line, false, true, 0, i); - set_caret_column(prev_column, i == 0, i); + remove_text(from_line, from_column, to_line, to_column); - adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); + set_caret_line(from_line, false, true, -1, i); + set_caret_column(from_column, i == 0, i); } - merge_overlapping_carets(); + + end_multicaret_edit(); end_complex_operation(); } +void CodeEdit::_cut_internal(int p_caret) { + // Overridden to unfold lines. + _copy_internal(p_caret); + + if (!is_editable()) { + return; + } + + if (has_selection(p_caret)) { + delete_selection(p_caret); + return; + } + if (p_caret == -1) { + delete_lines(); + } else { + unfold_line(get_caret_line(p_caret)); + remove_line_at(get_caret_line(p_caret)); + } +} + /* Indent management */ void CodeEdit::set_indent_size(const int p_size) { ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0."); @@ -838,13 +868,17 @@ void CodeEdit::do_indent() { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column(i)); if (spaces_to_add > 0) { insert_text_at_caret(String(" ").repeat(spaces_to_add), i); } } + end_multicaret_edit(); end_complex_operation(); } @@ -854,51 +888,28 @@ void CodeEdit::indent_lines() { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &c : caret_edit_order) { - // This value informs us by how much we changed selection position by indenting right. - // Default is 1 for tab indentation. - int selection_offset = 1; - - int start_line = get_caret_line(c); - int end_line = start_line; - if (has_selection(c)) { - start_line = get_selection_from_line(c); - end_line = get_selection_to_line(c); + begin_multicaret_edit(); - // Ignore the last line if the selection is not past the first column. - if (get_selection_to_column(c) == 0) { - selection_offset = 0; - end_line--; - } - } - - for (int i = start_line; i <= end_line; i++) { + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + for (Point2i line_range : line_ranges) { + for (int i = line_range.x; i <= line_range.y; i++) { const String line_text = get_line(i); - if (line_text.size() == 0 && has_selection(c)) { + if (line_text.size() == 0) { + // Ignore empty lines. continue; } - if (!indent_using_spaces) { - set_line(i, '\t' + line_text); - continue; + if (indent_using_spaces) { + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); + insert_text(String(" ").repeat(spaces_to_add), i, 0, false); + } else { + insert_text("\t", i, 0, false); } - - // We don't really care where selection is - we just need to know indentation level at the beginning of the line. - // Since we will add this many spaces, we want to move the whole selection and caret by this much. - int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); - set_line(i, String(" ").repeat(spaces_to_add) + line_text); - selection_offset = spaces_to_add; - } - - // Fix selection and caret being off after shifting selection right. - if (has_selection(c)) { - select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c); } - set_caret_column(get_caret_column(c) + selection_offset, false, c); } + + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } void CodeEdit::unindent_lines() { @@ -907,76 +918,25 @@ void CodeEdit::unindent_lines() { } begin_complex_operation(); + begin_multicaret_edit(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &c : caret_edit_order) { - // Moving caret and selection after unindenting can get tricky because - // changing content of line can move caret and selection on its own (if new line ends before previous position of either) - // therefore we just remember initial values and at the end of the operation offset them by number of removed characters. - int removed_characters = 0; - int initial_selection_end_column = 0; - int initial_cursor_column = get_caret_column(c); - - int start_line = get_caret_line(c); - int end_line = start_line; - if (has_selection(c)) { - start_line = get_selection_from_line(c); - end_line = get_selection_to_line(c); - - // Ignore the last line if the selection is not past the first column. - initial_selection_end_column = get_selection_to_column(c); - if (initial_selection_end_column == 0) { - end_line--; - } - } - - bool first_line_edited = false; - bool last_line_edited = false; - - for (int i = start_line; i <= end_line; i++) { - String line_text = get_line(i); + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + for (Point2i line_range : line_ranges) { + for (int i = line_range.x; i <= line_range.y; i++) { + const String line_text = get_line(i); if (line_text.begins_with("\t")) { - line_text = line_text.substr(1, line_text.length()); - - set_line(i, line_text); - removed_characters = 1; - - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; - continue; - } - - if (line_text.begins_with(" ")) { - // When unindenting we aim to remove spaces before line that has selection no matter what is selected. - // Here we remove only enough spaces to align text to nearest full multiple of indentation_size. - // In case where selection begins at the start of indentation_size multiple we remove whole indentation level. + remove_text(i, 0, i, 1); + } else if (line_text.begins_with(" ")) { + // Remove only enough spaces to align text to nearest full multiple of indentation_size. int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i)); - line_text = line_text.substr(spaces_to_remove, line_text.length()); - - set_line(i, line_text); - removed_characters = spaces_to_remove; - - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; + remove_text(i, 0, i, spaces_to_remove); } } - - if (has_selection(c)) { - // Fix selection being off by one on the first line. - if (first_line_edited) { - select(get_selection_from_line(c), get_selection_from_column(c) - removed_characters, get_selection_to_line(c), initial_selection_end_column, c); - } - - // Fix selection being off by one on the last line. - if (last_line_edited) { - select(get_selection_from_line(c), get_selection_from_column(c), get_selection_to_line(c), initial_selection_end_column - removed_characters, c); - } - } - set_caret_column(initial_cursor_column - removed_characters, false, c); } + + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } void CodeEdit::convert_indent(int p_from_line, int p_to_line) { @@ -992,27 +952,6 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { ERR_FAIL_COND(p_to_line >= get_line_count()); ERR_FAIL_COND(p_to_line < p_from_line); - // Store caret states. - Vector<int> caret_columns; - Vector<Pair<int, int>> from_selections; - Vector<Pair<int, int>> to_selections; - caret_columns.resize(get_caret_count()); - from_selections.resize(get_caret_count()); - to_selections.resize(get_caret_count()); - for (int c = 0; c < get_caret_count(); c++) { - caret_columns.write[c] = get_caret_column(c); - - // Set "selection_from_line" to -1 to allow checking if there was a selection later. - if (!has_selection(c)) { - from_selections.write[c].first = -1; - continue; - } - from_selections.write[c].first = get_selection_from_line(c); - from_selections.write[c].second = get_selection_from_column(c); - to_selections.write[c].first = get_selection_to_line(c); - to_selections.write[c].second = get_selection_to_column(c); - } - // Check lines within range. const char32_t from_indent_char = indent_using_spaces ? '\t' : ' '; int size_diff = indent_using_spaces ? indent_size - 1 : -(indent_size - 1); @@ -1044,23 +983,10 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { line_changed = true; if (!changed_indentation) { begin_complex_operation(); + begin_multicaret_edit(); changed_indentation = true; } - // Calculate new caret state. - for (int c = 0; c < get_caret_count(); c++) { - if (get_caret_line(c) != i || caret_columns[c] <= j) { - continue; - } - caret_columns.write[c] += size_diff; - - if (from_selections.write[c].first == -1) { - continue; - } - from_selections.write[c].second = from_selections[c].first == i ? from_selections[c].second + size_diff : from_selections[c].second; - to_selections.write[c].second = to_selections[c].first == i ? to_selections[c].second + size_diff : to_selections[c].second; - } - // Calculate new line. line = line.left(j + ((size_diff < 0) ? size_diff : 0)) + indent_text + line.substr(j + 1); @@ -1069,6 +995,7 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { } if (line_changed) { + // Use set line to preserve carets visual position. set_line(i, line); } } @@ -1077,16 +1004,9 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { return; } - // Restore caret states. - for (int c = 0; c < get_caret_count(); c++) { - set_caret_column(caret_columns[c], c == 0, c); - if (from_selections.write[c].first != -1) { - select(from_selections.write[c].first, from_selections.write[c].second, to_selections.write[c].first, to_selections.write[c].second, c); - } - } merge_overlapping_carets(); + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const { @@ -1107,15 +1027,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } // When not splitting the line, we need to factor in indentation from the end of the current line. const int cc = p_split_current_line ? get_caret_column(i) : get_line(get_caret_line(i)).length(); const int cl = get_caret_line(i); const String line = get_line(cl); - String ins = "\n"; + String ins = ""; + if (!p_above) { + ins = "\n"; + } // Append current indentation. int space_count = 0; @@ -1138,6 +1065,9 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } break; } + if (p_above) { + ins += "\n"; + } if (is_line_folded(cl)) { unfold_line(cl); @@ -1183,33 +1113,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } } - bool first_line = false; - if (!p_split_current_line) { + if (p_split_current_line) { + insert_text_at_caret(ins, i); + } else { + insert_text(ins, cl, p_above ? 0 : get_line(cl).length(), p_above, p_above); deselect(i); - - if (p_above) { - if (cl > 0) { - set_caret_line(cl - 1, false, true, 0, i); - set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); - } else { - set_caret_column(0, i == 0, i); - first_line = true; - } - } else { - set_caret_column(line.length(), i == 0, i); - } + set_caret_line(p_above ? cl : cl + 1, false, true, -1, i); + set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); } - - insert_text_at_caret(ins, i); - - if (first_line) { - set_caret_line(0, i == 0, true, 0, i); - } else if (brace_indent) { + if (brace_indent) { + // Move to inner indented line. set_caret_line(get_caret_line(i) - 1, false, true, 0, i); set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); } } + end_multicaret_edit(); end_complex_operation(); } @@ -1700,27 +1619,8 @@ void CodeEdit::fold_line(int p_line) { _set_line_as_hidden(i, true); } - for (int i = 0; i < get_caret_count(); i++) { - // Fix selection. - if (has_selection(i)) { - if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) { - deselect(i); - } else if (_is_line_hidden(get_selection_from_line(i))) { - select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i); - } else if (_is_line_hidden(get_selection_to_line(i))) { - select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i); - } - } - - // Reset caret. - if (_is_line_hidden(get_caret_line(i))) { - set_caret_line(p_line, false, false, 0, i); - set_caret_column(get_line(p_line).length(), false, i); - } - } - - merge_overlapping_carets(); - queue_redraw(); + // Collapse any carets in the hidden area. + collapse_carets(p_line, get_line(p_line).length(), end_line, get_line(end_line).length(), true); } void CodeEdit::unfold_line(int p_line) { @@ -1769,6 +1669,23 @@ void CodeEdit::toggle_foldable_line(int p_line) { fold_line(p_line); } +void CodeEdit::toggle_foldable_lines_at_carets() { + begin_multicaret_edit(); + int previous_line = -1; + Vector<int> sorted = get_sorted_carets(); + for (int caret_idx : sorted) { + if (multicaret_edit_ignore_caret(caret_idx)) { + continue; + } + int line_idx = get_caret_line(caret_idx); + if (line_idx != previous_line) { + toggle_foldable_line(line_idx); + previous_line = line_idx; + } + } + end_multicaret_edit(); +} + bool CodeEdit::is_line_folded(int p_line) const { ERR_FAIL_INDEX_V(p_line, get_line_count(), false); return p_line + 1 < get_line_count() && !_is_line_hidden(p_line) && _is_line_hidden(p_line + 1); @@ -1795,49 +1712,29 @@ void CodeEdit::create_code_region() { WARN_PRINT_ONCE("Cannot create code region without any one line comment delimiters"); return; } + String region_name = atr(ETR("New Code Region")); + begin_complex_operation(); - // Merge selections if selection starts on the same line the previous one ends. - Vector<int> caret_edit_order = get_caret_index_edit_order(); - Vector<int> carets_to_remove; - for (int i = 1; i < caret_edit_order.size(); i++) { - int current_caret = caret_edit_order[i - 1]; - int next_caret = caret_edit_order[i]; - if (get_selection_from_line(current_caret) == get_selection_to_line(next_caret)) { - select(get_selection_from_line(next_caret), get_selection_from_column(next_caret), get_selection_to_line(current_caret), get_selection_to_column(current_caret), next_caret); - carets_to_remove.append(current_caret); - } - } - // Sort and remove backwards to preserve indices. - carets_to_remove.sort(); - for (int i = carets_to_remove.size() - 1; i >= 0; i--) { - remove_caret(carets_to_remove[i]); - } - - // Adding start and end region tags. - int first_region_start = -1; - for (int caret_idx : get_caret_index_edit_order()) { - if (!has_selection(caret_idx)) { - continue; - } - int from_line = get_selection_from_line(caret_idx); - if (first_region_start == -1 || from_line < first_region_start) { - first_region_start = from_line; - } - int to_line = get_selection_to_line(caret_idx); - set_line(to_line, get_line(to_line) + "\n" + code_region_end_string); - insert_line_at(from_line, code_region_start_string + " " + atr(ETR("New Code Region"))); - fold_line(from_line); + begin_multicaret_edit(); + Vector<Point2i> line_ranges = get_line_ranges_from_carets(true, false); + + // Add start and end region tags. + int line_offset = 0; + for (Point2i line_range : line_ranges) { + insert_text("\n" + code_region_end_string, line_range.y + line_offset, get_line(line_range.y + line_offset).length()); + insert_line_at(line_range.x + line_offset, code_region_start_string + " " + region_name); + fold_line(line_range.x + line_offset); + line_offset += 2; } + int first_region_start = line_ranges[0].x; // Select name of the first region to allow quick edit. remove_secondary_carets(); - set_caret_line(first_region_start); - int tag_length = code_region_start_string.length() + atr(ETR("New Code Region")).length() + 1; - set_caret_column(tag_length); + int tag_length = code_region_start_string.length() + region_name.length() + 1; select(first_region_start, code_region_start_string.length() + 1, first_region_start, tag_length); + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } String CodeEdit::get_code_region_start_tag() const { @@ -2236,8 +2133,12 @@ void CodeEdit::confirm_code_completion(bool p_replace) { char32_t caret_last_completion_char = 0; begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } int caret_line = get_caret_line(i); const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; @@ -2270,8 +2171,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) { // Replace. remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_remove_line, caret_col); - adjust_carets_after_edit(i, caret_line, caret_col - code_completion_base.length(), caret_remove_line, caret_col); - set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); insert_text_at_caret(insert_text, i); } else { // Get first non-matching char. @@ -2287,8 +2186,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) { // Remove base completion text. remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); - adjust_carets_after_edit(i, caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); - set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); // Merge with text. insert_text_at_caret(insert_text.substr(0, code_completion_base.length()), i); @@ -2313,12 +2210,10 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (has_string_delimiter(String::chr(last_completion_char))) { if (post_brace_pair != -1 && last_char_matches) { remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); - adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); } } else { if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && last_char_matches) { remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); - adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); } else if (auto_brace_completion_enabled && pre_brace_pair != -1) { insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i); set_caret_column(get_caret_column(i) - auto_brace_completion_pairs[pre_brace_pair].close_key.length(), i == 0, i); @@ -2329,13 +2224,16 @@ void CodeEdit::confirm_code_completion(bool p_replace) { pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i) + 1); if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1)) { remove_text(caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); - adjust_carets_after_edit(i, caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); - if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1) != pre_brace_pair) { - set_caret_column(get_caret_column(i) - 1, i == 0, i); + if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) + 1) != pre_brace_pair) { + set_caret_column(get_caret_column(i) + 1, i == 0, i); + } else { + set_caret_column(get_caret_column(i) + 2, i == 0, i); } } } } + + end_multicaret_edit(); end_complex_operation(); cancel_code_completion(); @@ -2418,65 +2316,154 @@ void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) { } /* Text manipulation */ -void CodeEdit::duplicate_lines() { +void CodeEdit::move_lines_up() { begin_complex_operation(); + begin_multicaret_edit(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &caret_index : caret_edit_order) { - // The text that will be inserted. All lines in one string. - String insert_text; - - // The new line position of the caret after the operation. - int new_caret_line = get_caret_line(caret_index); - // The new column position of the caret after the operation. - int new_caret_column = get_caret_column(caret_index); - // The caret positions of the selection. Stays -1 if there is no selection. - int select_from_line = -1; - int select_to_line = -1; - int select_from_column = -1; - int select_to_column = -1; - // Number of lines of the selection. - int select_num_lines = -1; - - if (has_selection(caret_index)) { - select_from_line = get_selection_from_line(caret_index); - select_to_line = get_selection_to_line(caret_index); - select_from_column = get_selection_from_column(caret_index); - select_to_column = get_selection_to_column(caret_index); - select_num_lines = select_to_line - select_from_line + 1; - - for (int i = select_from_line; i <= select_to_line; i++) { - insert_text += "\n" + get_line(i); - unfold_line(i); - } - new_caret_line = select_to_line + select_num_lines; - } else { - insert_text = "\n" + get_line(new_caret_line); - new_caret_line++; + // Move lines up by swapping each line with the one above it. + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + for (Point2i line_range : line_ranges) { + if (line_range.x == 0) { + continue; + } + unfold_line(line_range.x - 1); + for (int line = line_range.x; line <= line_range.y; line++) { + unfold_line(line); + swap_lines(line - 1, line); + } + } + + // Fix selection if it ended at column 0, since it wasn't moved. + for (int i = 0; i < get_caret_count(); i++) { + if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != 0) { + if (is_caret_after_selection_origin(i)) { + set_caret_line(get_caret_line(i) - 1, false, true, -1, i); + } else { + set_selection_origin_line(get_selection_origin_line(i) - 1, true, -1, i); + } + } + } + + end_multicaret_edit(); + end_complex_operation(); +} + +void CodeEdit::move_lines_down() { + begin_complex_operation(); + begin_multicaret_edit(); + + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + + // Fix selection if it ended at column 0, since it won't be moved. + for (int i = 0; i < get_caret_count(); i++) { + if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != get_line_count() - 1) { + if (is_caret_after_selection_origin(i)) { + set_caret_line(get_caret_line(i) + 1, false, true, -1, i); + } else { + set_selection_origin_line(get_selection_origin_line(i) + 1, true, -1, i); + } + } + } - unfold_line(get_caret_line(caret_index)); + // Move lines down by swapping each line with the one below it. + for (Point2i line_range : line_ranges) { + if (line_range.y == get_line_count() - 1) { + continue; + } + unfold_line(line_range.y + 1); + for (int line = line_range.y; line >= line_range.x; line--) { + unfold_line(line); + swap_lines(line + 1, line); } + } - // The text will be inserted at the end of the current line. - set_caret_column(get_line(get_caret_line(caret_index)).length(), false, caret_index); + end_multicaret_edit(); + end_complex_operation(); +} - deselect(caret_index); +void CodeEdit::delete_lines() { + begin_complex_operation(); + begin_multicaret_edit(); - insert_text_at_caret(insert_text, caret_index); - set_caret_line(new_caret_line, false, true, 0, caret_index); - set_caret_column(new_caret_column, true, caret_index); + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + int line_offset = 0; + for (Point2i line_range : line_ranges) { + // Remove last line of range separately to preserve carets. + unfold_line(line_range.y + line_offset); + remove_line_at(line_range.y + line_offset); + if (line_range.x != line_range.y) { + remove_text(line_range.x + line_offset, 0, line_range.y + line_offset, 0); + } + line_offset += line_range.x - line_range.y - 1; + } - if (select_from_line != -1) { - // Advance the selection by the number of duplicated lines. - select_from_line += select_num_lines; - select_to_line += select_num_lines; + // Deselect all. + deselect(); - select(select_from_line, select_from_column, select_to_line, select_to_column, caret_index); + end_multicaret_edit(); + end_complex_operation(); +} + +void CodeEdit::duplicate_selection() { + begin_complex_operation(); + begin_multicaret_edit(); + + // Duplicate lines from carets without selections first. + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; } + for (int l = get_selection_from_line(i); l <= get_selection_to_line(i); l++) { + unfold_line(l); + } + if (has_selection(i)) { + continue; + } + + String text_to_insert = get_line(get_caret_line(i)) + "\n"; + // Insert new text before the line, so the caret is on the second one. + insert_text(text_to_insert, get_caret_line(i), 0); } + // Duplicate selections. + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } + if (!has_selection(i)) { + continue; + } + + // Insert new text before the selection, so the caret is on the second one. + insert_text(get_selected_text(i), get_selection_from_line(i), get_selection_from_column(i)); + } + + end_multicaret_edit(); + end_complex_operation(); +} + +void CodeEdit::duplicate_lines() { + begin_complex_operation(); + begin_multicaret_edit(); + + Vector<Point2i> line_ranges = get_line_ranges_from_carets(false, false); + int line_offset = 0; + for (Point2i line_range : line_ranges) { + // The text that will be inserted. All lines in one string. + String text_to_insert; + + for (int i = line_range.x + line_offset; i <= line_range.y + line_offset; i++) { + text_to_insert += get_line(i) + "\n"; + unfold_line(i); + } + + // Insert new text before the line. + insert_text(text_to_insert, line_range.x + line_offset, 0); + line_offset += line_range.y - line_range.x + 1; + } + + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } /* Visual */ @@ -2578,6 +2565,7 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("fold_all_lines"), &CodeEdit::fold_all_lines); ClassDB::bind_method(D_METHOD("unfold_all_lines"), &CodeEdit::unfold_all_lines); ClassDB::bind_method(D_METHOD("toggle_foldable_line", "line"), &CodeEdit::toggle_foldable_line); + ClassDB::bind_method(D_METHOD("toggle_foldable_lines_at_carets"), &CodeEdit::toggle_foldable_lines_at_carets); ClassDB::bind_method(D_METHOD("is_line_folded", "line"), &CodeEdit::is_line_folded); ClassDB::bind_method(D_METHOD("get_folded_lines"), &CodeEdit::get_folded_lines); @@ -2679,6 +2667,10 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid); /* Text manipulation */ + ClassDB::bind_method(D_METHOD("move_lines_up"), &CodeEdit::move_lines_up); + ClassDB::bind_method(D_METHOD("move_lines_down"), &CodeEdit::move_lines_down); + ClassDB::bind_method(D_METHOD("delete_lines"), &CodeEdit::delete_lines); + ClassDB::bind_method(D_METHOD("duplicate_selection"), &CodeEdit::duplicate_selection); ClassDB::bind_method(D_METHOD("duplicate_lines"), &CodeEdit::duplicate_lines); /* Inspector */ @@ -2846,10 +2838,12 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { if (p_gutter == line_number_gutter) { remove_secondary_carets(); - set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0); - select(p_line, 0, p_line + 1, 0); - set_caret_line(p_line + 1); - set_caret_column(0); + set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE); + if (p_line == get_line_count() - 1) { + select(p_line, 0, p_line, INT_MAX); + } else { + select(p_line, 0, p_line + 1, 0); + } return; } @@ -3056,7 +3050,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) { } int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const { - if (delimiters.size() == 0) { + if (delimiters.size() == 0 || p_line >= delimiter_cache.size()) { return -1; } ERR_FAIL_INDEX_V(p_line, get_line_count(), 0); @@ -3398,9 +3392,16 @@ void CodeEdit::_filter_code_completion_candidates_impl() { int offset = option.default_value.get_type() == Variant::COLOR ? line_height : 0; if (in_string != -1) { + // The completion string may have a literal behind it, which should be removed before re-quoting. + String literal; + if (option.insert_text.substr(1).is_quoted()) { + literal = option.display.left(1); + option.display = option.display.substr(1); + option.insert_text = option.insert_text.substr(1); + } String quote = single_quote ? "'" : "\""; - option.display = option.display.unquote().quote(quote); - option.insert_text = option.insert_text.unquote().quote(quote); + option.display = literal + (option.display.unquote().quote(quote)); + option.insert_text = literal + (option.insert_text.unquote().quote(quote)); } if (option.display.length() == 0) { diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 1770d4f4d8..56f8cce548 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -309,11 +309,14 @@ protected: static void _bind_compatibility_methods(); #endif + virtual void _unhide_carets() override; + /* Text manipulation */ // Overridable actions virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override; virtual void _backspace_internal(int p_caret) override; + virtual void _cut_internal(int p_caret) override; GDVIRTUAL1(_confirm_code_completion, bool) GDVIRTUAL1(_request_code_completion, bool) @@ -409,6 +412,7 @@ public: void fold_all_lines(); void unfold_all_lines(); void toggle_foldable_line(int p_line); + void toggle_foldable_lines_at_carets(); bool is_line_folded(int p_line) const; TypedArray<int> get_folded_lines() const; @@ -489,6 +493,10 @@ public: void set_symbol_lookup_word_as_valid(bool p_valid); /* Text manipulation */ + void move_lines_up(); + void move_lines_down(); + void delete_lines(); + void duplicate_selection(); void duplicate_lines(); CodeEdit(); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index ee2122f269..245a086dda 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -95,8 +95,8 @@ void ColorPicker::_notification(int p_what) { for (int i = 0; i < MODE_BUTTON_COUNT; i++) { mode_btns[i]->begin_bulk_theme_override(); - mode_btns[i]->add_theme_style_override(SNAME("pressed"), theme_cache.mode_button_pressed); - mode_btns[i]->add_theme_style_override(SNAME("normal"), theme_cache.mode_button_normal); + mode_btns[i]->add_theme_style_override(SceneStringName(pressed), theme_cache.mode_button_pressed); + mode_btns[i]->add_theme_style_override(CoreStringName(normal), theme_cache.mode_button_normal); mode_btns[i]->add_theme_style_override(SNAME("hover"), theme_cache.mode_button_hover); mode_btns[i]->end_bulk_theme_override(); } @@ -403,8 +403,9 @@ void ColorPicker::add_mode(ColorMode *p_mode) { } void ColorPicker::create_slider(GridContainer *gc, int idx) { - Label *lbl = memnew(Label()); + Label *lbl = memnew(Label); lbl->set_v_size_flags(SIZE_SHRINK_CENTER); + lbl->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); gc->add_child(lbl); HSlider *slider = memnew(HSlider); @@ -419,18 +420,18 @@ void ColorPicker::create_slider(GridContainer *gc, int idx) { LineEdit *vle = val->get_line_edit(); vle->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); - vle->connect("gui_input", callable_mp(this, &ColorPicker::_line_edit_input)); + vle->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_line_edit_input)); vle->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - val->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); + val->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_slider_or_spin_input)); slider->set_h_size_flags(SIZE_EXPAND_FILL); slider->connect("drag_started", callable_mp(this, &ColorPicker::_slider_drag_started)); slider->connect("value_changed", callable_mp(this, &ColorPicker::_slider_value_changed).unbind(1)); slider->connect("drag_ended", callable_mp(this, &ColorPicker::_slider_drag_ended).unbind(1)); - slider->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); - slider->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); + slider->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_slider_or_spin_input)); if (idx < SLIDER_COUNT) { sliders[idx] = slider; @@ -457,8 +458,8 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) { } } - for (int i = 0; i < preset_cache.size(); i++) { - presets.push_back(preset_cache[i]); + for (const Color &preset : preset_cache) { + presets.push_back(preset); } if (recent_preset_cache.is_empty()) { @@ -468,8 +469,8 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) { } } - for (int i = 0; i < recent_preset_cache.size(); i++) { - recent_presets.push_back(recent_preset_cache[i]); + for (const Color &preset : recent_preset_cache) { + recent_presets.push_back(preset); } _update_presets(); @@ -659,8 +660,8 @@ void ColorPicker::_update_presets() { for (int i = 1; i < preset_container->get_child_count(); i++) { preset_container->get_child(i)->queue_free(); } - for (int i = 0; i < preset_cache.size(); i++) { - _add_preset_button(preset_size, preset_cache[i]); + for (const Color &preset : preset_cache) { + _add_preset_button(preset_size, preset); } _notification(NOTIFICATION_VISIBILITY_CHANGED); } @@ -676,13 +677,13 @@ void ColorPicker::_update_recent_presets() { } recent_presets.clear(); - for (int i = 0; i < recent_preset_cache.size(); i++) { - recent_presets.push_back(recent_preset_cache[i]); + for (const Color &preset : recent_preset_cache) { + recent_presets.push_back(preset); } int preset_size = _get_preset_size(); - for (int i = 0; i < recent_presets.size(); i++) { - _add_recent_preset_button(preset_size, recent_presets[i]); + for (const Color &preset : recent_presets) { + _add_recent_preset_button(preset_size, preset); } _notification(NOTIFICATION_VISIBILITY_CHANGED); @@ -714,6 +715,10 @@ Color ColorPicker::get_pick_color() const { return color; } +Color ColorPicker::get_old_color() const { + return old_color; +} + void ColorPicker::set_picker_shape(PickerShapeType p_shape) { ERR_FAIL_INDEX(p_shape, SHAPE_MAX); if (p_shape == current_shape) { @@ -756,7 +761,7 @@ void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { btn_preset_new->set_button_group(preset_group); preset_container->add_child(btn_preset_new); btn_preset_new->set_pressed(true); - btn_preset_new->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); + btn_preset_new->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); } void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { @@ -932,8 +937,9 @@ void ColorPicker::erase_recent_preset(const Color &p_color) { PackedColorArray ColorPicker::get_presets() const { PackedColorArray arr; arr.resize(presets.size()); - for (int i = 0; i < presets.size(); i++) { - arr.set(i, presets[i]); + int i = 0; + for (List<Color>::ConstIterator itr = presets.begin(); itr != presets.end(); ++itr, ++i) { + arr.set(i, *itr); } return arr; } @@ -941,8 +947,9 @@ PackedColorArray ColorPicker::get_presets() const { PackedColorArray ColorPicker::get_recent_presets() const { PackedColorArray arr; arr.resize(recent_presets.size()); - for (int i = 0; i < recent_presets.size(); i++) { - arr.set(i, recent_presets[i]); + int i = 0; + for (List<Color>::ConstIterator itr = recent_presets.begin(); itr != recent_presets.end(); ++itr, ++i) { + arr.set(i, *itr); } return arr; } @@ -1503,7 +1510,7 @@ void ColorPicker::_pick_button_pressed() { if (!picker_window) { picker_window = memnew(Popup); picker_window->set_size(Vector2i(1, 1)); - picker_window->connect("visibility_changed", callable_mp(this, &ColorPicker::_pick_finished)); + picker_window->connect(SceneStringName(visibility_changed), callable_mp(this, &ColorPicker::_pick_finished)); add_child(picker_window, false, INTERNAL_MODE_FRONT); } picker_window->popup(); @@ -1514,7 +1521,7 @@ void ColorPicker::_pick_finished() { return; } - if (Input::get_singleton()->is_key_pressed(Key::ESCAPE)) { + if (Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"))) { set_pick_color(old_color); } else { emit_signal(SNAME("color_changed"), color); @@ -1539,7 +1546,7 @@ void ColorPicker::_pick_button_pressed_legacy() { picker_texture_rect->set_anchors_preset(Control::PRESET_FULL_RECT); picker_window->add_child(picker_texture_rect); picker_texture_rect->set_default_cursor_shape(CURSOR_POINTING_HAND); - picker_texture_rect->connect("gui_input", callable_mp(this, &ColorPicker::_picker_texture_input)); + picker_texture_rect->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_picker_texture_input)); picker_preview = memnew(Panel); picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP); @@ -1627,7 +1634,12 @@ void ColorPicker::_html_focus_exit() { if (c_text->is_menu_visible()) { return; } - _html_submitted(c_text->get_text()); + + if (is_visible_in_tree()) { + _html_submitted(c_text->get_text()); + } else { + _update_text_value(); + } } void ColorPicker::set_can_add_swatches(bool p_enabled) { @@ -1811,11 +1823,11 @@ ColorPicker::ColorPicker() { uv_edit = memnew(Control); hb_edit->add_child(uv_edit); - uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input).bind(uv_edit)); + uv_edit->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_uv_input).bind(uv_edit)); uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); uv_edit->set_h_size_flags(SIZE_EXPAND_FILL); uv_edit->set_v_size_flags(SIZE_EXPAND_FILL); - uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(0, uv_edit)); + uv_edit->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(0, uv_edit)); sample_hbc = memnew(HBoxContainer); real_vbox->add_child(sample_hbc); @@ -1824,18 +1836,18 @@ ColorPicker::ColorPicker() { sample_hbc->add_child(btn_pick); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE)) { btn_pick->set_tooltip_text(ETR("Pick a color from the screen.")); - btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed)); + btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed)); } else { // On unsupported platforms, use a legacy method for color picking. btn_pick->set_tooltip_text(ETR("Pick a color from the application window.")); - btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); + btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); } sample = memnew(TextureRect); sample_hbc->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); - sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input)); - sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw)); + sample->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_sample_input)); + sample->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_sample_draw)); btn_shape = memnew(MenuButton); btn_shape->set_flat(false); @@ -1871,7 +1883,7 @@ ColorPicker::ColorPicker() { mode_btns[i]->set_toggle_mode(true); mode_btns[i]->set_text(modes[i]->get_name()); mode_btns[i]->set_button_group(mode_group); - mode_btns[i]->connect("pressed", callable_mp(this, &ColorPicker::set_color_mode).bind((ColorModeType)i)); + mode_btns[i]->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::set_color_mode).bind((ColorModeType)i)); } mode_btns[0]->set_pressed(true); @@ -1924,7 +1936,7 @@ ColorPicker::ColorPicker() { text_type->set_text("#"); text_type->set_tooltip_text(RTR("Switch between hexadecimal and code values.")); if (Engine::get_singleton()->is_editor_hint()) { - text_type->connect("pressed", callable_mp(this, &ColorPicker::_text_type_toggled)); + text_type->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_text_type_toggled)); } else { text_type->set_flat(true); text_type->set_mouse_filter(MOUSE_FILTER_IGNORE); @@ -1938,7 +1950,7 @@ ColorPicker::ColorPicker() { c_text->set_placeholder(ETR("Hex code or named color")); c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted)); c_text->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); - c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); + c_text->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPicker::_html_focus_exit)); wheel_edit = memnew(AspectRatioContainer); wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL); @@ -1957,19 +1969,19 @@ ColorPicker::ColorPicker() { wheel = memnew(Control); wheel_margin->add_child(wheel); wheel->set_mouse_filter(MOUSE_FILTER_PASS); - wheel->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(2, wheel)); + wheel->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(2, wheel)); wheel_uv = memnew(Control); wheel_margin->add_child(wheel_uv); - wheel_uv->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input).bind(wheel_uv)); - wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(0, wheel_uv)); + wheel_uv->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_uv_input).bind(wheel_uv)); + wheel_uv->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(0, wheel_uv)); w_edit = memnew(Control); hb_edit->add_child(w_edit); w_edit->set_h_size_flags(SIZE_FILL); w_edit->set_v_size_flags(SIZE_EXPAND_FILL); - w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input)); - w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(1, w_edit)); + w_edit->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_w_input)); + w_edit->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(1, w_edit)); _update_controls(); updating = false; @@ -2014,7 +2026,7 @@ ColorPicker::ColorPicker() { btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_add_preset->set_tooltip_text(ETR("Add current color as a preset.")); - btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); + btn_add_preset->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_add_preset_pressed)); preset_container->add_child(btn_add_preset); } @@ -2026,6 +2038,15 @@ ColorPicker::~ColorPicker() { ///////////////// +void ColorPickerPopupPanel::_input_from_window(const Ref<InputEvent> &p_event) { + if (p_event->is_action_pressed(SNAME("ui_accept"), false, true)) { + _close_pressed(); + } + PopupPanel::_input_from_window(p_event); +} + +///////////////// + void ColorPickerButton::_about_to_popup() { set_pressed(true); if (picker) { @@ -2040,6 +2061,10 @@ void ColorPickerButton::_color_changed(const Color &p_color) { } void ColorPickerButton::_modal_closed() { + if (Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"))) { + set_pick_color(picker->get_old_color()); + emit_signal(SNAME("color_changed"), color); + } emit_signal(SNAME("popup_closed")); set_pressed(false); } @@ -2137,7 +2162,7 @@ PopupPanel *ColorPickerButton::get_popup() { void ColorPickerButton::_update_picker() { if (!picker) { - popup = memnew(PopupPanel); + popup = memnew(ColorPickerPopupPanel); popup->set_wrap_controls(true); picker = memnew(ColorPicker); picker->set_anchors_and_offsets_preset(PRESET_FULL_RECT); @@ -2146,7 +2171,7 @@ void ColorPickerButton::_update_picker() { picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup)); popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed)); - picker->connect("minimum_size_changed", callable_mp((Window *)popup, &Window::reset_size)); + picker->connect(SceneStringName(minimum_size_changed), callable_mp((Window *)popup, &Window::reset_size)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); picker->set_display_old_color(true); diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 282926d1ff..ad028584b1 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -317,12 +317,11 @@ public: void set_edit_alpha(bool p_show); bool is_editing_alpha() const; - int get_preset_size(); - void _set_pick_color(const Color &p_color, bool p_update_sliders); void set_pick_color(const Color &p_color); Color get_pick_color() const; void set_old_color(const Color &p_color); + Color get_old_color() const; void set_display_old_color(bool p_enabled); bool is_displaying_old_color() const; @@ -375,6 +374,10 @@ public: ~ColorPicker(); }; +class ColorPickerPopupPanel : public PopupPanel { + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; +}; + class ColorPickerButton : public Button { GDCLASS(ColorPickerButton, Button); diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index c6e66c95c6..f1faf3e899 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -30,8 +30,6 @@ #include "container.h" -#include "scene/scene_string_names.h" - void Container::_child_minsize_changed() { update_minimum_size(); queue_sort(); @@ -45,9 +43,9 @@ void Container::add_child_notify(Node *p_child) { return; } - control->connect(SNAME("size_flags_changed"), callable_mp(this, &Container::queue_sort)); - control->connect(SNAME("minimum_size_changed"), callable_mp(this, &Container::_child_minsize_changed)); - control->connect(SNAME("visibility_changed"), callable_mp(this, &Container::_child_minsize_changed)); + control->connect(SceneStringName(size_flags_changed), callable_mp(this, &Container::queue_sort)); + control->connect(SceneStringName(minimum_size_changed), callable_mp(this, &Container::_child_minsize_changed)); + control->connect(SceneStringName(visibility_changed), callable_mp(this, &Container::_child_minsize_changed)); update_minimum_size(); queue_sort(); @@ -72,9 +70,9 @@ void Container::remove_child_notify(Node *p_child) { return; } - control->disconnect("size_flags_changed", callable_mp(this, &Container::queue_sort)); - control->disconnect("minimum_size_changed", callable_mp(this, &Container::_child_minsize_changed)); - control->disconnect("visibility_changed", callable_mp(this, &Container::_child_minsize_changed)); + control->disconnect(SceneStringName(size_flags_changed), callable_mp(this, &Container::queue_sort)); + control->disconnect(SceneStringName(minimum_size_changed), callable_mp(this, &Container::_child_minsize_changed)); + control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Container::_child_minsize_changed)); update_minimum_size(); queue_sort(); @@ -86,10 +84,10 @@ void Container::_sort_children() { } notification(NOTIFICATION_PRE_SORT_CHILDREN); - emit_signal(SceneStringNames::get_singleton()->pre_sort_children); + emit_signal(SceneStringName(pre_sort_children)); notification(NOTIFICATION_SORT_CHILDREN); - emit_signal(SceneStringNames::get_singleton()->sort_children); + emit_signal(SceneStringName(sort_children)); pending_sort = false; } @@ -141,6 +139,14 @@ void Container::queue_sort() { pending_sort = true; } +Control *Container::as_sortable_control(Node *p_node) const { + Control *c = Object::cast_to<Control>(p_node); + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { + return nullptr; + } + return c; +} + Vector<int> Container::get_allowed_size_flags_horizontal() const { Vector<int> flags; if (GDVIRTUAL_CALL(_get_allowed_size_flags_horizontal, flags)) { diff --git a/scene/gui/container.h b/scene/gui/container.h index 94c3c540d7..405220cee6 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -42,6 +42,8 @@ class Container : public Control { protected: void queue_sort(); + Control *as_sortable_control(Node *p_node) const; + virtual void add_child_notify(Node *p_child) override; virtual void move_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 974ecd1edc..0d5c69b207 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -42,7 +42,6 @@ #include "scene/gui/panel.h" #include "scene/main/canvas_layer.h" #include "scene/main/window.h" -#include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" #include "scene/theme/theme_owner.h" #include "servers/rendering_server.h" @@ -142,8 +141,8 @@ Size2 Control::_edit_get_scale() const { void Control::_edit_set_rect(const Rect2 &p_edit_rect) { 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)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); - set_size(p_edit_rect.size.snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); + set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); + set_size(p_edit_rect.size.snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); } Rect2 Control::_edit_get_rect() const { @@ -208,28 +207,42 @@ void Control::set_root_layout_direction(int p_root_dir) { #ifdef TOOLS_ENABLED void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { ERR_READ_THREAD_GUARD; - CanvasItem::get_argument_options(p_function, p_idx, r_options); - if (p_idx == 0) { - List<StringName> sn; const String pf = p_function; - if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") { - ThemeDB::get_singleton()->get_default_theme()->get_color_list(get_class(), &sn); - } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") { - ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(get_class(), &sn); - } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") { - ThemeDB::get_singleton()->get_default_theme()->get_font_list(get_class(), &sn); - } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") { - ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(get_class(), &sn); - } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") { - ThemeDB::get_singleton()->get_default_theme()->get_constant_list(get_class(), &sn); - } + Theme::DataType type = Theme::DATA_TYPE_MAX; + + if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color" || pf == "remove_theme_color_override") { + type = Theme::DATA_TYPE_COLOR; + } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant" || pf == "remove_theme_constant_override") { + type = Theme::DATA_TYPE_CONSTANT; + } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font" || pf == "remove_theme_font_override") { + type = Theme::DATA_TYPE_FONT; + } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size" || pf == "remove_theme_font_size_override") { + type = Theme::DATA_TYPE_FONT_SIZE; + } else if (pf == "add_theme_icon_override" || pf == "has_theme_icon" || pf == "has_theme_icon_override" || pf == "get_theme_icon" || pf == "remove_theme_icon_override") { + type = Theme::DATA_TYPE_ICON; + } else if (pf == "add_theme_stylebox_override" || pf == "has_theme_stylebox" || pf == "has_theme_stylebox_override" || pf == "get_theme_stylebox" || pf == "remove_theme_stylebox_override") { + type = Theme::DATA_TYPE_STYLEBOX; + } + + if (type != Theme::DATA_TYPE_MAX) { + List<ThemeDB::ThemeItemBind> theme_items; + ThemeDB::get_singleton()->get_class_items(get_class_name(), &theme_items, true, type); + + List<StringName> sn; + for (const ThemeDB::ThemeItemBind &E : theme_items) { + if (E.data_type == type) { + sn.push_back(E.item_name); + } + } - sn.sort_custom<StringName::AlphCompare>(); - for (const StringName &name : sn) { - r_options->push_back(String(name).quote()); + sn.sort_custom<StringName::AlphCompare>(); + for (const StringName &name : sn) { + r_options->push_back(String(name).quote()); + } } } + CanvasItem::get_argument_options(p_function, p_idx, r_options); } #endif @@ -241,10 +254,6 @@ PackedStringArray Control::get_configuration_warnings() const { warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); } - if (get_z_index() != 0) { - warnings.push_back(RTR("Changing the Z index of a control only affects the drawing order, not the input event handling order.")); - } - return warnings; } @@ -1387,6 +1396,15 @@ void Control::_set_position(const Point2 &p_point) { void Control::set_position(const Point2 &p_point, bool p_keep_offsets) { ERR_MAIN_THREAD_GUARD; + +#ifdef TOOLS_ENABLED + // Can't compute anchors, set position directly and return immediately. + if (saving && !is_inside_tree()) { + data.pos_cache = p_point; + return; + } +#endif + if (p_keep_offsets) { _compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor); } else { @@ -1406,15 +1424,11 @@ void Control::_set_global_position(const Point2 &p_point) { void Control::set_global_position(const Point2 &p_point, bool p_keep_offsets) { ERR_MAIN_THREAD_GUARD; - - Transform2D global_transform_cache = get_global_transform(); - if (p_point == global_transform_cache.get_origin()) { - return; // Edge case, but avoids calculation. - } - - Point2 internal_position = global_transform_cache.affine_inverse().xform(p_point); - - set_position(internal_position + data.pos_cache, p_keep_offsets); + // (parent_global_transform * T(new_position) * internal_transform).origin == new_global_position + // (T(new_position) * internal_transform).origin == new_position_in_parent_space + // new_position == new_position_in_parent_space - internal_transform.origin + Point2 position_in_parent_space = data.parent_canvas_item ? data.parent_canvas_item->get_global_transform().affine_inverse().xform(p_point) : p_point; + set_position(position_in_parent_space - _get_internal_transform().get_origin(), p_keep_offsets); } Point2 Control::get_global_position() const { @@ -1449,6 +1463,14 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) { new_size.y = min.y; } +#ifdef TOOLS_ENABLED + // Can't compute anchors, set size directly and return immediately. + if (saving && !is_inside_tree()) { + data.size_cache = new_size; + return; + } +#endif + if (p_keep_offsets) { _compute_anchors(Rect2(data.pos_cache, new_size), data.offset, data.anchor); } else { @@ -1583,7 +1605,7 @@ void Control::_update_minimum_size() { if (minsize != data.last_minimum_size) { data.last_minimum_size = minsize; _size_changed(); - emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + emit_signal(SceneStringName(minimum_size_changed)); } } @@ -1657,8 +1679,7 @@ Size2 Control::get_custom_minimum_size() const { void Control::_update_minimum_size_cache() { Size2 minsize = get_minimum_size(); - minsize.x = MAX(minsize.x, data.custom_minimum_size.x); - minsize.y = MAX(minsize.y, data.custom_minimum_size.y); + minsize = minsize.max(data.custom_minimum_size); data.minimum_size_cache = minsize; data.minimum_size_valid = true; @@ -1748,7 +1769,7 @@ void Control::set_h_size_flags(BitField<SizeFlags> p_flags) { return; } data.h_size_flags = p_flags; - emit_signal(SceneStringNames::get_singleton()->size_flags_changed); + emit_signal(SceneStringName(size_flags_changed)); } BitField<Control::SizeFlags> Control::get_h_size_flags() const { @@ -1762,7 +1783,7 @@ void Control::set_v_size_flags(BitField<SizeFlags> p_flags) { return; } data.v_size_flags = p_flags; - emit_signal(SceneStringNames::get_singleton()->size_flags_changed); + emit_signal(SceneStringName(size_flags_changed)); } BitField<Control::SizeFlags> Control::get_v_size_flags() const { @@ -1777,7 +1798,7 @@ void Control::set_stretch_ratio(real_t p_ratio) { } data.expand = p_ratio; - emit_signal(SceneStringNames::get_singleton()->size_flags_changed); + emit_signal(SceneStringName(size_flags_changed)); } real_t Control::get_stretch_ratio() const { @@ -1789,7 +1810,7 @@ real_t Control::get_stretch_ratio() const { void Control::_call_gui_input(const Ref<InputEvent> &p_event) { if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) { - emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); // Signal should be first, so it's possible to override an event (and then accept it). + emit_signal(SceneStringName(gui_input), p_event); // Signal should be first, so it's possible to override an event (and then accept it). } if (!is_inside_tree() || get_viewport()->is_input_handled()) { return; // Input was handled, abort. @@ -3149,6 +3170,14 @@ Control *Control::make_custom_tooltip(const String &p_text) const { void Control::_notification(int p_notification) { ERR_MAIN_THREAD_GUARD; switch (p_notification) { +#ifdef TOOLS_ENABLED + case NOTIFICATION_EDITOR_PRE_SAVE: { + saving = true; + } break; + case NOTIFICATION_EDITOR_POST_SAVE: { + saving = false; + } break; +#endif case NOTIFICATION_POSTINITIALIZE: { data.initialized = true; @@ -3193,7 +3222,7 @@ void Control::_notification(int p_notification) { case NOTIFICATION_READY: { #ifdef DEBUG_ENABLED - connect("ready", callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONE_SHOT); + connect(SceneStringName(ready), callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONE_SHOT); #endif } break; @@ -3232,7 +3261,7 @@ void Control::_notification(int p_notification) { data.parent_canvas_item = get_parent_item(); if (data.parent_canvas_item) { - data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed)); + data.parent_canvas_item->connect(SceneStringName(item_rect_changed), callable_mp(this, &Control::_size_changed)); } else { // Connect viewport. Viewport *viewport = get_viewport(); @@ -3243,7 +3272,7 @@ void Control::_notification(int p_notification) { case NOTIFICATION_EXIT_CANVAS: { if (data.parent_canvas_item) { - data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed)); + data.parent_canvas_item->disconnect(SceneStringName(item_rect_changed), callable_mp(this, &Control::_size_changed)); data.parent_canvas_item = nullptr; } else { // Disconnect viewport. @@ -3269,7 +3298,7 @@ void Control::_notification(int p_notification) { } break; case NOTIFICATION_RESIZED: { - emit_signal(SceneStringNames::get_singleton()->resized); + emit_signal(SceneStringName(resized)); } break; case NOTIFICATION_DRAW: { @@ -3279,25 +3308,25 @@ void Control::_notification(int p_notification) { } break; case NOTIFICATION_MOUSE_ENTER: { - emit_signal(SceneStringNames::get_singleton()->mouse_entered); + emit_signal(SceneStringName(mouse_entered)); } break; case NOTIFICATION_MOUSE_EXIT: { - emit_signal(SceneStringNames::get_singleton()->mouse_exited); + emit_signal(SceneStringName(mouse_exited)); } break; case NOTIFICATION_FOCUS_ENTER: { - emit_signal(SceneStringNames::get_singleton()->focus_entered); + emit_signal(SceneStringName(focus_entered)); queue_redraw(); } break; case NOTIFICATION_FOCUS_EXIT: { - emit_signal(SceneStringNames::get_singleton()->focus_exited); + emit_signal(SceneStringName(focus_exited)); queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { - emit_signal(SceneStringNames::get_singleton()->theme_changed); + emit_signal(SceneStringName(theme_changed)); _invalidate_theme_cache(); _update_theme_item_cache(); @@ -3558,7 +3587,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "localize_numeral_system"), "set_localize_numeral_system", "is_localizing_numeral_system"); #ifndef DISABLE_DEPRECATED - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_translate", "is_auto_translating"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_auto_translate", "is_auto_translating"); #endif ADD_GROUP("Tooltip", "tooltip_"); @@ -3694,6 +3723,8 @@ void Control::_bind_methods() { Control::Control() { data.theme_owner = memnew(ThemeOwner(this)); + + set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_OFF); } Control::~Control() { diff --git a/scene/gui/control.h b/scene/gui/control.h index bdb908e089..c784d4330d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -47,6 +47,10 @@ class ThemeContext; class Control : public CanvasItem { GDCLASS(Control, CanvasItem); +#ifdef TOOLS_ENABLED + bool saving = false; +#endif + public: enum Anchor { ANCHOR_BEGIN = 0, diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 3746b667f6..3d8be38fbd 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -47,7 +47,7 @@ void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) { } void AcceptDialog::_parent_focused() { - if (!is_exclusive() && get_flag(FLAG_POPUP)) { + if (popped_up && !is_exclusive() && get_flag(FLAG_POPUP)) { _cancel_pressed(); } } @@ -68,16 +68,25 @@ void AcceptDialog::_notification(int p_what) { parent_visible = get_parent_visible_window(); if (parent_visible) { - parent_visible->connect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused)); + parent_visible->connect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused)); } } else { + popped_up = false; if (parent_visible) { - parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused)); + parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused)); parent_visible = nullptr; } } } break; + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { + if (!is_in_edited_scene_root()) { + if (has_focus()) { + popped_up = true; + } + } + } break; + case NOTIFICATION_THEME_CHANGED: { bg_panel->add_theme_style_override("panel", theme_cache.panel_style); @@ -89,7 +98,7 @@ void AcceptDialog::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { if (parent_visible) { - parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused)); + parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused)); parent_visible = nullptr; } } break; @@ -114,8 +123,14 @@ void AcceptDialog::_text_submitted(const String &p_text) { _ok_pressed(); } +void AcceptDialog::_post_popup() { + Window::_post_popup(); + popped_up = true; +} + void AcceptDialog::_ok_pressed() { if (hide_on_ok) { + popped_up = false; set_visible(false); } ok_pressed(); @@ -124,9 +139,10 @@ void AcceptDialog::_ok_pressed() { } void AcceptDialog::_cancel_pressed() { + popped_up = false; Window *parent_window = parent_visible; if (parent_visible) { - parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused)); + parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused)); parent_visible = nullptr; } @@ -202,7 +218,7 @@ void AcceptDialog::register_text_enter(LineEdit *p_line_edit) { } void AcceptDialog::_update_child_rects() { - Size2 dlg_size = get_size(); + Size2 dlg_size = Vector2(get_size()) / get_content_scale_factor(); float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT); float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); @@ -210,6 +226,15 @@ void AcceptDialog::_update_child_rects() { bg_panel->set_position(Point2()); bg_panel->set_size(dlg_size); + for (int i = 0; i < buttons_hbox->get_child_count(); i++) { + Button *b = Object::cast_to<Button>(buttons_hbox->get_child(i)); + if (!b) { + continue; + } + + b->set_custom_minimum_size(Size2(theme_cache.buttons_min_width, theme_cache.buttons_min_height)); + } + // Place the buttons from the bottom edge to their minimum required size. Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y); @@ -252,14 +277,7 @@ Size2 AcceptDialog::_get_contents_minimum_size() const { } Size2 child_minsize = c->get_combined_minimum_size(); - content_minsize.x = MAX(child_minsize.x, content_minsize.x); - content_minsize.y = MAX(child_minsize.y, content_minsize.y); - } - - // Then we take the background panel as it provides the offsets, - // which are always added to the minimum size. - if (theme_cache.panel_style.is_valid()) { - content_minsize += theme_cache.panel_style->get_minimum_size(); + content_minsize = child_minsize.max(content_minsize); } // Then we add buttons. Horizontally we're interested in whichever @@ -270,6 +288,12 @@ Size2 AcceptDialog::_get_contents_minimum_size() const { // Plus there is a separation size added on top. content_minsize.y += theme_cache.buttons_separation; + // Then we take the background panel as it provides the offsets, + // which are always added to the minimum size. + if (theme_cache.panel_style.is_valid()) { + content_minsize += theme_cache.panel_style->get_minimum_size(); + } + return content_minsize; } @@ -300,7 +324,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin } button->set_meta("__right_spacer", right_spacer); - button->connect("visibility_changed", callable_mp(this, &AcceptDialog::_custom_button_visibility_changed).bind(button)); + button->connect(SceneStringName(visibility_changed), callable_mp(this, &AcceptDialog::_custom_button_visibility_changed).bind(button)); child_controls_changed(); if (is_visible()) { @@ -308,7 +332,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin } if (!p_action.is_empty()) { - button->connect("pressed", callable_mp(this, &AcceptDialog::_custom_action).bind(p_action)); + button->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action).bind(p_action)); } return button; @@ -322,7 +346,7 @@ Button *AcceptDialog::add_cancel_button(const String &p_cancel) { Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c); - b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); + b->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed)); return b; } @@ -337,12 +361,12 @@ void AcceptDialog::remove_button(Button *p_button) { ERR_FAIL_COND_MSG(right_spacer->get_parent() != buttons_hbox, vformat("Cannot remove button %s as its associated spacer does not belong to this dialog.", p_button->get_name())); } - p_button->disconnect("visibility_changed", callable_mp(this, &AcceptDialog::_custom_button_visibility_changed)); - if (p_button->is_connected("pressed", callable_mp(this, &AcceptDialog::_custom_action))) { - p_button->disconnect("pressed", callable_mp(this, &AcceptDialog::_custom_action)); + p_button->disconnect(SceneStringName(visibility_changed), callable_mp(this, &AcceptDialog::_custom_button_visibility_changed)); + if (p_button->is_connected(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action))) { + p_button->disconnect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action)); } - if (p_button->is_connected("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed))) { - p_button->disconnect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); + if (p_button->is_connected(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed))) { + p_button->disconnect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed)); } if (right_spacer) { @@ -390,6 +414,8 @@ void AcceptDialog::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, AcceptDialog, panel_style, "panel"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_width); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_height); } bool AcceptDialog::swap_cancel_ok = false; @@ -423,7 +449,7 @@ AcceptDialog::AcceptDialog() { buttons_hbox->add_child(ok_button); buttons_hbox->add_spacer(); - ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed)); + ok_button->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_ok_pressed)); set_title(ETR("Alert!")); } diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 6f9f450778..404237bfd8 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -51,12 +51,15 @@ class AcceptDialog : public Window { HBoxContainer *buttons_hbox = nullptr; Button *ok_button = nullptr; + bool popped_up = false; bool hide_on_ok = true; bool close_on_escape = true; struct ThemeCache { Ref<StyleBox> panel_style; int buttons_separation = 0; + int buttons_min_width = 0; + int buttons_min_height = 0; } theme_cache; void _custom_action(const String &p_action); @@ -70,6 +73,7 @@ class AcceptDialog : public Window { protected: virtual Size2 _get_contents_minimum_size() const override; virtual void _input_from_window(const Ref<InputEvent> &p_event) override; + virtual void _post_popup() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index a53dffd0d2..0c146ce173 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -53,12 +53,23 @@ void FileDialog::_focus_file_text() { int lp = file->get_text().rfind("."); if (lp != -1) { file->select(0, lp); - if (file->is_inside_tree() && !get_tree()->is_node_being_edited(file)) { + if (file->is_inside_tree() && !is_part_of_edited_scene()) { file->grab_focus(); } } } +void FileDialog::_native_popup() { + // Show native dialog directly. + String root; + if (access == ACCESS_RESOURCES) { + root = ProjectSettings::get_singleton()->get_resource_path(); + } else if (access == ACCESS_USERDATA) { + root = OS::get_singleton()->get_user_data_dir(); + } + DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb)); +} + void FileDialog::popup(const Rect2i &p_rect) { _update_option_controls(); @@ -68,21 +79,17 @@ void FileDialog::popup(const Rect2i &p_rect) { } #endif - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { - String root; - if (access == ACCESS_RESOURCES) { - root = ProjectSettings::get_singleton()->get_resource_path(); - } else if (access == ACCESS_USERDATA) { - root = OS::get_singleton()->get_user_data_dir(); - } - DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb)); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + _native_popup(); } else { ConfirmationDialog::popup(p_rect); } } void FileDialog::set_visible(bool p_visible) { - _update_option_controls(); + if (p_visible) { + _update_option_controls(); + } #ifdef TOOLS_ENABLED if (is_part_of_edited_scene()) { @@ -91,68 +98,63 @@ void FileDialog::set_visible(bool p_visible) { } #endif - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { - if (p_visible) { - String root; - if (access == ACCESS_RESOURCES) { - root = ProjectSettings::get_singleton()->get_resource_path(); - } else if (access == ACCESS_USERDATA) { - root = OS::get_singleton()->get_user_data_dir(); - } - DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb)); - } + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + _native_popup(); } else { ConfirmationDialog::set_visible(p_visible); } } void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) { - if (p_ok) { - if (p_files.size() > 0) { - Vector<String> files = p_files; - if (access != ACCESS_FILESYSTEM) { - for (String &file_name : files) { - file_name = ProjectSettings::get_singleton()->localize_path(file_name); - } - } - String f = files[0]; - if (mode == FILE_MODE_OPEN_FILES) { - emit_signal(SNAME("files_selected"), files); - } else { - if (mode == FILE_MODE_SAVE_FILE) { - if (p_filter >= 0 && p_filter < filters.size()) { - bool valid = false; - String flt = filters[p_filter].get_slice(";", 0); - int filter_slice_count = flt.get_slice_count(","); - for (int j = 0; j < filter_slice_count; j++) { - String str = (flt.get_slice(",", j).strip_edges()); - if (f.match(str)) { - valid = true; - break; - } - } - - if (!valid && filter_slice_count > 0) { - String str = (flt.get_slice(",", 0).strip_edges()); - f += str.substr(1, str.length() - 1); - } + if (!p_ok) { + file->set_text(""); + emit_signal(SNAME("canceled")); + return; + } + + if (p_files.is_empty()) { + return; + } + + Vector<String> files = p_files; + if (access != ACCESS_FILESYSTEM) { + for (String &file_name : files) { + file_name = ProjectSettings::get_singleton()->localize_path(file_name); + } + } + String f = files[0]; + if (mode == FILE_MODE_OPEN_FILES) { + emit_signal(SNAME("files_selected"), files); + } else { + if (mode == FILE_MODE_SAVE_FILE) { + if (p_filter >= 0 && p_filter < filters.size()) { + bool valid = false; + String flt = filters[p_filter].get_slice(";", 0); + int filter_slice_count = flt.get_slice_count(","); + for (int j = 0; j < filter_slice_count; j++) { + String str = (flt.get_slice(",", j).strip_edges()); + if (f.match(str)) { + valid = true; + break; } - emit_signal(SNAME("file_selected"), f); - } else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { - emit_signal(SNAME("file_selected"), f); - } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { - emit_signal(SNAME("dir_selected"), f); + } + + if (!valid && filter_slice_count > 0) { + String str = (flt.get_slice(",", 0).strip_edges()); + f += str.substr(1, str.length() - 1); } } - file->set_text(f); - dir->set_text(f.get_base_dir()); - selected_options = p_selected_options; - filter->select(p_filter); + emit_signal(SNAME("file_selected"), f); + } else if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { + emit_signal(SNAME("file_selected"), f); + } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { + emit_signal(SNAME("dir_selected"), f); } - } else { - file->set_text(""); - emit_signal(SNAME("canceled")); } + file->set_text(f); + dir->set_text(f.get_base_dir()); + selected_options = p_selected_options; + filter->select(p_filter); } VBoxContainer *FileDialog::get_vbox() { @@ -168,6 +170,20 @@ void FileDialog::_validate_property(PropertyInfo &p_property) const { void FileDialog::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene()) { + return; + } +#endif + + // Replace the built-in dialog with the native one if it started visible. + if (is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + ConfirmationDialog::set_visible(false); + _native_popup(); + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible()) { set_process_shortcut_input(false); @@ -197,15 +213,15 @@ void FileDialog::_notification(int p_what) { dir_up->end_bulk_theme_override(); dir_prev->begin_bulk_theme_override(); - dir_prev->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); - dir_prev->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_prev->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + dir_prev->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); dir_prev->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); dir_prev->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); dir_prev->end_bulk_theme_override(); dir_next->begin_bulk_theme_override(); - dir_next->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); - dir_next->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_next->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + dir_next->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); dir_next->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); dir_next->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); dir_next->end_bulk_theme_override(); @@ -671,8 +687,8 @@ void FileDialog::update_file_list() { item = dir_access->get_next(); } - dirs.sort_custom<NaturalNoCaseComparator>(); - files.sort_custom<NaturalNoCaseComparator>(); + dirs.sort_custom<FileNoCaseComparator>(); + files.sort_custom<FileNoCaseComparator>(); while (!dirs.is_empty()) { String &dir_name = dirs.front()->get(); @@ -1110,7 +1126,7 @@ void FileDialog::_update_option_controls() { } options_dirty = false; - while (grid_options->get_child_count(false) > 0) { + while (grid_options->get_child_count() > 0) { Node *child = grid_options->get_child(0); grid_options->remove_child(child); child->queue_free(); @@ -1222,9 +1238,8 @@ void FileDialog::add_option(const String &p_name, const Vector<String> &p_values void FileDialog::set_option_count(int p_count) { ERR_FAIL_COND(p_count < 0); - int prev_size = options.size(); - if (prev_size == p_count) { + if (options.size() == p_count) { return; } options.resize(p_count); @@ -1240,52 +1255,6 @@ int FileDialog::get_option_count() const { return options.size(); } -bool FileDialog::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - set_option_name(item_index, p_value); - return true; - } else if (property == "values") { - set_option_values(item_index, p_value); - return true; - } else if (property == "default") { - set_option_default(item_index, p_value); - return true; - } - } - return false; -} - -bool FileDialog::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - r_ret = get_option_name(item_index); - return true; - } else if (property == "values") { - r_ret = get_option_values(item_index); - return true; - } else if (property == "default") { - r_ret = get_option_default(item_index); - return true; - } - } - return false; -} - -void FileDialog::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < options.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("option_%d/name", i))); - p_list->push_back(PropertyInfo(Variant::PACKED_STRING_ARRAY, vformat("option_%d/values", i))); - p_list->push_back(PropertyInfo(Variant::INT, vformat("option_%d/default", i))); - } -} - void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); @@ -1298,10 +1267,10 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_option_default", "option"), &FileDialog::get_option_default); ClassDB::bind_method(D_METHOD("set_option_name", "option", "name"), &FileDialog::set_option_name); ClassDB::bind_method(D_METHOD("set_option_values", "option", "values"), &FileDialog::set_option_values); - ClassDB::bind_method(D_METHOD("set_option_default", "option", "index"), &FileDialog::set_option_default); + ClassDB::bind_method(D_METHOD("set_option_default", "option", "default_value_index"), &FileDialog::set_option_default); ClassDB::bind_method(D_METHOD("set_option_count", "count"), &FileDialog::set_option_count); ClassDB::bind_method(D_METHOD("get_option_count"), &FileDialog::get_option_count); - ClassDB::bind_method(D_METHOD("add_option", "name", "values", "index"), &FileDialog::add_option); + ClassDB::bind_method(D_METHOD("add_option", "name", "values", "default_value_index"), &FileDialog::add_option); ClassDB::bind_method(D_METHOD("get_selected_options"), &FileDialog::get_selected_options); ClassDB::bind_method(D_METHOD("get_current_dir"), &FileDialog::get_current_dir); ClassDB::bind_method(D_METHOD("get_current_file"), &FileDialog::get_current_file); @@ -1371,6 +1340,14 @@ void FileDialog::_bind_methods() { BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color", "Button"); + + Option defaults; + + base_property_helper.set_prefix("option_"); + base_property_helper.set_array_length_getter(&FileDialog::get_option_count); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values); + base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default); } void FileDialog::set_show_hidden_files(bool p_show) { @@ -1391,6 +1368,18 @@ void FileDialog::set_default_show_hidden_files(bool p_show) { void FileDialog::set_use_native_dialog(bool p_native) { use_native_dialog = p_native; + +#ifdef TOOLS_ENABLED + if (is_part_of_edited_scene()) { + return; + } +#endif + + // Replace the built-in dialog with the native one if it's currently visible. + if (is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + ConfirmationDialog::set_visible(false); + _native_popup(); + } } bool FileDialog::get_use_native_dialog() const { @@ -1420,9 +1409,9 @@ FileDialog::FileDialog() { hbc->add_child(dir_prev); hbc->add_child(dir_next); hbc->add_child(dir_up); - dir_prev->connect("pressed", callable_mp(this, &FileDialog::_go_back)); - dir_next->connect("pressed", callable_mp(this, &FileDialog::_go_forward)); - dir_up->connect("pressed", callable_mp(this, &FileDialog::_go_up)); + dir_prev->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_back)); + dir_next->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_forward)); + dir_up->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_up)); hbc->add_child(memnew(Label(ETR("Path:")))); @@ -1441,7 +1430,7 @@ FileDialog::FileDialog() { refresh = memnew(Button); refresh->set_theme_type_variation("FlatButton"); refresh->set_tooltip_text(ETR("Refresh files.")); - refresh->connect("pressed", callable_mp(this, &FileDialog::update_file_list)); + refresh->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list)); hbc->add_child(refresh); show_hidden = memnew(Button); @@ -1458,7 +1447,7 @@ FileDialog::FileDialog() { makedir = memnew(Button); makedir->set_theme_type_variation("FlatButton"); makedir->set_tooltip_text(ETR("Create a new folder.")); - makedir->connect("pressed", callable_mp(this, &FileDialog::_make_dir)); + makedir->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir)); hbc->add_child(makedir); vbox->add_child(hbc); @@ -1536,6 +1525,8 @@ FileDialog::FileDialog() { if (register_func) { register_func(this); } + + property_helper.setup_for_instance(base_property_helper, this); } FileDialog::~FileDialog() { diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 1b53c7e05e..4236f0a56b 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -37,6 +37,7 @@ #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" #include "scene/gui/tree.h" +#include "scene/property_list_helper.h" class GridContainer; @@ -137,6 +138,10 @@ private: Vector<String> values; int default_idx = 0; }; + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + Vector<Option> options; Dictionary selected_options; bool options_dirty = false; @@ -172,6 +177,7 @@ private: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + void _native_popup(); void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options); bool _is_open_should_be_disabled(); @@ -186,9 +192,11 @@ private: protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, options.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); public: diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index 9f79da2905..ceffd9d103 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -38,6 +38,7 @@ struct _LineData { int min_line_length = 0; int stretch_avail = 0; float stretch_ratio_total = 0; + bool is_filled = false; }; void FlowContainer::_resort() { @@ -58,14 +59,12 @@ void FlowContainer::_resort() { float line_stretch_ratio_total = 0; int current_container_size = vertical ? get_rect().size.y : get_rect().size.x; int children_in_current_line = 0; + Control *last_child = nullptr; // First pass for line wrapping and minimum size calculation. for (int i = 0; i < get_child_count(); i++) { - Control *child = Object::cast_to<Control>(get_child(i)); - if (!child || !child->is_visible()) { - continue; - } - if (child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i)); + if (!child) { continue; } @@ -77,7 +76,7 @@ void FlowContainer::_resort() { } if (ofs.y + child_msc.y > current_container_size) { line_length = ofs.y - theme_cache.v_separation; - lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); + lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true }); // Move in new column (vertical line). ofs.x += line_height + theme_cache.h_separation; @@ -99,7 +98,7 @@ void FlowContainer::_resort() { } if (ofs.x + child_msc.x > current_container_size) { line_length = ofs.x - theme_cache.h_separation; - lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); + lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true }); // Move in new line. ofs.y += line_height + theme_cache.v_separation; @@ -116,11 +115,16 @@ void FlowContainer::_resort() { ofs.x += child_msc.x; } + last_child = child; children_minsize_cache[child] = child_msc; children_in_current_line++; } line_length = vertical ? (ofs.y) : (ofs.x); - lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); + bool is_filled = false; + if (last_child != nullptr) { + is_filled = vertical ? (ofs.y + last_child->get_combined_minimum_size().y > current_container_size ? true : false) : (ofs.x + last_child->get_combined_minimum_size().x > current_container_size ? true : false); + } + lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, is_filled }); // Second pass for in-line expansion and alignment. @@ -131,11 +135,8 @@ void FlowContainer::_resort() { ofs.y = 0; for (int i = 0; i < get_child_count(); i++) { - Control *child = Object::cast_to<Control>(get_child(i)); - if (!child || !child->is_visible()) { - continue; - } - if (child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i)); + if (!child) { continue; } Size2i child_size = children_minsize_cache[child]; @@ -158,17 +159,43 @@ void FlowContainer::_resort() { // but only if the line doesn't contain a child that expands. if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) { int alignment_ofs = 0; + bool is_not_first_line_and_not_filled = current_line_idx != 0 && !line_data.is_filled; + float prior_stretch_avail = is_not_first_line_and_not_filled ? lines_data[current_line_idx - 1].stretch_avail : 0.0; switch (alignment) { - case ALIGNMENT_CENTER: - alignment_ofs = line_data.stretch_avail / 2; - break; - case ALIGNMENT_END: - alignment_ofs = line_data.stretch_avail; - break; + case ALIGNMENT_BEGIN: { + if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && is_not_first_line_and_not_filled) { + if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) { + alignment_ofs = line_data.stretch_avail - prior_stretch_avail; + } else if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_CENTER) { + alignment_ofs = (line_data.stretch_avail - prior_stretch_avail) * 0.5; + } + } + } break; + case ALIGNMENT_CENTER: { + if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_CENTER && is_not_first_line_and_not_filled) { + if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) { + alignment_ofs = line_data.stretch_avail - (prior_stretch_avail * 0.5); + } else { // Is LAST_WRAP_ALIGNMENT_BEGIN + alignment_ofs = prior_stretch_avail * 0.5; + } + } else { + alignment_ofs = line_data.stretch_avail * 0.5; + } + } break; + case ALIGNMENT_END: { + if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_END && is_not_first_line_and_not_filled) { + if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_BEGIN) { + alignment_ofs = prior_stretch_avail; + } else { // Is LAST_WRAP_ALIGNMENT_CENTER + alignment_ofs = prior_stretch_avail + (line_data.stretch_avail - prior_stretch_avail) * 0.5; + } + } else { + alignment_ofs = line_data.stretch_avail; + } + } break; default: break; } - if (vertical) { /* VERTICAL */ ofs.y += alignment_ofs; } else { /* HORIZONTAL */ @@ -223,17 +250,10 @@ Size2 FlowContainer::get_minimum_size() const { Size2i minimum; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + Control *c = as_sortable_control(get_child(i)); if (!c) { continue; } - if (c->is_set_as_top_level()) { - continue; - } - - if (!c->is_visible()) { - continue; - } Size2i size = c->get_combined_minimum_size(); @@ -314,6 +334,18 @@ FlowContainer::AlignmentMode FlowContainer::get_alignment() const { return alignment; } +void FlowContainer::set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment) { + if (last_wrap_alignment == p_last_wrap_alignment) { + return; + } + last_wrap_alignment = p_last_wrap_alignment; + _resort(); +} + +FlowContainer::LastWrapAlignmentMode FlowContainer::get_last_wrap_alignment() const { + return last_wrap_alignment; +} + void FlowContainer::set_vertical(bool p_vertical) { ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + "."); vertical = p_vertical; @@ -346,6 +378,8 @@ void FlowContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment); ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment); + ClassDB::bind_method(D_METHOD("set_last_wrap_alignment", "last_wrap_alignment"), &FlowContainer::set_last_wrap_alignment); + ClassDB::bind_method(D_METHOD("get_last_wrap_alignment"), &FlowContainer::get_last_wrap_alignment); ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical); ClassDB::bind_method(D_METHOD("set_reverse_fill", "reverse_fill"), &FlowContainer::set_reverse_fill); @@ -354,8 +388,13 @@ void FlowContainer::_bind_methods() { BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN); BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); BIND_ENUM_CONSTANT(ALIGNMENT_END); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_INHERIT); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_BEGIN); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_CENTER); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_END); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "last_wrap_alignment", PROPERTY_HINT_ENUM, "Inherit,Begin,Center,End"), "set_last_wrap_alignment", "get_last_wrap_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverse_fill"), "set_reverse_fill", "is_reverse_fill"); diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index 90da73aaab..65ebc89c78 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -42,6 +42,12 @@ public: ALIGNMENT_CENTER, ALIGNMENT_END }; + enum LastWrapAlignmentMode { + LAST_WRAP_ALIGNMENT_INHERIT, + LAST_WRAP_ALIGNMENT_BEGIN, + LAST_WRAP_ALIGNMENT_CENTER, + LAST_WRAP_ALIGNMENT_END + }; private: int cached_size = 0; @@ -50,6 +56,7 @@ private: bool vertical = false; bool reverse_fill = false; AlignmentMode alignment = ALIGNMENT_BEGIN; + LastWrapAlignmentMode last_wrap_alignment = LAST_WRAP_ALIGNMENT_INHERIT; struct ThemeCache { int h_separation = 0; @@ -71,6 +78,9 @@ public: void set_alignment(AlignmentMode p_alignment); AlignmentMode get_alignment() const; + void set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment); + LastWrapAlignmentMode get_last_wrap_alignment() const; + void set_vertical(bool p_vertical); bool is_vertical() const; @@ -102,5 +112,6 @@ public: }; VARIANT_ENUM_CAST(FlowContainer::AlignmentMode); +VARIANT_ENUM_CAST(FlowContainer::LastWrapAlignmentMode); #endif // FLOW_CONTAINER_H diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 056872a4fe..6c2a61d255 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -181,8 +181,7 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { if (is_resizing) { // Prevent setting minimap wider than GraphEdit. Vector2 new_minimap_size; - new_minimap_size.width = MIN(get_size().width - mm->get_relative().x, ge->get_size().width - 2.0 * minimap_padding.x); - new_minimap_size.height = MIN(get_size().height - mm->get_relative().y, ge->get_size().height - 2.0 * minimap_padding.y); + new_minimap_size = (get_size() - mm->get_relative()).min(ge->get_size() - 2.0 * minimap_padding); ge->set_minimap_size(new_minimap_size); queue_redraw(); @@ -443,11 +442,42 @@ void GraphEdit::_update_scroll() { updating = false; } -void GraphEdit::_graph_element_moved_to_front(Node *p_node) { - GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); - ERR_FAIL_NULL(graph_element); +void GraphEdit::_ensure_node_order_from(Node *p_node) { + GraphElement *graph_node = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_NULL(graph_node); + GraphFrame *frame = Object::cast_to<GraphFrame>(p_node); + + // Move a non-frame node directly to the front. + if (!frame) { + graph_node->move_to_front(); + return; + } + + // Reorder the frames behind the connection layer. + List<GraphFrame *> attached_nodes_to_move; + attached_nodes_to_move.push_back(frame); + + while (!attached_nodes_to_move.is_empty()) { + GraphFrame *attached_frame = attached_nodes_to_move.front()->get(); + attached_nodes_to_move.pop_front(); + + // Move the frame to the front of the background node index range. + attached_frame->get_parent()->call_deferred("move_child", attached_frame, background_nodes_separator_idx - 1); + + if (!frame_attached_nodes.has(attached_frame->get_name())) { + continue; + } - graph_element->move_to_front(); + for (const StringName &attached_node_name : frame_attached_nodes.get(attached_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node(NodePath(attached_node_name))); + + GraphFrame *attached_child_frame_node = Object::cast_to<GraphFrame>(attached_node); + + if (attached_child_frame_node && (attached_child_frame_node != frame)) { + attached_nodes_to_move.push_back(attached_child_frame_node); + } + } + } } void GraphEdit::_graph_element_selected(Node *p_node) { @@ -464,11 +494,42 @@ void GraphEdit::_graph_element_deselected(Node *p_node) { emit_signal(SNAME("node_deselected"), graph_element); } -void GraphEdit::_graph_element_resized(Vector2 p_new_minsize, Node *p_node) { +void GraphEdit::_graph_element_resize_request(const Vector2 &p_new_minsize, Node *p_node) { GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); ERR_FAIL_NULL(graph_element); - graph_element->set_size(p_new_minsize); + // Snap the new size to the grid if snapping is enabled. + Vector2 new_size = p_new_minsize; + if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { + new_size = new_size.snappedf(snapping_distance); + } + + // Disallow resizing the frame to a size smaller than the minimum size of the attached nodes. + GraphFrame *frame = Object::cast_to<GraphFrame>(graph_element); + if (frame && !frame->is_autoshrink_enabled()) { + Rect2 frame_rect = _compute_shrinked_frame_rect(frame); + Vector2 computed_min_size = (frame_rect.position + frame_rect.size) - frame->get_position_offset(); + frame->set_size(new_size.max(computed_min_size)); + } else { + graph_element->set_size(new_size); + } + + // Update all parent frames recursively bottom-up. + if (linked_parent_map.has(graph_element->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node_or_null(NodePath(linked_parent_map[graph_element->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } +} + +void GraphEdit::_graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, GraphFrame *p_frame) { + _update_graph_frame(p_frame); + + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); + callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred(); } void GraphEdit::_graph_element_moved(Node *p_node) { @@ -503,6 +564,26 @@ void GraphEdit::_graph_node_rect_changed(GraphNode *p_node) { connections_layer->queue_redraw(); callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred(); + + // Update all parent frames recursively bottom-up. + if (linked_parent_map.has(p_node->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node(NodePath(linked_parent_map[p_node->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } +} + +void GraphEdit::_ensure_node_order_from_root(const StringName &p_node) { + // Find the root frame node of the frame tree starting from p_node. + GraphElement *root_frame = Object::cast_to<GraphElement>(get_node(NodePath(p_node))); + ERR_FAIL_NULL(root_frame); + + while (linked_parent_map.has(root_frame->get_name())) { + root_frame = Object::cast_to<GraphElement>(get_node(NodePath(linked_parent_map[root_frame->get_name()]))); + } + + _ensure_node_order_from(root_frame); } void GraphEdit::add_child_notify(Node *p_child) { @@ -520,12 +601,25 @@ void GraphEdit::add_child_notify(Node *p_child) { GraphNode *graph_node = Object::cast_to<GraphNode>(graph_element); if (graph_node) { graph_node->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(graph_element)); - graph_node->connect("item_rect_changed", callable_mp(this, &GraphEdit::_graph_node_rect_changed).bind(graph_node)); + graph_node->connect(SceneStringName(item_rect_changed), callable_mp(this, &GraphEdit::_graph_node_rect_changed).bind(graph_node)); + _ensure_node_order_from(graph_node); } - graph_element->connect("raise_request", callable_mp(this, &GraphEdit::_graph_element_moved_to_front).bind(graph_element)); - graph_element->connect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resized).bind(graph_element)); - graph_element->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(graph_element); + if (graph_frame) { + background_nodes_separator_idx++; + + callable_mp((Node *)this, &Node::move_child).call_deferred(graph_frame, 0); + callable_mp((Node *)this, &Node::move_child).call_deferred(connections_layer, background_nodes_separator_idx); + + _update_graph_frame(graph_frame); + + graph_frame->connect("autoshrink_changed", callable_mp(this, &GraphEdit::_graph_frame_autoshrink_changed).bind(graph_element)); + } + graph_element->connect("raise_request", callable_mp(this, &GraphEdit::_ensure_node_order_from).bind(graph_element)); + graph_element->connect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resize_request).bind(graph_element)); + graph_element->connect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + graph_element->connect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); graph_element->set_scale(Vector2(zoom, zoom)); _graph_element_moved(graph_element); @@ -557,7 +651,7 @@ void GraphEdit::remove_child_notify(Node *p_child) { GraphNode *graph_node = Object::cast_to<GraphNode>(graph_element); if (graph_node) { graph_node->disconnect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated)); - graph_node->disconnect("item_rect_changed", callable_mp(this, &GraphEdit::_graph_node_rect_changed)); + graph_node->disconnect(SceneStringName(item_rect_changed), callable_mp(this, &GraphEdit::_graph_node_rect_changed)); // Invalidate all adjacent connections, so that they are removed before the next redraw. for (const Ref<Connection> &conn : connection_map[graph_node->get_name()]) { @@ -566,12 +660,39 @@ void GraphEdit::remove_child_notify(Node *p_child) { connections_layer->queue_redraw(); } - graph_element->disconnect("raise_request", callable_mp(this, &GraphEdit::_graph_element_moved_to_front)); - graph_element->disconnect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resized)); + GraphFrame *frame = Object::cast_to<GraphFrame>(graph_element); + if (frame) { + background_nodes_separator_idx--; + graph_element->disconnect("autoshrink_changed", callable_mp(this, &GraphEdit::_graph_frame_autoshrink_changed)); + } + + if (linked_parent_map.has(graph_element->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node(NodePath(linked_parent_map[graph_element->get_name()]))); + if (parent_frame) { + if (frame_attached_nodes.has(parent_frame->get_name())) { + frame_attached_nodes.get(parent_frame->get_name()).erase(graph_element->get_name()); + } + linked_parent_map.erase(graph_element->get_name()); + _update_graph_frame(parent_frame); + } + } + + if (frame_attached_nodes.has(graph_element->get_name())) { + for (const StringName &attached_node_name : frame_attached_nodes.get(graph_element->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node(NodePath(attached_node_name))); + if (attached_node) { + linked_parent_map.erase(attached_node->get_name()); + } + } + frame_attached_nodes.erase(graph_element->get_name()); + } + + graph_element->disconnect("raise_request", callable_mp(this, &GraphEdit::_ensure_node_order_from)); + graph_element->disconnect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resize_request)); // In case of the whole GraphEdit being destroyed these references can already be freed. if (minimap != nullptr && minimap->is_inside_tree()) { - graph_element->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); + graph_element->disconnect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); } } } @@ -621,6 +742,7 @@ void GraphEdit::_notification(int p_what) { _draw_grid(); } } break; + case NOTIFICATION_RESIZED: { _update_scroll(); minimap->queue_redraw(); @@ -629,6 +751,118 @@ void GraphEdit::_notification(int p_what) { } } +Rect2 GraphEdit::_compute_shrinked_frame_rect(const GraphFrame *p_frame) { + Vector2 min_point{ FLT_MAX, FLT_MAX }; + Vector2 max_point{ -FLT_MAX, -FLT_MAX }; + + if (!frame_attached_nodes.has(p_frame->get_name())) { + return Rect2(p_frame->get_position_offset(), Size2()); + } + + int autoshrink_margin = p_frame->get_autoshrink_margin(); + + for (const StringName &attached_node_name : frame_attached_nodes.get(p_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node_or_null(NodePath(attached_node_name))); + + if (!attached_node || attached_node == p_frame) { + if (!attached_node) { + frame_attached_nodes.get(p_frame->get_name()).erase(attached_node_name); + } + continue; + } + + Vector2 node_pos = attached_node->get_position_offset(); + Vector2 size = attached_node->get_size(); + min_point = min_point.min(node_pos); + max_point = max_point.max(node_pos + size); + } + + // It's sufficient to check only one value here. + if (min_point.x == FLT_MAX) { + return Rect2(p_frame->get_position_offset(), Size2()); + } + + min_point -= Size2(autoshrink_margin, autoshrink_margin); + max_point += Size2(autoshrink_margin, autoshrink_margin); + + return Rect2(min_point, max_point - min_point); +} + +void GraphEdit::_update_graph_frame(GraphFrame *p_frame) { + Rect2 frame_rect = _compute_shrinked_frame_rect(p_frame); + + Vector2 min_point = frame_rect.position; + Vector2 max_point = frame_rect.position + frame_rect.size; + + // Only update the size if there are attached nodes. + if (frame_attached_nodes.has(p_frame->get_name()) && frame_attached_nodes.get(p_frame->get_name()).size() > 0) { + if (!p_frame->is_autoshrink_enabled()) { + Vector2 old_offset = p_frame->get_position_offset(); + min_point = min_point.min(old_offset); + max_point = max_point.max(old_offset + p_frame->get_size()); + } + + Rect2 old_rect = p_frame->get_rect(); + + p_frame->set_position_offset(min_point); + p_frame->set_size(max_point - min_point); + + // Emit the signal only if the frame rect has changed. + if (old_rect != p_frame->get_rect()) { + emit_signal(SNAME("frame_rect_changed"), p_frame, p_frame->get_rect()); + } + } + + // Update all parent frames recursively bottom-up. + if (linked_parent_map.has(p_frame->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node_or_null(NodePath(linked_parent_map[p_frame->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } +} + +void GraphEdit::_set_drag_frame_attached_nodes(GraphFrame *p_frame, bool p_drag) { + if (!frame_attached_nodes.has(p_frame->get_name())) { + return; + } + + for (const StringName &attached_node_name : frame_attached_nodes.get(p_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node(NodePath(attached_node_name))); + + attached_node->set_drag(p_drag); + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(attached_node); + if (graph_frame) { + _set_drag_frame_attached_nodes(graph_frame, p_drag); + } + } +} + +void GraphEdit::_set_position_of_frame_attached_nodes(GraphFrame *p_frame, const Vector2 &p_pos) { + if (!frame_attached_nodes.has(p_frame->get_name())) { + return; + } + + for (const StringName &attached_node_name : frame_attached_nodes.get(p_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node_or_null(NodePath(attached_node_name))); + if (!attached_node) { + continue; + } + + Vector2 pos = (attached_node->get_drag_from() * zoom + drag_accum) / zoom; + if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { + pos = pos.snappedf(snapping_distance); + } + + // Recursively move graph frames. + attached_node->set_position_offset(pos); + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(attached_node); + if (graph_frame) { + _set_position_of_frame_attached_nodes(graph_frame, p_pos); + } + } +} + bool GraphEdit::_filter_input(const Point2 &p_point) { for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); @@ -664,6 +898,12 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { return true; } } + + // This prevents interactions with a port hotzone that is behind another node. + Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); + if (graph_node_rect.has_point(click_pos * zoom)) { + break; + } } return false; @@ -793,6 +1033,12 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { return; } } + + // This prevents interactions with a port hotzone that is behind another node. + Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); + if (graph_node_rect.has_point(click_pos * zoom)) { + break; + } } } @@ -825,7 +1071,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); int type = graph_node->get_output_port_type(j); - if ((type == connecting_type || + if ((type == connecting_type || graph_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(type, connecting_type))) && is_in_output_hotzone(graph_node, j, mpos, port_size)) { if (!is_node_hover_valid(graph_node->get_name(), j, connecting_from_node, connecting_from_port_index)) { @@ -850,7 +1096,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); int type = graph_node->get_input_port_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnectionType(connecting_type, type))) && + if ((type == connecting_type || graph_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(connecting_type, type))) && is_in_input_hotzone(graph_node, j, mpos, port_size)) { if (!is_node_hover_valid(connecting_from_node, connecting_from_port_index, graph_node->get_name(), j)) { continue; @@ -883,6 +1129,8 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { emit_signal(SNAME("connection_from_empty"), connecting_from_node, connecting_from_port_index, mb->get_position()); } } + } else { + set_selected(get_node_or_null(NodePath(connecting_from_node))); } if (connecting) { @@ -1051,18 +1299,26 @@ List<Ref<GraphEdit::Connection>> GraphEdit::get_connections_intersecting_with_re return intersecting_connections; } -void GraphEdit::_draw_minimap_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_from_color, const Color &p_to_color) { - const Vector<Vector2> &points = get_connection_line(p_from, p_to); +void GraphEdit::_draw_minimap_connection_line(const Vector2 &p_from_graph_position, const Vector2 &p_to_graph_position, const Color &p_from_color, const Color &p_to_color) { + Vector<Vector2> points = get_connection_line(p_from_graph_position, p_to_graph_position); + ERR_FAIL_COND_MSG(points.size() < 2, "\"_get_connection_line()\" returned an invalid line."); + // Convert to minimap points. + for (Vector2 &point : points) { + point = minimap->_convert_from_graph_position(point) + minimap->minimap_offset; + } + + // Setup polyline colors. LocalVector<Color> colors; colors.reserve(points.size()); - - float length_inv = 1.0 / (p_from).distance_to(p_to); + const Vector2 &from = points[0]; + const Vector2 &to = points[points.size() - 1]; + float length_inv = 1.0 / (from).distance_to(to); for (const Vector2 &point : points) { - float normalized_curve_position = (p_from).distance_to(point) * length_inv; + float normalized_curve_position = from.distance_to(point) * length_inv; colors.push_back(p_from_color.lerp(p_to_color, normalized_curve_position)); } - p_where->draw_polyline_colors(points, colors, 0.5, lines_antialiased); + minimap->draw_polyline_colors(points, colors, 0.5, lines_antialiased); } void GraphEdit::_update_connections() { @@ -1266,7 +1522,33 @@ void GraphEdit::_minimap_draw() { Vector2 graph_offset = minimap->_get_graph_offset(); Vector2 minimap_offset = minimap->minimap_offset; - // Draw graph nodes. + // Draw frame nodes. + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(get_child(i)); + if (!graph_frame || !graph_frame->is_visible()) { + continue; + } + + Vector2 node_position = minimap->_convert_from_graph_position(graph_frame->get_position_offset() * zoom - graph_offset) + minimap_offset; + Vector2 node_size = minimap->_convert_from_graph_position(graph_frame->get_size() * zoom); + Rect2 node_rect = Rect2(node_position, node_size); + + Ref<StyleBoxFlat> sb_minimap = minimap->theme_cache.node_style->duplicate(); + + // Override default values with colors provided by the GraphNode's stylebox, if possible. + Ref<StyleBoxFlat> sb_frame = graph_frame->get_theme_stylebox(graph_frame->is_selected() ? SNAME("panel_selected") : SNAME("panel")); + if (sb_frame.is_valid()) { + Color node_color = sb_frame->get_bg_color(); + if (graph_frame->is_tint_color_enabled()) { + node_color = graph_frame->get_tint_color(); + } + sb_minimap->set_bg_color(node_color); + } + + minimap->draw_style_box(sb_minimap, node_rect); + } + + // Draw regular graph nodes. for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); if (!graph_node || !graph_node->is_visible()) { @@ -1291,8 +1573,8 @@ void GraphEdit::_minimap_draw() { // Draw node connections. for (const Ref<Connection> &c : connections) { - Vector2 from_position = minimap->_convert_from_graph_position(c->_cache.from_pos * zoom - graph_offset) + minimap_offset; - Vector2 to_position = minimap->_convert_from_graph_position(c->_cache.to_pos * zoom - graph_offset) + minimap_offset; + Vector2 from_graph_position = c->_cache.from_pos * zoom - graph_offset; + Vector2 to_graph_position = c->_cache.to_pos * zoom - graph_offset; Color from_color = c->_cache.from_color; Color to_color = c->_cache.to_color; @@ -1300,7 +1582,8 @@ void GraphEdit::_minimap_draw() { from_color = from_color.lerp(theme_cache.activity_color, c->activity); to_color = to_color.lerp(theme_cache.activity_color, c->activity); } - _draw_minimap_connection_line(minimap, from_position, to_position, from_color, to_color); + + _draw_minimap_connection_line(from_graph_position, to_graph_position, from_color, to_color); } // Draw the "camera" viewport. @@ -1376,12 +1659,12 @@ void GraphEdit::_draw_grid() { void GraphEdit::set_selected(Node *p_child) { for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); - if (!graph_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (!graph_element) { continue; } - graph_node->set_selected(graph_node == p_child); + graph_element->set_selected(graph_element == p_child); } } @@ -1401,6 +1684,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { hovered_connection = new_highlighted_connection; } + // Logic for moving graph controls via mouse drag. if (mm.is_valid() && dragging) { if (!moving_selection) { emit_signal(SNAME("begin_node_move")); @@ -1417,10 +1701,23 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Snapping can be toggled temporarily by holding down Ctrl. // This is done here as to not toggle the grid when holding down Ctrl. if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { - pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); + pos = pos.snappedf(snapping_distance); } graph_element->set_position_offset(pos); + + if (linked_parent_map.has(graph_element->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node_or_null(NodePath(linked_parent_map[graph_element->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } + + // Update all frame transforms recursively. + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(get_child(i)); + if (graph_frame) { + _set_position_of_frame_attached_nodes(graph_frame, drag_accum); + } } } } @@ -1437,10 +1734,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { continue; } + // Only select frames when the box selection is fully enclosing them. + bool is_frame = Object::cast_to<GraphFrame>(graph_element); Rect2 r = graph_element->get_rect(); - bool in_box = r.intersects(box_selecting_rect); + bool should_be_selected = is_frame ? box_selecting_rect.encloses(r) : box_selecting_rect.intersects(r); - if (in_box) { + if (should_be_selected) { graph_element->set_selected(box_selection_mode_additive); } else { graph_element->set_selected(prev_selected.find(graph_element) != nullptr); @@ -1495,6 +1794,10 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); if (graph_element && graph_element->is_selected()) { graph_element->set_drag(false); + GraphFrame *frame = Object::cast_to<GraphFrame>(get_child(i)); + if (frame) { + _set_drag_frame_attached_nodes(frame, false); + } } } } @@ -1502,6 +1805,46 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (moving_selection) { emit_signal(SNAME("end_node_move")); moving_selection = false; + + Vector<GraphElement *> dragged_nodes; + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphElement *moved_node = Object::cast_to<GraphElement>(get_child(i)); + if (moved_node && moved_node->is_selected() && moved_node->is_draggable()) { + dragged_nodes.push_back(moved_node); + } + } + + GraphFrame *frame_dropped_on = nullptr; + + // Find frame on which the node(s) is/were dropped. + // Count down to find the topmost frame. + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphFrame *frame = Object::cast_to<GraphFrame>(get_child(i)); + + if (!frame || frame->is_resizing()) { + continue; + } + + Rect2 frame_rect = frame->get_rect(); + if (frame_rect.has_point(mb->get_position()) && !dragged_nodes.has(frame)) { + frame_dropped_on = frame; + break; + } + } + + if (frame_dropped_on) { + dragged_nodes.erase(frame_dropped_on); + + TypedArray<StringName> dragged_node_names; + for (GraphElement *moved_node : dragged_nodes) { + if (!linked_parent_map.has(moved_node->get_name())) { + dragged_node_names.push_back(moved_node->get_name()); + } + } + if (dragged_node_names.size() > 0) { + emit_signal(SNAME("graph_elements_linked_to_frame_request"), dragged_node_names, frame_dropped_on->get_name()); + } + } } dragging = false; @@ -1562,6 +1905,11 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (child_element->is_selected()) { child_element->set_drag(true); + GraphFrame *frame_node = Object::cast_to<GraphFrame>(get_child(i)); + if (frame_node) { + _ensure_node_order_from(frame_node); + _set_drag_frame_attached_nodes(frame_node, true); + } } } @@ -1637,12 +1985,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { TypedArray<StringName> nodes; for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (!graph_element) { continue; } - if (gn->is_selected()) { - nodes.push_back(gn->get_name()); + if (graph_element->is_selected()) { + nodes.push_back(graph_element->get_name()); } } @@ -1927,6 +2275,58 @@ bool GraphEdit::is_valid_connection_type(int p_type, int p_with_type) const { return valid_connection_types.has(ct); } +void GraphEdit::attach_graph_element_to_frame(const StringName &p_graph_element, const StringName &p_parent_frame) { + GraphFrame *frame = Object::cast_to<GraphFrame>(get_node(NodePath(p_parent_frame))); + ERR_FAIL_NULL_MSG(frame, "Frame does not exist or is not of type GraphFrame."); + GraphElement *graph_element = Object::cast_to<GraphElement>(get_node(NodePath(p_graph_element))); + ERR_FAIL_NULL_MSG(graph_element, "Graph element to attach does not exist or is not of type GraphElement."); + ERR_FAIL_COND_MSG(frame == graph_element, "Cannot attach a frame to itself."); + + linked_parent_map.insert(p_graph_element, p_parent_frame); + frame_attached_nodes[p_parent_frame].insert(p_graph_element); + + _ensure_node_order_from_root(p_graph_element); + _update_graph_frame(frame); +} + +void GraphEdit::detach_graph_element_from_frame(const StringName &p_graph_element) { + if (!linked_parent_map.has(p_graph_element)) { + return; + } + GraphFrame *frame = Object::cast_to<GraphFrame>(get_node(NodePath(linked_parent_map[p_graph_element]))); + ERR_FAIL_NULL_MSG(frame, "Frame does not exist or is not of type GraphFrame."); + GraphElement *graph_element = Object::cast_to<GraphElement>(get_node(NodePath(p_graph_element))); + ERR_FAIL_NULL_MSG(graph_element, "Graph element to detach does not exist or is not of type GraphElement."); + + frame_attached_nodes.get(frame->get_name()).erase(p_graph_element); + linked_parent_map.erase(p_graph_element); + + _update_graph_frame(frame); +} + +GraphFrame *GraphEdit::get_element_frame(const StringName &p_attached_graph_element) { + if (!linked_parent_map.has(p_attached_graph_element)) { + return nullptr; + } + + Node *parent = get_node_or_null(NodePath(linked_parent_map[p_attached_graph_element])); + + return Object::cast_to<GraphFrame>(parent); +} + +TypedArray<StringName> GraphEdit::get_attached_nodes_of_frame(const StringName &p_graph_frame) { + if (!frame_attached_nodes.has(p_graph_frame)) { + return TypedArray<StringName>(); + } + + TypedArray<StringName> attached_nodes; + for (const StringName &node : frame_attached_nodes.get(p_graph_frame)) { + attached_nodes.push_back(node); + } + + return attached_nodes; +} + void GraphEdit::set_snapping_enabled(bool p_enable) { if (snapping_enabled == p_enable) { return; @@ -2192,6 +2592,11 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_valid_connection_type", "from_type", "to_type"), &GraphEdit::is_valid_connection_type); ClassDB::bind_method(D_METHOD("get_connection_line", "from_node", "to_node"), &GraphEdit::get_connection_line); + ClassDB::bind_method(D_METHOD("attach_graph_element_to_frame", "element", "frame"), &GraphEdit::attach_graph_element_to_frame); + ClassDB::bind_method(D_METHOD("detach_graph_element_from_frame", "element"), &GraphEdit::detach_graph_element_from_frame); + ClassDB::bind_method(D_METHOD("get_element_frame", "element"), &GraphEdit::get_element_frame); + ClassDB::bind_method(D_METHOD("get_attached_nodes_of_frame", "frame"), &GraphEdit::get_attached_nodes_of_frame); + ClassDB::bind_method(D_METHOD("set_panning_scheme", "scheme"), &GraphEdit::set_panning_scheme); ClassDB::bind_method(D_METHOD("get_panning_scheme"), &GraphEdit::get_panning_scheme); @@ -2315,11 +2720,13 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("node_selected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("node_deselected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("frame_rect_changed", PropertyInfo(Variant::OBJECT, "frame", PROPERTY_HINT_RESOURCE_TYPE, "GraphFrame"), PropertyInfo(Variant::VECTOR2, "new_rect"))); - ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position"))); + ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "at_position"))); ADD_SIGNAL(MethodInfo("begin_node_move")); ADD_SIGNAL(MethodInfo("end_node_move")); + ADD_SIGNAL(MethodInfo("graph_elements_linked_to_frame_request", PropertyInfo(Variant::ARRAY, "elements"), PropertyInfo(Variant::STRING_NAME, "frame"))); ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset"))); BIND_ENUM_CONSTANT(SCROLL_ZOOMS); @@ -2371,12 +2778,12 @@ GraphEdit::GraphEdit() { add_child(top_layer, false, INTERNAL_MODE_BACK); top_layer->set_mouse_filter(MOUSE_FILTER_IGNORE); top_layer->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw)); - top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); + top_layer->connect(SceneStringName(draw), callable_mp(this, &GraphEdit::_top_layer_draw)); + top_layer->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); connections_layer = memnew(Control); - add_child(connections_layer, false, INTERNAL_MODE_FRONT); - connections_layer->connect("draw", callable_mp(this, &GraphEdit::_update_connections)); + add_child(connections_layer, false); + connections_layer->connect(SceneStringName(draw), callable_mp(this, &GraphEdit::_update_connections)); connections_layer->set_name("_connection_layer"); connections_layer->set_disable_visibility_clip(true); // Necessary, so it can draw freely and be offset. connections_layer->set_mouse_filter(MOUSE_FILTER_IGNORE); @@ -2388,7 +2795,7 @@ GraphEdit::GraphEdit() { top_connection_layer->set_mouse_filter(MOUSE_FILTER_PASS); top_connection_layer->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - top_connection_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_connection_layer_input)); + top_connection_layer->connect(SceneStringName(gui_input), callable_mp(this, &GraphEdit::_top_connection_layer_input)); dragged_connection_line = memnew(Line2D); dragged_connection_line->set_texture_mode(Line2D::LINE_TEXTURE_STRETCH); @@ -2438,7 +2845,7 @@ GraphEdit::GraphEdit() { zoom_minus_button->set_tooltip_text(ETR("Zoom Out")); zoom_minus_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(zoom_minus_button); - zoom_minus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus)); + zoom_minus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_minus)); zoom_reset_button = memnew(Button); zoom_reset_button->set_theme_type_variation("FlatButton"); @@ -2446,7 +2853,7 @@ GraphEdit::GraphEdit() { zoom_reset_button->set_tooltip_text(ETR("Zoom Reset")); zoom_reset_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(zoom_reset_button); - zoom_reset_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset)); + zoom_reset_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_reset)); zoom_plus_button = memnew(Button); zoom_plus_button->set_theme_type_variation("FlatButton"); @@ -2454,7 +2861,7 @@ GraphEdit::GraphEdit() { zoom_plus_button->set_tooltip_text(ETR("Zoom In")); zoom_plus_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(zoom_plus_button); - zoom_plus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus)); + zoom_plus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_plus)); // Grid controls. @@ -2466,7 +2873,7 @@ GraphEdit::GraphEdit() { toggle_grid_button->set_tooltip_text(ETR("Toggle the visual grid.")); toggle_grid_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(toggle_grid_button); - toggle_grid_button->connect("pressed", callable_mp(this, &GraphEdit::_show_grid_toggled)); + toggle_grid_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_show_grid_toggled)); toggle_snapping_button = memnew(Button); toggle_snapping_button->set_theme_type_variation("FlatButton"); @@ -2476,7 +2883,7 @@ GraphEdit::GraphEdit() { toggle_snapping_button->set_pressed(snapping_enabled); toggle_snapping_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(toggle_snapping_button); - toggle_snapping_button->connect("pressed", callable_mp(this, &GraphEdit::_snapping_toggled)); + toggle_snapping_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_snapping_toggled)); snapping_distance_spinbox = memnew(SpinBox); snapping_distance_spinbox->set_visible(show_grid_buttons); @@ -2498,12 +2905,12 @@ GraphEdit::GraphEdit() { minimap_button->set_pressed(show_grid); minimap_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(minimap_button); - minimap_button->connect("pressed", callable_mp(this, &GraphEdit::_minimap_toggled)); + minimap_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_minimap_toggled)); arrange_button = memnew(Button); arrange_button->set_theme_type_variation("FlatButton"); arrange_button->set_visible(show_arrange_button); - arrange_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes)); + arrange_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::arrange_nodes)); arrange_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(arrange_button); arrange_button->set_tooltip_text(ETR("Automatically arrange selected nodes.")); @@ -2525,7 +2932,7 @@ GraphEdit::GraphEdit() { minimap->set_offset(Side::SIDE_TOP, -minimap_size.height - MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_RIGHT, -MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_BOTTOM, -MINIMAP_OFFSET); - minimap->connect("draw", callable_mp(this, &GraphEdit::_minimap_draw)); + minimap->connect(SceneStringName(draw), callable_mp(this, &GraphEdit::_minimap_draw)); set_clip_contents(true); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index e24f039e84..20c98c462c 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -32,6 +32,7 @@ #define GRAPH_EDIT_H #include "scene/gui/box_container.h" +#include "scene/gui/graph_frame.h" #include "scene/gui/graph_node.h" class Button; @@ -294,6 +295,13 @@ private: float port_hotzone_outer_extent = 0.0; } theme_cache; + // This separates the children in two layers to ensure the order + // of both background nodes (e.g frame nodes) and foreground nodes (connectable nodes). + int background_nodes_separator_idx = 0; + + HashMap<StringName, HashSet<StringName>> frame_attached_nodes; + HashMap<StringName, StringName> linked_parent_map; + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); @@ -304,12 +312,15 @@ private: void _graph_element_selected(Node *p_node); void _graph_element_deselected(Node *p_node); - void _graph_element_moved_to_front(Node *p_node); - void _graph_element_resized(Vector2 p_new_minsize, Node *p_node); + void _graph_element_resize_request(const Vector2 &p_new_minsize, Node *p_node); + void _graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, GraphFrame *p_frame); void _graph_element_moved(Node *p_node); void _graph_node_slot_updated(int p_index, Node *p_node); void _graph_node_rect_changed(GraphNode *p_node); + void _ensure_node_order_from_root(const StringName &p_node); + void _ensure_node_order_from(Node *p_node); + void _update_scroll(); void _update_scroll_offset(); void _scroll_moved(double); @@ -317,7 +328,7 @@ private: void _top_connection_layer_input(const Ref<InputEvent> &p_ev); float _get_shader_line_width(); - void _draw_minimap_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color); + void _draw_minimap_connection_line(const Vector2 &p_from_graph_position, const Vector2 &p_to_graph_position, const Color &p_from_color, const Color &p_to_color); void _invalidate_connection_line_cache(); void _update_top_connection_layer(); void _update_connections(); @@ -332,6 +343,10 @@ private: Dictionary _get_closest_connection_at_point(const Vector2 &p_point, float p_max_distance = 4.0) const; TypedArray<Dictionary> _get_connections_intersecting_with_rect(const Rect2 &p_rect) const; + Rect2 _compute_shrinked_frame_rect(const GraphFrame *p_frame); + void _set_drag_frame_attached_nodes(GraphFrame *p_frame, bool p_drag); + void _set_position_of_frame_attached_nodes(GraphFrame *p_frame, const Vector2 &p_pos); + friend class GraphEditFilter; bool _filter_input(const Point2 &p_point); void _snapping_toggled(); @@ -377,6 +392,11 @@ public: PackedStringArray get_configuration_warnings() const override; + // This method has to be public (for undo redo). + // TODO: Find a better way to do this. + void _update_graph_frame(GraphFrame *p_frame); + + // Connection related methods. Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); @@ -397,6 +417,12 @@ public: void remove_valid_connection_type(int p_type, int p_with_type); bool is_valid_connection_type(int p_type, int p_with_type) const; + // GraphFrame related methods. + void attach_graph_element_to_frame(const StringName &p_graph_element, const StringName &p_parent_frame); + void detach_graph_element_from_frame(const StringName &p_graph_element); + GraphFrame *get_element_frame(const StringName &p_attached_graph_element); + TypedArray<StringName> get_attached_nodes_of_frame(const StringName &p_graph_frame); + void set_panning_scheme(PanningScheme p_scheme); PanningScheme get_panning_scheme() const; diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp index 49998beb42..fa1059c667 100644 --- a/scene/gui/graph_edit_arranger.cpp +++ b/scene/gui/graph_edit_arranger.cpp @@ -180,7 +180,7 @@ void GraphEditArranger::arrange_nodes() { if (graph_edit->is_snapping_enabled()) { float snapping_distance = graph_edit->get_snapping_distance(); - pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); + pos = pos.snappedf(snapping_distance); } graph_node->set_position_offset(pos); graph_node->set_drag(false); diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp index 7fa5b0ceec..e231b05d7f 100644 --- a/scene/gui/graph_element.cpp +++ b/scene/gui/graph_element.cpp @@ -49,14 +49,10 @@ void GraphElement::_resort() { Size2 size = get_size(); for (int i = 0; i < get_child_count(); i++) { - Control *child = Object::cast_to<Control>(get_child(i)); - if (!child || !child->is_visible_in_tree()) { - continue; - } - if (child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i)); + if (!child) { continue; } - fit_child_in_rect(child, Rect2(Point2(), size)); } } @@ -65,17 +61,13 @@ Size2 GraphElement::get_minimum_size() const { Size2 minsize; for (int i = 0; i < get_child_count(); i++) { Control *child = Object::cast_to<Control>(get_child(i)); - if (!child) { - continue; - } - if (child->is_set_as_top_level()) { + if (!child || child->is_set_as_top_level()) { continue; } Size2i size = child->get_combined_minimum_size(); - minsize.width = MAX(minsize.width, size.width); - minsize.height = MAX(minsize.height, size.height); + minsize = minsize.max(size); } return minsize; @@ -167,7 +159,11 @@ void GraphElement::gui_input(const Ref<InputEvent> &p_ev) { } if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - resizing = false; + if (resizing) { + resizing = false; + emit_signal(SNAME("resize_end"), get_size()); + return; + } } } @@ -238,7 +234,8 @@ void GraphElement::_bind_methods() { ADD_SIGNAL(MethodInfo("raise_request")); ADD_SIGNAL(MethodInfo("delete_request")); - ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_minsize"))); + ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_size"))); + ADD_SIGNAL(MethodInfo("resize_end", PropertyInfo(Variant::VECTOR2, "new_size"))); ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); ADD_SIGNAL(MethodInfo("position_offset_changed")); diff --git a/scene/gui/graph_frame.cpp b/scene/gui/graph_frame.cpp new file mode 100644 index 0000000000..8cd7dbbeb5 --- /dev/null +++ b/scene/gui/graph_frame.cpp @@ -0,0 +1,353 @@ +/**************************************************************************/ +/* graph_frame.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "graph_frame.h" + +#include "core/string/translation.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/resources/style_box_flat.h" +#include "scene/resources/style_box_texture.h" +#include "scene/theme/theme_db.h" + +void GraphFrame::gui_input(const Ref<InputEvent> &p_ev) { + ERR_FAIL_COND(p_ev.is_null()); + + Ref<InputEventMouseButton> mb = p_ev; + if (mb.is_valid()) { + ERR_FAIL_NULL_MSG(get_parent_control(), "GraphFrame must be the child of a GraphEdit node."); + + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + Vector2 mpos = mb->get_position(); + + Ref<Texture2D> resizer = theme_cache.resizer; + + if (resizable && mpos.x > get_size().x - resizer->get_width() && mpos.y > get_size().y - resizer->get_height()) { + resizing = true; + resizing_from = mpos; + resizing_from_size = get_size(); + accept_event(); + return; + } + + emit_signal(SNAME("raise_request")); + } + + if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + if (resizing) { + resizing = false; + emit_signal(SNAME("resize_end"), get_size()); + return; + } + } + } + + Ref<InputEventMouseMotion> mm = p_ev; + + // Only resize if the frame is not auto-resizing based on linked nodes. + if (resizing && !autoshrink_enabled && mm.is_valid()) { + Vector2 mpos = mm->get_position(); + + Vector2 diff = mpos - resizing_from; + + emit_signal(SNAME("resize_request"), resizing_from_size + diff); + } +} + +Control::CursorShape GraphFrame::get_cursor_shape(const Point2 &p_pos) const { + if (resizable && !autoshrink_enabled) { + if (resizing || (p_pos.x > get_size().x - theme_cache.resizer->get_width() && p_pos.y > get_size().y - theme_cache.resizer->get_height())) { + return CURSOR_FDIAGSIZE; + } + } + + return Control::get_cursor_shape(p_pos); +} + +void GraphFrame::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAW: { + // Used for layout calculations. + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + + // Used for drawing. + Ref<StyleBox> sb_to_draw_panel = selected ? theme_cache.panel_selected : sb_panel; + Ref<StyleBox> sb_to_draw_titlebar = selected ? theme_cache.titlebar_selected : sb_titlebar; + Ref<StyleBoxFlat> sb_panel_flat = sb_to_draw_panel; + Ref<StyleBoxTexture> sb_panel_texture = sb_to_draw_panel; + + Rect2 titlebar_rect(Point2(), titlebar_hbox->get_size() + sb_titlebar->get_minimum_size()); + Size2 body_size = get_size(); + body_size.y -= titlebar_rect.size.height; + Rect2 body_rect(Point2(0, titlebar_rect.size.height), body_size); + + // Draw body stylebox. + if (tint_color_enabled) { + if (sb_panel_flat.is_valid()) { + Color original_border_color = sb_panel_flat->get_border_color(); + sb_panel_flat = sb_panel_flat->duplicate(); + sb_panel_flat->set_bg_color(tint_color); + sb_panel_flat->set_border_color(selected ? original_border_color : tint_color.lightened(0.3)); + draw_style_box(sb_panel_flat, body_rect); + } else if (sb_panel_texture.is_valid()) { + sb_panel_texture = sb_panel_texture->duplicate(); + sb_panel_texture->set_modulate(tint_color); + draw_style_box(sb_panel_texture, body_rect); + } + } else { + draw_style_box(sb_panel_flat, body_rect); + } + + // Draw title bar stylebox above. + draw_style_box(sb_to_draw_titlebar, titlebar_rect); + + // Only draw the resize handle if the frame is not auto-resizing. + if (resizable && !autoshrink_enabled) { + Ref<Texture2D> resizer = theme_cache.resizer; + Color resizer_color = theme_cache.resizer_color; + if (resizable) { + draw_texture(resizer, get_size() - resizer->get_size(), resizer_color); + } + } + + } break; + } +} + +void GraphFrame::_resort() { + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + + // Resort titlebar first. + Size2 titlebar_size = Size2(get_size().width, titlebar_hbox->get_size().height); + titlebar_size -= sb_titlebar->get_minimum_size(); + Rect2 titlebar_rect = Rect2(sb_titlebar->get_offset(), titlebar_size); + + fit_child_in_rect(titlebar_hbox, titlebar_rect); + + // After resort the children of the titlebar container may have changed their height (e.g. Label autowrap). + Size2i titlebar_min_size = titlebar_hbox->get_combined_minimum_size(); + + Size2 size = get_size() - sb_panel->get_minimum_size() - Size2(0, titlebar_min_size.height + sb_titlebar->get_minimum_size().height); + Point2 offset = Point2(sb_panel->get_margin(SIDE_LEFT), sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height); + + for (int i = 0; i < get_child_count(false); i++) { + Control *child = as_sortable_control(get_child(i, false)); + if (!child) { + continue; + } + fit_child_in_rect(child, Rect2(offset, size)); + } +} + +void GraphFrame::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphFrame::set_title); + ClassDB::bind_method(D_METHOD("get_title"), &GraphFrame::get_title); + + ClassDB::bind_method(D_METHOD("get_titlebar_hbox"), &GraphFrame::get_titlebar_hbox); + + ClassDB::bind_method(D_METHOD("set_autoshrink_enabled", "shrink"), &GraphFrame::set_autoshrink_enabled); + ClassDB::bind_method(D_METHOD("is_autoshrink_enabled"), &GraphFrame::is_autoshrink_enabled); + + ClassDB::bind_method(D_METHOD("set_autoshrink_margin", "autoshrink_margin"), &GraphFrame::set_autoshrink_margin); + ClassDB::bind_method(D_METHOD("get_autoshrink_margin"), &GraphFrame::get_autoshrink_margin); + + ClassDB::bind_method(D_METHOD("set_drag_margin", "drag_margin"), &GraphFrame::set_drag_margin); + ClassDB::bind_method(D_METHOD("get_drag_margin"), &GraphFrame::get_drag_margin); + + ClassDB::bind_method(D_METHOD("set_tint_color_enabled", "enable"), &GraphFrame::set_tint_color_enabled); + ClassDB::bind_method(D_METHOD("is_tint_color_enabled"), &GraphFrame::is_tint_color_enabled); + + ClassDB::bind_method(D_METHOD("set_tint_color", "color"), &GraphFrame::set_tint_color); + ClassDB::bind_method(D_METHOD("get_tint_color"), &GraphFrame::get_tint_color); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoshrink_enabled"), "set_autoshrink_enabled", "is_autoshrink_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autoshrink_margin", PROPERTY_HINT_RANGE, "0,128,1"), "set_autoshrink_margin", "get_autoshrink_margin"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_margin", PROPERTY_HINT_RANGE, "0,128,1"), "set_drag_margin", "get_drag_margin"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tint_color_enabled"), "set_tint_color_enabled", "is_tint_color_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_color"), "set_tint_color", "get_tint_color"); + + ADD_SIGNAL(MethodInfo(SNAME("autoshrink_changed"))); + + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, panel); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, panel_selected); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, titlebar); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, titlebar_selected); + + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphFrame, resizer); + BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphFrame, resizer_color); +} + +void GraphFrame::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "resizable") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +void GraphFrame::set_title(const String &p_title) { + if (title == p_title) { + return; + } + title = p_title; + if (title_label) { + title_label->set_text(title); + } + update_minimum_size(); +} + +String GraphFrame::get_title() const { + return title; +} + +void GraphFrame::set_autoshrink_enabled(bool p_shrink) { + if (autoshrink_enabled == p_shrink) { + return; + } + + autoshrink_enabled = p_shrink; + + emit_signal("autoshrink_changed", get_size()); + queue_redraw(); +} + +bool GraphFrame::is_autoshrink_enabled() const { + return autoshrink_enabled; +} + +void GraphFrame::set_autoshrink_margin(const int &p_margin) { + if (autoshrink_margin == p_margin) { + return; + } + + autoshrink_margin = p_margin; + + emit_signal("autoshrink_changed", get_size()); +} + +int GraphFrame::get_autoshrink_margin() const { + return autoshrink_margin; +} + +HBoxContainer *GraphFrame::get_titlebar_hbox() { + return titlebar_hbox; +} + +void GraphFrame::set_drag_margin(int p_margin) { + drag_margin = p_margin; +} + +int GraphFrame::get_drag_margin() const { + return drag_margin; +} + +void GraphFrame::set_tint_color_enabled(bool p_enable) { + tint_color_enabled = p_enable; + queue_redraw(); +} + +bool GraphFrame::is_tint_color_enabled() const { + return tint_color_enabled; +} + +void GraphFrame::set_tint_color(const Color &p_color) { + tint_color = p_color; + queue_redraw(); +} + +Color GraphFrame::get_tint_color() const { + return tint_color; +} + +bool GraphFrame::has_point(const Point2 &p_point) const { + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + Ref<Texture2D> resizer = theme_cache.resizer; + + if (Rect2(get_size() - resizer->get_size(), resizer->get_size()).has_point(p_point)) { + return true; + } + + // For grabbing on the titlebar. + int titlebar_height = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height; + if (Rect2(0, 0, get_size().width, titlebar_height).has_point(p_point)) { + return true; + } + + // Allow grabbing on all sides of the frame. + Rect2 frame_rect = Rect2(0, 0, get_size().width, get_size().height); + Rect2 no_drag_rect = frame_rect.grow(-drag_margin); + + if (frame_rect.has_point(p_point) && !no_drag_rect.has_point(p_point)) { + return true; + } + + return false; +} + +Size2 GraphFrame::get_minimum_size() const { + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + + Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size(); + + for (int i = 0; i < get_child_count(false); i++) { + Control *child = as_sortable_control(get_child(i, false)); + if (!child) { + continue; + } + + Size2i size = child->get_combined_minimum_size(); + size.width += sb_panel->get_minimum_size().width; + + minsize.x = MAX(minsize.x, size.x); + minsize.y += MAX(minsize.y, size.y); + } + + minsize.height += sb_panel->get_minimum_size().height; + + return minsize; +} + +GraphFrame::GraphFrame() { + titlebar_hbox = memnew(HBoxContainer); + titlebar_hbox->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(titlebar_hbox, false, INTERNAL_MODE_FRONT); + + title_label = memnew(Label); + title_label->set_theme_type_variation("GraphFrameTitleLabel"); + title_label->set_h_size_flags(SIZE_EXPAND_FILL); + title_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + titlebar_hbox->add_child(title_label); + + set_mouse_filter(MOUSE_FILTER_STOP); +} diff --git a/scene/gui/graph_frame.h b/scene/gui/graph_frame.h new file mode 100644 index 0000000000..21346586c8 --- /dev/null +++ b/scene/gui/graph_frame.h @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* graph_frame.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GRAPH_FRAME_H +#define GRAPH_FRAME_H + +#include "scene/gui/graph_element.h" + +class HBoxContainer; + +class GraphFrame : public GraphElement { + GDCLASS(GraphFrame, GraphElement); + + struct _MinSizeCache { + int min_size = 0; + bool will_stretch = false; + int final_size = 0; + }; + + struct ThemeCache { + Ref<StyleBox> panel; + Ref<StyleBox> panel_selected; + Ref<StyleBox> titlebar; + Ref<StyleBox> titlebar_selected; + + Ref<Texture2D> resizer; + Color resizer_color; + } theme_cache; + +private: + String title; + + HBoxContainer *titlebar_hbox = nullptr; + Label *title_label = nullptr; + + bool autoshrink_enabled = true; + int autoshrink_margin = 40; + int drag_margin = 16; + + bool tint_color_enabled = false; + Color tint_color = Color(0.3, 0.3, 0.3, 0.75); + +protected: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + + void _notification(int p_what); + static void _bind_methods(); + + void _validate_property(PropertyInfo &p_property) const; + + virtual void _resort() override; + +public: + void set_title(const String &p_title); + String get_title() const; + + void set_autoshrink_enabled(bool p_enable); + bool is_autoshrink_enabled() const; + + void set_autoshrink_margin(const int &p_margin); + int get_autoshrink_margin() const; + + HBoxContainer *get_titlebar_hbox(); + + void set_drag_margin(int p_margin); + int get_drag_margin() const; + + void set_tint_color_enabled(bool p_enable); + bool is_tint_color_enabled() const; + + void set_tint_color(const Color &p_tint_color); + Color get_tint_color() const; + + virtual bool has_point(const Point2 &p_point) const override; + virtual Size2 get_minimum_size() const override; + + GraphFrame(); +}; + +#endif // GRAPH_FRAME_H diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 1b960a9b62..d804f83e1c 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -175,9 +175,8 @@ void GraphNode::_resort() { HashMap<Control *, _MinSizeCache> min_size_cache; for (int i = 0; i < get_child_count(false); i++) { - Control *child = Object::cast_to<Control>(get_child(i, false)); - - if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i, false)); + if (!child) { continue; } @@ -218,8 +217,8 @@ void GraphNode::_resort() { bool refit_successful = true; for (int i = 0; i < get_child_count(false); i++) { - Control *child = Object::cast_to<Control>(get_child(i, false)); - if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i, false)); + if (!child) { continue; } @@ -256,8 +255,8 @@ void GraphNode::_resort() { int width = new_size.width - sb_panel->get_minimum_size().width; int valid_children_idx = 0; for (int i = 0; i < get_child_count(false); i++) { - Control *child = Object::cast_to<Control>(get_child(i, false)); - if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i, false)); + if (!child) { continue; } @@ -336,7 +335,8 @@ void GraphNode::_notification(int p_what) { int width = get_size().width - sb_panel->get_minimum_size().x; - if (get_child_count() > 0) { + // Take the HboxContainer child into account. + if (get_child_count(false) > 0) { int slot_index = 0; for (const KeyValue<int, Slot> &E : slot_table) { if (E.key < 0 || E.key >= slot_y_cache.size()) { @@ -604,6 +604,14 @@ void GraphNode::set_slot_draw_stylebox(int p_slot_index, bool p_enable) { emit_signal(SNAME("slot_updated"), p_slot_index); } +void GraphNode::set_ignore_invalid_connection_type(bool p_ignore) { + ignore_invalid_connection_type = p_ignore; +} + +bool GraphNode::is_ignoring_valid_connection_type() const { + return ignore_invalid_connection_type; +} + Size2 GraphNode::get_minimum_size() const { Ref<StyleBox> sb_panel = theme_cache.panel; Ref<StyleBox> sb_titlebar = theme_cache.titlebar; @@ -613,8 +621,8 @@ Size2 GraphNode::get_minimum_size() const { Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size(); for (int i = 0; i < get_child_count(false); i++) { - Control *child = Object::cast_to<Control>(get_child(i, false)); - if (!child || !child->is_visible() || child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i, false)); + if (!child) { continue; } @@ -647,6 +655,7 @@ void GraphNode::_port_pos_update() { left_port_cache.clear(); right_port_cache.clear(); int vertical_ofs = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height + sb_panel->get_margin(SIDE_TOP); + int slot_index = 0; for (int i = 0; i < get_child_count(false); i++) { Control *child = Object::cast_to<Control>(get_child(i, false)); @@ -656,27 +665,28 @@ void GraphNode::_port_pos_update() { Size2i size = child->get_rect().size; - if (slot_table.has(i)) { - if (slot_table[i].enable_left) { + if (slot_table.has(slot_index)) { + if (slot_table[slot_index].enable_left) { PortCache port_cache; port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2); - port_cache.type = slot_table[i].type_left; - port_cache.color = slot_table[i].color_left; - port_cache.slot_index = child->get_index(false); + port_cache.type = slot_table[slot_index].type_left; + port_cache.color = slot_table[slot_index].color_left; + port_cache.slot_index = slot_index; left_port_cache.push_back(port_cache); } - if (slot_table[i].enable_right) { + if (slot_table[slot_index].enable_right) { PortCache port_cache; port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2); - port_cache.type = slot_table[i].type_right; - port_cache.color = slot_table[i].color_right; - port_cache.slot_index = child->get_index(false); + port_cache.type = slot_table[slot_index].type_right; + port_cache.color = slot_table[slot_index].color_right; + port_cache.slot_index = slot_index; right_port_cache.push_back(port_cache); } } vertical_ofs += separation; vertical_ofs += size.height; + slot_index++; } port_pos_dirty = false; @@ -857,6 +867,9 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "slot_index"), &GraphNode::is_slot_draw_stylebox); ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "slot_index", "enable"), &GraphNode::set_slot_draw_stylebox); + ClassDB::bind_method(D_METHOD("set_ignore_invalid_connection_type", "ignore"), &GraphNode::set_ignore_invalid_connection_type); + ClassDB::bind_method(D_METHOD("is_ignoring_valid_connection_type"), &GraphNode::is_ignoring_valid_connection_type); + ClassDB::bind_method(D_METHOD("get_input_port_count"), &GraphNode::get_input_port_count); ClassDB::bind_method(D_METHOD("get_input_port_position", "port_idx"), &GraphNode::get_input_port_position); ClassDB::bind_method(D_METHOD("get_input_port_type", "port_idx"), &GraphNode::get_input_port_type); @@ -872,6 +885,8 @@ void GraphNode::_bind_methods() { GDVIRTUAL_BIND(_draw_port, "slot_index", "position", "left", "color") ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_invalid_connection_type"), "set_ignore_invalid_connection_type", "is_ignoring_valid_connection_type"); + ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "slot_index"))); BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index a0610b37fb..27af3192c8 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -62,9 +62,9 @@ class GraphNode : public GraphElement { }; struct _MinSizeCache { - int min_size; - bool will_stretch; - int final_size; + int min_size = 0; + bool will_stretch = false; + int final_size = 0; }; HBoxContainer *titlebar_hbox = nullptr; @@ -95,6 +95,8 @@ class GraphNode : public GraphElement { bool port_pos_dirty = true; + bool ignore_invalid_connection_type = false; + void _port_pos_update(); protected: @@ -147,6 +149,9 @@ public: bool is_slot_draw_stylebox(int p_slot_index) const; void set_slot_draw_stylebox(int p_slot_index, bool p_enable); + void set_ignore_invalid_connection_type(bool p_ignore); + bool is_ignoring_valid_connection_type() const; + int get_input_port_count(); Vector2 get_input_port_position(int p_port_idx); int get_input_port_type(int p_port_idx); diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index a4baf3bb8d..a67bba786b 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -44,11 +44,8 @@ void GridContainer::_notification(int p_what) { // Compute the per-column/per-row data. int valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } @@ -186,8 +183,8 @@ void GridContainer::_notification(int p_what) { valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } int row = valid_controls_index / columns; @@ -282,8 +279,8 @@ Size2 GridContainer::get_minimum_size() const { int valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } int row = valid_controls_index / columns; diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 8376ef48b6..cfb46aebc8 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -1882,6 +1882,7 @@ void ItemList::_bind_methods() { Item defaults(true); base_property_helper.set_prefix("item_"); + base_property_helper.set_array_length_getter(&ItemList::get_item_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &ItemList::set_item_text, &ItemList::get_item_text); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &ItemList::set_item_icon, &ItemList::get_item_icon); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "selectable"), defaults.selectable, &ItemList::set_item_selectable, &ItemList::is_item_selectable); @@ -1893,7 +1894,7 @@ ItemList::ItemList() { add_child(scroll_bar, false, INTERNAL_MODE_FRONT); scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed)); - connect("mouse_exited", callable_mp(this, &ItemList::_mouse_exited)); + connect(SceneStringName(mouse_exited), callable_mp(this, &ItemList::_mouse_exited)); set_focus_mode(FOCUS_ALL); set_clip_contents(true); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 56da1332e7..42b4e56b48 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -316,7 +316,7 @@ inline void draw_glyph(const Glyph &p_gl, const RID &p_canvas, const Color &p_fo } } -inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { +inline void draw_glyph_shadow(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_shadow_color, int p_shadow_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { if (p_gl.font_rid != RID()) { if (p_font_shadow_color.a > 0) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); @@ -324,6 +324,11 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col if (p_font_shadow_color.a > 0 && p_shadow_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); } + } +} + +inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_outline_color, int p_outline_size, const Vector2 &p_ofs) { + if (p_gl.font_rid != RID()) { if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); } @@ -537,25 +542,36 @@ void Label::_notification(int p_what) { const Glyph *ellipsis_glyphs = TS->shaped_text_get_ellipsis_glyphs(lines_rid[i]); int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); - // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. - int processed_glyphs_ol = processed_glyphs; - if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) { - Vector2 offset = ofs; + // Draw shadow, outline and text. Note: Do not merge this into the single loop iteration, to prevent overlaps. + for (int step = DRAW_STEP_SHADOW; step < DRAW_STEP_MAX; step++) { + if (step == DRAW_STEP_SHADOW && (font_shadow_color.a == 0)) { + continue; + } + if (step == DRAW_STEP_OUTLINE && (outline_size <= 0 || font_outline_color.a == 0)) { + continue; + } + + int processed_glyphs_step = processed_glyphs; + Vector2 offset_step = ofs; // Draw RTL ellipsis string when necessary. if (rtl && ellipsis_pos >= 0) { for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); if (!skip) { - draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + if (step == DRAW_STEP_SHADOW) { + draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs); + } else if (step == DRAW_STEP_OUTLINE) { + draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step); + } else if (step == DRAW_STEP_TEXT) { + draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, offset_step); + } } - processed_glyphs_ol++; - offset.x += ellipsis_glyphs[gl_idx].advance; + processed_glyphs_step++; + offset_step.x += ellipsis_glyphs[gl_idx].advance; } } } - // Draw main text. for (int j = 0; j < gl_size; j++) { // Trim when necessary. @@ -571,85 +587,37 @@ void Label::_notification(int p_what) { } } for (int k = 0; k < glyphs[j].repeat; k++) { - bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - - // Draw glyph outlines and shadow. + bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); if (!skip) { - draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + if (step == DRAW_STEP_SHADOW) { + draw_glyph_shadow(glyphs[j], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs); + } else if (step == DRAW_STEP_OUTLINE) { + draw_glyph_outline(glyphs[j], ci, font_outline_color, outline_size, offset_step); + } else if (step == DRAW_STEP_TEXT) { + draw_glyph(glyphs[j], ci, font_color, offset_step); + } } - processed_glyphs_ol++; - offset.x += glyphs[j].advance; + processed_glyphs_step++; + offset_step.x += glyphs[j].advance; } } // Draw LTR ellipsis string when necessary. if (!rtl && ellipsis_pos >= 0) { for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); if (!skip) { - draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + if (step == DRAW_STEP_SHADOW) { + draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs); + } else if (step == DRAW_STEP_OUTLINE) { + draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step); + } else if (step == DRAW_STEP_TEXT) { + draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, offset_step); + } } - processed_glyphs_ol++; - offset.x += ellipsis_glyphs[gl_idx].advance; - } - } - } - } - - // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. - - // Draw RTL ellipsis string when necessary. - if (rtl && ellipsis_pos >= 0) { - for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. - if (!skip) { - draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); - } - processed_glyphs++; - ofs.x += ellipsis_glyphs[gl_idx].advance; - } - } - } - - // Draw main text. - for (int j = 0; j < gl_size; j++) { - // Trim when necessary. - if (trim_pos >= 0) { - if (rtl) { - if (j < trim_pos) { - continue; - } - } else { - if (j >= trim_pos) { - break; - } - } - } - for (int k = 0; k < glyphs[j].repeat; k++) { - bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); - - // Draw glyph outlines and shadow. - if (!skip) { - draw_glyph(glyphs[j], ci, font_color, ofs); - } - processed_glyphs++; - ofs.x += glyphs[j].advance; - } - } - // Draw LTR ellipsis string when necessary. - if (!rtl && ellipsis_pos >= 0) { - for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. - if (!skip) { - draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); + processed_glyphs_step++; + offset_step.x += ellipsis_glyphs[gl_idx].advance; } - processed_glyphs++; - ofs.x += ellipsis_glyphs[gl_idx].advance; } } } diff --git a/scene/gui/label.h b/scene/gui/label.h index 4bd0e53605..e0ebca944a 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -38,6 +38,13 @@ class Label : public Control { GDCLASS(Label, Control); private: + enum LabelDrawStep { + DRAW_STEP_SHADOW, + DRAW_STEP_OUTLINE, + DRAW_STEP_TEXT, + DRAW_STEP_MAX, + }; + HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT; VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP; String text; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 1a94d92855..729e219825 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -771,7 +771,7 @@ void LineEdit::_notification(int p_what) { switch (p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_ENTER_TREE: { - if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) { + if (Engine::get_singleton()->is_editor_hint() && !is_part_of_edited_scene()) { set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink")); set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval")); @@ -1805,7 +1805,6 @@ void LineEdit::clear_internal() { } Size2 LineEdit::get_minimum_size() const { - Ref<StyleBox> style = theme_cache.normal; Ref<Font> font = theme_cache.font; int font_size = theme_cache.font_size; @@ -1834,7 +1833,8 @@ Size2 LineEdit::get_minimum_size() const { } min_size.width += icon_max_width; - return style->get_minimum_size() + min_size; + Size2 style_min_size = theme_cache.normal->get_minimum_size().max(theme_cache.read_only->get_minimum_size()); + return style_min_size + min_size; } void LineEdit::deselect() { @@ -2473,8 +2473,8 @@ void LineEdit::_generate_context_menu() { menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); - menu->connect(SNAME("focus_entered"), callable_mp(this, &LineEdit::_validate_caret_can_draw)); - menu->connect(SNAME("focus_exited"), callable_mp(this, &LineEdit::_validate_caret_can_draw)); + menu->connect(SceneStringName(focus_entered), callable_mp(this, &LineEdit::_validate_caret_can_draw)); + menu->connect(SceneStringName(focus_exited), callable_mp(this, &LineEdit::_validate_caret_can_draw)); } void LineEdit::_update_context_menu() { diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index 6d331afbb5..06e4a7cc13 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -37,13 +37,7 @@ Size2 MarginContainer::get_minimum_size() const { for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { - continue; - } - if (c->is_set_as_top_level()) { - continue; - } - if (!c->is_visible()) { + if (!c || !c->is_visible() || c->is_set_as_top_level()) { continue; } @@ -103,13 +97,10 @@ void MarginContainer::_notification(int p_what) { Size2 s = get_size(); for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + Control *c = as_sortable_control(get_child(i)); if (!c) { continue; } - if (c->is_set_as_top_level()) { - continue; - } int w = s.width - theme_cache.margin_left - theme_cache.margin_right; int h = s.height - theme_cache.margin_top - theme_cache.margin_bottom; diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 8eb455d0ac..2ce12794a7 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -236,12 +236,12 @@ void MenuBar::bind_global_menu() { RID submenu_rid = popups[i]->bind_global_menu(); if (!popups[i]->is_system_menu()) { int index = nmenu->add_submenu_item(main_menu, menu_cache[i].name, submenu_rid, global_menu_tag + "#" + itos(i), global_start_idx + i); - menu_cache.write[i].global_index = index; + menu_cache.write[i].submenu_rid = submenu_rid; nmenu->set_item_hidden(main_menu, index, menu_cache[i].hidden); nmenu->set_item_disabled(main_menu, index, menu_cache[i].disabled); nmenu->set_item_tooltip(main_menu, index, menu_cache[i].tooltip); } else { - menu_cache.write[i].global_index = -1; + menu_cache.write[i].submenu_rid = RID(); } } } @@ -257,11 +257,14 @@ void MenuBar::unbind_global_menu() { Vector<PopupMenu *> popups = _get_popups(); for (int i = menu_cache.size() - 1; i >= 0; i--) { if (!popups[i]->is_system_menu()) { - popups[i]->unbind_global_menu(); - if (menu_cache[i].global_index >= 0) { - nmenu->remove_item(main_menu, menu_cache[i].global_index); + if (menu_cache[i].submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid); + if (item_idx >= 0) { + nmenu->remove_item(main_menu, item_idx); + } } - menu_cache.write[i].global_index = -1; + popups[i]->unbind_global_menu(); + menu_cache.write[i].submenu_rid = RID(); } } @@ -292,8 +295,11 @@ void MenuBar::_notification(int p_what) { RID main_menu = is_global ? nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID) : RID(); for (int i = 0; i < menu_cache.size(); i++) { shape(menu_cache.write[i]); - if (is_global && menu_cache[i].global_index >= 0) { - nmenu->set_item_text(main_menu, menu_cache[i].global_index, atr(menu_cache[i].name)); + if (is_global && menu_cache[i].submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[i].name)); + } } } } break; @@ -500,8 +506,11 @@ void MenuBar::_refresh_menu_names() { if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) { menu_cache.write[i].name = popups[i]->get_name(); shape(menu_cache.write[i]); - if (is_global && menu_cache[i].global_index >= 0) { - nmenu->set_item_text(main_menu, menu_cache[i].global_index, atr(menu_cache[i].name)); + if (is_global && menu_cache[i].submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[i].name)); + } } } } @@ -554,8 +563,8 @@ void MenuBar::add_child_notify(Node *p_child) { RID submenu_rid = pm->bind_global_menu(); if (!pm->is_system_menu()) { - int index = nmenu->add_submenu_item(main_menu, atr(menu.name), submenu_rid, global_menu_tag + "#" + itos(menu_cache.size() - 1), _find_global_start_index() + menu_cache.size() - 1); - menu_cache.write[menu_cache.size() - 1].global_index = index; + nmenu->add_submenu_item(main_menu, atr(menu.name), submenu_rid, global_menu_tag + "#" + itos(menu_cache.size() - 1), _find_global_start_index() + menu_cache.size() - 1); + menu_cache.write[menu_cache.size() - 1].submenu_rid = submenu_rid; } } update_minimum_size(); @@ -589,13 +598,14 @@ void MenuBar::move_child_notify(Node *p_child) { RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); int global_start = _find_global_start_index(); - if (menu.global_index >= 0) { - nmenu->remove_item(main_menu, menu.global_index); + if (menu.submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu.submenu_rid); + if (item_idx >= 0) { + nmenu->remove_item(main_menu, item_idx); + } } if (new_idx != -1) { - RID submenu_rid = pm->bind_global_menu(); - int index = nmenu->add_submenu_item(main_menu, atr(menu.name), submenu_rid, global_menu_tag + "#" + itos(new_idx), global_start + new_idx); - menu_cache.write[new_idx].global_index = index; + nmenu->add_submenu_item(main_menu, atr(menu.name), menu.submenu_rid, global_menu_tag + "#" + itos(new_idx), global_start + new_idx); } } } @@ -611,20 +621,22 @@ void MenuBar::remove_child_notify(Node *p_child) { int idx = get_menu_idx_from_control(pm); - menu_cache.remove_at(idx); - if (!global_menu_tag.is_empty()) { if (!pm->is_system_menu()) { - pm->unbind_global_menu(); - if (menu_cache[idx].global_index >= 0) { + if (menu_cache[idx].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->remove_item(main_menu, menu_cache[idx].global_index); - menu_cache.write[idx].global_index = -1; + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[idx].submenu_rid); + if (item_idx >= 0) { + nmenu->remove_item(main_menu, item_idx); + } } + pm->unbind_global_menu(); } } + menu_cache.remove_at(idx); + p_child->remove_meta("_menu_name"); p_child->remove_meta("_menu_tooltip"); @@ -817,10 +829,13 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) { } menu_cache.write[p_menu].name = p_title; shape(menu_cache.write[p_menu]); - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_text(main_menu, menu_cache[p_menu].global_index, atr(menu_cache[p_menu].name)); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[p_menu].name)); + } } update_minimum_size(); } @@ -835,10 +850,13 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) { PopupMenu *pm = get_menu_popup(p_menu); pm->set_meta("_menu_tooltip", p_tooltip); menu_cache.write[p_menu].tooltip = p_tooltip; - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_tooltip(main_menu, menu_cache[p_menu].global_index, p_tooltip); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_tooltip(main_menu, item_idx, p_tooltip); + } } } @@ -850,10 +868,13 @@ String MenuBar::get_menu_tooltip(int p_menu) const { void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) { ERR_FAIL_INDEX(p_menu, menu_cache.size()); menu_cache.write[p_menu].disabled = p_disabled; - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_disabled(main_menu, menu_cache[p_menu].global_index, p_disabled); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_disabled(main_menu, item_idx, p_disabled); + } } } @@ -865,10 +886,13 @@ bool MenuBar::is_menu_disabled(int p_menu) const { void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) { ERR_FAIL_INDEX(p_menu, menu_cache.size()); menu_cache.write[p_menu].hidden = p_hidden; - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_hidden(main_menu, menu_cache[p_menu].global_index, p_hidden); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_hidden(main_menu, item_idx, p_hidden); + } } update_minimum_size(); } diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index 631b791e1b..04f6afc2fa 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -55,7 +55,7 @@ class MenuBar : public Control { Ref<TextLine> text_buf; bool hidden = false; bool disabled = false; - int global_index = -1; + RID submenu_rid; Menu(const String &p_name) { name = p_name; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 89c627a7a8..998f99b2f9 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -153,54 +153,25 @@ void MenuButton::_notification(int p_what) { } bool MenuButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); return valid; } return false; } bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); + r_ret = popup->get(sname.trim_prefix("popup/"), &valid); return valid; } return false; } -void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"); - pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i)); - pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); @@ -215,6 +186,18 @@ void MenuButton::_bind_methods() { ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_SIGNAL(MethodInfo("about_to_popup")); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.set_array_length_getter(&MenuButton::get_item_count); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator); } void MenuButton::set_disable_shortcuts(bool p_disabled) { @@ -235,6 +218,8 @@ MenuButton::MenuButton(const String &p_text) : add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true)); popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index eea6b8e877..2bd577ddd0 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class MenuButton : public Button { GDCLASS(MenuButton, Button); @@ -42,13 +43,18 @@ class MenuButton : public Button { bool disable_shortcuts = false; PopupMenu *popup = nullptr; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _popup_visibility_changed(bool p_visible); protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); virtual void shortcut_input(const Ref<InputEvent> &p_event) override; diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp index e2ae824e60..f9181d2a2d 100644 --- a/scene/gui/nine_patch_rect.cpp +++ b/scene/gui/nine_patch_rect.cpp @@ -30,7 +30,6 @@ #include "nine_patch_rect.h" -#include "scene/scene_string_names.h" #include "servers/rendering_server.h" void NinePatchRect::_notification(int p_what) { @@ -111,7 +110,7 @@ void NinePatchRect::set_texture(const Ref<Texture2D> &p_tex) { queue_redraw(); update_minimum_size(); - emit_signal(SceneStringNames::get_singleton()->texture_changed); + emit_signal(SceneStringName(texture_changed)); } Ref<Texture2D> NinePatchRect::get_texture() const { diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 0e10652f07..68e72ea996 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -60,7 +60,7 @@ Size2 OptionButton::get_minimum_size() const { } if (has_theme_icon(SNAME("arrow"))) { - const Size2 padding = _get_current_stylebox()->get_minimum_size(); + const Size2 padding = _get_largest_stylebox_size(); const Size2 arrow_size = theme_cache.arrow_icon->get_size(); Size2 content_size = minsize - padding; @@ -154,23 +154,20 @@ void OptionButton::_notification(int p_what) { } bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } + int index; + const String sname = p_name; + if (property_helper.is_property_valid(sname, &index)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); - int idx = components[1].get_slice("_", 1).to_int(); - if (idx == current) { + if (index == current) { // Force refreshing currently displayed item. current = NONE_SELECTED; - _select(idx, false); + _select(index, false); } + const String property = sname.get_slice("/", 2); if (property == "text" || property == "icon") { _queue_update_size_cache(); } @@ -180,42 +177,6 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { return false; } -bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } - - bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); - return valid; - } - return false; -} - -void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void OptionButton::_focused(int p_which) { emit_signal(SNAME("item_focused"), p_which); } @@ -606,6 +567,16 @@ void OptionButton::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, OptionButton, arrow_icon, "arrow"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, arrow_margin); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, modulate_arrow); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.set_array_length_getter(&OptionButton::get_item_count); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator); } void OptionButton::set_disable_shortcuts(bool p_disabled) { @@ -625,6 +596,8 @@ OptionButton::OptionButton(const String &p_text) : popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected)); popup->connect("id_focused", callable_mp(this, &OptionButton::_focused)); popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 9c15b295a9..4b5164161a 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class OptionButton : public Button { GDCLASS(OptionButton, Button); @@ -64,11 +65,15 @@ class OptionButton : public Button { int modulate_arrow = 0; } theme_cache; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _focused(int p_which); void _selected(int p_which); void _select(int p_which, bool p_emit = false); void _select_int(int p_which); void _refresh_size_cache(); + void _dummy_setter() {} // Stub for PropertyListHelper (_set() doesn't use it). virtual void pressed() override; @@ -78,8 +83,10 @@ protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index 306ef4936f..76fde26b26 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -35,17 +35,13 @@ Size2 PanelContainer::get_minimum_size() const { Size2 ms; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } Size2 minsize = c->get_combined_minimum_size(); - ms.width = MAX(ms.width, minsize.width); - ms.height = MAX(ms.height, minsize.height); + ms = ms.max(minsize); } if (theme_cache.panel_style.is_valid()) { @@ -88,11 +84,8 @@ void PanelContainer::_notification(int p_what) { } for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 0c5a882044..38204af6d5 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -37,6 +37,7 @@ void Popup::_input_from_window(const Ref<InputEvent> &p_event) { if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { + hide_reason = HIDE_REASON_CANCELED; // ESC pressed, mark as canceled unconditionally. _close_pressed(); } Window::_input_from_window(p_event); @@ -51,8 +52,8 @@ void Popup::_initialize_visible_parents() { parent_window = parent_window->get_parent_visible_window(); if (parent_window) { visible_parents.push_back(parent_window); - parent_window->connect("focus_entered", callable_mp(this, &Popup::_parent_focused)); - parent_window->connect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents)); + parent_window->connect(SceneStringName(focus_entered), callable_mp(this, &Popup::_parent_focused)); + parent_window->connect(SceneStringName(tree_exited), callable_mp(this, &Popup::_deinitialize_visible_parents)); } } } @@ -61,8 +62,8 @@ void Popup::_initialize_visible_parents() { void Popup::_deinitialize_visible_parents() { if (is_embedded()) { for (Window *parent_window : visible_parents) { - parent_window->disconnect("focus_entered", callable_mp(this, &Popup::_parent_focused)); - parent_window->disconnect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents)); + parent_window->disconnect(SceneStringName(focus_entered), callable_mp(this, &Popup::_parent_focused)); + parent_window->disconnect(SceneStringName(tree_exited), callable_mp(this, &Popup::_deinitialize_visible_parents)); } visible_parents.clear(); @@ -77,6 +78,9 @@ void Popup::_notification(int p_what) { _initialize_visible_parents(); } else { _deinitialize_visible_parents(); + if (hide_reason == HIDE_REASON_NONE) { + hide_reason = HIDE_REASON_CANCELED; + } emit_signal(SNAME("popup_hide")); popped_up = false; } @@ -87,6 +91,7 @@ void Popup::_notification(int p_what) { if (!is_in_edited_scene_root()) { if (has_focus()) { popped_up = true; + hide_reason = HIDE_REASON_NONE; } } } break; @@ -100,12 +105,18 @@ void Popup::_notification(int p_what) { case NOTIFICATION_WM_CLOSE_REQUEST: { if (!is_in_edited_scene_root()) { + if (hide_reason == HIDE_REASON_NONE) { + hide_reason = HIDE_REASON_UNFOCUSED; + } _close_pressed(); } } break; case NOTIFICATION_APPLICATION_FOCUS_OUT: { if (!is_in_edited_scene_root() && get_flag(FLAG_POPUP)) { + if (hide_reason == HIDE_REASON_NONE) { + hide_reason = HIDE_REASON_UNFOCUSED; + } _close_pressed(); } } break; @@ -114,6 +125,9 @@ void Popup::_notification(int p_what) { void Popup::_parent_focused() { if (popped_up && get_flag(FLAG_POPUP)) { + if (hide_reason == HIDE_REASON_NONE) { + hide_reason = HIDE_REASON_UNFOCUSED; + } _close_pressed(); } } @@ -194,8 +208,6 @@ Rect2i Popup::_popup_adjust_rect() const { void Popup::_bind_methods() { ADD_SIGNAL(MethodInfo("popup_hide")); - - BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Popup, panel_style, "panel"); } Popup::Popup() { @@ -224,8 +236,7 @@ Size2 PopupPanel::_get_contents_minimum_size() const { } Size2 cms = c->get_combined_minimum_size(); - ms.x = MAX(cms.x, ms.x); - ms.y = MAX(cms.y, ms.y); + ms = cms.max(ms); } return ms + theme_cache.panel_style->get_minimum_size(); @@ -233,7 +244,8 @@ Size2 PopupPanel::_get_contents_minimum_size() const { void PopupPanel::_update_child_rects() { Vector2 cpos(theme_cache.panel_style->get_offset()); - Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size()); + Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor(); + Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size(); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -247,7 +259,7 @@ void PopupPanel::_update_child_rects() { if (c == panel) { c->set_position(Vector2()); - c->set_size(get_size()); + c->set_size(panel_size); } else { c->set_position(cpos); c->set_size(csize); diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 25edca3657..69a81ad98c 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -43,9 +43,15 @@ class Popup : public Window { LocalVector<Window *> visible_parents; bool popped_up = false; - struct ThemeCache { - Ref<StyleBox> panel_style; - } theme_cache; +public: + enum HideReason { + HIDE_REASON_NONE, + HIDE_REASON_CANCELED, // E.g., because of rupture of UI flow (app unfocused). Includes closed programmatically. + HIDE_REASON_UNFOCUSED, // E.g., user clicked outside. + }; + +private: + HideReason hide_reason = HIDE_REASON_NONE; void _initialize_visible_parents(); void _deinitialize_visible_parents(); @@ -64,6 +70,8 @@ protected: virtual void _post_popup() override; public: + HideReason get_hide_reason() const { return hide_reason; } + Popup(); ~Popup(); }; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 25d999851b..bdd0102b63 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -92,7 +92,7 @@ RID PopupMenu::bind_global_menu() { NativeMenu *nmenu = NativeMenu::get_singleton(); - if (system_menu_id != NativeMenu::INVALID_MENU_ID) { + if (system_menu_id != NativeMenu::INVALID_MENU_ID && nmenu->has_system_menu(system_menu_id)) { if (system_menus.has(system_menu_id)) { WARN_PRINT(vformat("Attempting to bind PopupMenu to the system menu %s, but another menu is already bound to it. This menu: %s, current menu: %s", nmenu->get_system_menu_name(system_menu_id), get_description(), system_menus[system_menu_id]->get_description())); global_menu = nmenu->create_menu(); @@ -276,6 +276,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { } } + minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing. return minsize; } @@ -312,18 +313,17 @@ int PopupMenu::_get_items_total_height() const { } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { - if (p_over.x < 0 || p_over.x >= get_size().width || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP)) { + float win_scale = get_content_scale_factor(); + if (p_over.x < 0 || p_over.x >= get_size().width * win_scale || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP) * win_scale) { return -1; } - Point2 ofs; + Point2 ofs = Point2(0, theme_cache.v_separation * 0.5) * win_scale; for (int i = 0; i < items.size(); i++) { - ofs.y += theme_cache.v_separation; - - ofs.y += _get_item_height(i); - - if (p_over.y - control->get_position().y < ofs.y) { + ofs.y += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; + ofs.y += _get_item_height(i) * win_scale; + if (p_over.y - control->get_position().y * win_scale < ofs.y) { return i; } } @@ -341,15 +341,17 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { Rect2 this_rect(this_pos, get_size()); float scroll_offset = control->get_position().y; + float scaled_ofs_cache = items[p_over]._ofs_cache * get_content_scale_factor(); + float scaled_height_cache = items[p_over]._height_cache * get_content_scale_factor(); submenu_popup->reset_size(); // Shrink the popup size to its contents. Size2 submenu_size = submenu_popup->get_size(); Point2 submenu_pos; if (control->is_layout_rtl()) { - submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(-submenu_size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } else { - submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(this_rect.size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } // Fix pos if going outside parent rect. @@ -386,8 +388,8 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. Rect2 safe_area = this_rect; - safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation; + safe_area.position.y += scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; + safe_area.size.y = scaled_height_cache + theme_cache.v_separation; Viewport *vp = submenu_popup->get_embedder(); if (vp) { vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area); @@ -400,11 +402,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Autohide area above the submenu item. submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); // If there is an area below the submenu item, add an autohide area there. - if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; + if (scaled_ofs_cache + scaled_height_cache + scroll_offset <= control->get_size().height) { + int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -576,6 +578,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { } item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width; } + item_clickable_area.size = item_clickable_area.size * get_content_scale_factor(); Ref<InputEventMouseButton> b = p_event; @@ -640,11 +643,17 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { for (const Rect2 &E : autohide_areas) { if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) { + // The mouse left the safe area, prepare to close. _close_pressed(); return; } } + if (!minimum_lifetime_timer->is_stopped()) { + // The mouse left the safe area, but came back again, so cancel the auto-closing. + minimum_lifetime_timer->stop(); + } + if (!item_clickable_area.has_point(m->get_position())) { return; } @@ -1005,9 +1014,6 @@ void PopupMenu::_notification(int p_what) { float pm_delay = pm->get_submenu_popup_delay(); set_submenu_popup_delay(pm_delay); } - if (!is_embedded()) { - set_flag(FLAG_NO_FOCUS, true); - } if (system_menu_id != NativeMenu::INVALID_MENU_ID) { bind_global_menu(); } @@ -2799,6 +2805,7 @@ void PopupMenu::_bind_methods() { Item defaults(true); base_property_helper.set_prefix("item_"); + base_property_helper.set_array_length_getter(&PopupMenu::get_item_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &PopupMenu::set_item_text, &PopupMenu::get_item_text); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &PopupMenu::set_item_icon, &PopupMenu::get_item_icon); base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"), defaults.checkable_type, &PopupMenu::_set_item_checkable_type, &PopupMenu::_get_item_checkable_type); @@ -2819,8 +2826,18 @@ void PopupMenu::popup(const Rect2i &p_bounds) { if (native) { NativeMenu::get_singleton()->popup(global_menu, (p_bounds != Rect2i()) ? p_bounds.position : get_position()); } else { + set_flag(FLAG_NO_FOCUS, !is_embedded()); + moved = Vector2(); popup_time_msec = OS::get_singleton()->get_ticks_msec(); + if (!is_embedded()) { + float win_scale = get_parent_visible_window()->get_content_scale_factor(); + set_content_scale_factor(win_scale); + Size2 minsize = get_contents_minimum_size() * win_scale; + minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing. + set_min_size(minsize); // `height` is truncated here by the cast to Size2i for Window.min_size. + set_size(Vector2(0, 0)); // Shrinkwraps to min size. + } Popup::popup(p_bounds); } } @@ -2838,6 +2855,8 @@ void PopupMenu::set_visible(bool p_visible) { NativeMenu::get_singleton()->popup(global_menu, get_position()); } } else { + set_flag(FLAG_NO_FOCUS, !is_embedded()); + Popup::set_visible(p_visible); } } @@ -2856,7 +2875,7 @@ PopupMenu::PopupMenu() { control->set_h_size_flags(Control::SIZE_EXPAND_FILL); control->set_v_size_flags(Control::SIZE_EXPAND_FILL); scroll_container->add_child(control, false, INTERNAL_MODE_FRONT); - control->connect("draw", callable_mp(this, &PopupMenu::_draw_items)); + control->connect(SceneStringName(draw), callable_mp(this, &PopupMenu::_draw_items)); submenu_timer = memnew(Timer); submenu_timer->set_wait_time(0.3); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index f7097fffaf..832c1bcc8b 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -97,6 +97,10 @@ class PopupMenu : public Popup { static inline PropertyListHelper base_property_helper; PropertyListHelper property_helper; + // To make Item available. + friend class OptionButton; + friend class MenuButton; + RID global_menu; RID system_menu; NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID; diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index c66b30f130..90ce01e383 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -35,15 +35,13 @@ Size2 ProgressBar::get_minimum_size() const { Size2 minimum_size = theme_cache.background_style->get_minimum_size(); - minimum_size.height = MAX(minimum_size.height, theme_cache.fill_style->get_minimum_size().height); - minimum_size.width = MAX(minimum_size.width, theme_cache.fill_style->get_minimum_size().width); + minimum_size = minimum_size.max(theme_cache.fill_style->get_minimum_size()); if (show_percentage) { String txt = "100%"; TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size); minimum_size.height = MAX(minimum_size.height, theme_cache.background_style->get_minimum_size().height + tl.get_size().y); } else { // this is needed, else the progressbar will collapse - minimum_size.width = MAX(minimum_size.width, 1); - minimum_size.height = MAX(minimum_size.height, 1); + minimum_size = minimum_size.maxf(1); } return minimum_size; } diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 236dfcc864..00f4a1089a 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -60,7 +60,7 @@ void Range::Shared::emit_value_changed() { } void Range::_changed_notify(const char *p_what) { - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); queue_redraw(); } diff --git a/scene/gui/rich_text_label.compat.inc b/scene/gui/rich_text_label.compat.inc index 626278a405..97739c4b79 100644 --- a/scene/gui/rich_text_label.compat.inc +++ b/scene/gui/rich_text_label.compat.inc @@ -38,9 +38,14 @@ void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image, add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false); } +bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) { + return remove_paragraph(p_paragraph, false); +} + void RichTextLabel::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024); ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2())); + ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098); } #endif // DISABLE_DEPRECATED diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index cb7ccb583e..f6942ca206 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -39,7 +39,6 @@ #include "scene/gui/label.h" #include "scene/gui/rich_text_effect.h" #include "scene/resources/atlas_texture.h" -#include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" #include "servers/display_server.h" @@ -835,10 +834,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw dropcap. int dc_lines = l.text_buf->get_dropcap_lines(); float h_off = l.text_buf->get_dropcap_size().x; - if (l.dc_ol_size > 0) { - l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + bool skip_dc = (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!skip_dc) { + if (l.dc_ol_size > 0) { + l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color); + } + l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); } - l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color); int line_count = 0; Size2 ctrl_size = get_size(); @@ -894,7 +896,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; } - if (!prefix.is_empty() && line == 0) { + bool skip_prefix = (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && l.char_offset == visible_characters) || (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!prefix.is_empty() && line == 0 && !skip_prefix) { Ref<Font> font = theme_cache.normal_font; int font_size = theme_cache.normal_font_size; @@ -927,9 +930,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } RID rid = l.text_buf->get_line_rid(line); - //draw_rect(Rect2(p_ofs + off, TS->shaped_text_get_size(rid)), Color(1,0,0), false, 2); //DEBUG_RECTS + double l_ascent = TS->shaped_text_get_ascent(rid); + Size2 l_size = TS->shaped_text_get_size(rid); + double upos = TS->shaped_text_get_underline_position(rid); + double uth = TS->shaped_text_get_underline_thickness(rid); - off.y += TS->shaped_text_get_ascent(rid); + off.y += l_ascent; // Draw inlined objects. Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { @@ -946,12 +952,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); - //draw_rect(rect, Color(1,0,0), false, 2); //DEBUG_RECTS switch (it->type) { case ITEM_IMAGE: { ItemImage *img = static_cast<ItemImage *>(it); if (img->pad) { - Size2 pad_size = Size2(MIN(rect.size.x, img->image->get_width()), MIN(rect.size.y, img->image->get_height())); + Size2 pad_size = rect.size.min(img->image->get_size()); Vector2 pad_off = (rect.size - pad_size) / 2; img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); } else { @@ -1011,514 +1016,416 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); int gl_size = TS->shaped_text_get_glyph_count(rid); + Vector2i chr_range = TS->shaped_text_get_range(rid); - Vector2 gloff = off; - // Draw outlines and shadow. - int processed_glyphs_ol = r_processed_glyphs; - for (int i = 0; i < gl_size; i++) { - Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); - int size = _find_outline_size(it, p_outline_size); - Color font_color = _find_color(it, p_base_color); - Color font_outline_color = _find_outline_color(it, p_outline_color); - Color font_shadow_color = p_font_shadow_color; - if ((size <= 0 || font_outline_color.a == 0) && (font_shadow_color.a == 0)) { - gloff.x += glyphs[i].advance; - continue; - } - - // Get FX. - ItemFade *fade = nullptr; - Item *fade_item = it; - while (fade_item) { - if (fade_item->type == ITEM_FADE) { - fade = static_cast<ItemFade *>(fade_item); - break; - } - fade_item = fade_item->parent; - } - - Vector<ItemFX *> fx_stack; - _fetch_item_fx_stack(it, fx_stack); - bool custom_fx_ok = true; - - Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off); - RID frid = glyphs[i].font_rid; - uint32_t gl = glyphs[i].index; - uint16_t gl_fl = glyphs[i].flags; - uint8_t gl_cn = glyphs[i].count; - bool cprev_cluster = false; - bool cprev_conn = false; - if (gl_cn == 0) { // Parts of the same cluster, always connected. - cprev_cluster = true; - } - if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. - if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { - cprev_conn = true; - } - } else { - if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { - cprev_conn = true; - } - } + int sel_start = -1; + int sel_end = -1; - //Apply fx. - if (fade) { - float faded_visibility = 1.0f; - if (glyphs[i].start >= fade->starting_index) { - faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length; - faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility; - } - font_outline_color.a = faded_visibility; - font_shadow_color.a = faded_visibility; - } - - bool txt_visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); - Transform2D char_xform; - char_xform.set_origin(gloff + p_ofs); - - for (int j = 0; j < fx_stack.size(); j++) { - ItemFX *item_fx = fx_stack[j]; - bool cn = cprev_cluster || (cprev_conn && item_fx->connected); - - if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) { - ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx); - - Ref<CharFXTransform> charfx = item_custom->char_fx_transform; - Ref<RichTextEffect> custom_effect = item_custom->custom_effect; - - if (!custom_effect.is_null()) { - charfx->elapsed_time = item_custom->elapsed_time; - charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs; - charfx->visibility = txt_visible; - charfx->outline = true; - charfx->font = frid; - charfx->glyph_index = gl; - charfx->glyph_flags = gl_fl; - charfx->glyph_count = gl_cn; - charfx->offset = fx_offset; - charfx->color = font_color; - charfx->transform = char_xform; - - bool effect_status = custom_effect->_process_effect_impl(charfx); - custom_fx_ok = effect_status; - - char_xform = charfx->transform; - fx_offset += charfx->offset; - font_color = charfx->color; - frid = charfx->font; - gl = charfx->glyph_index; - txt_visible &= charfx->visibility; + if (selection.active && (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) <= (l.char_offset + chr_range.y) && (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) >= (l.char_offset + chr_range.x)) { + sel_start = MAX(chr_range.x, (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) - l.char_offset); + sel_end = MIN(chr_range.y, (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) - l.char_offset); + } + + int processed_glyphs_step = 0; + for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) { + Vector2 off_step = off; + processed_glyphs_step = r_processed_glyphs; + + Vector2 ul_start; + bool ul_started = false; + Color ul_color_prev; + Color ul_color; + + Vector2 dot_ul_start; + bool dot_ul_started = false; + Color dot_ul_color_prev; + Color dot_ul_color; + + Vector2 st_start; + bool st_started = false; + Color st_color_prev; + Color st_color; + + float box_start = 0.0; + Color last_color = Color(0, 0, 0, 0); + + for (int i = 0; i < gl_size; i++) { + bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end); + Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); + + Color font_color = (step == DRAW_STEP_SHADOW || step == DRAW_STEP_OUTLINE || step == DRAW_STEP_TEXT) ? _find_color(it, p_base_color) : Color(); + int outline_size = (step == DRAW_STEP_OUTLINE) ? _find_outline_size(it, p_outline_size) : 0; + Color font_outline_color = (step == DRAW_STEP_OUTLINE) ? _find_outline_color(it, p_outline_color) : Color(); + Color font_shadow_color = p_font_shadow_color; + bool txt_visible = false; + if (step == DRAW_STEP_OUTLINE) { + txt_visible = (font_outline_color.a != 0 && outline_size > 0); + } else if (step == DRAW_STEP_SHADOW) { + txt_visible = (font_shadow_color.a != 0); + } else if (step == DRAW_STEP_TEXT) { + txt_visible = (font_color.a != 0); + bool has_ul = _find_underline(it); + if (!has_ul && underline_meta) { + ItemMeta *meta = nullptr; + if (_find_meta(it, nullptr, &meta) && meta) { + switch (meta->underline) { + case META_UNDERLINE_ALWAYS: { + has_ul = true; + } break; + case META_UNDERLINE_NEVER: { + has_ul = false; + } break; + case META_UNDERLINE_ON_HOVER: { + has_ul = (meta == meta_hovering); + } break; + } + } } - } else if (item_fx->type == ITEM_SHAKE) { - ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - - if (!cn) { - uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); - uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); - uint64_t max_rand = 2147483647; - double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); - n_time = (n_time > 1.0) ? 1.0 : n_time; - item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; + if (has_ul) { + if (ul_started && font_color != ul_color_prev) { + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width); + ul_start = p_ofs + Vector2(off_step.x, off_step.y); + ul_color_prev = font_color; + ul_color = font_color; + ul_color.a *= 0.5; + } else if (!ul_started) { + ul_started = true; + ul_start = p_ofs + Vector2(off_step.x, off_step.y); + ul_color_prev = font_color; + ul_color = font_color; + ul_color.a *= 0.5; + } + } else if (ul_started) { + ul_started = false; + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width); } - fx_offset += item_shake->prev_off; - } else if (item_fx->type == ITEM_WAVE) { - ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - - if (!cn) { - double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f); - item_wave->prev_off = Point2(0, 1) * value; + if (_find_hint(it, nullptr) && underline_hint) { + if (dot_ul_started && font_color != dot_ul_color_prev) { + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); + dot_ul_start = p_ofs + Vector2(off_step.x, off_step.y); + dot_ul_color_prev = font_color; + dot_ul_color = font_color; + dot_ul_color.a *= 0.5; + } else if (!dot_ul_started) { + dot_ul_started = true; + dot_ul_start = p_ofs + Vector2(off_step.x, off_step.y); + dot_ul_color_prev = font_color; + dot_ul_color = font_color; + dot_ul_color.a *= 0.5; + } + } else if (dot_ul_started) { + dot_ul_started = false; + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); } - fx_offset += item_wave->prev_off; - } else if (item_fx->type == ITEM_TORNADO) { - ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - - if (!cn) { - double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); - double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); - item_tornado->prev_off = Point2(torn_x, torn_y); + if (_find_strikethrough(it)) { + if (st_started && font_color != st_color_prev) { + float y_off = -l_ascent + l_size.y / 2; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width); + st_start = p_ofs + Vector2(off_step.x, off_step.y); + st_color_prev = font_color; + st_color = font_color; + st_color.a *= 0.5; + } else if (!st_started) { + st_started = true; + st_start = p_ofs + Vector2(off_step.x, off_step.y); + st_color_prev = font_color; + st_color = font_color; + st_color.a *= 0.5; + } + } else if (st_started) { + st_started = false; + float y_off = -l_ascent + l_size.y / 2; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width); } - fx_offset += item_tornado->prev_off; - } else if (item_fx->type == ITEM_RAINBOW) { - ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx); - - font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + gloff.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a); - } else if (item_fx->type == ITEM_PULSE) { - ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx); - - const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease)); - font_color = font_color.lerp(font_color * item_pulse->color, sined_time); } - } + if (step == DRAW_STEP_SHADOW || step == DRAW_STEP_OUTLINE || step == DRAW_STEP_TEXT) { + ItemFade *fade = nullptr; + Item *fade_item = it; + while (fade_item) { + if (fade_item->type == ITEM_FADE) { + fade = static_cast<ItemFade *>(fade_item); + break; + } + fade_item = fade_item->parent; + } - if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { - fx_offset = fx_offset.round(); - } - Vector2 char_off = char_xform.get_origin(); - - // Draw glyph outlines. - const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a); - const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a); - for (int j = 0; j < glyphs[i].repeat; j++) { - if (txt_visible) { - bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - if (!skip && frid != RID()) { - if (modulated_shadow_color.a > 0) { - Transform2D char_reverse_xform; - char_reverse_xform.set_origin(-char_off - p_shadow_ofs); - Transform2D char_final_xform = char_xform * char_reverse_xform; - char_final_xform.columns[2] += p_shadow_ofs; - draw_set_transform_matrix(char_final_xform); - - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off + p_shadow_ofs, gl, modulated_shadow_color); - if (p_shadow_outline_size > 0) { - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, fx_offset + char_off + p_shadow_ofs, gl, modulated_shadow_color); - } + Vector<ItemFX *> fx_stack; + _fetch_item_fx_stack(it, fx_stack); + bool custom_fx_ok = true; + + Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off); + RID frid = glyphs[i].font_rid; + uint32_t gl = glyphs[i].index; + uint16_t gl_fl = glyphs[i].flags; + uint8_t gl_cn = glyphs[i].count; + bool cprev_cluster = false; + bool cprev_conn = false; + if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected. + cprev_cluster = true; + } + if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. + if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { + cprev_conn = true; + } + } else { + if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { + cprev_conn = true; } - if (modulated_outline_color.a != 0.0 && size > 0) { - Transform2D char_reverse_xform; - char_reverse_xform.set_origin(-char_off); - Transform2D char_final_xform = char_xform * char_reverse_xform; - draw_set_transform_matrix(char_final_xform); + } - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, fx_offset + char_off, gl, modulated_outline_color); + //Apply fx. + if (fade) { + float faded_visibility = 1.0f; + if (glyphs[i].start >= fade->starting_index) { + faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length; + faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility; } + font_color.a = faded_visibility; } - processed_glyphs_ol++; - } - gloff.x += glyphs[i].advance; - } - } - draw_set_transform_matrix(Transform2D()); - Vector2 fbg_line_off = off + p_ofs; - // Draw background color box - Vector2i chr_range = TS->shaped_text_get_range(rid); - _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); + Transform2D char_xform; + char_xform.set_origin(p_ofs + off_step); + + for (int j = 0; j < fx_stack.size(); j++) { + ItemFX *item_fx = fx_stack[j]; + bool cn = cprev_cluster || (cprev_conn && item_fx->connected); + + if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) { + ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx); + + Ref<CharFXTransform> charfx = item_custom->char_fx_transform; + Ref<RichTextEffect> custom_effect = item_custom->custom_effect; + + if (!custom_effect.is_null()) { + charfx->elapsed_time = item_custom->elapsed_time; + charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); + charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs; + charfx->visibility = txt_visible; + charfx->outline = (step == DRAW_STEP_SHADOW) || (step == DRAW_STEP_OUTLINE); + charfx->font = frid; + charfx->glyph_index = gl; + charfx->glyph_flags = gl_fl; + charfx->glyph_count = gl_cn; + charfx->offset = fx_offset; + charfx->color = font_color; + charfx->transform = char_xform; + + bool effect_status = custom_effect->_process_effect_impl(charfx); + custom_fx_ok = effect_status; + + char_xform = charfx->transform; + fx_offset += charfx->offset; + font_color = charfx->color; + frid = charfx->font; + gl = charfx->glyph_index; + txt_visible &= charfx->visibility; + } + } else if (item_fx->type == ITEM_SHAKE) { + ItemShake *item_shake = static_cast<ItemShake *>(item_fx); + + if (!cn) { + uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); + uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); + uint64_t max_rand = 2147483647; + double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); + n_time = (n_time > 1.0) ? 1.0 : n_time; + item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; + } + fx_offset += item_shake->prev_off; + } else if (item_fx->type == ITEM_WAVE) { + ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - // Draw main text. - Color selection_bg = theme_cache.selection_color; + if (!cn) { + double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off_step.x) / 50)) * (item_wave->amplitude / 10.0f); + item_wave->prev_off = Point2(0, 1) * value; + } + fx_offset += item_wave->prev_off; + } else if (item_fx->type == ITEM_TORNADO) { + ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); + + if (!cn) { + double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off_step.x) / 50)) * (item_tornado->radius); + double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off_step.x) / 50)) * (item_tornado->radius); + item_tornado->prev_off = Point2(torn_x, torn_y); + } + fx_offset += item_tornado->prev_off; + } else if (item_fx->type == ITEM_RAINBOW) { + ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx); - int sel_start = -1; - int sel_end = -1; + font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + off_step.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a); + } else if (item_fx->type == ITEM_PULSE) { + ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx); - if (selection.active && (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) <= (l.char_offset + TS->shaped_text_get_range(rid).y) && (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) >= (l.char_offset + TS->shaped_text_get_range(rid).x)) { - sel_start = MAX(TS->shaped_text_get_range(rid).x, (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) - l.char_offset); - sel_end = MIN(TS->shaped_text_get_range(rid).y, (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) - l.char_offset); - - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_start, sel_end); - for (int i = 0; i < sel.size(); i++) { - Rect2 rect = Rect2(sel[i].x + p_ofs.x + off.x, p_ofs.y + off.y - TS->shaped_text_get_ascent(rid), sel[i].y - sel[i].x, TS->shaped_text_get_size(rid).y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_bg); - } - } - - Vector2 ul_start; - bool ul_started = false; - Color ul_color_prev; - Color ul_color; - - Vector2 dot_ul_start; - bool dot_ul_started = false; - Color dot_ul_color_prev; - Color dot_ul_color; - - Vector2 st_start; - bool st_started = false; - Color st_color_prev; - Color st_color; - - for (int i = 0; i < gl_size; i++) { - bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end); - Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); - bool has_ul = _find_underline(it); - if (!has_ul && underline_meta) { - ItemMeta *meta = nullptr; - if (_find_meta(it, nullptr, &meta) && meta) { - switch (meta->underline) { - case META_UNDERLINE_ALWAYS: { - has_ul = true; - } break; - case META_UNDERLINE_NEVER: { - has_ul = false; - } break; - case META_UNDERLINE_ON_HOVER: { - has_ul = (meta == meta_hovering); - } break; + const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease)); + font_color = font_color.lerp(font_color * item_pulse->color, sined_time); + } } - } - } - Color font_color = _find_color(it, p_base_color); - if (has_ul) { - if (ul_started && font_color != ul_color_prev) { - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); - ul_start = p_ofs + Vector2(off.x, off.y); - ul_color_prev = font_color; - ul_color = font_color; - ul_color.a *= 0.5; - } else if (!ul_started) { - ul_started = true; - ul_start = p_ofs + Vector2(off.x, off.y); - ul_color_prev = font_color; - ul_color = font_color; - ul_color.a *= 0.5; - } - } else if (ul_started) { - ul_started = false; - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); - } - if (_find_hint(it, nullptr) && underline_hint) { - if (dot_ul_started && font_color != dot_ul_color_prev) { - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); - dot_ul_start = p_ofs + Vector2(off.x, off.y); - dot_ul_color_prev = font_color; - dot_ul_color = font_color; - dot_ul_color.a *= 0.5; - } else if (!dot_ul_started) { - dot_ul_started = true; - dot_ul_start = p_ofs + Vector2(off.x, off.y); - dot_ul_color_prev = font_color; - dot_ul_color = font_color; - dot_ul_color.a *= 0.5; - } - } else if (dot_ul_started) { - dot_ul_started = false; - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); - } - if (_find_strikethrough(it)) { - if (st_started && font_color != st_color_prev) { - float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); - st_start = p_ofs + Vector2(off.x, off.y); - st_color_prev = font_color; - st_color = font_color; - st_color.a *= 0.5; - } else if (!st_started) { - st_started = true; - st_start = p_ofs + Vector2(off.x, off.y); - st_color_prev = font_color; - st_color = font_color; - st_color.a *= 0.5; - } - } else if (st_started) { - st_started = false; - float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); - } - - // Get FX. - ItemFade *fade = nullptr; - Item *fade_item = it; - while (fade_item) { - if (fade_item->type == ITEM_FADE) { - fade = static_cast<ItemFade *>(fade_item); - break; - } - fade_item = fade_item->parent; - } - - Vector<ItemFX *> fx_stack; - _fetch_item_fx_stack(it, fx_stack); - bool custom_fx_ok = true; - - Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off); - RID frid = glyphs[i].font_rid; - uint32_t gl = glyphs[i].index; - uint16_t gl_fl = glyphs[i].flags; - uint8_t gl_cn = glyphs[i].count; - bool cprev_cluster = false; - bool cprev_conn = false; - if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected. - cprev_cluster = true; - } - if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. - if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { - cprev_conn = true; - } - } else { - if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { - cprev_conn = true; - } - } - //Apply fx. - if (fade) { - float faded_visibility = 1.0f; - if (glyphs[i].start >= fade->starting_index) { - faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length; - faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility; - } - font_color.a = faded_visibility; - } - - bool txt_visible = (font_color.a != 0); - - Transform2D char_xform; - char_xform.set_origin(p_ofs + off); - - for (int j = 0; j < fx_stack.size(); j++) { - ItemFX *item_fx = fx_stack[j]; - bool cn = cprev_cluster || (cprev_conn && item_fx->connected); - - if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) { - ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx); - - Ref<CharFXTransform> charfx = item_custom->char_fx_transform; - Ref<RichTextEffect> custom_effect = item_custom->custom_effect; - - if (!custom_effect.is_null()) { - charfx->elapsed_time = item_custom->elapsed_time; - charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs; - charfx->visibility = txt_visible; - charfx->outline = false; - charfx->font = frid; - charfx->glyph_index = gl; - charfx->glyph_flags = gl_fl; - charfx->glyph_count = gl_cn; - charfx->offset = fx_offset; - charfx->color = font_color; - charfx->transform = char_xform; - - bool effect_status = custom_effect->_process_effect_impl(charfx); - custom_fx_ok = effect_status; - - char_xform = charfx->transform; - fx_offset += charfx->offset; - font_color = charfx->color; - frid = charfx->font; - gl = charfx->glyph_index; - txt_visible &= charfx->visibility; + if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { + fx_offset = fx_offset.round(); } - } else if (item_fx->type == ITEM_SHAKE) { - ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - - if (!cn) { - uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); - uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); - uint64_t max_rand = 2147483647; - double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); - n_time = (n_time > 1.0) ? 1.0 : n_time; - item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; + + Vector2 char_off = char_xform.get_origin(); + Transform2D char_reverse_xform; + if (step == DRAW_STEP_TEXT) { + if (selected && use_selected_font_color) { + font_color = theme_cache.font_selected_color; + } + + char_reverse_xform.set_origin(-char_off); + Transform2D char_final_xform = char_xform * char_reverse_xform; + draw_set_transform_matrix(char_final_xform); + } else if (step == DRAW_STEP_SHADOW) { + font_color = font_shadow_color * Color(1, 1, 1, font_color.a); + + char_reverse_xform.set_origin(-char_off - p_shadow_ofs); + Transform2D char_final_xform = char_xform * char_reverse_xform; + char_final_xform.columns[2] += p_shadow_ofs; + draw_set_transform_matrix(char_final_xform); + } else if (step == DRAW_STEP_OUTLINE) { + font_color = font_outline_color * Color(1, 1, 1, font_color.a); + + char_reverse_xform.set_origin(-char_off); + Transform2D char_final_xform = char_xform * char_reverse_xform; + draw_set_transform_matrix(char_final_xform); } - fx_offset += item_shake->prev_off; - } else if (item_fx->type == ITEM_WAVE) { - ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - if (!cn) { - double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); - item_wave->prev_off = Point2(0, 1) * value; + // Draw glyphs. + for (int j = 0; j < glyphs[i].repeat; j++) { + bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); + if (!skip) { + if (txt_visible) { + if (step == DRAW_STEP_TEXT) { + if (frid != RID()) { + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color); + } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { + TS->draw_hex_code_box(ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color); + } + } else if (step == DRAW_STEP_SHADOW && frid != RID()) { + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off + p_shadow_ofs, gl, font_color); + if (p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, fx_offset + char_off + p_shadow_ofs, gl, font_color); + } + } else if (step == DRAW_STEP_OUTLINE && frid != RID() && outline_size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, outline_size, fx_offset + char_off, gl, font_color); + } + } + processed_glyphs_step++; + } + if (step == DRAW_STEP_TEXT && skip) { + // Finish underline/overline/strikethrough is previous glyph is skipped. + if (ul_started) { + ul_started = false; + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width); + } + if (dot_ul_started) { + dot_ul_started = false; + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); + } + if (st_started) { + st_started = false; + float y_off = -l_ascent + l_size.y / 2; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width); + } + } + off_step.x += glyphs[i].advance; } - fx_offset += item_wave->prev_off; - } else if (item_fx->type == ITEM_TORNADO) { - ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - - if (!cn) { - double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); - double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); - item_tornado->prev_off = Point2(torn_x, torn_y); + draw_set_transform_matrix(Transform2D()); + } + // Draw boxes. + if (step == DRAW_STEP_BACKGROUND || step == DRAW_STEP_FOREGROUND) { + for (int j = 0; j < glyphs[i].repeat; j++) { + bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); + if (!skip) { + Color color; + if (step == DRAW_STEP_BACKGROUND) { + color = _find_bgcolor(it); + } else if (step == DRAW_STEP_FOREGROUND) { + color = _find_fgcolor(it); + } + if (color != last_color) { + if (last_color.a > 0.0) { + Vector2 rect_off = p_ofs + Vector2(box_start - theme_cache.text_highlight_h_padding, off_step.y - l_ascent - theme_cache.text_highlight_v_padding); + Vector2 rect_size = Vector2(off_step.x - box_start + 2 * theme_cache.text_highlight_h_padding, l_size.y + 2 * theme_cache.text_highlight_v_padding); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(rect_off, rect_size), last_color); + } + if (color.a > 0.0) { + box_start = off_step.x; + } + } + last_color = color; + processed_glyphs_step++; + } else { + // Finish box is previous glyph is skipped. + if (last_color.a > 0.0) { + Vector2 rect_off = p_ofs + Vector2(box_start - theme_cache.text_highlight_h_padding, off_step.y - l_ascent - theme_cache.text_highlight_v_padding); + Vector2 rect_size = Vector2(off_step.x - box_start + 2 * theme_cache.text_highlight_h_padding, l_size.y + 2 * theme_cache.text_highlight_v_padding); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(rect_off, rect_size), last_color); + } + last_color = Color(0, 0, 0, 0); + } + off_step.x += glyphs[i].advance; } - fx_offset += item_tornado->prev_off; - } else if (item_fx->type == ITEM_RAINBOW) { - ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx); - - font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + off.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a); - } else if (item_fx->type == ITEM_PULSE) { - ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx); - - const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease)); - font_color = font_color.lerp(font_color * item_pulse->color, sined_time); } } - - if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { - fx_offset = fx_offset.round(); + // Finish lines and boxes. + if (step == DRAW_STEP_BACKGROUND) { + if (sel_start != -1) { + Color selection_bg = theme_cache.selection_color; + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_start, sel_end); + for (int i = 0; i < sel.size(); i++) { + Rect2 rect = Rect2(sel[i].x + p_ofs.x + off.x, p_ofs.y + off.y - l_ascent, sel[i].y - sel[i].x, l_size.y); // Note: use "off" not "off_step", selection is relative to the line start. + RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_bg); + } + } } - Vector2 char_off = char_xform.get_origin(); - - Transform2D char_reverse_xform; - char_reverse_xform.set_origin(-char_off); - char_xform = char_xform * char_reverse_xform; - draw_set_transform_matrix(char_xform); - - if (selected && use_selected_font_color) { - font_color = theme_cache.font_selected_color; + if (step == DRAW_STEP_BACKGROUND || step == DRAW_STEP_FOREGROUND) { + if (last_color.a > 0.0) { + Vector2 rect_off = p_ofs + Vector2(box_start - theme_cache.text_highlight_h_padding, off_step.y - l_ascent - theme_cache.text_highlight_v_padding); + Vector2 rect_size = Vector2(off_step.x - box_start + 2 * theme_cache.text_highlight_h_padding, l_size.y + 2 * theme_cache.text_highlight_v_padding); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(rect_off, rect_size), last_color); + } } - - // Draw glyphs. - for (int j = 0; j < glyphs[i].repeat; j++) { - bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); - if (txt_visible) { - if (!skip) { - if (frid != RID()) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color); - } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color); - } - } - r_processed_glyphs++; + if (step == DRAW_STEP_TEXT) { + if (ul_started) { + ul_started = false; + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width); } - if (skip) { - // End underline/overline/strikethrough is previous glyph is skipped. - if (ul_started) { - ul_started = false; - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); - } - if (dot_ul_started) { - dot_ul_started = false; - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); - } - if (st_started) { - st_started = false; - float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); - } + if (dot_ul_started) { + dot_ul_started = false; + float y_off = upos; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); + } + if (st_started) { + st_started = false; + float y_off = -l_ascent + l_size.y / 2; + float underline_width = MAX(1.0, uth * theme_cache.base_scale); + draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width); } - off.x += glyphs[i].advance; } - - draw_set_transform_matrix(Transform2D()); - } - if (ul_started) { - ul_started = false; - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); - } - if (dot_ul_started) { - dot_ul_started = false; - float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2)); } - if (st_started) { - st_started = false; - float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale); - draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); - } - // Draw foreground color box - _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1); + r_processed_glyphs = processed_glyphs_step; off.y += TS->shaped_text_get_descent(rid); } @@ -1700,8 +1607,34 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (p_meta) { int64_t glyph_idx = TS->shaped_text_hit_test_grapheme(rid, p_click.x - rect.position.x); if (glyph_idx >= 0) { + float baseline_y = rect.position.y + TS->shaped_text_get_ascent(rid); const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); - char_pos = glyphs[glyph_idx].start; + if (glyphs[glyph_idx].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) { + // Emebedded object. + for (int i = 0; i < objects.size(); i++) { + if (TS->shaped_text_get_object_glyph(rid, objects[i]) == glyph_idx) { + Rect2 obj_rect = TS->shaped_text_get_object_rect(rid, objects[i]); + obj_rect.position.y += baseline_y; + if (p_click.y >= obj_rect.position.y && p_click.y <= obj_rect.position.y + obj_rect.size.y) { + char_pos = glyphs[glyph_idx].start; + } + break; + } + } + } else if (glyphs[glyph_idx].font_rid != RID()) { + // Normal glyph. + float fa = TS->font_get_ascent(glyphs[glyph_idx].font_rid, glyphs[glyph_idx].font_size); + float fd = TS->font_get_descent(glyphs[glyph_idx].font_rid, glyphs[glyph_idx].font_size); + if (p_click.y >= baseline_y - fa && p_click.y <= baseline_y + fd) { + char_pos = glyphs[glyph_idx].start; + } + } else if (!(glyphs[glyph_idx].flags & TextServer::GRAPHEME_IS_VIRTUAL)) { + // Hex code box. + Vector2 gl_size = TS->get_hex_code_box_size(glyphs[glyph_idx].font_size, glyphs[glyph_idx].index); + if (p_click.y >= baseline_y - gl_size.y * 0.9 && p_click.y <= baseline_y + gl_size.y * 0.2) { + char_pos = glyphs[glyph_idx].start; + } + } } } else { char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); @@ -1799,7 +1732,7 @@ void RichTextLabel::_scroll_changed(double) { return; } - if (scroll_follow && vscroll->get_value() >= (vscroll->get_max() - Math::round(vscroll->get_page()))) { + if (scroll_follow && vscroll->get_value() > (vscroll->get_max() - vscroll->get_page() - 1)) { scroll_following = true; } else { scroll_following = false; @@ -2145,12 +2078,12 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (b->get_button_index() == MouseButton::WHEEL_UP) { if (scroll_active) { - vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8); + vscroll->scroll(-vscroll->get_page() * b->get_factor() * 0.5 / 8); } } if (b->get_button_index() == MouseButton::WHEEL_DOWN) { if (scroll_active) { - vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8); + vscroll->scroll(vscroll->get_page() * b->get_factor() * 0.5 / 8); } } if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { @@ -2165,7 +2098,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventPanGesture> pan_gesture = p_event; if (pan_gesture.is_valid()) { if (scroll_active) { - vscroll->set_value(vscroll->get_value() + vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8); + vscroll->scroll(vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8); } return; @@ -2178,27 +2111,27 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { bool handled = false; if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - vscroll->get_page()); + vscroll->scroll(-vscroll->get_page()); handled = true; } if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() + vscroll->get_page()); + vscroll->scroll(vscroll->get_page()); handled = true; } if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size)); + vscroll->scroll(-theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); + vscroll->scroll(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(0); + vscroll->scroll_to(0); handled = true; } if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_max()); + vscroll->scroll_to(vscroll->get_max()); handled = true; } if (is_shortcut_keys_enabled()) { @@ -3085,7 +3018,7 @@ void RichTextLabel::_process_line_caches() { if (fit_content) { update_minimum_size(); } - emit_signal(SNAME("finished")); + emit_signal(SceneStringName(finished)); } void RichTextLabel::_invalidate_current_line(ItemFrame *p_frame) { @@ -3208,33 +3141,6 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) queue_redraw(); } -void RichTextLabel::_remove_item(Item *p_item, const int p_line) { - int size = p_item->subitems.size(); - if (size == 0) { - p_item->parent->subitems.erase(p_item); - // If a newline was erased, all lines AFTER the newline need to be decremented. - if (p_item->type == ITEM_NEWLINE) { - current_frame->lines.remove_at(p_line); - if (p_line < (int)current_frame->lines.size() && current_frame->lines[p_line].from) { - for (List<Item *>::Element *E = current_frame->lines[p_line].from->E; E; E = E->next()) { - if (E->get()->line > p_line) { - E->get()->line--; - } - } - } - } - } else { - // First, remove all child items for the provided item. - while (p_item->subitems.size()) { - _remove_item(p_item->subitems.front()->get(), p_line); - } - // Then remove the provided item itself. - p_item->parent->subitems.erase(p_item); - } - items.free(p_item->rid); - memdelete(p_item); -} - Size2 RichTextLabel::_get_image_size(const Ref<Texture2D> &p_image, int p_width, int p_height, const Rect2 &p_region) { Size2 ret; if (p_width > 0) { @@ -3420,49 +3326,134 @@ void RichTextLabel::add_newline() { queue_redraw(); } -bool RichTextLabel::remove_paragraph(const int p_paragraph) { +void RichTextLabel::_remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset) { + Line &l = p_frame->lines[p_line]; + Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + if (!p_erase) { + l.char_offset -= p_char_offset; + } + + for (Item *it = l.from; it && it != it_to;) { + Item *next_it = _get_next_item(it); + it->line -= p_line_offset; + if (!p_erase) { + while (r_erase_list.has(it->parent)) { + it->E->erase(); + it->parent = it->parent->parent; + it->E = it->parent->subitems.push_back(it); + } + } + if (it->type == ITEM_TABLE) { + ItemTable *table = static_cast<ItemTable *>(it); + for (List<Item *>::Element *sub_it = table->subitems.front(); sub_it; sub_it = sub_it->next()) { + ERR_CONTINUE(sub_it->get()->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(sub_it->get()); + for (int i = 0; i < (int)frame->lines.size(); i++) { + _remove_frame(r_erase_list, frame, i, p_erase, p_char_offset, 0); + } + if (p_erase) { + r_erase_list.insert(frame); + } else { + frame->char_ofs -= p_char_offset; + } + } + } + if (p_erase) { + r_erase_list.insert(it); + } else { + it->char_ofs -= p_char_offset; + } + it = next_it; + } +} + +bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) { _stop_thread(); MutexLock data_lock(data_mutex); - if (p_paragraph >= (int)current_frame->lines.size() || p_paragraph < 0) { + if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) { return false; } - // Remove all subitems with the same line as that provided. - Vector<List<Item *>::Element *> subitem_to_remove; - if (current_frame->lines[p_paragraph].from) { - for (List<Item *>::Element *E = current_frame->lines[p_paragraph].from->E; E; E = E->next()) { - if (E->get()->line == p_paragraph) { - subitem_to_remove.push_back(E); + if (main->lines.size() == 1) { + // Clear all. + main->_clear_children(); + current = main; + current_frame = main; + main->lines.clear(); + main->lines.resize(1); + + current_char_ofs = 0; + } else { + HashSet<Item *> erase_list; + Line &l = main->lines[p_paragraph]; + int off = l.char_count; + for (int i = p_paragraph; i < (int)main->lines.size(); i++) { + if (i == p_paragraph) { + _remove_frame(erase_list, main, i, true, off, 0); } else { - break; + _remove_frame(erase_list, main, i, false, off, 1); } } - } - - bool had_newline = false; - // Reverse for loop to remove items from the end first. - for (int i = subitem_to_remove.size() - 1; i >= 0; i--) { - List<Item *>::Element *subitem = subitem_to_remove[i]; - had_newline = had_newline || subitem->get()->type == ITEM_NEWLINE; - if (subitem->get() == current) { - pop(); + for (HashSet<Item *>::Iterator E = erase_list.begin(); E; ++E) { + Item *it = *E; + if (current_frame == it) { + current_frame = main; + } + if (current == it) { + current = main; + } + if (!erase_list.has(it->parent)) { + it->E->erase(); + } + items.free(it->rid); + it->subitems.clear(); + memdelete(it); } - _remove_item(subitem->get(), p_paragraph); + main->lines.remove_at(p_paragraph); + current_char_ofs -= off; } - if (!had_newline) { - current_frame->lines.remove_at(p_paragraph); - } + selection.click_frame = nullptr; + selection.click_item = nullptr; + selection.active = false; - if (current_frame->lines.is_empty()) { - current_frame->lines.resize(1); + if (p_no_invalidate) { + // Do not invalidate cache, only update vertical offsets of the paragraphs after deleted one and scrollbar. + int to_line = main->first_invalid_line.load() - 1; + float total_height = (p_paragraph == 0) ? 0 : _calculate_line_vertical_offset(main->lines[p_paragraph - 1]); + for (int i = p_paragraph; i < to_line; i++) { + MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); + main->lines[i].offset.y = total_height; + total_height = _calculate_line_vertical_offset(main->lines[i]); + } + updating_scroll = true; + vscroll->set_max(total_height); + updating_scroll = false; + + main->first_invalid_line.store(MAX(main->first_invalid_line.load() - 1, 0)); + main->first_resized_line.store(MAX(main->first_resized_line.load() - 1, 0)); + main->first_invalid_font_line.store(MAX(main->first_invalid_font_line.load() - 1, 0)); + } else { + // Invalidate cache after the deleted paragraph. + main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph)); + main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph)); + main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph)); } + queue_redraw(); + + return true; +} + +bool RichTextLabel::invalidate_paragraph(int p_paragraph) { + _stop_thread(); + MutexLock data_lock(data_mutex); - if (p_paragraph == 0 && current->subitems.size() > 0) { - main->lines[0].from = main; + if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) { + return false; } + // Invalidate cache. main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph)); main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph)); main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph)); @@ -4095,7 +4086,7 @@ bool RichTextLabel::is_scroll_active() const { void RichTextLabel::set_scroll_follow(bool p_follow) { scroll_follow = p_follow; - if (!vscroll->is_visible_in_tree() || vscroll->get_value() >= (vscroll->get_max() - vscroll->get_page())) { + if (!vscroll->is_visible_in_tree() || vscroll->get_value() > (vscroll->get_max() - vscroll->get_page() - 1)) { scroll_following = true; } } @@ -4406,6 +4397,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { push_strikethrough(); pos = brk_end + 1; tag_stack.push_front(tag); + } else if (tag.begins_with("char=")) { + int32_t char_code = tag.substr(5, tag.length()).hex_to_int(); + add_text(String::chr(char_code)); + pos = brk_end + 1; } else if (tag == "lb") { add_text("["); pos = brk_end + 1; @@ -5189,7 +5184,7 @@ void RichTextLabel::scroll_to_paragraph(int p_paragraph) { } int RichTextLabel::get_paragraph_count() const { - return current_frame->lines.size(); + return main->lines.size(); } int RichTextLabel::get_visible_paragraph_count() const { @@ -5917,7 +5912,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(Variant()), DEFVAL(false), DEFVAL(String()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("update_image", "key", "mask", "image", "width", "height", "color", "inline_align", "region", "pad", "tooltip", "size_in_percent"), &RichTextLabel::update_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(false), DEFVAL(String()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); - ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::remove_paragraph); + ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph", "no_invalidate"), &RichTextLabel::remove_paragraph, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("invalidate_paragraph", "paragraph"), &RichTextLabel::invalidate_paragraph); ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font, DEFVAL(0)); ClassDB::bind_method(D_METHOD("push_font_size", "font_size"), &RichTextLabel::push_font_size); ClassDB::bind_method(D_METHOD("push_normal"), &RichTextLabel::push_normal); @@ -6374,65 +6370,6 @@ void RichTextLabel::menu_option(int p_option) { } } -void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag) { - Vector2i fbg_index = Vector2i(end, start); - Color last_color = Color(0, 0, 0, 0); - bool draw_box = false; - // Draw a box based on color tags associated with glyphs - for (int i = start; i < end; i++) { - Item *it = _get_item_at_pos(it_from, it_to, i); - Color color; - - if (fbg_flag == 0) { - color = _find_bgcolor(it); - } else { - color = _find_fgcolor(it); - } - - bool change_to_color = ((color.a > 0) && ((last_color.a - 0.0) < 0.01)); - bool change_from_color = (((color.a - 0.0) < 0.01) && (last_color.a > 0.0)); - bool change_color = (((color.a > 0) == (last_color.a > 0)) && (color != last_color)); - - if (change_to_color) { - fbg_index.x = MIN(i, fbg_index.x); - fbg_index.y = MAX(i, fbg_index.y); - } - - if (change_from_color || change_color) { - fbg_index.x = MIN(i, fbg_index.x); - fbg_index.y = MAX(i, fbg_index.y); - draw_box = true; - } - - if (draw_box) { - Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y); - for (int j = 0; j < sel.size(); j++) { - Vector2 rect_off = line_off + Vector2(sel[j].x - theme_cache.text_highlight_h_padding, -TS->shaped_text_get_ascent(p_rid) - theme_cache.text_highlight_v_padding); - Vector2 rect_size = Vector2(sel[j].y - sel[j].x + 2 * theme_cache.text_highlight_h_padding, TS->shaped_text_get_size(p_rid).y + 2 * theme_cache.text_highlight_v_padding); - RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); - } - fbg_index = Vector2i(end, start); - draw_box = false; - } - - if (change_color) { - fbg_index.x = MIN(i, fbg_index.x); - fbg_index.y = MAX(i, fbg_index.y); - } - - last_color = color; - } - - if (last_color.a > 0) { - Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end); - for (int i = 0; i < sel.size(); i++) { - Vector2 rect_off = line_off + Vector2(sel[i].x - theme_cache.text_highlight_h_padding, -TS->shaped_text_get_ascent(p_rid) - theme_cache.text_highlight_v_padding); - Vector2 rect_size = Vector2(sel[i].y - sel[i].x + 2 * theme_cache.text_highlight_h_padding, TS->shaped_text_get_size(p_rid).y + 2 * theme_cache.text_highlight_v_padding); - RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); - } - } -} - Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) { for (int i = 0; i < custom_effects.size(); i++) { Ref<RichTextEffect> effect = custom_effects[i]; diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index f8dd9e663c..189ee1da6e 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -43,6 +43,15 @@ class RichTextEffect; class RichTextLabel : public Control { GDCLASS(RichTextLabel, Control); + enum RTLDrawStep { + DRAW_STEP_BACKGROUND, + DRAW_STEP_SHADOW, + DRAW_STEP_OUTLINE, + DRAW_STEP_TEXT, + DRAW_STEP_FOREGROUND, + DRAW_STEP_MAX, + }; + public: enum ListType { LIST_NUMBERS, @@ -125,6 +134,7 @@ protected: #ifndef DISABLE_DEPRECATED void _push_meta_bind_compat_89024(const Variant &p_meta); void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region); + bool _remove_paragraph_bind_compat_91098(int p_paragraph); static void _bind_compatibility_methods(); #endif @@ -485,7 +495,7 @@ private: _FORCE_INLINE_ float _update_scroll_exceeds(float p_total_height, float p_ctrl_height, float p_width, int p_idx, float p_old_scroll, float p_text_rect_height); void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false); - void _remove_item(Item *p_item, const int p_line); + void _remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset); void _texture_changed(RID p_item); @@ -596,7 +606,6 @@ private: Size2 _get_image_size(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2()); - void _draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag); #ifndef DISABLE_DEPRECATED // Kept for compatibility from 3.x to 4.0. bool _set(const StringName &p_name, const Variant &p_value); @@ -656,7 +665,8 @@ public: void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false); void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false); void add_newline(); - bool remove_paragraph(const int p_paragraph); + bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false); + bool invalidate_paragraph(int p_paragraph); void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0)); void _push_def_font(DefaultFont p_def_font); void _push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size = -1); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 1310cac2c7..af9f08e389 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -46,9 +46,6 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> m = p_event; - if (!m.is_valid() || drag.active) { - emit_signal(SNAME("scrolling")); - } Ref<InputEventMouseButton> b = p_event; @@ -57,13 +54,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) { double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0; - set_value(get_value() + MAX(change, get_step())); + scroll(MAX(change, get_step())); accept_event(); } if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) { double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0; - set_value(get_value() - MAX(change, get_step())); + scroll(-MAX(change, get_step())); accept_event(); } @@ -84,14 +81,14 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (ofs < decr_size) { decr_active = true; - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + scroll(-(custom_step >= 0 ? custom_step : get_step())); queue_redraw(); return; } if (ofs > total - incr_size) { incr_active = true; - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + scroll(custom_step >= 0 ? custom_step : get_step()); queue_redraw(); return; } @@ -110,7 +107,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { scrolling = true; set_physics_process_internal(true); } else { - set_value(target_scroll); + scroll_to(target_scroll); } return; } @@ -134,7 +131,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { scrolling = true; set_physics_process_internal(true); } else { - set_value(target_scroll); + scroll_to(target_scroll); } } @@ -158,7 +155,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { double diff = (ofs - drag.pos_at_click) / get_area_size(); + double prev_scroll = get_value(); + set_as_ratio(drag.value_at_click + diff); + + if (!Math::is_equal_approx(prev_scroll, get_value())) { + emit_signal(SNAME("scrolling")); + } } else { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; Ref<Texture2D> decr = theme_cache.decrement_icon; @@ -192,32 +195,32 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (orientation != HORIZONTAL) { return; } - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + scroll(-(custom_step >= 0 ? custom_step : get_step())); } else if (p_event->is_action("ui_right", true)) { if (orientation != HORIZONTAL) { return; } - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + scroll(custom_step >= 0 ? custom_step : get_step()); } else if (p_event->is_action("ui_up", true)) { if (orientation != VERTICAL) { return; } - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + scroll(-(custom_step >= 0 ? custom_step : get_step())); } else if (p_event->is_action("ui_down", true)) { if (orientation != VERTICAL) { return; } - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + scroll(custom_step >= 0 ? custom_step : get_step()); } else if (p_event->is_action("ui_home", true)) { - set_value(get_min()); + scroll_to(get_min()); } else if (p_event->is_action("ui_end", true)) { - set_value(get_max()); + scroll_to(get_max()); } } } @@ -307,15 +310,15 @@ void ScrollBar::_notification(int p_what) { } if (drag_node) { - drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT); + drag_node->connect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input)); + drag_node->connect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT); } } break; case NOTIFICATION_EXIT_TREE: { if (drag_node) { - drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->disconnect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit)); + drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input)); + drag_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit)); } drag_node = nullptr; @@ -329,11 +332,11 @@ void ScrollBar::_notification(int p_what) { double vel = ((target / dist) * 500) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { - set_value(target_scroll); + scroll_to(target_scroll); scrolling = false; set_physics_process_internal(false); } else { - set_value(get_value() + vel); + scroll(vel); } } else { scrolling = false; @@ -358,7 +361,7 @@ void ScrollBar::_notification(int p_what) { turnoff = true; } - set_value(pos.x); + scroll_to(pos.x); float sgn_x = drag_node_speed.x < 0 ? -1 : 1; float val_x = Math::abs(drag_node_speed.x); @@ -381,7 +384,7 @@ void ScrollBar::_notification(int p_what) { turnoff = true; } - set_value(pos.y); + scroll_to(pos.y); float sgn_y = drag_node_speed.y < 0 ? -1 : 1; float val_y = Math::abs(drag_node_speed.y); @@ -497,6 +500,18 @@ Size2 ScrollBar::get_minimum_size() const { return minsize; } +void ScrollBar::scroll(double p_amount) { + scroll_to(get_value() + p_amount); +} + +void ScrollBar::scroll_to(double p_position) { + double prev_scroll = get_value(); + set_value(p_position); + if (!Math::is_equal_approx(prev_scroll, get_value())) { + emit_signal(SNAME("scrolling")); + } +} + void ScrollBar::set_custom_step(float p_custom_step) { custom_step = p_custom_step; } @@ -507,7 +522,7 @@ float ScrollBar::get_custom_step() const { void ScrollBar::_drag_node_exit() { if (drag_node) { - drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); + drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input)); } drag_node = nullptr; } @@ -561,11 +576,11 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { Vector2 diff = drag_node_from + drag_node_accum; if (orientation == HORIZONTAL) { - set_value(diff.x); + scroll_to(diff.x); } if (orientation == VERTICAL) { - set_value(diff.y); + scroll_to(diff.y); } time_since_motion = 0; @@ -576,8 +591,8 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { void ScrollBar::set_drag_node(const NodePath &p_path) { if (is_inside_tree()) { if (drag_node) { - drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->disconnect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit)); + drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input)); + drag_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit)); } } @@ -591,8 +606,8 @@ void ScrollBar::set_drag_node(const NodePath &p_path) { } if (drag_node) { - drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT); + drag_node->connect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input)); + drag_node->connect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT); } } } diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index deadbb53d6..ad88d826a2 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -111,6 +111,9 @@ protected: static void _bind_methods(); public: + void scroll(double p_amount); + void scroll_to(double p_position); + void set_custom_step(float p_custom_step); float get_custom_step() const; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 89d308de3f..6f5d0cdcfb 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -43,11 +43,8 @@ Size2 ScrollContainer::get_minimum_size() const { largest_child_min_size = Size2(); for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } if (c == h_scroll || c == v_scroll) { @@ -56,8 +53,7 @@ Size2 ScrollContainer::get_minimum_size() const { Size2 child_min_size = c->get_combined_minimum_size(); - largest_child_min_size.x = MAX(largest_child_min_size.x, child_min_size.x); - largest_child_min_size.y = MAX(largest_child_min_size.y, child_min_size.y); + largest_child_min_size = largest_child_min_size.max(child_min_size); } if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { @@ -115,19 +111,19 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MouseButton::WHEEL_UP) { // By default, the vertical orientation takes precedence. This is an exception. if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { - h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (v_scroll_enabled) { - v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } if (mb->get_button_index() == MouseButton::WHEEL_DOWN) { if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { - h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (v_scroll_enabled) { - v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } @@ -136,19 +132,19 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { // By default, the horizontal orientation takes precedence. This is an exception. if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { - v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (h_scroll_enabled) { - h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { - v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } else if (h_scroll_enabled) { - h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor()); scroll_value_modified = true; } } @@ -214,12 +210,12 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } Vector2 diff = drag_from + drag_accum; if (h_scroll_enabled) { - h_scroll->set_value(diff.x); + h_scroll->scroll_to(diff.x); } else { drag_accum.x = 0; } if (v_scroll_enabled) { - v_scroll->set_value(diff.y); + v_scroll->scroll_to(diff.y); } else { drag_accum.y = 0; } @@ -236,10 +232,10 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventPanGesture> pan_gesture = p_gui_input; if (pan_gesture.is_valid()) { if (h_scroll_enabled) { - h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8); + h_scroll->scroll(h_scroll->get_page() * pan_gesture->get_delta().x / 8); } if (v_scroll_enabled) { - v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8); + v_scroll->scroll(v_scroll->get_page() * pan_gesture->get_delta().y / 8); } if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { @@ -309,11 +305,8 @@ void ScrollContainer::_reposition_children() { } for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i)); + if (!c) { continue; } if (c == h_scroll || c == v_scroll) { @@ -392,10 +385,10 @@ void ScrollContainer::_notification(int p_what) { } if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { - h_scroll->set_value(pos.x); + h_scroll->scroll_to(pos.x); } if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { - v_scroll->set_value(pos.y); + v_scroll->scroll_to(pos.y); } float sgn_x = drag_speed.x < 0 ? -1 : 1; @@ -543,13 +536,10 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const { int found = 0; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + Control *c = as_sortable_control(get_child(i)); if (!c) { continue; } - if (c->is_set_as_top_level()) { - continue; - } if (c == h_scroll || c == v_scroll) { continue; } diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 70acaf7adf..bfea7b0fbe 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -409,9 +409,9 @@ SpinBox::SpinBox() { line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED); - line_edit->connect("focus_entered", callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED); - line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED); - line_edit->connect("gui_input", callable_mp(this, &SpinBox::_line_edit_input)); + line_edit->connect(SceneStringName(focus_entered), callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED); + line_edit->connect(SceneStringName(focus_exited), callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED); + line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input)); range_click_timer = memnew(Timer); range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout)); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 5f4586a6d5..925600756a 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -39,7 +39,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - if (sc->collapsed || !sc->get_containable_child(0) || !sc->get_containable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) { + if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) { return; } @@ -122,15 +122,12 @@ void SplitContainerDragger::_notification(int p_what) { } } -Control *SplitContainer::get_containable_child(int p_idx) const { +Control *SplitContainer::_get_sortable_child(int p_idx) const { int idx = 0; for (int i = 0; i < get_child_count(false); i++) { - Control *c = Object::cast_to<Control>(get_child(i, false)); - if (!c || !c->is_visible()) { - continue; - } - if (c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i, false)); + if (!c) { continue; } @@ -157,8 +154,8 @@ Ref<Texture2D> SplitContainer::_get_grabber_icon() const { } void SplitContainer::_compute_middle_sep(bool p_clamp) { - Control *first = get_containable_child(0); - Control *second = get_containable_child(1); + Control *first = _get_sortable_child(0); + Control *second = _get_sortable_child(1); // Determine expanded children. bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; @@ -199,8 +196,8 @@ void SplitContainer::_compute_middle_sep(bool p_clamp) { } void SplitContainer::_resort() { - Control *first = get_containable_child(0); - Control *second = get_containable_child(1); + Control *first = _get_sortable_child(0); + Control *second = _get_sortable_child(1); // If we have only one element. if (!first || !second) { @@ -261,7 +258,7 @@ Size2 SplitContainer::get_minimum_size() const { int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; for (int i = 0; i < 2; i++) { - if (!get_containable_child(i)) { + if (!_get_sortable_child(i)) { break; } @@ -273,7 +270,7 @@ Size2 SplitContainer::get_minimum_size() const { } } - Size2 ms = get_containable_child(i)->get_combined_minimum_size(); + Size2 ms = _get_sortable_child(i)->get_combined_minimum_size(); if (vertical) { minimum.height += ms.height; @@ -325,7 +322,7 @@ int SplitContainer::get_split_offset() const { } void SplitContainer::clamp_split_offset() { - if (!get_containable_child(0) || !get_containable_child(1)) { + if (!_get_sortable_child(0) || !_get_sortable_child(1)) { return; } diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index 0f45ef166d..95f26f5e0b 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -82,12 +82,11 @@ private: Ref<Texture2D> _get_grabber_icon() const; void _compute_middle_sep(bool p_clamp); void _resort(); + Control *_get_sortable_child(int p_idx) const; protected: bool is_fixed = false; - Control *get_containable_child(int p_idx) const; - void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 0d33774e20..c715aceb0b 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -45,8 +45,7 @@ Size2 SubViewportContainer::get_minimum_size() const { } Size2 minsize = c->get_size(); - ms.width = MAX(ms.width, minsize.width); - ms.height = MAX(ms.height, minsize.height); + ms = ms.max(minsize); } return ms; @@ -288,7 +287,7 @@ void SubViewportContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_stretch_shrink"), &SubViewportContainer::get_stretch_shrink); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink"), "set_stretch_shrink", "get_stretch_shrink"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), "set_stretch_shrink", "get_stretch_shrink"); GDVIRTUAL_BIND(_propagate_input_event, "event"); } diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index d20fef8164..ddc757c452 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -323,6 +323,19 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } } +String TabBar::get_tooltip(const Point2 &p_pos) const { + int tab_idx = get_tab_idx_at_point(p_pos); + if (tab_idx < 0) { + return Control::get_tooltip(p_pos); + } + + if (tabs[tab_idx].tooltip.is_empty() && tabs[tab_idx].truncated) { + return tabs[tab_idx].text; + } + + return tabs[tab_idx].tooltip; +} + void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->clear(); tabs.write[p_tab].text_buf->set_width(-1); @@ -757,6 +770,16 @@ String TabBar::get_tab_title(int p_tab) const { return tabs[p_tab].text; } +void TabBar::set_tab_tooltip(int p_tab, const String &p_tooltip) { + ERR_FAIL_INDEX(p_tab, tabs.size()); + tabs.write[p_tab].tooltip = p_tooltip; +} + +String TabBar::get_tab_tooltip(int p_tab) const { + ERR_FAIL_INDEX_V(p_tab, tabs.size(), ""); + return tabs[p_tab].tooltip; +} + void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) { ERR_FAIL_INDEX(p_tab, tabs.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); @@ -998,7 +1021,8 @@ void TabBar::_update_cache(bool p_update_hover) { tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x); tabs.write[i].size_cache = get_tab_width(i); - if (max_width > 0 && tabs[i].size_cache > max_width) { + tabs.write[i].truncated = max_width > 0 && tabs[i].size_cache > max_width; + if (tabs[i].truncated) { int size_textless = tabs[i].size_cache - tabs[i].size_text; int mw = MAX(size_textless, max_width); @@ -1720,58 +1744,6 @@ bool TabBar::get_deselect_enabled() const { return deselect_enabled; } -bool TabBar::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - set_tab_title(tab_index, p_value); - return true; - } else if (property == "icon") { - set_tab_icon(tab_index, p_value); - return true; - } else if (property == "disabled") { - set_tab_disabled(tab_index, p_value); - return true; - } - } - return false; -} - -bool TabBar::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - r_ret = get_tab_title(tab_index); - return true; - } else if (property == "icon") { - r_ret = get_tab_icon(tab_index); - return true; - } else if (property == "disabled") { - r_ret = is_tab_disabled(tab_index); - return true; - } - } - return false; -} - -void TabBar::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < tabs.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("tab_%d/title", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("tab_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(get_tab_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("tab_%d/disabled", i)); - pi.usage &= ~(!is_tab_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tab_count", "count"), &TabBar::set_tab_count); ClassDB::bind_method(D_METHOD("get_tab_count"), &TabBar::get_tab_count); @@ -1782,6 +1754,8 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("select_next_available"), &TabBar::select_next_available); ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabBar::set_tab_title); ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title); + ClassDB::bind_method(D_METHOD("set_tab_tooltip", "tab_idx", "tooltip"), &TabBar::set_tab_tooltip); + ClassDB::bind_method(D_METHOD("get_tab_tooltip", "tab_idx"), &TabBar::get_tab_tooltip); ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction); ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &TabBar::get_tab_text_direction); ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &TabBar::set_tab_language); @@ -1890,10 +1864,21 @@ void TabBar::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, close_icon, "close"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_pressed_style, "button_pressed"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_hl_style, "button_highlight"); + + Tab defaults(true); + + base_property_helper.set_prefix("tab_"); + base_property_helper.set_array_length_getter(&TabBar::get_tab_count); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "title"), defaults.text, &TabBar::set_tab_title, &TabBar::get_tab_title); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "tooltip"), defaults.tooltip, &TabBar::set_tab_tooltip, &TabBar::get_tab_tooltip); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &TabBar::set_tab_disabled, &TabBar::is_tab_disabled); } TabBar::TabBar() { set_size(Size2(get_size().width, get_minimum_size().height)); set_focus_mode(FOCUS_ALL); - connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited)); + connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited)); + + property_helper.setup_for_instance(base_property_helper, this); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 65a1d5bd4f..52f1da5ec8 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -32,6 +32,7 @@ #define TAB_BAR_H #include "scene/gui/control.h" +#include "scene/property_list_helper.h" #include "scene/resources/text_line.h" class TabBar : public Control { @@ -55,6 +56,7 @@ public: private: struct Tab { String text; + String tooltip; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; @@ -65,6 +67,8 @@ private: bool disabled = false; bool hidden = false; + bool truncated = false; + Variant metadata; int ofs_cache = 0; int size_cache = 0; @@ -77,8 +81,13 @@ private: Tab() { text_buf.instantiate(); } + + Tab(bool p_dummy) {} }; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + int offset = 0; int max_drawn_tab = 0; int highlight_arrow = -1; @@ -162,10 +171,13 @@ private: protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual String get_tooltip(const Point2 &p_pos) const override; - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, tabs.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); @@ -184,6 +196,9 @@ public: void set_tab_title(int p_tab, const String &p_title); String get_tab_title(int p_tab) const; + void set_tab_tooltip(int p_tab, const String &p_tooltip); + String get_tab_tooltip(int p_tab) const; + void set_tab_text_direction(int p_tab, TextDirection p_text_direction); TextDirection get_tab_text_direction(int p_tab) const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 741b38a35a..d0c3f3d65e 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -30,9 +30,6 @@ #include "tab_container.h" -#include "scene/gui/box_container.h" -#include "scene/gui/label.h" -#include "scene/gui/texture_rect.h" #include "scene/theme/theme_db.h" int TabContainer::_get_tab_height() const { @@ -150,9 +147,7 @@ void TabContainer::_notification(int p_what) { if (get_tab_count() > 0) { _refresh_tab_names(); } - } break; - case NOTIFICATION_POST_ENTER_TREE: { if (setup_current_tab >= -1) { set_current_tab(setup_current_tab); setup_current_tab = -2; @@ -194,6 +189,25 @@ void TabContainer::_notification(int p_what) { } } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible() || setup_current_tab > -2) { + return; + } + + updating_visibility = true; + + // As the visibility change notification will be triggered for all children soon after, + // beat it to the punch and make sure that the correct node is the only one visible first. + // Otherwise, it can prevent a tab change done right before this container was made visible. + Vector<Control *> controls = _get_tab_controls(); + int current = get_current_tab(); + for (int i = 0; i < controls.size(); i++) { + controls[i]->set_visible(i == current); + } + + updating_visibility = false; + } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { @@ -403,6 +417,7 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_ // Get the tab properties before they get erased by the child removal. String tab_title = p_from->get_tab_title(p_from_index); + String tab_tooltip = p_from->get_tab_tooltip(p_from_index); Ref<Texture2D> tab_icon = p_from->get_tab_icon(p_from_index); Ref<Texture2D> tab_button_icon = p_from->get_tab_button_icon(p_from_index); bool tab_disabled = p_from->is_tab_disabled(p_from_index); @@ -420,6 +435,7 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_ move_child(moving_tabc, get_tab_control(p_to_index)->get_index(false)); set_tab_title(p_to_index, tab_title); + set_tab_tooltip(p_to_index, tab_tooltip); set_tab_icon(p_to_index, tab_icon); set_tab_button_icon(p_to_index, tab_button_icon); set_tab_disabled(p_to_index, tab_disabled); @@ -500,6 +516,13 @@ void TabContainer::_on_tab_visibility_changed(Control *p_child) { updating_visibility = false; } +void TabContainer::_refresh_tab_indices() { + Vector<Control *> controls = _get_tab_controls(); + for (int i = 0; i < controls.size(); i++) { + controls[i]->set_meta("_tab_index", i); + } +} + void TabContainer::_refresh_tab_names() { Vector<Control *> controls = _get_tab_controls(); for (int i = 0; i < controls.size(); i++) { @@ -523,6 +546,7 @@ void TabContainer::add_child_notify(Node *p_child) { c->hide(); tab_bar->add_tab(p_child->get_name()); + c->set_meta("_tab_index", tab_bar->get_tab_count() - 1); _update_margins(); if (get_tab_count() == 1) { @@ -530,7 +554,7 @@ void TabContainer::add_child_notify(Node *p_child) { } p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names)); - p_child->connect(SNAME("visibility_changed"), callable_mp(this, &TabContainer::_on_tab_visibility_changed).bind(c)); + p_child->connect(SceneStringName(visibility_changed), callable_mp(this, &TabContainer::_on_tab_visibility_changed).bind(c)); // TabBar won't emit the "tab_changed" signal when not inside the tree. if (!is_inside_tree()) { @@ -547,19 +571,10 @@ void TabContainer::move_child_notify(Node *p_child) { Control *c = Object::cast_to<Control>(p_child); if (c && !c->is_set_as_top_level()) { - int old_idx = -1; - String tab_name = String(c->get_meta("_tab_name", c->get_name())); - - // Find the previous tab index of the control. - for (int i = 0; i < get_tab_count(); i++) { - if (get_tab_title(i) == tab_name) { - old_idx = i; - break; - } - } - - tab_bar->move_tab(old_idx, get_tab_idx_from_control(c)); + tab_bar->move_tab(c->get_meta("_tab_index"), get_tab_idx_from_control(c)); } + + _refresh_tab_indices(); } void TabContainer::remove_child_notify(Node *p_child) { @@ -578,7 +593,10 @@ void TabContainer::remove_child_notify(Node *p_child) { // As the child hasn't been removed yet, keep track of it so when the "tab_changed" signal is fired it can be ignored. children_removing.push_back(c); + tab_bar->remove_tab(idx); + _refresh_tab_indices(); + children_removing.erase(c); _update_margins(); @@ -586,9 +604,10 @@ void TabContainer::remove_child_notify(Node *p_child) { queue_redraw(); } + p_child->remove_meta("_tab_index"); p_child->remove_meta("_tab_name"); p_child->disconnect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names)); - p_child->disconnect(SNAME("visibility_changed"), callable_mp(this, &TabContainer::_on_tab_visibility_changed)); + p_child->disconnect(SceneStringName(visibility_changed), callable_mp(this, &TabContainer::_on_tab_visibility_changed)); // TabBar won't emit the "tab_changed" signal when not inside the tree. if (!is_inside_tree()) { @@ -609,6 +628,7 @@ void TabContainer::set_current_tab(int p_current) { setup_current_tab = p_current; return; } + tab_bar->set_current_tab(p_current); } @@ -760,16 +780,22 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { child->set_meta("_tab_name", p_title); } - _update_margins(); - if (!get_clip_tabs()) { - update_minimum_size(); - } + _repaint(); + queue_redraw(); } String TabContainer::get_tab_title(int p_tab) const { return tab_bar->get_tab_title(p_tab); } +void TabContainer::set_tab_tooltip(int p_tab, const String &p_tooltip) { + tab_bar->set_tab_tooltip(p_tab, p_tooltip); +} + +String TabContainer::get_tab_tooltip(int p_tab) const { + return tab_bar->get_tab_tooltip(p_tab); +} + void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { if (tab_bar->get_tab_icon(p_tab) == p_icon) { return; @@ -779,12 +805,29 @@ void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { _update_margins(); _repaint(); + queue_redraw(); } Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { return tab_bar->get_tab_icon(p_tab); } +void TabContainer::set_tab_icon_max_width(int p_tab, int p_width) { + if (tab_bar->get_tab_icon_max_width(p_tab) == p_width) { + return; + } + + tab_bar->set_tab_icon_max_width(p_tab, p_width); + + _update_margins(); + _repaint(); + queue_redraw(); +} + +int TabContainer::get_tab_icon_max_width(int p_tab) const { + return tab_bar->get_tab_icon_max_width(p_tab); +} + void TabContainer::set_tab_disabled(int p_tab, bool p_disabled) { if (tab_bar->is_tab_disabled(p_tab) == p_disabled) { return; @@ -871,8 +914,7 @@ Size2 TabContainer::get_minimum_size() const { } Size2 cms = c->get_combined_minimum_size(); - largest_child_min_size.x = MAX(largest_child_min_size.x, cms.x); - largest_child_min_size.y = MAX(largest_child_min_size.y, cms.y); + largest_child_min_size = largest_child_min_size.max(cms); } ms.y += largest_child_min_size.y; @@ -978,8 +1020,12 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("is_all_tabs_in_front"), &TabContainer::is_all_tabs_in_front); ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabContainer::set_tab_title); ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabContainer::get_tab_title); + ClassDB::bind_method(D_METHOD("set_tab_tooltip", "tab_idx", "tooltip"), &TabContainer::set_tab_tooltip); + ClassDB::bind_method(D_METHOD("get_tab_tooltip", "tab_idx"), &TabContainer::get_tab_tooltip); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabContainer::set_tab_icon); ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); + ClassDB::bind_method(D_METHOD("set_tab_icon_max_width", "tab_idx", "width"), &TabContainer::set_tab_icon_max_width); + ClassDB::bind_method(D_METHOD("get_tab_icon_max_width", "tab_idx"), &TabContainer::get_tab_icon_max_width); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabContainer::is_tab_disabled); ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabContainer::set_tab_hidden); @@ -1075,5 +1121,5 @@ TabContainer::TabContainer() { tab_bar->connect("tab_button_pressed", callable_mp(this, &TabContainer::_on_tab_button_pressed)); tab_bar->connect("active_tab_rearranged", callable_mp(this, &TabContainer::_on_active_tab_rearranged)); - connect("mouse_exited", callable_mp(this, &TabContainer::_on_mouse_exited)); + connect(SceneStringName(mouse_exited), callable_mp(this, &TabContainer::_on_mouse_exited)); } diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 03eff5d944..e00bc780d4 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -101,6 +101,7 @@ private: Vector<Control *> _get_tab_controls() const; void _on_theme_changed(); void _repaint(); + void _refresh_tab_indices(); void _refresh_tab_names(); void _update_margins(); void _on_mouse_exited(); @@ -154,9 +155,15 @@ public: void set_tab_title(int p_tab, const String &p_title); String get_tab_title(int p_tab) const; + void set_tab_tooltip(int p_tab, const String &p_tooltip); + String get_tab_tooltip(int p_tab) const; + void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon); Ref<Texture2D> get_tab_icon(int p_tab) const; + void set_tab_icon_max_width(int p_tab, int p_width); + int get_tab_icon_max_width(int p_tab) const; + void set_tab_disabled(int p_tab, bool p_disabled); bool is_tab_disabled(int p_tab) const; diff --git a/scene/2d/tile_map_layer_group.h b/scene/gui/text_edit.compat.inc index d80c244f80..bf73229868 100644 --- a/scene/2d/tile_map_layer_group.h +++ b/scene/gui/text_edit.compat.inc @@ -1,5 +1,5 @@ /**************************************************************************/ -/* tile_map_layer_group.h */ +/* text_edit.compat.inc */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,46 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef TILE_MAP_LAYER_GROUP_H -#define TILE_MAP_LAYER_GROUP_H +#ifndef DISABLE_DEPRECATED -#include "scene/2d/node_2d.h" +void TextEdit::_set_selection_mode_compat_86978(SelectionMode p_mode, int p_line, int p_column, int p_caret) { + set_selection_mode(p_mode); +} -class TileSet; +void TextEdit::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::_set_selection_mode_compat_86978, DEFVAL(-1), DEFVAL(-1), DEFVAL(0)); +} -class TileMapLayerGroup : public Node2D { - GDCLASS(TileMapLayerGroup, Node2D); - -private: - mutable Vector<StringName> selected_layers; - bool highlight_selected_layer = true; - -#ifdef TOOLS_ENABLED - void _cleanup_selected_layers(); -#endif - void _tile_set_changed(); - -protected: - Ref<TileSet> tile_set; - - virtual void remove_child_notify(Node *p_child) override; - - static void _bind_methods(); - -public: -#ifdef TOOLS_ENABLED - // For editor use. - void set_selected_layers(Vector<StringName> p_layer_names); - Vector<StringName> get_selected_layers() const; - void set_highlight_selected_layer(bool p_highlight_selected_layer); - bool is_highlighting_selected_layer() const; #endif - - // Accessors. - void set_tileset(const Ref<TileSet> &p_tileset); - Ref<TileSet> get_tileset() const; - - ~TileMapLayerGroup(); -}; - -#endif // TILE_MAP_LAYER_GROUP_H diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 2d7a66d4c0..1dd00fab4d 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "text_edit.h" +#include "text_edit.compat.inc" #include "core/config/project_settings.h" #include "core/input/input.h" @@ -451,7 +452,7 @@ void TextEdit::_notification(int p_what) { callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred(); } if (text_changed_dirty) { - callable_mp(this, &TextEdit::_text_changed_emit).call_deferred(); + callable_mp(this, &TextEdit::_emit_text_changed).call_deferred(); } _update_wrap_at_column(true); } break; @@ -539,7 +540,6 @@ void TextEdit::_notification(int p_what) { _update_scrollbars(); RID ci = get_canvas_item(); - RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); int xmargin_beg = theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding; int xmargin_end = size.width - theme_cache.style_normal->get_margin(SIDE_RIGHT); @@ -558,17 +558,15 @@ void TextEdit::_notification(int p_what) { int visible_rows = get_visible_line_count() + 1; - Color color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color; - if (theme_cache.background_color.a > 0.01) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), theme_cache.background_color); } Vector<BraceMatchingData> brace_matching; if (highlight_matching_braces_enabled) { - brace_matching.resize(carets.size()); + brace_matching.resize(get_caret_count()); - for (int caret = 0; caret < carets.size(); caret++) { + for (int caret = 0; caret < get_caret_count(); caret++) { if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) { continue; } @@ -734,7 +732,7 @@ void TextEdit::_notification(int p_what) { if (draw_minimap) { int minimap_visible_lines = get_minimap_visible_lines(); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); - int minimap_tab_size = minimap_char_size.x * text.get_tab_size(); + int tab_size = text.get_tab_size(); // Calculate viewport size and y offset. int viewport_height = (draw_amount - 1) * minimap_line_height; @@ -753,16 +751,15 @@ void TextEdit::_notification(int p_what) { // Draw the minimap. // Add visual feedback when dragging or hovering the visible area rectangle. - float viewport_alpha; + Color viewport_color = theme_cache.caret_color; if (dragging_minimap) { - viewport_alpha = 0.25; + viewport_color.a = 0.25; } else if (hovering_minimap) { - viewport_alpha = 0.175; + viewport_color.a = 0.175; } else { - viewport_alpha = 0.1; + viewport_color.a = 0.1; } - const Color viewport_color = (theme_cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha); if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color); } else { @@ -840,68 +837,74 @@ void TextEdit::_notification(int p_what) { } } - Color previous_color; + Color next_color = current_color; int characters = 0; - int tabs = 0; + int tab_alignment = 0; + int xpos = xmargin_end + 2 + indent_px; for (int j = 0; j < str.length(); j++) { - const Variant *color_data = color_map.getptr(last_wrap_column + j); - if (color_data != nullptr) { - current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); - if (!editable) { - current_color.a = theme_cache.font_readonly_color.a; + bool next_is_whitespace = false; + bool next_is_tab = false; + // Get the number of characters to draw together. + for (characters = 0; j + characters < str.length(); characters++) { + int next_char_index = j + characters; + const Variant *color_data = color_map.getptr(last_wrap_column + next_char_index); + if (color_data != nullptr) { + next_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); + if (!editable) { + next_color.a = theme_cache.font_readonly_color.a; + } + next_color.a *= 0.6; } - } - color = current_color; - - if (j == 0) { - previous_color = color; - } - - int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs; - bool out_of_bounds = (xpos >= xmargin_end + minimap_width); - - bool whitespace = is_whitespace(str[j]); - if (!whitespace) { - characters++; - - if (j < str.length() - 1 && color == previous_color && !out_of_bounds) { - continue; + if (characters == 0) { + current_color = next_color; } - - // If we've changed color we are at the start of a new section, therefore we need to go back to the end - // of the previous section to draw it, we'll also add the character back on. - if (color != previous_color) { - characters--; - j--; - - if (str[j] == '\t') { - tabs -= minimap_tab_size; + if (next_color != current_color) { + break; + } + next_is_whitespace = is_whitespace(str[next_char_index]); + if (next_is_whitespace) { + if (str[next_char_index] == '\t') { + next_is_tab = true; } + break; + } + bool out_of_bounds = xpos + minimap_char_size.x * characters >= xmargin_end + minimap_width; + if (out_of_bounds) { + break; } } + if (!next_is_whitespace && characters == 0) { + break; + } if (characters > 0) { - previous_color.a *= 0.6; - // Take one for zero indexing, and if we hit whitespace / the end of a word. - int chars = MAX(0, (j - (characters - 1)) - (whitespace ? 1 : 0)) + 1; - int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs; if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - char_x_ofs - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - xpos - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), current_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(xpos, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), current_color); } } - if (out_of_bounds) { - break; - } + j += characters - 1; + xpos += minimap_char_size.x * characters; + tab_alignment += characters; - if (str[j] == '\t') { - tabs += minimap_tab_size; + if (next_is_whitespace) { + if (next_is_tab) { + tab_alignment %= tab_size; + xpos += minimap_char_size.x * (tab_size - tab_alignment); + tab_alignment = 0; + } else { + xpos += minimap_char_size.x; + tab_alignment += 1; + } + j += 1; } - previous_color = color; - characters = 0; + if (xpos >= xmargin_end + minimap_width) { + // Out of bounds. + break; + } } } } @@ -1000,23 +1003,12 @@ void TextEdit::_notification(int p_what) { } } - if (str.length() == 0) { - // Draw line background if empty as we won't loop at all. - if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) { - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); - } - } - } else { - // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. - if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) { - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); - } + // Draw current line highlight. + if (highlight_current_line && caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index)) { + if (rtl) { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); + } else { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } } @@ -1106,14 +1098,14 @@ void TextEdit::_notification(int p_what) { // Draw selections. float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width; - for (int c = 0; c < carets.size(); c++) { + for (int c = 0; c < get_caret_count(); c++) { if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to); // Show selection at the end of line. - if (line < get_selection_to_line(c)) { + if (line_wrap_index == line_wrap_amount && line < get_selection_to_line(c)) { if (rtl) { sel.push_back(Vector2(-char_w, 0)); } else { @@ -1123,7 +1115,7 @@ void TextEdit::_notification(int p_what) { } for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, Math::ceil(sel[j].y - sel[j].x), row_height); + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, Math::ceil(sel[j].y) - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { continue; } @@ -1188,7 +1180,8 @@ void TextEdit::_notification(int p_what) { } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word - if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { + if (is_ascii_alphabet_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { + Color highlight_underline_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color; int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int lookup_symbol_word_len = lookup_symbol_word.length(); while (lookup_symbol_word_col != -1) { @@ -1206,7 +1199,7 @@ void TextEdit::_notification(int p_what) { } rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size)); rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size)); - draw_rect(rect, color); + draw_rect(rect, highlight_underline_color); } lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len); @@ -1259,7 +1252,7 @@ void TextEdit::_notification(int p_what) { } Color gl_color = current_color; - for (int c = 0; c < carets.size(); c++) { + for (int c = 0; c < get_caret_count(); c++) { if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); @@ -1273,7 +1266,7 @@ void TextEdit::_notification(int p_what) { float char_pos = char_ofs + char_margin + ofs_x; if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { - for (int c = 0; c < carets.size(); c++) { + for (int c = 0; c < get_caret_count(); c++) { if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) || (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) { if (brace_matching[c].open_mismatch) { @@ -1564,10 +1557,15 @@ void TextEdit::_notification(int p_what) { case MainLoop::NOTIFICATION_OS_IME_UPDATE: { if (has_focus()) { + bool had_ime_text = has_ime_text(); ime_text = DisplayServer::get_singleton()->ime_get_text(); ime_selection = DisplayServer::get_singleton()->ime_get_selection(); - if (!ime_text.is_empty() && has_selection()) { + if (!had_ime_text && has_ime_text()) { + _cancel_drag_and_drop_text(); + } + + if (has_ime_text() && has_selection()) { delete_selection(); } @@ -1578,7 +1576,7 @@ void TextEdit::_notification(int p_what) { } break; case NOTIFICATION_DRAG_BEGIN: { - selecting_mode = SelectionMode::SELECTION_MODE_NONE; + set_selection_mode(SelectionMode::SELECTION_MODE_NONE); drag_action = true; dragging_minimap = false; dragging_selection = false; @@ -1589,19 +1587,31 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_DRAG_END: { if (is_drag_successful()) { if (selection_drag_attempt) { - selection_drag_attempt = false; + // Dropped elsewhere. if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { delete_selection(); } else if (deselect_on_focus_loss_enabled) { deselect(); } } - } else { - selection_drag_attempt = false; } + if (drag_caret_index >= 0) { + if (drag_caret_index < carets.size()) { + remove_caret(drag_caret_index); + } + drag_caret_index = -1; + } + selection_drag_attempt = false; drag_action = false; drag_caret_force_displayed = false; } break; + + case NOTIFICATION_MOUSE_EXIT_SELF: { + if (drag_caret_force_displayed) { + drag_caret_force_displayed = false; + queue_redraw(); + } + } break; } } @@ -1704,15 +1714,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); } + if (mb->get_button_index() == MouseButton::LEFT) { _reset_caret_blink_timer(); apply_ime(); Point2i pos = get_line_column_at_pos(mpos); - int row = pos.y; + int line = pos.y; int col = pos.x; + // Gutters. int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT); for (int i = 0; i < gutters.size(); i++) { if (!gutters[i].draw || gutters[i].width <= 0) { @@ -1720,14 +1732,14 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) { - emit_signal(SNAME("gutter_clicked"), row, i); + emit_signal(SNAME("gutter_clicked"), line, i); return; } left_margin += gutters[i].width; } - // Minimap + // Minimap. if (draw_minimap) { _update_minimap_click(); if (dragging_minimap) { @@ -1735,121 +1747,86 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } + // Update caret. + int caret = carets.size() - 1; int prev_col = get_caret_column(caret); int prev_line = get_caret_line(caret); + int mouse_over_selection_caret = get_selection_at_line_column(line, col, true); + const int triple_click_timeout = 600; const int triple_click_tolerance = 5; bool is_triple_click = (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance); if (!mb->is_double_click() && !is_triple_click) { if (mb->is_alt_pressed()) { - prev_line = row; + prev_line = line; prev_col = col; // Remove caret at clicked location. - if (carets.size() > 1) { - for (int i = 0; i < carets.size(); i++) { - // Deselect if clicked on caret or its selection. - if ((get_caret_column(i) == col && get_caret_line(i) == row) || is_mouse_over_selection(true, i)) { - remove_caret(i); - last_dblclk = 0; - return; - } + if (get_caret_count() > 1) { + // Deselect if clicked on caret or its selection. + int clicked_caret = get_selection_at_line_column(line, col, true, false); + if (clicked_caret != -1) { + remove_caret(clicked_caret); + last_dblclk = 0; + return; } } - if (is_mouse_over_selection()) { + if (mouse_over_selection_caret >= 0) { + // Did not remove selection under mouse, don't add a new caret. return; } - caret = add_caret(row, col); + // Create new caret at clicked location. + caret = add_caret(line, col); if (caret == -1) { return; } - carets.write[caret].selection.selecting_line = row; - carets.write[caret].selection.selecting_column = col; - last_dblclk = 0; - } else if (!mb->is_shift_pressed() && !is_mouse_over_selection()) { - caret = 0; - remove_secondary_carets(); - } - } - - _push_current_op(); - set_caret_line(row, false, true, 0, caret); - set_caret_column(col, false, caret); - selection_drag_attempt = false; - - if (selecting_enabled && mb->is_shift_pressed() && (get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line)) { - if (!has_selection(caret)) { - carets.write[caret].selection.active = true; - selecting_mode = SelectionMode::SELECTION_MODE_POINTER; - carets.write[caret].selection.from_column = prev_col; - carets.write[caret].selection.from_line = prev_line; - carets.write[caret].selection.to_column = carets[caret].column; - carets.write[caret].selection.to_line = carets[caret].line; - - if (get_selection_from_line(caret) > get_selection_to_line(caret) || (get_selection_from_line(caret) == get_selection_to_line(caret) && get_selection_from_column(caret) > get_selection_to_column(caret))) { - SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column); - SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line); - carets.write[caret].selection.shiftclick_left = false; - } else { - carets.write[caret].selection.shiftclick_left = true; - } - carets.write[caret].selection.selecting_line = prev_line; - carets.write[caret].selection.selecting_column = prev_col; - caret_index_edit_dirty = true; - merge_overlapping_carets(); - queue_redraw(); - } else { - if (carets[caret].line < get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column < get_selection_column(caret))) { - if (carets[caret].selection.shiftclick_left) { - carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left; - } - carets.write[caret].selection.from_column = carets[caret].column; - carets.write[caret].selection.from_line = carets[caret].line; - - } else if (carets[caret].line > get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column > get_selection_column(caret))) { - if (!carets[caret].selection.shiftclick_left) { - SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column); - SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line); - carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left; - } - carets.write[caret].selection.to_column = carets[caret].column; - carets.write[caret].selection.to_line = carets[caret].line; - + } else if (!mb->is_shift_pressed()) { + if (drag_and_drop_selection_enabled && mouse_over_selection_caret >= 0) { + // Try to drag and drop. + set_selection_mode(SelectionMode::SELECTION_MODE_NONE); + selection_drag_attempt = true; + drag_and_drop_origin_caret_index = mouse_over_selection_caret; + last_dblclk = 0; + // Don't update caret until we know if it is not drag and drop. + return; } else { - deselect(caret); + // A regular click clears all other carets. + caret = 0; + remove_secondary_carets(); + deselect(); } - caret_index_edit_dirty = true; - merge_overlapping_carets(); - queue_redraw(); } - } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) { - set_selection_mode(SelectionMode::SELECTION_MODE_NONE, get_selection_line(caret), get_selection_column(caret), caret); - // We use the main caret for dragging, so reset this one. - set_caret_line(prev_line, false, true, 0, caret); - set_caret_column(prev_col, false, caret); - selection_drag_attempt = true; - } else if (caret == 0) { - deselect(); - set_selection_mode(SelectionMode::SELECTION_MODE_POINTER, row, col); - } - if (is_triple_click) { - // Triple-click select line. - selecting_mode = SelectionMode::SELECTION_MODE_LINE; + _push_current_op(); + set_caret_line(line, false, true, -1, caret); + set_caret_column(col, false, caret); selection_drag_attempt = false; - _update_selection_mode_line(); + bool caret_moved = get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line; + + if (selecting_enabled && mb->is_shift_pressed() && !has_selection(caret) && caret_moved) { + // Select from the previous caret position. + select(prev_line, prev_col, line, col, caret); + } + + // Start regular select mode. + set_selection_mode(SelectionMode::SELECTION_MODE_POINTER); + _update_selection_mode_pointer(true); + } else if (is_triple_click) { + // Start triple-click select line mode. + set_selection_mode(SelectionMode::SELECTION_MODE_LINE); + _update_selection_mode_line(true); last_dblclk = 0; - } else if (mb->is_double_click() && text[get_caret_line(caret)].length()) { - // Double-click select word. - selecting_mode = SelectionMode::SELECTION_MODE_WORD; - _update_selection_mode_word(); + } else if (mb->is_double_click()) { + // Start double-click select word mode. + set_selection_mode(SelectionMode::SELECTION_MODE_WORD); + _update_selection_mode_word(true); last_dblclk = OS::get_singleton()->get_ticks_msec(); last_dblclk_pos = mb->get_position(); } @@ -1865,34 +1842,20 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { _push_current_op(); _reset_caret_blink_timer(); apply_ime(); + _cancel_drag_and_drop_text(); Point2i pos = get_line_column_at_pos(mpos); - int row = pos.y; - int col = pos.x; + int mouse_line = pos.y; + int mouse_column = pos.x; - bool selection_clicked = false; if (is_move_caret_on_right_click_enabled()) { - if (has_selection()) { - for (int i = 0; i < get_caret_count(); i++) { - int from_line = get_selection_from_line(i); - int to_line = get_selection_to_line(i); - int from_column = get_selection_from_column(i); - int to_column = get_selection_to_column(i); - - if (row >= from_line && row <= to_line && (row != from_line || col >= from_column) && (row != to_line || col <= to_column)) { - // Right click in one of the selected text - selection_clicked = true; - break; - } - } - } + bool selection_clicked = get_selection_at_line_column(mouse_line, mouse_column, true) >= 0; if (!selection_clicked) { deselect(); remove_secondary_carets(); - set_caret_line(row, false, false); - set_caret_column(col); + set_caret_line(mouse_line, false, false, -1); + set_caret_column(mouse_column); } - merge_overlapping_carets(); } if (context_menu_enabled) { @@ -1910,22 +1873,20 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (mb->get_button_index() == MouseButton::LEFT) { - if (selection_drag_attempt && is_mouse_over_selection()) { + if (!drag_action && selection_drag_attempt && is_mouse_over_selection()) { + // This is not a drag and drop attempt, update the caret. + selection_drag_attempt = false; remove_secondary_carets(); + deselect(); Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - set_caret_line(pos.y, false, true, 0, 0); + set_caret_line(pos.y, false, true, -1, 0); set_caret_column(pos.x, true, 0); - - deselect(); } dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; click_select_held->stop(); - if (!drag_action) { - selection_drag_attempt = false; - } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } @@ -1960,7 +1921,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. + if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && get_viewport()->gui_get_drag_data() == Variant()) { + // Update if not in drag and drop. _reset_caret_blink_timer(); if (draw_minimap && !dragging_selection) { @@ -2013,10 +1975,19 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) { apply_ime(); + // Update drag and drop caret. drag_caret_force_displayed = true; Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - set_caret_line(pos.y, false, true, 0, 0); - set_caret_column(pos.x, true, 0); + + if (drag_caret_index == -1) { + // Force create a new caret for drag and drop. + carets.push_back(Caret()); + drag_caret_index = carets.size() - 1; + } + + drag_caret_force_displayed = true; + set_caret_line(pos.y, false, true, -1, drag_caret_index); + set_caret_column(pos.x, true, drag_caret_index); dragging_selection = true; } } @@ -2045,6 +2016,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { return; } + _cancel_drag_and_drop_text(); + _reset_caret_blink_timer(); // Allow unicode handling if: @@ -2115,7 +2088,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (is_shortcut_keys_enabled()) { - // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, + // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, SKIP SELECTION FOR NEXT OCCURRENCE, // CLEAR CARETS AND SELECTIONS, CUT, COPY, PASTE. if (k->is_action("ui_text_select_all", true)) { select_all(); @@ -2132,6 +2105,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } + if (k->is_action("ui_text_skip_selection_for_next_occurrence", true)) { + skip_selection_for_next_occurrence(); + accept_event(); + return; + } if (k->is_action("ui_text_clear_carets_and_selection", true)) { // Since the default shortcut is ESC, accepts the event only if it's actually performed. if (_clear_carets_and_selection()) { @@ -2318,42 +2296,36 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { - bool first_line = false; - if (!p_split_current_line) { - deselect(i); - if (p_above) { - if (get_caret_line(i) > 0) { - set_caret_line(get_caret_line(i) - 1, false, true, 0, i); - set_caret_column(text[get_caret_line(i)].length(), i == 0, i); - } else { - set_caret_column(0, i == 0, i); - first_line = true; - } - } else { - set_caret_column(text[get_caret_line(i)].length(), i == 0, i); - } - } - - insert_text_at_caret("\n", i); + begin_multicaret_edit(); - if (first_line) { - set_caret_line(0, i == 0, true, 0, i); + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } + if (p_split_current_line) { + insert_text_at_caret("\n", i); + } else { + int line = get_caret_line(i); + insert_text("\n", line, p_above ? 0 : text[line].length(), p_above, p_above); + deselect(i); + set_caret_line(p_above ? line : line + 1, false, true, -1, i); + set_caret_column(0, i == 0, i); } } + + end_multicaret_edit(); end_complex_operation(); } void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { // Handle selection. if (p_select) { _pre_shift_selection(i); } else if (has_selection(i) && !p_move_by_word) { // If a selection is active, move caret to start of selection. - set_caret_line(get_selection_from_line(i), false, true, 0, i); + set_caret_line(get_selection_from_line(i), false, true, -1, i); set_caret_column(get_selection_from_column(i), i == 0, i); deselect(i); continue; @@ -2365,7 +2337,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { int cc = get_caret_column(i); // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. if (cc == 0 && get_caret_line(i) > 0) { - set_caret_line(get_caret_line(i) - 1, false, true, 0, i); + set_caret_line(get_caret_line(i) - 1, false, true, -1, i); set_caret_column(text[get_caret_line(i)].length(), i == 0, i); } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); @@ -2386,7 +2358,8 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. if (get_caret_column(i) == 0) { if (get_caret_line(i) > 0) { - set_caret_line(get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1), false, true, 0, i); + int new_caret_line = get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1); + set_caret_line(new_caret_line, false, true, -1, i); set_caret_column(text[get_caret_line(i)].length(), i == 0, i); } } else { @@ -2397,23 +2370,19 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { } } } - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { // Handle selection. if (p_select) { _pre_shift_selection(i); } else if (has_selection(i) && !p_move_by_word) { // If a selection is active, move caret to end of selection. - set_caret_line(get_selection_to_line(i), false, true, 0, i); + set_caret_line(get_selection_to_line(i), false, true, -1, i); set_caret_column(get_selection_to_column(i), i == 0, i); deselect(i); continue; @@ -2425,7 +2394,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { int cc = get_caret_column(i); // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line. if (cc == text[get_caret_line(i)].length() && get_caret_line(i) < text.size() - 1) { - set_caret_line(get_caret_line(i) + 1, false, true, 0, i); + set_caret_line(get_caret_line(i) + 1, false, true, -1, i); set_caret_column(0, i == 0, i); } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); @@ -2446,7 +2415,8 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { // If we are at the end of the line, move the caret to the next line down. if (get_caret_column(i) == text[get_caret_line(i)].length()) { if (get_caret_line(i) < text.size() - 1) { - set_caret_line(get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1), false, false, 0, i); + int new_caret_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1); + set_caret_line(new_caret_line, false, false, -1, i); set_caret_column(0, i == 0, i); } } else { @@ -2457,17 +2427,13 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { } } } - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } void TextEdit::_move_caret_up(bool p_select) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (p_select) { _pre_shift_selection(i); } else { @@ -2487,17 +2453,13 @@ void TextEdit::_move_caret_up(bool p_select) { set_caret_line(new_line, i == 0, false, 0, i); } } - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } void TextEdit::_move_caret_down(bool p_select) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (p_select) { _pre_shift_selection(i); } else { @@ -2513,17 +2475,13 @@ void TextEdit::_move_caret_down(bool p_select) { int new_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1); set_caret_line(new_line, i == 0, false, 0, i); } - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } void TextEdit::_move_caret_to_line_start(bool p_select) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (p_select) { _pre_shift_selection(i); } else { @@ -2548,17 +2506,13 @@ void TextEdit::_move_caret_to_line_start(bool p_select) { } else { set_caret_column(row_start_col, i == 0, i); } - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } void TextEdit::_move_caret_to_line_end(bool p_select) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (p_select) { _pre_shift_selection(i); } else { @@ -2577,19 +2531,13 @@ void TextEdit::_move_caret_to_line_end(bool p_select) { } else { set_caret_column(row_end_col, i == 0, i); } - - carets.write[i].last_fit_x = INT_MAX; - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } void TextEdit::_move_caret_page_up(bool p_select) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (p_select) { _pre_shift_selection(i); } else { @@ -2599,17 +2547,13 @@ void TextEdit::_move_caret_page_up(bool p_select) { Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), -get_visible_line_count()); int n_line = get_caret_line(i) - next_line.x + 1; set_caret_line(n_line, i == 0, false, next_line.y, i); - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } void TextEdit::_move_caret_page_down(bool p_select) { _push_current_op(); - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (p_select) { _pre_shift_selection(i); } else { @@ -2619,10 +2563,6 @@ void TextEdit::_move_caret_page_down(bool p_select) { Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), get_visible_line_count()); int n_line = get_caret_line(i) + next_line.x - 1; set_caret_line(n_line, i == 0, false, next_line.y, i); - - if (p_select) { - _post_shift_selection(i); - } } merge_overlapping_carets(); } @@ -2633,58 +2573,47 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { } start_action(EditAction::ACTION_BACKSPACE); - Vector<int> carets_to_remove; + begin_multicaret_edit(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (int i = 0; i < caret_edit_order.size(); i++) { - int caret_idx = caret_edit_order[i]; - if (get_caret_column(caret_idx) == 0 && get_caret_line(caret_idx) == 0 && !has_selection(caret_idx)) { + Vector<int> sorted_carets = get_sorted_carets(); + sorted_carets.reverse(); + for (int i = 0; i < sorted_carets.size(); i++) { + int caret_index = sorted_carets[i]; + if (multicaret_edit_ignore_caret(caret_index)) { continue; } - if (has_selection(caret_idx) || (!p_all_to_left && !p_word) || get_caret_column(caret_idx) == 0) { - backspace(caret_idx); + if (get_caret_column(caret_index) == 0 && get_caret_line(caret_index) == 0 && !has_selection(caret_index)) { continue; } - if (p_all_to_left) { - int caret_current_column = get_caret_column(caret_idx); - set_caret_column(0, caret_idx == 0, caret_idx); - _remove_text(get_caret_line(caret_idx), 0, get_caret_line(caret_idx), caret_current_column); - adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), caret_current_column, get_caret_line(caret_idx), get_caret_column(caret_idx)); - - // Check for any overlapping carets since we removed the entire line. - for (int j = i + 1; j < caret_edit_order.size(); j++) { - // Selection only end on this line, only the one as carets cannot overlap. - if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx) && get_selection_to_line(caret_edit_order[j]) == get_caret_line(caret_idx)) { - carets.write[caret_edit_order[j]].selection.to_column = 0; - break; - } - - // Check for caret. - if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) || (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx))) { - break; - } + if (has_selection(caret_index) || (!p_all_to_left && !p_word) || get_caret_column(caret_index) == 0) { + backspace(caret_index); + continue; + } - deselect(caret_edit_order[j]); - carets_to_remove.push_back(caret_edit_order[j]); - set_caret_column(0, caret_idx == 0, caret_idx); - i = j; - } + if (p_all_to_left) { + // Remove everything to left of caret to the start of the line. + int caret_current_column = get_caret_column(caret_index); + _remove_text(get_caret_line(caret_index), 0, get_caret_line(caret_index), caret_current_column); + collapse_carets(get_caret_line(caret_index), 0, get_caret_line(caret_index), caret_current_column); + set_caret_column(0, caret_index == 0, caret_index); + _offset_carets_after(get_caret_line(caret_index), caret_current_column, get_caret_line(caret_index), 0); continue; } if (p_word) { - // Save here as the caret may change when resolving overlaps. - int from_column = get_caret_column(caret_idx); - int column = get_caret_column(caret_idx); + // Remove text to the start of the word left of the caret. + int from_column = get_caret_column(caret_index); + int column = get_caret_column(caret_index); // Check for the case "<word><space><caret>" and ignore the space. // No need to check for column being 0 since it is checked above. - if (is_whitespace(text[get_caret_line(caret_idx)][get_caret_column(caret_idx) - 1])) { + if (is_whitespace(text[get_caret_line(caret_index)][get_caret_column(caret_index) - 1])) { column -= 1; } + // Get a list with the indices of the word bounds of the given text line. - const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_idx))->get_rid()); + const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_index))->get_rid()); if (words.is_empty() || column <= words[0]) { // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. column = 0; @@ -2698,57 +2627,14 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { } } - // Check for any other carets in this range. - int overlapping_caret_index = -1; - for (int j = i + 1; j < caret_edit_order.size(); j++) { - // Check caret and selection in on the right line. - if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) && (!has_selection(caret_edit_order[j]) || get_selection_to_line(caret_edit_order[j]) != get_caret_line(caret_idx))) { - break; - } - - // If it has a selection, check it ends with in the range. - if ((has_selection(caret_edit_order[j]) && get_selection_to_column(caret_edit_order[j]) < column)) { - break; - } - - // If it has a selection and it starts outside our word, we need to adjust the selection, and handle it later to prevent overlap. - if ((has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) < column)) { - carets.write[caret_edit_order[j]].selection.to_column = column; - overlapping_caret_index = caret_edit_order[j]; - break; - } - - // Otherwise we can remove it. - if (get_caret_column(caret_edit_order[j]) > column || (has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) > column)) { - deselect(caret_edit_order[j]); - carets_to_remove.push_back(caret_edit_order[j]); - set_caret_column(0, caret_idx == 0, caret_idx); - i = j; - } - } - - _remove_text(get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column); - - set_caret_line(get_caret_line(caret_idx), false, true, 0, caret_idx); - set_caret_column(column, caret_idx == 0, caret_idx); - adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column); - - // Now we can clean up the overlapping caret. - if (overlapping_caret_index != -1) { - backspace(overlapping_caret_index); - i++; - carets_to_remove.push_back(overlapping_caret_index); - set_caret_column(get_caret_column(overlapping_caret_index), caret_idx == 0, caret_idx); - } - continue; + _remove_text(get_caret_line(caret_index), column, get_caret_line(caret_index), from_column); + collapse_carets(get_caret_line(caret_index), column, get_caret_line(caret_index), from_column); + set_caret_column(column, caret_index == 0, caret_index); + _offset_carets_after(get_caret_line(caret_index), from_column, get_caret_line(caret_index), column); } } - // Sort and remove backwards to preserve indexes. - carets_to_remove.sort(); - for (int i = carets_to_remove.size() - 1; i >= 0; i--) { - remove_caret(carets_to_remove[i]); - } + end_multicaret_edit(); end_action(); } @@ -2758,61 +2644,40 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { } start_action(EditAction::ACTION_DELETE); - Vector<int> carets_to_remove; + begin_multicaret_edit(); + + Vector<int> sorted_carets = get_sorted_carets(); + for (int i = 0; i < sorted_carets.size(); i++) { + int caret_index = sorted_carets[i]; + if (multicaret_edit_ignore_caret(caret_index)) { + continue; + } - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (int i = 0; i < caret_edit_order.size(); i++) { - int caret_idx = caret_edit_order[i]; - if (has_selection(caret_idx)) { - delete_selection(caret_idx); + if (has_selection(caret_index)) { + delete_selection(caret_index); continue; } - int curline_len = text[get_caret_line(caret_idx)].length(); - if (get_caret_line(caret_idx) == text.size() - 1 && get_caret_column(caret_idx) == curline_len) { + int curline_len = text[get_caret_line(caret_index)].length(); + if (get_caret_line(caret_index) == text.size() - 1 && get_caret_column(caret_index) == curline_len) { continue; // Last line, last column: Nothing to do. } - int next_line = get_caret_column(caret_idx) < curline_len ? get_caret_line(caret_idx) : get_caret_line(caret_idx) + 1; + int next_line = get_caret_column(caret_index) < curline_len ? get_caret_line(caret_index) : get_caret_line(caret_index) + 1; int next_column; if (p_all_to_right) { - // Get caret furthest to the left. - for (int j = i + 1; j < caret_edit_order.size(); j++) { - if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { - break; - } - - if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { - break; - } - - if (!has_selection(caret_edit_order[j])) { - i = j; - caret_idx = caret_edit_order[i]; - } - } - - if (get_caret_column(caret_idx) == curline_len) { + if (get_caret_column(caret_index) == curline_len) { continue; } // Delete everything to right of caret. next_column = curline_len; - next_line = get_caret_line(caret_idx); - - // Remove overlapping carets. - for (int j = i - 1; j >= 0; j--) { - if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { - break; - } - carets_to_remove.push_back(caret_edit_order[j]); - } - - } else if (p_word && get_caret_column(caret_idx) < curline_len - 1) { + next_line = get_caret_line(caret_index); + } else if (p_word && get_caret_column(caret_index) < curline_len - 1) { // Delete next word to right of caret. - int line = get_caret_line(caret_idx); - int column = get_caret_column(caret_idx); + int line = get_caret_line(caret_index); + int column = get_caret_column(caret_index); PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); for (int j = 1; j < words.size(); j = j + 2) { @@ -2824,49 +2689,22 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { next_line = line; next_column = column; - - // Remove overlapping carets. - for (int j = i - 1; j >= 0; j--) { - if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { - break; - } - - if (get_caret_column(caret_edit_order[j]) > column) { - break; - } - carets_to_remove.push_back(caret_edit_order[j]); - } } else { // Delete one character. if (caret_mid_grapheme_enabled) { - next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0; + next_column = get_caret_column(caret_index) < curline_len ? (get_caret_column(caret_index) + 1) : 0; } else { - next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0; - } - - // Remove overlapping carets. - if (i > 0) { - int prev_caret_idx = caret_edit_order[i - 1]; - if (get_caret_line(prev_caret_idx) == next_line && get_caret_column(prev_caret_idx) == next_column) { - carets_to_remove.push_back(prev_caret_idx); - } + next_column = get_caret_column(caret_index) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_index))->get_rid(), (get_caret_column(caret_index))) : 0; } } - _remove_text(get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column); - adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column); - } - - // Sort and remove backwards to preserve indexes. - carets_to_remove.sort(); - for (int i = carets_to_remove.size() - 1; i >= 0; i--) { - remove_caret(carets_to_remove[i]); + _remove_text(get_caret_line(caret_index), get_caret_column(caret_index), next_line, next_column); + collapse_carets(get_caret_line(caret_index), get_caret_column(caret_index), next_line, next_column); + _offset_carets_after(next_line, next_column, get_caret_line(caret_index), get_caret_column(caret_index)); } - // If we are deleting from the end of a line, due to column preservation we could still overlap with another caret. - merge_overlapping_carets(); + end_multicaret_edit(); end_action(); - queue_redraw(); } void TextEdit::_move_caret_document_start(bool p_select) { @@ -2877,12 +2715,8 @@ void TextEdit::_move_caret_document_start(bool p_select) { deselect(); } - set_caret_line(0, false); + set_caret_line(0, false, true, -1); set_caret_column(0); - - if (p_select) { - _post_shift_selection(0); - } } void TextEdit::_move_caret_document_end(bool p_select) { @@ -2893,12 +2727,8 @@ void TextEdit::_move_caret_document_end(bool p_select) { deselect(); } - set_caret_line(get_last_unhidden_line(), true, false, 9999); + set_caret_line(get_last_unhidden_line(), true, false, -1); set_caret_column(text[get_caret_line()].length()); - - if (p_select) { - _post_shift_selection(0); - } } bool TextEdit::_clear_carets_and_selection() { @@ -2916,51 +2746,6 @@ bool TextEdit::_clear_carets_and_selection() { return false; } -void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const { - if (p_last_fit_x == -1) { - p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column); - } - - // Calculate the new line and wrap index. - p_new_line = p_old_line; - int caret_wrap_index = p_old_wrap_index; - if (p_below) { - if (caret_wrap_index < get_line_wrap_count(p_new_line)) { - caret_wrap_index++; - } else { - p_new_line++; - caret_wrap_index = 0; - } - } else { - if (caret_wrap_index == 0) { - p_new_line--; - caret_wrap_index = get_line_wrap_count(p_new_line); - } else { - caret_wrap_index--; - } - } - - // Boundary checks. - if (p_new_line < 0) { - p_new_line = 0; - } - if (p_new_line >= text.size()) { - p_new_line = text.size() - 1; - } - - p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index); - if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) { - Vector<String> rows = get_line_wrapped_text(p_new_line); - int row_end_col = 0; - for (int i = 0; i < caret_wrap_index + 1; i++) { - row_end_col += rows[i].length(); - } - if (p_new_column >= row_end_col) { - p_new_column -= 1; - } - } -} - void TextEdit::_update_placeholder() { if (theme_cache.font.is_null() || theme_cache.font_size <= 0) { return; // Not in tree? @@ -3126,53 +2911,48 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { if (p_data.get_type() == Variant::STRING && is_editable()) { Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - int caret_row_tmp = pos.y; - int caret_column_tmp = pos.x; + int drop_at_line = pos.y; + int drop_at_column = pos.x; + int selection_index = get_selection_at_line_column(drop_at_line, drop_at_column, !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)); + + // Remove drag caret before the complex operation starts so it won't appear in undo. + remove_caret(drag_caret_index); + + if (selection_drag_attempt && selection_index >= 0 && selection_index == drag_and_drop_origin_caret_index) { + // Dropped onto original selection, do nothing. + selection_drag_attempt = false; + return; + } + + begin_complex_operation(); + begin_multicaret_edit(); if (selection_drag_attempt) { + // Drop from self. selection_drag_attempt = false; - if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) { - // Set caret back at selection for undo / redo. - set_caret_line(get_selection_to_line(), false, false); - set_caret_column(get_selection_to_column()); - - begin_complex_operation(); - if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { - if (caret_row_tmp > get_selection_to_line()) { - caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line()); - } else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) { - caret_column_tmp = caret_column_tmp - (get_selection_to_column() - get_selection_from_column()); - } - delete_selection(); - } else { - deselect(); - } + if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { + // Delete all selections. + int temp_caret = add_caret(drop_at_line, drop_at_column); - remove_secondary_carets(); - set_caret_line(caret_row_tmp, true, false); - set_caret_column(caret_column_tmp); - insert_text_at_caret(p_data); - end_complex_operation(); - } - } else if (is_mouse_over_selection()) { - remove_secondary_carets(); - caret_row_tmp = get_selection_from_line(); - caret_column_tmp = get_selection_from_column(); - set_caret_line(caret_row_tmp, true, false); - set_caret_column(caret_column_tmp); - insert_text_at_caret(p_data); - grab_focus(); - } else { - remove_secondary_carets(); - deselect(); - set_caret_line(caret_row_tmp, true, false); - set_caret_column(caret_column_tmp); - insert_text_at_caret(p_data); - grab_focus(); - } + delete_selection(); - if (caret_row_tmp != get_caret_line() || caret_column_tmp != get_caret_column()) { - select(caret_row_tmp, caret_column_tmp, get_caret_line(), get_caret_column()); + // Use a temporary caret to update the drop at position. + drop_at_line = get_caret_line(temp_caret); + drop_at_column = get_caret_column(temp_caret); + } } + remove_secondary_carets(); + deselect(); + + // Insert the dragged text. + set_caret_line(drop_at_line, true, false, -1); + set_caret_column(drop_at_column); + insert_text_at_caret(p_data); + + select(drop_at_line, drop_at_column, get_caret_line(), get_caret_column()); + grab_focus(); + adjust_viewport_to_caret(); + end_multicaret_edit(); + end_complex_operation(); } } @@ -3458,7 +3238,7 @@ void TextEdit::_clear() { clear_undo_history(); text.clear(); remove_secondary_carets(); - set_caret_line(0, false); + set_caret_line(0, false, true, -1); set_caret_column(0); first_visible_col = 0; first_visible_line = 0; @@ -3531,17 +3311,36 @@ void TextEdit::set_line(int p_line, const String &p_new_text) { return; } begin_complex_operation(); - _remove_text(p_line, 0, p_line, text[p_line].length()); - _insert_text(p_line, 0, p_new_text); - for (int i = 0; i < carets.size(); i++) { - if (get_caret_line(i) == p_line && get_caret_column(i) > p_new_text.length()) { - set_caret_column(p_new_text.length(), false, i); + + int old_column = text[p_line].length(); + + // Set the affected carets column to update their last offset x. + for (int i = 0; i < get_caret_count(); i++) { + if (_is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_line, 0, p_line, old_column)) { + set_caret_column(get_caret_column(i), false, i); } + if (has_selection(i) && _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_line, 0, p_line, old_column)) { + set_selection_origin_column(get_selection_origin_column(i), i); + } + } + + _remove_text(p_line, 0, p_line, old_column); + int new_line, new_column; + _insert_text(p_line, 0, p_new_text, &new_line, &new_column); - if (has_selection(i) && p_line == get_selection_to_line(i) && get_selection_to_column(i) > text[p_line].length()) { - carets.write[i].selection.to_column = text[p_line].length(); + // Don't offset carets that were on the old line. + _offset_carets_after(p_line, old_column, new_line, new_column, false, false); + + // Set the caret lines to update the column to match visually. + for (int i = 0; i < get_caret_count(); i++) { + if (_is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_line, 0, p_line, old_column)) { + set_caret_line(get_caret_line(i), false, true, 0, i); + } + if (has_selection(i) && _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_line, 0, p_line, old_column)) { + set_selection_origin_line(get_selection_origin_line(i), true, 0, i); } } + merge_overlapping_carets(); end_complex_operation(); } @@ -3595,71 +3394,163 @@ void TextEdit::swap_lines(int p_from_line, int p_to_line) { ERR_FAIL_INDEX(p_from_line, text.size()); ERR_FAIL_INDEX(p_to_line, text.size()); - String tmp = get_line(p_from_line); - String tmp2 = get_line(p_to_line); + if (p_from_line == p_to_line) { + return; + } + + String from_line_text = get_line(p_from_line); + String to_line_text = get_line(p_to_line); begin_complex_operation(); - set_line(p_to_line, tmp); - set_line(p_from_line, tmp2); + begin_multicaret_edit(); + // Don't use set_line to avoid clamping and updating carets. + _remove_text(p_to_line, 0, p_to_line, text[p_to_line].length()); + _insert_text(p_to_line, 0, from_line_text); + _remove_text(p_from_line, 0, p_from_line, text[p_from_line].length()); + _insert_text(p_from_line, 0, to_line_text); + + // Swap carets. + for (int i = 0; i < get_caret_count(); i++) { + bool selected = has_selection(i); + if (get_caret_line(i) == p_from_line || get_caret_line(i) == p_to_line) { + int caret_new_line = get_caret_line(i) == p_from_line ? p_to_line : p_from_line; + int caret_column = get_caret_column(i); + set_caret_line(caret_new_line, false, true, -1, i); + set_caret_column(caret_column, false, i); + } + if (selected && (get_selection_origin_line(i) == p_from_line || get_selection_origin_line(i) == p_to_line)) { + int origin_new_line = get_selection_origin_line(i) == p_from_line ? p_to_line : p_from_line; + int origin_column = get_selection_origin_column(i); + select(origin_new_line, origin_column, get_caret_line(i), get_caret_column(i), i); + } + } + // If only part of a selection was changed, it may now overlap. + merge_overlapping_carets(); + + end_multicaret_edit(); end_complex_operation(); } -void TextEdit::insert_line_at(int p_at, const String &p_text) { - ERR_FAIL_INDEX(p_at, text.size()); +void TextEdit::insert_line_at(int p_line, const String &p_text) { + ERR_FAIL_INDEX(p_line, text.size()); - _insert_text(p_at, 0, p_text + "\n"); + // Use a complex operation so subsequent calls aren't merged together. + begin_complex_operation(); - for (int i = 0; i < carets.size(); i++) { - if (get_caret_line(i) >= p_at) { - // Offset caret when located after inserted line. - set_caret_line(get_caret_line(i) + 1, false, true, 0, i); - } - if (has_selection(i)) { - if (get_selection_from_line(i) >= p_at) { - // Offset selection when located after inserted line. - select(get_selection_from_line(i) + 1, get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i); - } else if (get_selection_to_line(i) >= p_at) { - // Extend selection that includes inserted line. - select(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i); + int new_line, new_column; + _insert_text(p_line, 0, p_text + "\n", &new_line, &new_column); + _offset_carets_after(p_line, 0, new_line, new_column); + + end_complex_operation(); +} + +void TextEdit::remove_line_at(int p_line, bool p_move_carets_down) { + ERR_FAIL_INDEX(p_line, text.size()); + + if (get_line_count() == 1) { + // Only one line, just remove contents. + begin_complex_operation(); + int line_length = get_line(p_line).length(); + _remove_text(p_line, 0, p_line, line_length); + collapse_carets(p_line, 0, p_line, line_length, true); + end_complex_operation(); + return; + } + + begin_complex_operation(); + + bool is_last_line = p_line == get_line_count() - 1; + int from_line = is_last_line ? p_line - 1 : p_line; + int next_line = is_last_line ? p_line : p_line + 1; + int from_column = is_last_line ? get_line(from_line).length() : 0; + int next_column = is_last_line ? get_line(next_line).length() : 0; + + if ((!is_last_line && p_move_carets_down) || (p_line != 0 && !p_move_carets_down)) { + // Set the carets column to update their last offset x. + for (int i = 0; i < get_caret_count(); i++) { + if (get_caret_line(i) == p_line) { + set_caret_column(get_caret_column(i), false, i); + } + if (has_selection(i) && get_selection_origin_line(i) == p_line) { + set_selection_origin_column(get_selection_origin_column(i), i); } } } - // Need to apply the above adjustments to the undo / redo carets. - current_op.end_carets = carets; - queue_redraw(); + // Remove line. + _remove_text(from_line, from_column, next_line, next_column); + + begin_multicaret_edit(); + if ((is_last_line && p_move_carets_down) || (p_line == 0 && !p_move_carets_down)) { + // Collapse carets. + collapse_carets(from_line, from_column, next_line, next_column, true); + } else { + // Move carets to visually line up. + int target_line = p_move_carets_down ? p_line : p_line - 1; + for (int i = 0; i < get_caret_count(); i++) { + bool selected = has_selection(i); + if (get_caret_line(i) == p_line) { + set_caret_line(target_line, i == 0, true, 0, i); + } + if (selected && get_selection_origin_line(i) == p_line) { + set_selection_origin_line(target_line, true, 0, i); + select(get_selection_origin_line(i), get_selection_origin_column(i), get_caret_line(i), get_caret_column(i), i); + } + } + + merge_overlapping_carets(); + } + _offset_carets_after(next_line, next_column, from_line, from_column); + end_multicaret_edit(); + end_complex_operation(); } void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { if (p_caret != -1 && p_caret != i) { continue; } + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } delete_selection(i); int from_line = get_caret_line(i); int from_col = get_caret_column(i); - int new_column, new_line; + int new_line, new_column; _insert_text(from_line, from_col, p_text, &new_line, &new_column); _update_scrollbars(); + _offset_carets_after(from_line, from_col, new_line, new_column); - set_caret_line(new_line, false, true, 0, i); + set_caret_line(new_line, false, true, -1, i); set_caret_column(new_column, i == 0, i); - - adjust_carets_after_edit(i, new_line, new_column, from_line, from_col); } if (has_ime_text()) { _update_ime_text(); } + end_multicaret_edit(); + end_complex_operation(); +} + +void TextEdit::insert_text(const String &p_text, int p_line, int p_column, bool p_before_selection_begin, bool p_before_selection_end) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_INDEX(p_column, text[p_line].length() + 1); + + begin_complex_operation(); + + int new_line, new_column; + _insert_text(p_line, p_column, p_text, &new_line, &new_column); + + _offset_carets_after(p_line, p_column, new_line, new_column, p_before_selection_begin, p_before_selection_end); + end_complex_operation(); - queue_redraw(); } void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -3670,7 +3561,13 @@ void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, in ERR_FAIL_COND(p_to_line < p_from_line); ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); + begin_complex_operation(); + _remove_text(p_from_line, p_from_column, p_to_line, p_to_column); + collapse_carets(p_from_line, p_from_column, p_to_line, p_to_column); + _offset_carets_after(p_to_line, p_to_column, p_from_line, p_from_column); + + end_complex_operation(); } int TextEdit::get_last_unhidden_line() const { @@ -4039,7 +3936,7 @@ void TextEdit::undo() { _push_current_op(); if (undo_stack_pos == nullptr) { - if (!undo_stack.size()) { + if (undo_stack.is_empty()) { return; // Nothing to undo. } @@ -4058,6 +3955,7 @@ void TextEdit::undo() { current_op.version = op.prev_version; if (undo_stack_pos->get().chain_backward) { + // This was part of a complex operation, undo until the chain forward at the start of the complex operation. while (true) { ERR_BREAK(!undo_stack_pos->prev()); undo_stack_pos = undo_stack_pos->prev(); @@ -4071,9 +3969,9 @@ void TextEdit::undo() { } _update_scrollbars(); - bool dirty_carets = carets.size() != undo_stack_pos->get().start_carets.size(); + bool dirty_carets = get_caret_count() != undo_stack_pos->get().start_carets.size(); if (!dirty_carets) { - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (carets[i].line != undo_stack_pos->get().start_carets[i].line || carets[i].column != undo_stack_pos->get().start_carets[i].column) { dirty_carets = true; break; @@ -4083,11 +3981,11 @@ void TextEdit::undo() { carets = undo_stack_pos->get().start_carets; - if (dirty_carets && !caret_pos_dirty) { - if (is_inside_tree()) { - callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred(); - } - caret_pos_dirty = true; + _unhide_carets(); + + if (dirty_carets) { + _caret_changed(); + _selection_changed(); } adjust_viewport_to_caret(); } @@ -4112,6 +4010,7 @@ void TextEdit::redo() { _do_text_op(op, false); current_op.version = op.version; if (undo_stack_pos->get().chain_forward) { + // This was part of a complex operation, redo until the chain backward at the end of the complex operation. while (true) { ERR_BREAK(!undo_stack_pos->next()); undo_stack_pos = undo_stack_pos->next(); @@ -4125,9 +4024,9 @@ void TextEdit::redo() { } _update_scrollbars(); - bool dirty_carets = carets.size() != undo_stack_pos->get().end_carets.size(); + bool dirty_carets = get_caret_count() != undo_stack_pos->get().end_carets.size(); if (!dirty_carets) { - for (int i = 0; i < carets.size(); i++) { + for (int i = 0; i < get_caret_count(); i++) { if (carets[i].line != undo_stack_pos->get().end_carets[i].line || carets[i].column != undo_stack_pos->get().end_carets[i].column) { dirty_carets = true; break; @@ -4138,11 +4037,11 @@ void TextEdit::redo() { carets = undo_stack_pos->get().end_carets; undo_stack_pos = undo_stack_pos->next(); - if (dirty_carets && !caret_pos_dirty) { - if (is_inside_tree()) { - callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred(); - } - caret_pos_dirty = true; + _unhide_carets(); + + if (dirty_carets) { + _caret_changed(); + _selection_changed(); } adjust_viewport_to_caret(); } @@ -4338,8 +4237,11 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { } Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds) const { - float rows = p_pos.y; - rows -= theme_cache.style_normal->get_margin(SIDE_TOP); + float rows = p_pos.y - theme_cache.style_normal->get_margin(SIDE_TOP); + if (!editable) { + rows -= theme_cache.style_readonly->get_offset().y / 2; + rows += theme_cache.style_normal->get_offset().y / 2; + } rows /= get_line_height(); rows += _get_v_scroll_offset(); int first_vis_line = get_first_visible_line(); @@ -4357,13 +4259,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ } } - if (row < 0) { - row = 0; - } - - if (row >= text.size()) { - row = text.size() - 1; - } + row = CLAMP(row, 0, text.size() - 1); int visible_lines = get_visible_line_count_in_range(first_vis_line, row); if (rows > visible_lines) { @@ -4376,6 +4272,10 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ int col = 0; int colx = p_pos.x - (theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); colx += first_visible_col; + if (!editable) { + colx -= theme_cache.style_readonly->get_offset().x / 2; + colx += theme_cache.style_normal->get_offset().x / 2; + } col = _get_char_pos_for_line(colx, row, wrap_index); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { // Move back one if we are at the end of the row. @@ -4509,29 +4409,13 @@ bool TextEdit::is_dragging_cursor() const { } bool TextEdit::is_mouse_over_selection(bool p_edges, int p_caret) const { - for (int i = 0; i < carets.size(); i++) { - if (p_caret != -1 && p_caret != i) { - continue; - } - - if (!has_selection(i)) { - continue; - } - - Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - int row = pos.y; - int col = pos.x; - if (p_edges) { - if ((row == get_selection_from_line(i) && col == get_selection_from_column(i)) || (row == get_selection_to_line(i) && col == get_selection_to_column(i))) { - return true; - } - } + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + int line = pos.y; + int column = pos.x; - if (row >= get_selection_from_line(i) && row <= get_selection_to_line(i) && (row > get_selection_from_line(i) || col > get_selection_from_column(i)) && (row < get_selection_to_line(i) || col < get_selection_to_column(i))) { - return true; - } + if ((p_caret == -1 && get_selection_at_line_column(line, column, p_edges) != -1) || (p_caret != -1 && _selection_contains(p_caret, line, column, p_edges))) { + return true; } - return false; } @@ -4611,6 +4495,9 @@ void TextEdit::set_multiple_carets_enabled(bool p_enabled) { multi_carets_enabled = p_enabled; if (!multi_carets_enabled) { remove_secondary_carets(); + multicaret_edit_count = 0; + multicaret_edit_ignore_carets.clear(); + multicaret_edit_merge_queued = false; } } @@ -4618,270 +4505,411 @@ bool TextEdit::is_multiple_carets_enabled() const { return multi_carets_enabled; } -int TextEdit::add_caret(int p_line, int p_col) { +int TextEdit::add_caret(int p_line, int p_column) { if (!multi_carets_enabled) { return -1; } + _cancel_drag_and_drop_text(); p_line = CLAMP(p_line, 0, text.size() - 1); - p_col = CLAMP(p_col, 0, get_line(p_line).length()); + p_column = CLAMP(p_column, 0, get_line(p_line).length()); - for (int i = 0; i < carets.size(); i++) { - if (get_caret_line(i) == p_line && get_caret_column(i) == p_col) { + if (!is_in_mulitcaret_edit()) { + // Carets cannot overlap. + if (get_selection_at_line_column(p_line, p_column, true, false) != -1) { return -1; } - - if (has_selection(i)) { - if (p_line >= get_selection_from_line(i) && p_line <= get_selection_to_line(i) && (p_line > get_selection_from_line(i) || p_col >= get_selection_from_column(i)) && (p_line < get_selection_to_line(i) || p_col <= get_selection_to_column(i))) { - return -1; - } - } } carets.push_back(Caret()); - set_caret_line(p_line, false, false, 0, carets.size() - 1); - set_caret_column(p_col, false, carets.size() - 1); - caret_index_edit_dirty = true; - return carets.size() - 1; + int new_index = carets.size() - 1; + set_caret_line(p_line, false, false, -1, new_index); + set_caret_column(p_column, false, new_index); + _caret_changed(new_index); + + if (is_in_mulitcaret_edit()) { + multicaret_edit_ignore_carets.insert(new_index); + merge_overlapping_carets(); + } + return new_index; } void TextEdit::remove_caret(int p_caret) { ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed."); ERR_FAIL_INDEX(p_caret, carets.size()); + + _caret_changed(p_caret); carets.remove_at(p_caret); - caret_index_edit_dirty = true; + + if (drag_caret_index >= 0) { + if (p_caret == drag_caret_index) { + drag_caret_index = -1; + } else if (p_caret < drag_caret_index) { + drag_caret_index -= 1; + } + } } void TextEdit::remove_secondary_carets() { + if (carets.size() == 1) { + return; + } + + _caret_changed(); carets.resize(1); - caret_index_edit_dirty = true; - queue_redraw(); + + if (drag_caret_index >= 0) { + drag_caret_index = -1; + } } -void TextEdit::merge_overlapping_carets() { - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (int i = 0; i < caret_edit_order.size() - 1; i++) { - int first_caret = caret_edit_order[i]; - int second_caret = caret_edit_order[i + 1]; +int TextEdit::get_caret_count() const { + // Don't include drag caret. + if (drag_caret_index >= 0) { + return carets.size() - 1; + } + return carets.size(); +} - // Both have selection. - if (has_selection(first_caret) && has_selection(second_caret)) { - bool should_merge = false; - if (get_selection_from_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_from_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_from_line(first_caret) > get_selection_from_line(second_caret) || get_selection_from_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_from_line(first_caret) < get_selection_to_line(second_caret) || get_selection_from_column(first_caret) <= get_selection_to_column(second_caret))) { - should_merge = true; - } +void TextEdit::add_caret_at_carets(bool p_below) { + if (!multi_carets_enabled) { + return; + } + const int last_line_max_wrap = get_line_wrap_count(text.size() - 1); + + begin_multicaret_edit(); + int view_target_caret = -1; + int view_line = p_below ? -1 : INT_MAX; + int num_carets = get_caret_count(); + for (int i = 0; i < num_carets; i++) { + const int caret_line = get_caret_line(i); + const int caret_column = get_caret_column(i); + const bool is_selected = has_selection(i) || carets[i].last_fit_x != carets[i].selection.origin_last_fit_x; + const int selection_origin_line = get_selection_origin_line(i); + const int selection_origin_column = get_selection_origin_column(i); + const int caret_wrap_index = get_caret_wrap_index(i); + const int selection_origin_wrap_index = !is_selected ? -1 : get_line_wrap_index_at_column(selection_origin_line, selection_origin_column); + + if (caret_line == 0 && !p_below && (caret_wrap_index == 0 || selection_origin_wrap_index == 0)) { + // Can't add above the first line. + continue; + } + if (caret_line == text.size() - 1 && p_below && (caret_wrap_index == last_line_max_wrap || selection_origin_wrap_index == last_line_max_wrap)) { + // Can't add below the last line. + continue; + } - if (get_selection_to_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_to_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_to_line(first_caret) > get_selection_from_line(second_caret) || get_selection_to_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_to_line(first_caret) < get_selection_to_line(second_caret) || get_selection_to_column(first_caret) <= get_selection_to_column(second_caret))) { - should_merge = true; - } + // Add a new caret. + int new_caret_index = add_caret(caret_line, caret_column); + ERR_FAIL_COND_MSG(new_caret_index < 0, "Failed to add a caret."); - if (!should_merge) { - continue; - } + // Copy the selection origin and last fit. + set_selection_origin_line(selection_origin_line, true, -1, new_caret_index); + set_selection_origin_column(selection_origin_column, new_caret_index); + carets.write[new_caret_index].last_fit_x = carets[i].last_fit_x; + carets.write[new_caret_index].selection.origin_last_fit_x = carets[i].selection.origin_last_fit_x; - // Save the newest one for Click + Drag. - int caret_to_save = first_caret; - int caret_to_remove = second_caret; - if (first_caret < second_caret) { - caret_to_save = second_caret; - caret_to_remove = first_caret; + // Move the caret up or down one visible line. + if (!p_below) { + // Move caret up. + if (caret_wrap_index > 0) { + set_caret_line(caret_line, false, false, caret_wrap_index - 1, new_caret_index); + } else { + int new_line = caret_line - get_next_visible_line_offset_from(caret_line - 1, -1); + if (is_line_wrapped(new_line)) { + set_caret_line(new_line, false, false, get_line_wrap_count(new_line), new_caret_index); + } else { + set_caret_line(new_line, false, false, 0, new_caret_index); + } } - - int from_line = MIN(get_selection_from_line(caret_to_save), get_selection_from_line(caret_to_remove)); - int to_line = MAX(get_selection_to_line(caret_to_save), get_selection_to_line(caret_to_remove)); - int from_col = get_selection_from_column(caret_to_save); - int to_col = get_selection_to_column(caret_to_save); - int selection_line = get_selection_line(caret_to_save); - int selection_col = get_selection_column(caret_to_save); - - bool at_from = (get_caret_line(caret_to_save) == get_selection_from_line(caret_to_save) && get_caret_column(caret_to_save) == get_selection_from_column(caret_to_save)); - - if (at_from) { - if (get_selection_line(caret_to_remove) > get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) >= get_selection_column(caret_to_save))) { - selection_line = get_selection_line(caret_to_remove); - selection_col = get_selection_column(caret_to_remove); + // Move selection origin up. + if (is_selected) { + if (selection_origin_wrap_index > 0) { + set_selection_origin_line(caret_line, false, selection_origin_wrap_index - 1, new_caret_index); + } else { + int new_line = selection_origin_line - get_next_visible_line_offset_from(selection_origin_line - 1, -1); + if (is_line_wrapped(new_line)) { + set_selection_origin_line(new_line, false, get_line_wrap_count(new_line), new_caret_index); + } else { + set_selection_origin_line(new_line, false, 0, new_caret_index); + } } - } else if (get_selection_line(caret_to_remove) < get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) <= get_selection_column(caret_to_save))) { - selection_line = get_selection_line(caret_to_remove); - selection_col = get_selection_column(caret_to_remove); } - - if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save) || (get_selection_from_line(caret_to_remove) == get_selection_from_line(caret_to_save) && get_selection_from_column(caret_to_remove) <= get_selection_from_column(caret_to_save))) { - from_col = get_selection_from_column(caret_to_remove); + if (get_caret_line(new_caret_index) < view_line) { + view_line = get_caret_line(new_caret_index); + view_target_caret = new_caret_index; + } + } else { + // Move caret down. + if (caret_wrap_index < get_line_wrap_count(caret_line)) { + set_caret_line(caret_line, false, false, caret_wrap_index + 1, new_caret_index); } else { - to_col = get_selection_to_column(caret_to_remove); + int new_line = caret_line + get_next_visible_line_offset_from(CLAMP(caret_line + 1, 0, text.size() - 1), 1); + set_caret_line(new_line, false, false, 0, new_caret_index); } + // Move selection origin down. + if (is_selected) { + if (selection_origin_wrap_index < get_line_wrap_count(selection_origin_line)) { + set_selection_origin_line(selection_origin_line, false, selection_origin_wrap_index + 1, new_caret_index); + } else { + int new_line = selection_origin_line + get_next_visible_line_offset_from(CLAMP(selection_origin_line + 1, 0, text.size() - 1), 1); + set_selection_origin_line(new_line, false, 0, new_caret_index); + } + } + if (get_caret_line(new_caret_index) > view_line) { + view_line = get_caret_line(new_caret_index); + view_target_caret = new_caret_index; + } + } + if (is_selected) { + // Make sure selection is active. + select(get_selection_origin_line(new_caret_index), get_selection_origin_column(new_caret_index), get_caret_line(new_caret_index), get_caret_column(new_caret_index), new_caret_index); + carets.write[new_caret_index].last_fit_x = carets[i].last_fit_x; + carets.write[new_caret_index].selection.origin_last_fit_x = carets[i].selection.origin_last_fit_x; + } - select(from_line, from_col, to_line, to_col, caret_to_save); - set_selection_mode(selecting_mode, selection_line, selection_col, caret_to_save); - set_caret_line((at_from ? from_line : to_line), caret_to_save == 0, true, 0, caret_to_save); - set_caret_column((at_from ? from_col : to_col), caret_to_save == 0, caret_to_save); - remove_caret(caret_to_remove); - i--; - caret_edit_order = get_caret_index_edit_order(); - continue; + bool check_edges = !has_selection(0) || !has_selection(new_caret_index); + bool will_merge_with_main_caret = _selection_contains(0, get_caret_line(new_caret_index), get_caret_column(new_caret_index), check_edges, false) || _selection_contains(new_caret_index, get_caret_line(0), get_caret_column(0), check_edges, false); + if (will_merge_with_main_caret) { + // Move next to the main caret so it stays the main caret after merging. + Caret new_caret = carets[new_caret_index]; + carets.remove_at(new_caret_index); + carets.insert(0, new_caret); + i++; } + } - // Only first has selection. - if (has_selection(first_caret)) { - if (get_caret_line(second_caret) >= get_selection_from_line(first_caret) && get_caret_line(second_caret) <= get_selection_to_line(first_caret) && (get_caret_line(second_caret) > get_selection_from_line(first_caret) || get_caret_column(second_caret) >= get_selection_from_column(first_caret)) && (get_caret_line(second_caret) < get_selection_to_line(first_caret) || get_caret_column(second_caret) <= get_selection_to_column(first_caret))) { - remove_caret(second_caret); - caret_edit_order = get_caret_index_edit_order(); - i--; - } - continue; + // Show the topmost caret if added above or bottommost caret if added below. + if (view_target_caret >= 0 && view_target_caret < get_caret_count()) { + adjust_viewport_to_caret(view_target_caret); + } + + merge_overlapping_carets(); + end_multicaret_edit(); +} + +struct _CaretSortComparator { + _FORCE_INLINE_ bool operator()(const Vector3i &a, const Vector3i &b) const { + // x is column, y is line, z is caret index. + if (a.y == b.y) { + return a.x < b.x; } + return a.y < b.y; + } +}; - // Only second has selection. - if (has_selection(second_caret)) { - if (get_caret_line(first_caret) >= get_selection_from_line(second_caret) && get_caret_line(first_caret) <= get_selection_to_line(second_caret) && (get_caret_line(first_caret) > get_selection_from_line(second_caret) || get_caret_column(first_caret) >= get_selection_from_column(second_caret)) && (get_caret_line(first_caret) < get_selection_to_line(second_caret) || get_caret_column(first_caret) <= get_selection_to_column(second_caret))) { - remove_caret(first_caret); - caret_edit_order = get_caret_index_edit_order(); - i--; - } +Vector<int> TextEdit::get_sorted_carets(bool p_include_ignored_carets) const { + // Returns caret indexes sorted by selection start or caret position from top to bottom of text. + Vector<Vector3i> caret_line_col_indexes; + for (int i = 0; i < get_caret_count(); i++) { + if (!p_include_ignored_carets && multicaret_edit_ignore_caret(i)) { continue; } + caret_line_col_indexes.push_back(Vector3i(get_selection_from_column(i), get_selection_from_line(i), i)); + } + caret_line_col_indexes.sort_custom<_CaretSortComparator>(); + Vector<int> sorted; + sorted.resize(caret_line_col_indexes.size()); + for (int i = 0; i < caret_line_col_indexes.size(); i++) { + sorted.set(i, caret_line_col_indexes[i].z); + } + return sorted; +} - // Both have no selection. - if (get_caret_line(first_caret) == get_caret_line(second_caret) && get_caret_column(first_caret) == get_caret_column(second_caret)) { - // Save the newest one for Click + Drag. - if (first_caret < second_caret) { - remove_caret(first_caret); - } else { - remove_caret(second_caret); +void TextEdit::collapse_carets(int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_inclusive) { + // Collapse carets in the selected range to the from position. + + // Clamp the collapse target position. + int collapse_line = CLAMP(p_from_line, 0, text.size() - 1); + int collapse_column = CLAMP(p_from_column, 0, text[collapse_line].length()); + + // Swap the lines if they are in the wrong order. + if (p_from_line > p_to_line) { + SWAP(p_from_line, p_to_line); + SWAP(p_from_column, p_to_column); + } + if (p_from_line == p_to_line && p_from_column > p_to_column) { + SWAP(p_from_column, p_to_column); + } + bool any_collapsed = false; + + // Intentionally includes carets in the multicaret_edit_ignore list so that they are moved together. + for (int i = 0; i < get_caret_count(); i++) { + bool is_caret_in = _is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_from_line, p_from_column, p_to_line, p_to_column, p_inclusive); + if (!has_selection(i)) { + if (is_caret_in) { + // Caret was in the collapsed area. + set_caret_line(collapse_line, false, true, -1, i); + set_caret_column(collapse_column, false, i); + if (is_in_mulitcaret_edit() && get_caret_count() > 1) { + multicaret_edit_ignore_carets.insert(i); + } + any_collapsed = true; + } + } else { + bool is_origin_in = _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_from_line, p_from_column, p_to_line, p_to_column, p_inclusive); + + if (is_caret_in && is_origin_in) { + // Selection was completely encapsulated. + deselect(i); + set_caret_line(collapse_line, false, true, -1, i); + set_caret_column(collapse_column, false, i); + if (is_in_mulitcaret_edit() && get_caret_count() > 1) { + multicaret_edit_ignore_carets.insert(i); + } + any_collapsed = true; + } else if (is_caret_in) { + // Only caret was inside. + set_caret_line(collapse_line, false, true, -1, i); + set_caret_column(collapse_column, false, i); + any_collapsed = true; + } else if (is_origin_in) { + // Only selection origin was inside. + set_selection_origin_line(collapse_line, true, -1, i); + set_selection_origin_column(collapse_column, i); + any_collapsed = true; } - i--; - caret_edit_order = get_caret_index_edit_order(); - continue; } + if (!p_inclusive && !any_collapsed) { + if ((get_caret_line(i) == collapse_line && get_caret_column(i) == collapse_column) || (get_selection_origin_line(i) == collapse_line && get_selection_origin_column(i) == collapse_column)) { + // Make sure to queue a merge, even if we didn't include it. + any_collapsed = true; + } + } + } + if (any_collapsed) { + merge_overlapping_carets(); } } -int TextEdit::get_caret_count() const { - return carets.size(); -} +void TextEdit::merge_overlapping_carets() { + if (is_in_mulitcaret_edit()) { + // Queue merge to be performed the end of the multicaret edit. + multicaret_edit_merge_queued = true; + return; + } -void TextEdit::add_caret_at_carets(bool p_below) { - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &caret_index : caret_edit_order) { - const int caret_line = get_caret_line(caret_index); - const int caret_column = get_caret_column(caret_index); - - // The last fit x will be cleared if the caret has a selection, - // but if it does not have a selection the last fit x will be - // transferred to the new caret. - int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x; - if (has_selection(caret_index)) { - // If the selection goes over multiple lines, deselect it. - if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) { - deselect(caret_index); + multicaret_edit_merge_queued = false; + multicaret_edit_ignore_carets.clear(); + + if (get_caret_count() == 1) { + return; + } + + Vector<int> sorted_carets = get_sorted_carets(true); + for (int i = 0; i < sorted_carets.size() - 1; i++) { + int first_caret = sorted_carets[i]; + int second_caret = sorted_carets[i + 1]; + + bool merge_carets; + if (!has_selection(first_caret) || !has_selection(second_caret)) { + // Merge if touching. + merge_carets = get_selection_from_line(second_caret) < get_selection_to_line(first_caret) || (get_selection_from_line(second_caret) == get_selection_to_line(first_caret) && get_selection_from_column(second_caret) <= get_selection_to_column(first_caret)); + } else { + // Merge two selections if overlapping. + merge_carets = get_selection_from_line(second_caret) < get_selection_to_line(first_caret) || (get_selection_from_line(second_caret) == get_selection_to_line(first_caret) && get_selection_from_column(second_caret) < get_selection_to_column(first_caret)); + } + + if (!merge_carets) { + continue; + } + + // Save the newest one for Click + Drag. + int caret_to_save = first_caret; + int caret_to_remove = second_caret; + if (first_caret < second_caret) { + caret_to_save = second_caret; + caret_to_remove = first_caret; + } + + if (get_selection_from_line(caret_to_save) != get_selection_from_line(caret_to_remove) || get_selection_to_line(caret_to_save) != get_selection_to_line(caret_to_remove) || get_selection_from_column(caret_to_save) != get_selection_from_column(caret_to_remove) || get_selection_to_column(caret_to_save) != get_selection_to_column(caret_to_remove)) { + // Selections are not the same, merge them into one bigger selection. + int new_from_line = MIN(get_selection_from_line(caret_to_remove), get_selection_from_line(caret_to_save)); + int new_to_line = MAX(get_selection_to_line(caret_to_remove), get_selection_to_line(caret_to_save)); + int new_from_col; + int new_to_col; + if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save)) { + new_from_col = get_selection_from_column(caret_to_remove); + } else if (get_selection_from_line(caret_to_remove) > get_selection_from_line(caret_to_save)) { + new_from_col = get_selection_from_column(caret_to_save); } else { - caret_from_column = get_selection_from_column(caret_index); - caret_to_column = get_selection_to_column(caret_index); - caret_last_fit_x = -1; - carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column); + new_from_col = MIN(get_selection_from_column(caret_to_remove), get_selection_from_column(caret_to_save)); + } + if (get_selection_to_line(caret_to_remove) < get_selection_to_line(caret_to_save)) { + new_to_col = get_selection_to_column(caret_to_save); + } else if (get_selection_to_line(caret_to_remove) > get_selection_to_line(caret_to_save)) { + new_to_col = get_selection_to_column(caret_to_remove); + } else { + new_to_col = MAX(get_selection_to_column(caret_to_remove), get_selection_to_column(caret_to_save)); } - } - // Get the line and column of the new caret as if you would move the caret by pressing the arrow keys. - int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0; - _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x); + // Use the direction from the last caret or the saved one. + int caret_dir_to_copy; + if (has_selection(caret_to_remove) && has_selection(caret_to_save)) { + caret_dir_to_copy = caret_to_remove == get_caret_count() - 1 ? caret_to_remove : caret_to_save; + } else { + caret_dir_to_copy = !has_selection(caret_to_remove) ? caret_to_save : caret_to_remove; + } - // If the caret does have a selection calculate the new from and to columns. - if (caret_from_column != caret_to_column) { - // We only need to calculate the selection columns if the column of the caret changed. - if (caret_column != new_caret_column) { - int _; // Unused placeholder for p_new_line. - _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column); - _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column); + if (is_caret_after_selection_origin(caret_dir_to_copy)) { + select(new_from_line, new_from_col, new_to_line, new_to_col, caret_to_save); } else { - new_caret_from_column = caret_from_column; - new_caret_to_column = caret_to_column; + select(new_to_line, new_to_col, new_from_line, new_from_col, caret_to_save); } } - // Add the new caret. - const int new_caret_index = add_caret(new_caret_line, new_caret_column); - - if (new_caret_index == -1) { - continue; + if (caret_to_save == 0) { + adjust_viewport_to_caret(caret_to_save); } - // Also add the selection if there should be one. - if (new_caret_from_column != new_caret_to_column) { - select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index); - // Necessary to properly modify the selection after adding the new caret. - carets.write[new_caret_index].selection.selecting_line = new_caret_line; - carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column; - continue; + remove_caret(caret_to_remove); + + // Update the rest of the sorted list. + for (int j = i; j < sorted_carets.size(); j++) { + if (sorted_carets[j] > caret_to_remove) { + // Shift the index since a caret before it was removed. + sorted_carets.write[j] -= 1; + } } + // Remove the caret from the sorted array. + sorted_carets.remove_at(caret_to_remove == first_caret ? i : i + 1); - // Copy the last fit x over. - carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x; + // Process the caret again, since it and the next caret might also overlap. + i--; } - - merge_overlapping_carets(); - queue_redraw(); } -Vector<int> TextEdit::get_caret_index_edit_order() { - if (!caret_index_edit_dirty) { - return caret_index_edit_order; +// Starts a multicaret edit operation. Call this before iterating over the carets and call [end_multicaret_edit] afterwards. +void TextEdit::begin_multicaret_edit() { + if (!multi_carets_enabled) { + return; } + multicaret_edit_count++; +} - caret_index_edit_order.clear(); - caret_index_edit_order.push_back(0); - for (int i = 1; i < carets.size(); i++) { - int j = 0; - - int line = has_selection(i) ? get_selection_to_line(i) : carets[i].line; - int col = has_selection(i) ? get_selection_to_column(i) : carets[i].column; +void TextEdit::end_multicaret_edit() { + if (!multi_carets_enabled) { + return; + } + if (multicaret_edit_count > 0) { + multicaret_edit_count--; + } + if (multicaret_edit_count != 0) { + return; + } - for (; j < caret_index_edit_order.size(); j++) { - int idx = caret_index_edit_order[j]; - int other_line = has_selection(idx) ? get_selection_to_line(idx) : carets[idx].line; - int other_col = has_selection(idx) ? get_selection_to_column(idx) : carets[idx].column; - if (line > other_line || (line == other_line && col > other_col)) { - break; - } - } - caret_index_edit_order.insert(j, i); + // This was the last multicaret edit operation. + if (multicaret_edit_merge_queued) { + merge_overlapping_carets(); } - caret_index_edit_dirty = false; - return caret_index_edit_order; + multicaret_edit_ignore_carets.clear(); } -void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) { - int edit_height = p_from_line - p_to_line; - int edit_size = ((edit_height == 0) ? p_from_col : 0) - p_to_col; - - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (int j = 0; j < caret_edit_order.size(); j++) { - if (caret_edit_order[j] == p_caret) { - return; - } - - // Adjust caret. - // set_caret_line could adjust the column, so save here. - int cc = get_caret_column(caret_edit_order[j]); - if (edit_height != 0) { - set_caret_line(get_caret_line(caret_edit_order[j]) + edit_height, false, true, 0, caret_edit_order[j]); - } - if (get_caret_line(p_caret) == get_caret_line(caret_edit_order[j])) { - set_caret_column(cc + edit_size, false, caret_edit_order[j]); - } +bool TextEdit::is_in_mulitcaret_edit() const { + return multicaret_edit_count > 0; +} - // Adjust selection. - if (!has_selection(caret_edit_order[j])) { - continue; - } - if (edit_height != 0) { - carets.write[caret_edit_order[j]].selection.from_line += edit_height; - carets.write[caret_edit_order[j]].selection.to_line += edit_height; - } - if (get_caret_line(p_caret) == get_selection_from_line(caret_edit_order[j])) { - carets.write[caret_edit_order[j]].selection.from_column += edit_size; - } - } +bool TextEdit::multicaret_edit_ignore_caret(int p_caret) const { + return multicaret_edit_ignore_carets.has(p_caret); } bool TextEdit::is_caret_visible(int p_caret) const { @@ -4901,16 +4929,10 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ } setting_caret_line = true; - if (p_line < 0) { - p_line = 0; - } - - if (p_line >= text.size()) { - p_line = text.size() - 1; - } + p_line = CLAMP(p_line, 0, text.size() - 1); if (!p_can_be_hidden) { - if (_is_line_hidden(CLAMP(p_line, 0, text.size() - 1))) { + if (_is_line_hidden(p_line)) { int move_down = get_next_visible_line_offset_from(p_line, 1) - 1; if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) { p_line += move_down; @@ -4919,7 +4941,7 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) { p_line -= move_up; } else { - WARN_PRINT(("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines.")); + WARN_PRINT("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines."); } } } @@ -4927,31 +4949,36 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ bool caret_moved = get_caret_line(p_caret) != p_line; carets.write[p_caret].line = p_line; - int n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index); - if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) { - Vector<String> rows = get_line_wrapped_text(p_line); - int row_end_col = 0; - for (int i = 0; i < p_wrap_index + 1; i++) { - row_end_col += rows[i].length(); - } - if (n_col >= row_end_col) { - n_col -= 1; + int n_col; + if (p_wrap_index >= 0) { + // Keep caret in same visual x position it was at previously. + n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index); + if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) { + // Offset by one to not go past the end of the wrapped line. + if (n_col >= text.get_line_wrap_ranges(p_line)[p_wrap_index].y) { + n_col -= 1; + } } + } else { + // Clamp the column. + n_col = MIN(get_caret_column(p_caret), get_line(p_line).length()); } caret_moved = (caret_moved || get_caret_column(p_caret) != n_col); carets.write[p_caret].column = n_col; + // Unselect if the caret moved to the selection origin. + if (p_wrap_index >= 0 && has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) { + deselect(p_caret); + } + if (is_inside_tree() && p_adjust_viewport) { adjust_viewport_to_caret(p_caret); } setting_caret_line = false; - if (caret_moved && !caret_pos_dirty) { - if (is_inside_tree()) { - callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred(); - } - caret_pos_dirty = true; + if (caret_moved) { + _caret_changed(p_caret); } } @@ -4960,29 +4987,32 @@ int TextEdit::get_caret_line(int p_caret) const { return carets[p_caret].line; } -void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport, int p_caret) { +void TextEdit::set_caret_column(int p_column, bool p_adjust_viewport, int p_caret) { ERR_FAIL_INDEX(p_caret, carets.size()); - if (p_col < 0) { - p_col = 0; - } - if (p_col > get_line(get_caret_line(p_caret)).length()) { - p_col = get_line(get_caret_line(p_caret)).length(); - } - bool caret_moved = get_caret_column(p_caret) != p_col; - carets.write[p_caret].column = p_col; + p_column = CLAMP(p_column, 0, get_line(get_caret_line(p_caret)).length()); + + bool caret_moved = get_caret_column(p_caret) != p_column; + carets.write[p_caret].column = p_column; carets.write[p_caret].last_fit_x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); + if (!has_selection(p_caret)) { + // Set the selection origin last fit x to be the same, so we can tell if there was a selection. + carets.write[p_caret].selection.origin_last_fit_x = carets[p_caret].last_fit_x; + } + + // Unselect if the caret moved to the selection origin. + if (has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) { + deselect(p_caret); + } + if (is_inside_tree() && p_adjust_viewport) { adjust_viewport_to_caret(p_caret); } - if (caret_moved && !caret_pos_dirty) { - if (is_inside_tree()) { - callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred(); - } - caret_pos_dirty = true; + if (caret_moved) { + _caret_changed(p_caret); } } @@ -4997,7 +5027,7 @@ int TextEdit::get_caret_wrap_index(int p_caret) const { } String TextEdit::get_word_under_caret(int p_caret) const { - ERR_FAIL_COND_V(p_caret > carets.size(), ""); + ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, ""); StringBuilder selected_text; for (int c = 0; c < carets.size(); c++) { @@ -5058,20 +5088,8 @@ bool TextEdit::is_drag_and_drop_selection_enabled() const { return drag_and_drop_selection_enabled; } -void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) { - ERR_FAIL_INDEX(p_caret, carets.size()); - +void TextEdit::set_selection_mode(SelectionMode p_mode) { selecting_mode = p_mode; - if (p_line >= 0) { - ERR_FAIL_INDEX(p_line, text.size()); - carets.write[p_caret].selection.selecting_line = p_line; - carets.write[p_caret].selection.selecting_column = CLAMP(carets[p_caret].selection.selecting_column, 0, text[carets[p_caret].selection.selecting_line].length()); - } - if (p_column >= 0) { - ERR_FAIL_INDEX(carets[p_caret].selection.selecting_line, text.size()); - ERR_FAIL_INDEX(p_column, text[carets[p_caret].selection.selecting_line].length() + 1); - carets.write[p_caret].selection.selecting_column = p_column; - } } TextEdit::SelectionMode TextEdit::get_selection_mode() const { @@ -5089,16 +5107,12 @@ void TextEdit::select_all() { } remove_secondary_carets(); + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT); select(0, 0, text.size() - 1, text[text.size() - 1].length()); - set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, 0, 0); - carets.write[0].selection.shiftclick_left = true; - set_caret_line(get_selection_to_line(), false); - set_caret_column(get_selection_to_column(), false); - queue_redraw(); } void TextEdit::select_word_under_caret(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= carets.size() || p_caret < -1); _push_current_op(); if (!selecting_enabled) { @@ -5139,8 +5153,6 @@ void TextEdit::select_word_under_caret(int p_caret) { } select(get_caret_line(c), begin, get_caret_line(c), end, c); - // Move the caret to the end of the word for easier editing. - set_caret_column(end, false, c); } merge_overlapping_carets(); } @@ -5185,53 +5197,85 @@ void TextEdit::add_selection_for_next_occurrence() { } } -void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) { - ERR_FAIL_INDEX(p_caret, carets.size()); +void TextEdit::skip_selection_for_next_occurrence() { if (!selecting_enabled) { return; } - p_from_line = CLAMP(p_from_line, 0, text.size() - 1); - p_from_column = CLAMP(p_from_column, 0, text[p_from_line].length()); - p_to_line = CLAMP(p_to_line, 0, text.size() - 1); - p_to_column = CLAMP(p_to_column, 0, text[p_to_line].length()); + if (text.size() == 1 && text[0].length() == 0) { + return; + } + + // Always use the last caret, to correctly search for + // the next occurrence that comes after this caret. + int caret = get_caret_count() - 1; + + // Supports getting the text under caret without selecting it. + // It allows to use this shortcut to simply jump to the next (under caret) word. + // Due to const and &(reference) presence, ternary operator is a way to avoid errors and warnings. + const String &searched_text = has_selection(caret) ? get_selected_text(caret) : get_word_under_caret(caret); + + int column = (has_selection(caret) ? get_selection_from_column(caret) : get_caret_column(caret)) + 1; + int line = get_caret_line(caret); + + const Point2i next_occurrence = search(searched_text, SEARCH_MATCH_CASE, line, column); + + if (next_occurrence.x == -1 || next_occurrence.y == -1) { + return; + } + + int to_column = (has_selection(caret) ? get_selection_to_column(caret) : get_caret_column(caret)) + 1; + int end = next_occurrence.x + (to_column - column); + int new_caret = add_caret(next_occurrence.y, end); - carets.write[p_caret].selection.from_line = p_from_line; - carets.write[p_caret].selection.from_column = p_from_column; - carets.write[p_caret].selection.to_line = p_to_line; - carets.write[p_caret].selection.to_column = p_to_column; + if (new_caret != -1) { + select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret); + adjust_viewport_to_caret(new_caret); + merge_overlapping_carets(); + } - carets.write[p_caret].selection.active = true; + // Deselect word under previous caret. + if (has_selection(caret)) { + select_word_under_caret(caret); + } - if (get_selection_from_line(p_caret) == get_selection_to_line(p_caret)) { - if (get_selection_from_column(p_caret) == get_selection_to_column(p_caret)) { - carets.write[p_caret].selection.active = false; + // Remove previous caret. + if (get_caret_count() > 1) { + remove_caret(caret); + } +} - } else if (get_selection_from_column(p_caret) > get_selection_to_column(p_caret)) { - carets.write[p_caret].selection.shiftclick_left = false; - SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column); - } else { - carets.write[p_caret].selection.shiftclick_left = true; - } - } else if (get_selection_from_line(p_caret) > get_selection_to_line(p_caret)) { - carets.write[p_caret].selection.shiftclick_left = false; - SWAP(carets.write[p_caret].selection.from_line, carets.write[p_caret].selection.to_line); - SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column); - } else { - carets.write[p_caret].selection.shiftclick_left = true; +void TextEdit::select(int p_origin_line, int p_origin_column, int p_caret_line, int p_caret_column, int p_caret) { + ERR_FAIL_INDEX(p_caret, get_caret_count()); + + p_caret_line = CLAMP(p_caret_line, 0, text.size() - 1); + p_caret_column = CLAMP(p_caret_column, 0, text[p_caret_line].length()); + set_caret_line(p_caret_line, false, true, -1, p_caret); + set_caret_column(p_caret_column, false, p_caret); + + if (!selecting_enabled) { + return; } - caret_index_edit_dirty = true; - queue_redraw(); + p_origin_line = CLAMP(p_origin_line, 0, text.size() - 1); + p_origin_column = CLAMP(p_origin_column, 0, text[p_origin_line].length()); + set_selection_origin_line(p_origin_line, true, -1, p_caret); + set_selection_origin_column(p_origin_column, p_caret); + + bool had_selection = has_selection(p_caret); + bool activate = p_origin_line != p_caret_line || p_origin_column != p_caret_column; + carets.write[p_caret].selection.active = activate; + if (had_selection != activate) { + _selection_changed(p_caret); + } } bool TextEdit::has_selection(int p_caret) const { - ERR_FAIL_COND_V(p_caret > carets.size(), false); + ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, false); + if (p_caret >= 0) { + return carets[p_caret].selection.active; + } for (int i = 0; i < carets.size(); i++) { - if (p_caret != -1 && p_caret != i) { - continue; - } - if (carets[i].selection.active) { return true; } @@ -5240,100 +5284,268 @@ bool TextEdit::has_selection(int p_caret) const { } String TextEdit::get_selected_text(int p_caret) { - ERR_FAIL_COND_V(p_caret > carets.size(), ""); + ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, ""); - StringBuilder selected_text; - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (int i = caret_edit_order.size() - 1; i >= 0; i--) { - int caret_idx = caret_edit_order[i]; - if (p_caret != -1 && p_caret != caret_idx) { - continue; + if (p_caret >= 0) { + if (!has_selection(p_caret)) { + return ""; } + return _base_get_text(get_selection_from_line(p_caret), get_selection_from_column(p_caret), get_selection_to_line(p_caret), get_selection_to_column(p_caret)); + } - if (!has_selection(caret_idx)) { + StringBuilder selected_text; + Vector<int> sorted_carets = get_sorted_carets(); + for (int i = 0; i < sorted_carets.size(); i++) { + int caret_index = sorted_carets[i]; + + if (!has_selection(caret_index)) { continue; } - selected_text += _base_get_text(get_selection_from_line(caret_idx), get_selection_from_column(caret_idx), get_selection_to_line(caret_idx), get_selection_to_column(caret_idx)); - if (p_caret == -1 && i != 0) { + if (selected_text.get_string_length() != 0) { selected_text += "\n"; } + selected_text += _base_get_text(get_selection_from_line(caret_index), get_selection_from_column(caret_index), get_selection_to_line(caret_index), get_selection_to_column(caret_index)); } return selected_text.as_string(); } -int TextEdit::get_selection_line(int p_caret) const { +int TextEdit::get_selection_at_line_column(int p_line, int p_column, bool p_include_edges, bool p_only_selections) const { + // Return the caret index of the found selection, or -1. + for (int i = 0; i < get_caret_count(); i++) { + if (_selection_contains(i, p_line, p_column, p_include_edges, p_only_selections)) { + return i; + } + } + return -1; +} + +Vector<Point2i> TextEdit::get_line_ranges_from_carets(bool p_only_selections, bool p_merge_adjacent) const { + // Get a series of line ranges that cover all lines that have a caret or selection. + // For each Point2i range, x is the first line and y is the last line. + Vector<Point2i> ret; + int last_to_line = INT_MIN; + + Vector<int> sorted_carets = get_sorted_carets(); + for (int i = 0; i < sorted_carets.size(); i++) { + int caret_index = sorted_carets[i]; + if (p_only_selections && !has_selection(caret_index)) { + continue; + } + Point2i range = Point2i(get_selection_from_line(caret_index), get_selection_to_line(caret_index)); + if (has_selection(caret_index) && get_selection_to_column(caret_index) == 0) { + // Dont include selection end line if it ends at column 0. + range.y--; + } + if (range.x == last_to_line || (p_merge_adjacent && range.x - 1 == last_to_line)) { + // Merge if starts on the same line or adjacent line. + ret.write[ret.size() - 1].y = range.y; + } else { + ret.append(range); + } + last_to_line = range.y; + } + return ret; +} + +TypedArray<Vector2i> TextEdit::get_line_ranges_from_carets_typed_array(bool p_only_selections, bool p_merge_adjacent) const { + // Wrapper for `get_line_ranges_from_carets` to return a datatype that can be exposed. + TypedArray<Vector2i> ret; + Vector<Point2i> ranges = get_line_ranges_from_carets(p_only_selections, p_merge_adjacent); + for (const Point2i &range : ranges) { + ret.push_back(range); + } + return ret; +} + +void TextEdit::set_selection_origin_line(int p_line, bool p_can_be_hidden, int p_wrap_index, int p_caret) { + if (!selecting_enabled) { + return; + } + ERR_FAIL_INDEX(p_caret, carets.size()); + p_line = CLAMP(p_line, 0, text.size() - 1); + + if (!p_can_be_hidden) { + if (_is_line_hidden(p_line)) { + int move_down = get_next_visible_line_offset_from(p_line, 1) - 1; + if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) { + p_line += move_down; + } else { + int move_up = get_next_visible_line_offset_from(p_line, -1) - 1; + if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) { + p_line -= move_up; + } else { + WARN_PRINT("Selection origin set to hidden line " + itos(p_line) + " and there are no nonhidden lines."); + } + } + } + } + + bool selection_moved = get_selection_origin_line(p_caret) != p_line; + carets.write[p_caret].selection.origin_line = p_line; + + int n_col; + if (p_wrap_index >= 0) { + // Keep selection origin in same visual x position it was at previously. + n_col = _get_char_pos_for_line(carets[p_caret].selection.origin_last_fit_x, p_line, p_wrap_index); + if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) { + // Offset by one to not go past the end of the wrapped line. + if (n_col >= text.get_line_wrap_ranges(p_line)[p_wrap_index].y) { + n_col -= 1; + } + } + } else { + // Clamp the column. + n_col = MIN(get_selection_origin_column(p_caret), get_line(p_line).length()); + } + selection_moved = (selection_moved || get_selection_origin_column(p_caret) != n_col); + carets.write[p_caret].selection.origin_column = n_col; + + // Unselect if the selection origin moved to the caret. + if (p_wrap_index >= 0 && has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) { + deselect(p_caret); + } + + if (selection_moved && has_selection(p_caret)) { + _selection_changed(p_caret); + } +} + +void TextEdit::set_selection_origin_column(int p_column, int p_caret) { + if (!selecting_enabled) { + return; + } + ERR_FAIL_INDEX(p_caret, carets.size()); + + p_column = CLAMP(p_column, 0, get_line(get_selection_origin_line(p_caret)).length()); + + bool selection_moved = get_selection_origin_column(p_caret) != p_column; + + carets.write[p_caret].selection.origin_column = p_column; + + carets.write[p_caret].selection.origin_last_fit_x = _get_column_x_offset_for_line(get_selection_origin_column(p_caret), get_selection_origin_line(p_caret), get_selection_origin_column(p_caret)); + + // Unselect if the selection origin moved to the caret. + if (has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) { + deselect(p_caret); + } + + if (selection_moved && has_selection(p_caret)) { + _selection_changed(p_caret); + } +} + +int TextEdit::get_selection_origin_line(int p_caret) const { ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); - ERR_FAIL_COND_V(!has_selection(p_caret), -1); - return carets[p_caret].selection.selecting_line; + return carets[p_caret].selection.origin_line; } -int TextEdit::get_selection_column(int p_caret) const { +int TextEdit::get_selection_origin_column(int p_caret) const { ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); - ERR_FAIL_COND_V(!has_selection(p_caret), -1); - return carets[p_caret].selection.selecting_column; + return carets[p_caret].selection.origin_column; } int TextEdit::get_selection_from_line(int p_caret) const { ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); - ERR_FAIL_COND_V(!has_selection(p_caret), -1); - return carets[p_caret].selection.from_line; + if (!has_selection(p_caret)) { + return carets[p_caret].line; + } + return MIN(carets[p_caret].selection.origin_line, carets[p_caret].line); } int TextEdit::get_selection_from_column(int p_caret) const { ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); - ERR_FAIL_COND_V(!has_selection(p_caret), -1); - return carets[p_caret].selection.from_column; + if (!has_selection(p_caret)) { + return carets[p_caret].column; + } + if (carets[p_caret].selection.origin_line < carets[p_caret].line) { + return carets[p_caret].selection.origin_column; + } else if (carets[p_caret].selection.origin_line > carets[p_caret].line) { + return carets[p_caret].column; + } else { + return MIN(carets[p_caret].selection.origin_column, carets[p_caret].column); + } } int TextEdit::get_selection_to_line(int p_caret) const { ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); - ERR_FAIL_COND_V(!has_selection(p_caret), -1); - return carets[p_caret].selection.to_line; + if (!has_selection(p_caret)) { + return carets[p_caret].line; + } + return MAX(carets[p_caret].selection.origin_line, carets[p_caret].line); } int TextEdit::get_selection_to_column(int p_caret) const { ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); - ERR_FAIL_COND_V(!has_selection(p_caret), -1); - return carets[p_caret].selection.to_column; + if (!has_selection(p_caret)) { + return carets[p_caret].column; + } + if (carets[p_caret].selection.origin_line < carets[p_caret].line) { + return carets[p_caret].column; + } else if (carets[p_caret].selection.origin_line > carets[p_caret].line) { + return carets[p_caret].selection.origin_column; + } else { + return MAX(carets[p_caret].selection.origin_column, carets[p_caret].column); + } +} + +bool TextEdit::is_caret_after_selection_origin(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), false); + if (!has_selection(p_caret)) { + return true; + } + return carets[p_caret].line > carets[p_caret].selection.origin_line || (carets[p_caret].line == carets[p_caret].selection.origin_line && carets[p_caret].column >= carets[p_caret].selection.origin_column); } void TextEdit::deselect(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); - for (int i = 0; i < carets.size(); i++) { - if (p_caret != -1 && p_caret != i) { - continue; + ERR_FAIL_COND(p_caret >= carets.size() || p_caret < -1); + bool selection_changed = false; + if (p_caret >= 0) { + selection_changed = carets.write[p_caret].selection.active; + carets.write[p_caret].selection.active = false; + } else { + for (int i = 0; i < carets.size(); i++) { + selection_changed |= carets.write[i].selection.active; + carets.write[i].selection.active = false; } - carets.write[i].selection.active = false; } - caret_index_edit_dirty = true; - queue_redraw(); + if (selection_changed) { + _selection_changed(p_caret); + } } void TextEdit::delete_selection(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { if (p_caret != -1 && p_caret != i) { continue; } + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } if (!has_selection(i)) { continue; } - selecting_mode = SelectionMode::SELECTION_MODE_NONE; - _remove_text(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i), get_selection_to_column(i)); - set_caret_line(get_selection_from_line(i), false, false, 0, i); - set_caret_column(get_selection_from_column(i), i == 0, i); - carets.write[i].selection.active = false; + int selection_from_line = get_selection_from_line(i); + int selection_from_column = get_selection_from_column(i); + int selection_to_line = get_selection_to_line(i); + int selection_to_column = get_selection_to_column(i); - adjust_carets_after_edit(i, carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column); + _remove_text(selection_from_line, selection_from_column, selection_to_line, selection_to_column); + _offset_carets_after(selection_to_line, selection_to_column, selection_from_line, selection_from_column); + merge_overlapping_carets(); + + deselect(i); + set_caret_line(selection_from_line, false, false, -1, i); + set_caret_column(selection_from_column, i == 0, i); } + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } /* Line wrapping. */ @@ -6175,8 +6387,10 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("swap_lines", "from_line", "to_line"), &TextEdit::swap_lines); ClassDB::bind_method(D_METHOD("insert_line_at", "line", "text"), &TextEdit::insert_line_at); + ClassDB::bind_method(D_METHOD("remove_line_at", "line", "move_carets_down"), &TextEdit::remove_line_at, DEFVAL(true)); ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text", "caret_index"), &TextEdit::insert_text_at_caret, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("insert_text", "text", "line", "column", "before_selection_begin", "before_selection_end"), &TextEdit::insert_text, DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text); ClassDB::bind_method(D_METHOD("get_last_unhidden_line"), &TextEdit::get_last_unhidden_line); @@ -6262,7 +6476,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_search_text", "search_text"), &TextEdit::set_search_text); ClassDB::bind_method(D_METHOD("set_search_flags", "flags"), &TextEdit::set_search_flags); - ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_colum"), &TextEdit::search); + ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_column"), &TextEdit::search); /* Tooltip */ ClassDB::bind_method(D_METHOD("set_tooltip_request_func", "callback"), &TextEdit::set_tooltip_request_func); @@ -6306,15 +6520,20 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_multiple_carets_enabled", "enabled"), &TextEdit::set_multiple_carets_enabled); ClassDB::bind_method(D_METHOD("is_multiple_carets_enabled"), &TextEdit::is_multiple_carets_enabled); - ClassDB::bind_method(D_METHOD("add_caret", "line", "col"), &TextEdit::add_caret); + ClassDB::bind_method(D_METHOD("add_caret", "line", "column"), &TextEdit::add_caret); ClassDB::bind_method(D_METHOD("remove_caret", "caret"), &TextEdit::remove_caret); ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets); - ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets); ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count); ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets); - ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order); - ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit); + ClassDB::bind_method(D_METHOD("get_sorted_carets", "include_ignored_carets"), &TextEdit::get_sorted_carets, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("collapse_carets", "from_line", "from_column", "to_line", "to_column", "inclusive"), &TextEdit::collapse_carets, DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets); + ClassDB::bind_method(D_METHOD("begin_multicaret_edit"), &TextEdit::begin_multicaret_edit); + ClassDB::bind_method(D_METHOD("end_multicaret_edit"), &TextEdit::end_multicaret_edit); + ClassDB::bind_method(D_METHOD("is_in_mulitcaret_edit"), &TextEdit::is_in_mulitcaret_edit); + ClassDB::bind_method(D_METHOD("multicaret_edit_ignore_caret", "caret_index"), &TextEdit::multicaret_edit_ignore_caret); ClassDB::bind_method(D_METHOD("is_caret_visible", "caret_index"), &TextEdit::is_caret_visible, DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_caret_draw_pos", "caret_index"), &TextEdit::get_caret_draw_pos, DEFVAL(0)); @@ -6345,26 +6564,33 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled); ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled); - ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("set_selection_mode", "mode"), &TextEdit::set_selection_mode); ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode); ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence); - ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("skip_selection_for_next_occurrence"), &TextEdit::skip_selection_for_next_occurrence); + ClassDB::bind_method(D_METHOD("select", "origin_line", "origin_column", "caret_line", "caret_column", "caret_index"), &TextEdit::select, DEFVAL(0)); ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_selected_text", "caret_index"), &TextEdit::get_selected_text, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_selection_at_line_column", "line", "column", "include_edges", "only_selections"), &TextEdit::get_selection_at_line_column, DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_line_ranges_from_carets", "only_selections", "merge_adjacent"), &TextEdit::get_line_ranges_from_carets_typed_array, DEFVAL(false), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_origin_line", "caret_index"), &TextEdit::get_selection_origin_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_origin_column", "caret_index"), &TextEdit::get_selection_origin_column, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("set_selection_origin_line", "line", "can_be_hidden", "wrap_index", "caret_index"), &TextEdit::set_selection_origin_line, DEFVAL(true), DEFVAL(-1), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("set_selection_origin_column", "column", "caret_index"), &TextEdit::set_selection_origin_column, DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_from_line", "caret_index"), &TextEdit::get_selection_from_line, DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_from_column", "caret_index"), &TextEdit::get_selection_from_column, DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_to_line", "caret_index"), &TextEdit::get_selection_to_line, DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_to_column", "caret_index"), &TextEdit::get_selection_to_column, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("is_caret_after_selection_origin", "caret_index"), &TextEdit::is_caret_after_selection_origin, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("deselect", "caret_index"), &TextEdit::deselect, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("delete_selection", "caret_index"), &TextEdit::delete_selection, DEFVAL(-1)); @@ -6500,6 +6726,14 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_menu_visible"), &TextEdit::is_menu_visible); ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option); + /* Deprecated */ +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit); + ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order); + ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0)); +#endif + /* Inspector */ ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder"); @@ -6567,7 +6801,7 @@ void TextEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("gutter_added")); ADD_SIGNAL(MethodInfo("gutter_removed")); - /* Theme items */ + // Theme items /* Search */ BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_color); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_border_color); @@ -6640,6 +6874,10 @@ void TextEdit::_unhide_all_lines() { queue_redraw(); } +void TextEdit::_unhide_carets() { + // Override for functionality. +} + void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) { ERR_FAIL_INDEX(p_line, text.size()); @@ -6667,14 +6905,17 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { // Overridable actions void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); if (!editable) { return; } start_action(EditAction::ACTION_TYPING); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } if (p_caret != -1 && p_caret != i) { continue; } @@ -6692,11 +6933,12 @@ void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_ca const char32_t chr[2] = { (char32_t)p_unicode, 0 }; insert_text_at_caret(chr, i); } + end_multicaret_edit(); end_action(); } void TextEdit::_backspace_internal(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); if (!editable) { return; } @@ -6707,194 +6949,163 @@ void TextEdit::_backspace_internal(int p_caret) { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } if (p_caret != -1 && p_caret != i) { continue; } - int cc = get_caret_column(i); - int cl = get_caret_line(i); + int to_line = get_caret_line(i); + int to_column = get_caret_column(i); - if (cc == 0 && cl == 0) { + if (to_column == 0 && to_line == 0) { continue; } - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); - - merge_gutters(prev_line, cl); + int from_line = to_column > 0 ? to_line : to_line - 1; + int from_column = to_column > 0 ? (to_column - 1) : (text[to_line - 1].length()); - if (_is_line_hidden(cl)) { - _set_line_as_hidden(prev_line, true); - } - _remove_text(prev_line, prev_column, cl, cc); + merge_gutters(from_line, to_line); - set_caret_line(prev_line, false, true, 0, i); - set_caret_column(prev_column, i == 0, i); + _remove_text(from_line, from_column, to_line, to_column); + collapse_carets(from_line, from_column, to_line, to_column); + _offset_carets_after(to_line, to_column, from_line, from_column); - adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); + set_caret_line(from_line, false, true, -1, i); + set_caret_column(from_column, i == 0, i); } - merge_overlapping_carets(); + end_multicaret_edit(); end_complex_operation(); } void TextEdit::_cut_internal(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); + + _copy_internal(p_caret); + if (!editable) { return; } if (has_selection(p_caret)) { - DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret)); delete_selection(p_caret); - cut_copy_line = ""; return; } + // Remove full lines. begin_complex_operation(); - Vector<int> carets_to_remove; - - StringBuilder clipboard; - // This is the exception and has to edit in reverse order else the string copied to the clipboard will be backwards. - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (int i = caret_edit_order.size() - 1; i >= 0; i--) { - int caret_idx = caret_edit_order[i]; - if (p_caret != -1 && p_caret != caret_idx) { - continue; - } - - int cl = get_caret_line(caret_idx); - int cc = get_caret_column(caret_idx); - int indent_level = get_indent_level(cl); - double hscroll = get_h_scroll(); - - // Check for overlapping carets. - // We don't need to worry about selections as that is caught before this entire section. - for (int j = i - 1; j >= 0; j--) { - if (get_caret_line(caret_edit_order[j]) == cl) { - carets_to_remove.push_back(caret_edit_order[j]); - i = j; - } - } - - clipboard += text[cl]; - if (p_caret == -1 && caret_idx != 0) { - clipboard += "\n"; - } - - if (cl == 0 && get_line_count() > 1) { - _remove_text(cl, 0, cl + 1, 0); - adjust_carets_after_edit(caret_idx, cl, 0, cl + 1, text[cl].length()); - } else { - _remove_text(cl, 0, cl, text[cl].length()); - set_caret_column(0, false, caret_idx); - backspace(caret_idx); - set_caret_line(get_caret_line(caret_idx) + 1, caret_idx == 0, 0, 0, caret_idx); - } - - // Correct the visually perceived caret column taking care of indentation level of the lines. - int diff_indent = indent_level - get_indent_level(get_caret_line(caret_idx)); - cc += diff_indent; - if (diff_indent != 0) { - cc += diff_indent > 0 ? -1 : 1; - } - - // Restore horizontal scroll and caret column modified by the backspace() call. - set_h_scroll(hscroll); - set_caret_column(cc, caret_idx == 0, caret_idx); + begin_multicaret_edit(); + Vector<Point2i> line_ranges; + if (p_caret == -1) { + line_ranges = get_line_ranges_from_carets(); + } else { + line_ranges.push_back(Point2i(get_caret_line(p_caret), get_caret_line(p_caret))); } - - // Sort and remove backwards to preserve indexes. - carets_to_remove.sort(); - for (int i = carets_to_remove.size() - 1; i >= 0; i--) { - remove_caret(carets_to_remove[i]); + int line_offset = 0; + for (Point2i line_range : line_ranges) { + // Preserve carets on the last line. + remove_line_at(line_range.y + line_offset); + if (line_range.x != line_range.y) { + remove_text(line_range.x + line_offset, 0, line_range.y + line_offset, 0); + } + line_offset += line_range.x - line_range.y - 1; } + end_multicaret_edit(); end_complex_operation(); - - String clipboard_string = clipboard.as_string(); - DisplayServer::get_singleton()->clipboard_set(clipboard_string); - cut_copy_line = clipboard_string; } void TextEdit::_copy_internal(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); if (has_selection(p_caret)) { DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret)); cut_copy_line = ""; return; } + // Copy full lines. StringBuilder clipboard; - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (int i = caret_edit_order.size() - 1; i >= 0; i--) { - int caret_idx = caret_edit_order[i]; - if (p_caret != -1 && p_caret != caret_idx) { - continue; - } - - int cl = get_caret_line(caret_idx); - if (text[cl].length() != 0) { - clipboard += _base_get_text(cl, 0, cl, text[cl].length()); - if (p_caret == -1 && i != 0) { - clipboard += "\n"; + Vector<Point2i> line_ranges; + if (p_caret == -1) { + // When there are multiple carets on a line, only copy it once. + line_ranges = get_line_ranges_from_carets(false, true); + } else { + line_ranges.push_back(Point2i(get_caret_line(p_caret), get_caret_line(p_caret))); + } + for (Point2i line_range : line_ranges) { + for (int i = line_range.x; i <= line_range.y; i++) { + if (text[i].length() != 0) { + clipboard += _base_get_text(i, 0, i, text[i].length()); } + clipboard += "\n"; } } String clipboard_string = clipboard.as_string(); DisplayServer::get_singleton()->clipboard_set(clipboard_string); - cut_copy_line = clipboard_string; + // Set the cut copy line so we know to paste as a line. + if (get_caret_count() == 1) { + cut_copy_line = clipboard_string; + } else { + cut_copy_line = ""; + } } void TextEdit::_paste_internal(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); if (!editable) { return; } String clipboard = DisplayServer::get_singleton()->clipboard_get(); + + // Paste a full line. Ignore '\r' characters that may have been added to the clipboard by the OS. + if (get_caret_count() == 1 && !has_selection(0) && !cut_copy_line.is_empty() && cut_copy_line == clipboard.replace("\r", "")) { + insert_text(clipboard, get_caret_line(), 0); + return; + } + + // Paste text at each caret or one line per caret. Vector<String> clipboad_lines = clipboard.split("\n"); - bool insert_line_per_caret = p_caret == -1 && carets.size() > 1 && clipboad_lines.size() == carets.size(); + bool insert_line_per_caret = p_caret == -1 && get_caret_count() > 1 && clipboad_lines.size() == get_caret_count(); begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - int clipboad_line = clipboad_lines.size() - 1; - for (const int &i : caret_edit_order) { - if (p_caret != -1 && p_caret != i) { + begin_multicaret_edit(); + Vector<int> sorted_carets = get_sorted_carets(); + for (int i = 0; i < sorted_carets.size(); i++) { + int caret_index = sorted_carets[i]; + if (p_caret != -1 && p_caret != caret_index) { continue; } - if (has_selection(i)) { - delete_selection(i); - } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { - set_caret_column(0, i == 0, i); - String ins = "\n"; - clipboard += ins; + if (has_selection(caret_index)) { + delete_selection(caret_index); } if (insert_line_per_caret) { - clipboard = clipboad_lines[clipboad_line]; + clipboard = clipboad_lines[i]; } - insert_text_at_caret(clipboard, i); - clipboad_line--; + insert_text_at_caret(clipboard, caret_index); } + end_multicaret_edit(); end_complex_operation(); } void TextEdit::_paste_primary_clipboard_internal(int p_caret) { - ERR_FAIL_COND(p_caret > carets.size()); + ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1); if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { return; } String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary(); - if (carets.size() == 1) { + if (get_caret_count() == 1) { Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); deselect(); - set_caret_line(pos.y, true, false); + set_caret_line(pos.y, true, false, -1); set_caret_column(pos.x); } @@ -7153,10 +7364,26 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con } /* Caret */ +void TextEdit::_caret_changed(int p_caret) { + queue_redraw(); + + if (has_selection(p_caret)) { + _selection_changed(p_caret); + } + + if (caret_pos_dirty) { + return; + } + + if (is_inside_tree()) { + callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred(); + } + caret_pos_dirty = true; +} + void TextEdit::_emit_caret_changed() { emit_signal(SNAME("caret_changed")); caret_pos_dirty = false; - caret_index_edit_dirty = true; } void TextEdit::_reset_caret_blink_timer() { @@ -7201,60 +7428,152 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column } } -/* Selection */ -void TextEdit::_click_selection_held() { - // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD - // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. - // I'm unsure if there's an actual fix that doesn't have a ton of side effects. - if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && get_selection_mode() != SelectionMode::SELECTION_MODE_NONE) { - switch (get_selection_mode()) { - case SelectionMode::SELECTION_MODE_POINTER: { - _update_selection_mode_pointer(); - } break; - case SelectionMode::SELECTION_MODE_WORD: { - _update_selection_mode_word(); - } break; - case SelectionMode::SELECTION_MODE_LINE: { - _update_selection_mode_line(); - } break; - default: { - break; +bool TextEdit::_is_line_col_in_range(int p_line, int p_column, int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_include_edges) const { + if (p_line >= p_from_line && p_line <= p_to_line && (p_line > p_from_line || p_column > p_from_column) && (p_line < p_to_line || p_column < p_to_column)) { + return true; + } + if (p_include_edges) { + if ((p_line == p_from_line && p_column == p_from_column) || (p_line == p_to_line && p_column == p_to_column)) { + return true; + } + } + return false; +} + +void TextEdit::_offset_carets_after(int p_old_line, int p_old_column, int p_new_line, int p_new_column, bool p_include_selection_begin, bool p_include_selection_end) { + // Moves all carets at or after old_line and old_column. + // Called after deleting or inserting text so that the carets stay with the text they are at. + + int edit_height = p_new_line - p_old_line; + int edit_size = p_new_column - p_old_column; + if (edit_height == 0 && edit_size == 0) { + return; + } + + // Intentionally includes carets in the multicaret_edit_ignore list so that they are moved together. + for (int i = 0; i < get_caret_count(); i++) { + bool selected = has_selection(i); + bool caret_at_end = selected && is_caret_after_selection_origin(i); + bool include_caret_at = caret_at_end ? p_include_selection_end : p_include_selection_begin; + + // Move caret. + int caret_line = get_caret_line(i); + int caret_column = get_caret_column(i); + bool caret_after = caret_line > p_old_line || (caret_line == p_old_line && caret_column > p_old_column); + bool caret_at = caret_line == p_old_line && caret_column == p_old_column; + if (caret_after || (caret_at && include_caret_at)) { + caret_line += edit_height; + if (caret_line == p_new_line) { + caret_column += edit_size; } + + if (edit_height != 0) { + set_caret_line(caret_line, false, true, -1, i); + } + set_caret_column(caret_column, false, i); } - } else { + + // Move selection origin. + if (!selected) { + continue; + } + bool include_selection_origin_at = !caret_at_end ? p_include_selection_end : p_include_selection_begin; + + int selection_origin_line = get_selection_origin_line(i); + int selection_origin_column = get_selection_origin_column(i); + bool selection_origin_after = selection_origin_line > p_old_line || (selection_origin_line == p_old_line && selection_origin_column > p_old_column); + bool selection_origin_at = selection_origin_line == p_old_line && selection_origin_column == p_old_column; + if (selection_origin_after || (selection_origin_at && include_selection_origin_at)) { + selection_origin_line += edit_height; + if (selection_origin_line == p_new_line) { + selection_origin_column += edit_size; + } + select(selection_origin_line, selection_origin_column, caret_line, caret_column, i); + } + } + if (!p_include_selection_begin && p_include_selection_end && has_selection()) { + // It is possible that two adjacent selections now overlap. + merge_overlapping_carets(); + } +} + +void TextEdit::_cancel_drag_and_drop_text() { + // Cancel the drag operation if drag originated from here. + if (selection_drag_attempt && get_viewport()) { + get_viewport()->gui_cancel_drag(); + } +} + +/* Selection */ +void TextEdit::_selection_changed(int p_caret) { + if (!selecting_enabled) { + return; + } + + _cancel_drag_and_drop_text(); + queue_redraw(); +} + +void TextEdit::_click_selection_held() { + // Update the selection mode on a timer so it is updated when the view scrolls even if the mouse isn't moving. + if (!Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) { click_select_held->stop(); + return; + } + switch (get_selection_mode()) { + case SelectionMode::SELECTION_MODE_POINTER: { + _update_selection_mode_pointer(); + } break; + case SelectionMode::SELECTION_MODE_WORD: { + _update_selection_mode_word(); + } break; + case SelectionMode::SELECTION_MODE_LINE: { + _update_selection_mode_line(); + } break; + default: { + break; + } } } -void TextEdit::_update_selection_mode_pointer() { - dragging_selection = true; +void TextEdit::_update_selection_mode_pointer(bool p_initial) { Point2 mp = get_local_mouse_pos(); Point2i pos = get_line_column_at_pos(mp); int line = pos.y; - int col = pos.x; - int caret_idx = carets.size() - 1; - - select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx); + int column = pos.x; + int caret_index = get_caret_count() - 1; + + if (p_initial && !has_selection(caret_index)) { + set_selection_origin_line(line, true, -1, caret_index); + set_selection_origin_column(column, caret_index); + // Set the word begin and end to the column in case the mode changes later. + carets.write[caret_index].selection.word_begin_column = column; + carets.write[caret_index].selection.word_end_column = column; + } else { + select(get_selection_origin_line(caret_index), get_selection_origin_column(caret_index), line, column, caret_index); + } + adjust_viewport_to_caret(caret_index); - set_caret_line(line, false, true, 0, caret_idx); - set_caret_column(col, true, caret_idx); - queue_redraw(); + if (has_selection(caret_index)) { + // Only set to true if any selection has been made. + dragging_selection = true; + } click_select_held->start(); merge_overlapping_carets(); } -void TextEdit::_update_selection_mode_word() { +void TextEdit::_update_selection_mode_word(bool p_initial) { dragging_selection = true; Point2 mp = get_local_mouse_pos(); Point2i pos = get_line_column_at_pos(mp); int line = pos.y; - int col = pos.x; - int caret_idx = carets.size() - 1; + int column = pos.x; + int caret_index = get_caret_count() - 1; - int caret_pos = CLAMP(col, 0, text[line].length()); + int caret_pos = CLAMP(column, 0, text[line].length()); int beg = caret_pos; int end = beg; PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); @@ -7266,70 +7585,57 @@ void TextEdit::_update_selection_mode_word() { } } - /* Initial selection. */ - if (!has_selection(caret_idx)) { - select(line, beg, line, end, caret_idx); - carets.write[caret_idx].selection.selecting_column = beg; - carets.write[caret_idx].selection.selected_word_beg = beg; - carets.write[caret_idx].selection.selected_word_end = end; - carets.write[caret_idx].selection.selected_word_origin = beg; - set_caret_line(line, false, true, 0, caret_idx); - set_caret_column(end, true, caret_idx); + if (p_initial && !has_selection(caret_index)) { + // Set the selection origin if there is no existing selection. + select(line, beg, line, end, caret_index); + carets.write[caret_index].selection.word_begin_column = beg; + carets.write[caret_index].selection.word_end_column = end; } else { - if ((col <= carets[caret_idx].selection.selected_word_origin && line == get_selection_line(caret_idx)) || line < get_selection_line(caret_idx)) { - carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_end; - select(line, beg, get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_end, caret_idx); - set_caret_line(line, false, true, 0, caret_idx); - set_caret_column(beg, true, caret_idx); - } else { - carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_beg; - select(get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_beg, line, end, caret_idx); - set_caret_line(get_selection_to_line(caret_idx), false, true, 0, caret_idx); - set_caret_column(get_selection_to_column(caret_idx), true, caret_idx); - } + // Expand the word selection to the mouse. + int origin_line = get_selection_origin_line(caret_index); + bool is_new_selection_dir_right = line > origin_line || (line == origin_line && column >= carets[caret_index].selection.word_begin_column); + int origin_col = is_new_selection_dir_right ? carets[caret_index].selection.word_begin_column : carets[caret_index].selection.word_end_column; + int caret_col = is_new_selection_dir_right ? end : beg; + + select(origin_line, origin_col, line, caret_col, caret_index); } + adjust_viewport_to_caret(caret_index); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - queue_redraw(); - click_select_held->start(); merge_overlapping_carets(); } -void TextEdit::_update_selection_mode_line() { +void TextEdit::_update_selection_mode_line(bool p_initial) { dragging_selection = true; Point2 mp = get_local_mouse_pos(); Point2i pos = get_line_column_at_pos(mp); int line = pos.y; - int col = pos.x; - int caret_idx = carets.size() - 1; - - col = 0; - if (line < carets[caret_idx].selection.selecting_line) { - // Caret is above us. - set_caret_line(line - 1, false, true, 0, caret_idx); - carets.write[caret_idx].selection.selecting_column = has_selection(caret_idx) - ? text[get_selection_line(caret_idx)].length() - : 0; - } else { - // Caret is below us. - set_caret_line(line + 1, false, true, 0, caret_idx); - carets.write[caret_idx].selection.selecting_column = 0; - col = text[line].length(); + int caret_index = get_caret_count() - 1; + + int origin_line = p_initial && !has_selection(caret_index) ? line : get_selection_origin_line(); + bool line_below = line >= origin_line; + int origin_col = line_below ? 0 : get_line(origin_line).length(); + int caret_line = line_below ? line + 1 : line; + int caret_col = caret_line < text.size() ? 0 : get_line(text.size() - 1).length(); + + select(origin_line, origin_col, caret_line, caret_col, caret_index); + adjust_viewport_to_caret(caret_index); + + if (p_initial) { + // Set the word begin and end to the start and end of the origin line in case the mode changes later. + carets.write[caret_index].selection.word_begin_column = 0; + carets.write[caret_index].selection.word_end_column = get_line(origin_line).length(); } - set_caret_column(0, false, caret_idx); - select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - queue_redraw(); - click_select_held->start(); merge_overlapping_carets(); } @@ -7339,23 +7645,23 @@ void TextEdit::_pre_shift_selection(int p_caret) { return; } - if (!has_selection(p_caret) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) { - carets.write[p_caret].selection.active = true; - set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_caret_line(p_caret), get_caret_column(p_caret), p_caret); + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT); + if (has_selection(p_caret)) { return; } - - set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_selection_line(p_caret), get_selection_column(p_caret), p_caret); + // Prepare selection to start at current caret position. + set_selection_origin_line(get_caret_line(p_caret), true, -1, p_caret); + set_selection_origin_column(get_caret_column(p_caret), p_caret); + carets.write[p_caret].selection.active = true; + carets.write[p_caret].selection.word_begin_column = get_caret_column(p_caret); + carets.write[p_caret].selection.word_end_column = get_caret_column(p_caret); } -void TextEdit::_post_shift_selection(int p_caret) { - if (!selecting_enabled) { - return; - } - - if (has_selection(p_caret) && get_selection_mode() == SelectionMode::SELECTION_MODE_SHIFT) { - select(get_selection_line(p_caret), get_selection_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret), p_caret); +bool TextEdit::_selection_contains(int p_caret, int p_line, int p_column, bool p_include_edges, bool p_only_selections) const { + if (!has_selection(p_caret)) { + return !p_only_selections && p_line == get_caret_line(p_caret) && p_column == get_caret_column(p_caret); } + return _is_line_col_in_range(p_line, p_column, get_selection_from_line(p_caret), get_selection_from_column(p_caret), get_selection_to_line(p_caret), get_selection_to_column(p_caret), p_include_edges); } /* Line Wrapping */ @@ -7441,7 +7747,7 @@ void TextEdit::_update_scrollbars() { updating_scrolls = true; - if (total_rows > visible_rows) { + if (!fit_content_height && total_rows > visible_rows) { v_scroll->show(); v_scroll->set_max(total_rows + _get_visible_lines_offset()); v_scroll->set_page(visible_rows + _get_visible_lines_offset()); @@ -7668,7 +7974,7 @@ void TextEdit::_update_minimap_click() { Point2 mp = get_local_mouse_pos(); int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT); - if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) { + if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.x > xmargin_end)) { minimap_clicked = false; return; } @@ -7730,9 +8036,43 @@ Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) { return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); } +/* Deprecated. */ +#ifndef DISABLE_DEPRECATED +Vector<int> TextEdit::get_caret_index_edit_order() { + Vector<int> carets_order = get_sorted_carets(); + carets_order.reverse(); + return carets_order; +} + +void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) { +} + +int TextEdit::get_selection_line(int p_caret) const { + return get_selection_origin_line(p_caret); +} + +int TextEdit::get_selection_column(int p_caret) const { + return get_selection_origin_column(p_caret); +} +#endif + /*** Super internal Core API. Everything builds on it. ***/ -void TextEdit::_text_changed_emit() { +void TextEdit::_text_changed() { + _cancel_drag_and_drop_text(); + queue_redraw(); + + if (text_changed_dirty || setting_text) { + return; + } + + if (is_inside_tree()) { + callable_mp(this, &TextEdit::_emit_text_changed).call_deferred(); + } + text_changed_dirty = true; +} + +void TextEdit::_emit_text_changed() { emit_signal(SNAME("text_changed")); text_changed_dirty = false; } @@ -7868,12 +8208,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i input_direction = (TextDirection)dir; } - if (!text_changed_dirty && !setting_text) { - if (is_inside_tree()) { - callable_mp(this, &TextEdit::_text_changed_emit).call_deferred(); - } - text_changed_dirty = true; - } + _text_changed(); emit_signal(SNAME("lines_edited_from"), p_line, r_end_line); } @@ -7914,12 +8249,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li text.remove_range(p_from_line, p_to_line); text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); - if (!text_changed_dirty && !setting_text) { - if (is_inside_tree()) { - callable_mp(this, &TextEdit::_text_changed_emit).call_deferred(); - } - text_changed_dirty = true; - } + _text_changed(); emit_signal(SNAME("lines_edited_from"), p_to_line, p_from_line); } @@ -7968,5 +8298,6 @@ TextEdit::TextEdit(const String &p_placeholder) { set_placeholder(p_placeholder); + set_clip_contents(true); set_editable(true); } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index b8e30c7900..efade39876 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -389,18 +389,12 @@ private: /* Caret. */ struct Selection { bool active = false; - bool shiftclick_left = false; - int selecting_line = 0; - int selecting_column = 0; - int selected_word_beg = 0; - int selected_word_end = 0; - int selected_word_origin = 0; - - int from_line = 0; - int from_column = 0; - int to_line = 0; - int to_column = 0; + int origin_line = 0; + int origin_column = 0; + int origin_last_fit_x = 0; + int word_begin_column = 0; + int word_end_column = 0; }; struct Caret { @@ -415,11 +409,13 @@ private: // Vector containing all the carets, index '0' is the "main caret" and should never be removed. Vector<Caret> carets; - Vector<int> caret_index_edit_order; bool setting_caret_line = false; bool caret_pos_dirty = false; - bool caret_index_edit_dirty = true; + + int multicaret_edit_count = 0; + bool multicaret_edit_merge_queued = false; + HashSet<int> multicaret_edit_ignore_carets; CaretType caret_type = CaretType::CARET_TYPE_LINE; @@ -438,12 +434,18 @@ private: bool drag_action = false; bool drag_caret_force_displayed = false; + void _caret_changed(int p_caret = -1); void _emit_caret_changed(); void _reset_caret_blink_timer(); void _toggle_draw_caret(); int _get_column_x_offset_for_line(int p_char, int p_line, int p_column) const; + bool _is_line_col_in_range(int p_line, int p_column, int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_include_edges = true) const; + + void _offset_carets_after(int p_old_line, int p_old_column, int p_new_line, int p_new_column, bool p_include_selection_begin = true, bool p_include_selection_end = true); + + void _cancel_drag_and_drop_text(); /* Selection. */ SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE; @@ -456,18 +458,23 @@ private: bool selection_drag_attempt = false; bool dragging_selection = false; + int drag_and_drop_origin_caret_index = -1; + int drag_caret_index = -1; Timer *click_select_held = nullptr; uint64_t last_dblclk = 0; Vector2 last_dblclk_pos; + + void _selection_changed(int p_caret = -1); void _click_selection_held(); - void _update_selection_mode_pointer(); - void _update_selection_mode_word(); - void _update_selection_mode_line(); + void _update_selection_mode_pointer(bool p_initial = false); + void _update_selection_mode_word(bool p_initial = false); + void _update_selection_mode_line(bool p_initial = false); void _pre_shift_selection(int p_caret); - void _post_shift_selection(int p_caret); + + bool _selection_contains(int p_caret, int p_line, int p_column, bool p_include_edges = true, bool p_only_selections = true) const; /* Line wrapping. */ LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE; @@ -599,7 +606,8 @@ private: /*** Super internal Core API. Everything builds on it. ***/ bool text_changed_dirty = false; - void _text_changed_emit(); + void _text_changed(); + void _emit_text_changed(); void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr); void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); @@ -625,13 +633,15 @@ private: void _move_caret_document_end(bool p_select); bool _clear_carets_and_selection(); - // Used in add_caret_at_carets - void _get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x = -1) const; - protected: void _notification(int p_what); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _set_selection_mode_compat_86978(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + virtual void _update_theme_item_cache() override; /* Internal API for CodeEdit, pending public API. */ @@ -659,6 +669,7 @@ protected: bool _is_line_hidden(int p_line) const; void _unhide_all_lines(); + virtual void _unhide_carets(); // Symbol lookup. String lookup_symbol_word; @@ -765,9 +776,11 @@ public: void swap_lines(int p_from_line, int p_to_line); - void insert_line_at(int p_at, const String &p_text); - void insert_text_at_caret(const String &p_text, int p_caret = -1); + void insert_line_at(int p_line, const String &p_text); + void remove_line_at(int p_line, bool p_move_carets_down = true); + void insert_text_at_caret(const String &p_text, int p_caret = -1); + void insert_text(const String &p_text, int p_line, int p_column, bool p_before_selection_begin = true, bool p_before_selection_end = false); void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); int get_last_unhidden_line() const; @@ -851,15 +864,20 @@ public: void set_multiple_carets_enabled(bool p_enabled); bool is_multiple_carets_enabled() const; - int add_caret(int p_line, int p_col); + int add_caret(int p_line, int p_column); void remove_caret(int p_caret); void remove_secondary_carets(); - void merge_overlapping_carets(); int get_caret_count() const; void add_caret_at_carets(bool p_below); - Vector<int> get_caret_index_edit_order(); - void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col); + Vector<int> get_sorted_carets(bool p_include_ignored_carets = false) const; + void collapse_carets(int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_inclusive = false); + + void merge_overlapping_carets(); + void begin_multicaret_edit(); + void end_multicaret_edit(); + bool is_in_mulitcaret_edit() const; + bool multicaret_edit_ignore_caret(int p_caret) const; bool is_caret_visible(int p_caret = 0) const; Point2 get_caret_draw_pos(int p_caret = 0) const; @@ -867,7 +885,7 @@ public: void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0, int p_caret = 0); int get_caret_line(int p_caret = 0) const; - void set_caret_column(int p_col, bool p_adjust_viewport = true, int p_caret = 0); + void set_caret_column(int p_column, bool p_adjust_viewport = true, int p_caret = 0); int get_caret_column(int p_caret = 0) const; int get_caret_wrap_index(int p_caret = 0) const; @@ -884,26 +902,34 @@ public: void set_drag_and_drop_selection_enabled(const bool p_enabled); bool is_drag_and_drop_selection_enabled() const; - void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0); + void set_selection_mode(SelectionMode p_mode); SelectionMode get_selection_mode() const; void select_all(); void select_word_under_caret(int p_caret = -1); void add_selection_for_next_occurrence(); - void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0); + void skip_selection_for_next_occurrence(); + void select(int p_origin_line, int p_origin_column, int p_caret_line, int p_caret_column, int p_caret = 0); bool has_selection(int p_caret = -1) const; String get_selected_text(int p_caret = -1); + int get_selection_at_line_column(int p_line, int p_column, bool p_include_edges = true, bool p_only_selections = true) const; + Vector<Point2i> get_line_ranges_from_carets(bool p_only_selections = false, bool p_merge_adjacent = true) const; + TypedArray<Vector2i> get_line_ranges_from_carets_typed_array(bool p_only_selections = false, bool p_merge_adjacent = true) const; - int get_selection_line(int p_caret = 0) const; - int get_selection_column(int p_caret = 0) const; + void set_selection_origin_line(int p_line, bool p_can_be_hidden = true, int p_wrap_index = -1, int p_caret = 0); + void set_selection_origin_column(int p_column, int p_caret = 0); + int get_selection_origin_line(int p_caret = 0) const; + int get_selection_origin_column(int p_caret = 0) const; int get_selection_from_line(int p_caret = 0) const; int get_selection_from_column(int p_caret = 0) const; int get_selection_to_line(int p_caret = 0) const; int get_selection_to_column(int p_caret = 0) const; + bool is_caret_after_selection_origin(int p_caret = 0) const; + void deselect(int p_caret = -1); void delete_selection(int p_caret = -1); @@ -1042,6 +1068,15 @@ public: Color get_font_color() const; + /* Deprecated. */ +#ifndef DISABLE_DEPRECATED + Vector<int> get_caret_index_edit_order(); + void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col); + + int get_selection_line(int p_caret = 0) const; + int get_selection_column(int p_caret = 0) const; +#endif + TextEdit(const String &p_placeholder = String()); }; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index dcbb25c41d..c267ff93c6 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -103,8 +103,8 @@ bool TextureButton::has_point(const Point2 &p_point) const { point *= scale; // finally, we need to check if the point is inside a rectangle with a position >= 0,0 and a size <= mask_size - rect.position = Point2(MAX(0, _texture_region.position.x), MAX(0, _texture_region.position.y)); - rect.size = Size2(MIN(mask_size.x, _texture_region.size.x), MIN(mask_size.y, _texture_region.size.y)); + rect.position = _texture_region.position.maxf(0); + rect.size = mask_size.min(_texture_region.size); } if (!rect.has_point(point)) { @@ -178,13 +178,14 @@ void TextureButton::_notification(int p_what) { texdraw = focused; } - if (texdraw.is_valid()) { - size = texdraw->get_size(); - _texture_region = Rect2(Point2(), texdraw->get_size()); + if (texdraw.is_valid() || click_mask.is_valid()) { + const Size2 texdraw_size = texdraw.is_valid() ? texdraw->get_size() : Size2(click_mask->get_size()); + + size = texdraw_size; + _texture_region = Rect2(Point2(), texdraw_size); _tile = false; switch (stretch_mode) { case STRETCH_KEEP: - size = texdraw->get_size(); break; case STRETCH_SCALE: size = get_size(); @@ -194,18 +195,17 @@ void TextureButton::_notification(int p_what) { _tile = true; break; case STRETCH_KEEP_CENTERED: - ofs = (get_size() - texdraw->get_size()) / 2; - size = texdraw->get_size(); + ofs = (get_size() - texdraw_size) / 2; break; case STRETCH_KEEP_ASPECT_CENTERED: case STRETCH_KEEP_ASPECT: { Size2 _size = get_size(); - float tex_width = texdraw->get_width() * _size.height / texdraw->get_height(); + float tex_width = texdraw_size.width * _size.height / texdraw_size.height; float tex_height = _size.height; if (tex_width > _size.width) { tex_width = _size.width; - tex_height = texdraw->get_height() * tex_width / texdraw->get_width(); + tex_height = texdraw_size.height * tex_width / texdraw_size.width; } if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { @@ -217,10 +217,9 @@ void TextureButton::_notification(int p_what) { } break; case STRETCH_KEEP_ASPECT_COVERED: { size = get_size(); - Size2 tex_size = texdraw->get_size(); - Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height); + Size2 scale_size = size / texdraw_size; float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height; - Size2 scaled_tex_size = tex_size * scale; + Size2 scaled_tex_size = texdraw_size * scale; Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f; _texture_region = Rect2(ofs2, size / scale); } break; @@ -233,10 +232,12 @@ void TextureButton::_notification(int p_what) { if (draw_focus_only) { // Do nothing, we only needed to calculate the rectangle. - } else if (_tile) { - draw_texture_rect(texdraw, Rect2(ofs, size), _tile); - } else { - draw_texture_rect_region(texdraw, Rect2(ofs, size), _texture_region); + } else if (texdraw.is_valid()) { + if (_tile) { + draw_texture_rect(texdraw, Rect2(ofs, size), _tile); + } else { + draw_texture_rect_region(texdraw, Rect2(ofs, size), _texture_region); + } } } else { _position_rect = Rect2(); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 248260a7d2..dc48945d9b 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -249,8 +249,7 @@ Point2 TextureProgressBar::get_relative_center() { p += rad_center_off; p.x /= progress->get_width(); p.y /= progress->get_height(); - p.x = CLAMP(p.x, 0, 1); - p.y = CLAMP(p.y, 0, 1); + p = p.clampf(0, 1); return p; } @@ -495,7 +494,7 @@ void TextureProgressBar::_notification(int p_what) { Rect2 source = Rect2(Point2(), progress->get_size()); draw_texture_rect_region(progress, region, source, tint_progress); } else if (val != 0) { - Array pts; + LocalVector<float> pts; float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1; float start; @@ -508,11 +507,11 @@ void TextureProgressBar::_notification(int p_what) { float end = start + direction * val; float from = MIN(start, end); float to = MAX(start, end); - pts.append(from); + pts.push_back(from); for (float corner = Math::floor(from * 4 + 0.5) * 0.25 + 0.125; corner < to; corner += 0.25) { - pts.append(corner); + pts.push_back(corner); } - pts.append(to); + pts.push_back(to); Ref<AtlasTexture> atlas_progress = progress; bool valid_atlas_progress = atlas_progress.is_valid() && atlas_progress->get_atlas().is_valid(); @@ -525,9 +524,9 @@ void TextureProgressBar::_notification(int p_what) { Vector<Point2> uvs; Vector<Point2> points; - for (int i = 0; i < pts.size(); i++) { - Point2 uv = unit_val_to_uv(pts[i]); - if (uvs.find(uv) >= 0) { + for (const float &f : pts) { + Point2 uv = unit_val_to_uv(f); + if (uvs.has(uv)) { continue; } points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y)); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 7842fc5fc0..fc5b942918 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -35,8 +35,6 @@ #include "core/math/math_funcs.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "core/string/print_string.h" -#include "core/string/translation.h" #include "scene/gui/box_container.h" #include "scene/gui/text_edit.h" #include "scene/main/window.h" @@ -381,8 +379,8 @@ void TreeItem::set_text_overrun_behavior(int p_column, TextServer::OverrunBehavi cells.write[p_column].text_buf->set_text_overrun_behavior(p_behavior); cells.write[p_column].dirty = true; - _changed_notify(p_column); cells.write[p_column].cached_minimum_size_dirty = true; + _changed_notify(p_column); } TextServer::OverrunBehavior TreeItem::get_text_overrun_behavior(int p_column) const { @@ -1514,7 +1512,9 @@ Size2 TreeItem::get_minimum_size(int p_column) { const TreeItem::Cell &cell = cells[p_column]; if (cell.cached_minimum_size_dirty) { - Size2 size; + Size2 size = Size2( + parent_tree->theme_cache.inner_item_margin_left + parent_tree->theme_cache.inner_item_margin_right, + parent_tree->theme_cache.inner_item_margin_top + parent_tree->theme_cache.inner_item_margin_bottom); // Text. if (!cell.text.is_empty()) { @@ -1522,7 +1522,9 @@ Size2 TreeItem::get_minimum_size(int p_column) { parent_tree->update_item_cell(this, p_column); } Size2 text_size = cell.text_buf->get_size(); - size.width += text_size.width; + if (get_text_overrun_behavior(p_column) == TextServer::OVERRUN_NO_TRIMMING) { + size.width += text_size.width; + } size.height = MAX(size.height, text_size.height); } @@ -1541,13 +1543,10 @@ Size2 TreeItem::get_minimum_size(int p_column) { Ref<Texture2D> texture = cell.buttons[i].texture; if (texture.is_valid()) { Size2 button_size = texture->get_size() + parent_tree->theme_cache.button_pressed->get_minimum_size(); - size.width += button_size.width; + size.width += button_size.width + parent_tree->theme_cache.button_margin; size.height = MAX(size.height, button_size.height); } } - if (cell.buttons.size() >= 2) { - size.width += (cell.buttons.size() - 1) * parent_tree->theme_cache.button_margin; - } cells.write[p_column].cached_minimum_size = size; cells.write[p_column].cached_minimum_size_dirty = false; @@ -2195,9 +2194,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if ((select_mode == SELECT_ROW && selected_item == p_item) || p_item->cells[i].selected || !p_item->has_meta("__focus_rect")) { Rect2i r = cell_rect; - p_item->set_meta("__focus_rect", Rect2(r.position, r.size)); - if (select_mode != SELECT_ROW) { + p_item->set_meta("__focus_rect", Rect2(r.position, r.size)); if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; } @@ -2208,6 +2206,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 theme_cache.selected->draw(ci, r); } } + } else { + p_item->set_meta("__focus_col_" + itos(i), Rect2(r.position, r.size)); } } @@ -2458,7 +2458,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (rtl) { button_ofs.x = get_size().width - button_ofs.x - button_texture->get_width(); } - p_item->cells.write[i].buttons.write[j].rect = Rect2i(button_ofs, button_size); button_texture->draw(ci, button_ofs, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color); item_width_with_buttons -= button_size.width + theme_cache.button_margin; } @@ -2693,7 +2692,6 @@ void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_c if (p_selected == p_current && (!c.selected || allow_reselect)) { c.selected = true; selected_item = p_selected; - selected_col = 0; if (!emitted_row) { emit_signal(SNAME("item_selected")); emitted_row = true; @@ -2704,6 +2702,9 @@ void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_c c.selected = false; } } + if (&selected_cell == &c) { + selected_col = i; + } } else if (select_mode == SELECT_SINGLE || select_mode == SELECT_MULTI) { if (!r_in_range && &selected_cell == &c) { if (!selected_cell.selected || allow_reselect) { @@ -2827,7 +2828,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int return -1; } - if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + theme_cache.item_margin))) { + if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x < (x_ofs + theme_cache.item_margin))) { if (enable_recursive_folding && p_mod->is_shift_pressed()) { p_item->set_collapsed_recursive(!p_item->is_collapsed()); } else { @@ -3151,10 +3152,12 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } void Tree::_text_editor_popup_modal_close() { - if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) || - Input::get_singleton()->is_key_pressed(Key::KP_ENTER) || - Input::get_singleton()->is_key_pressed(Key::ENTER)) { - return; + if (popup_edit_commited) { + return; // Already processed by LineEdit/TextEdit commit. + } + + if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) { + return; // ESC pressed, app focus lost, or forced close from code. } if (value_editor->has_point(value_editor->get_local_mouse_position())) { @@ -3173,9 +3176,18 @@ void Tree::_text_editor_popup_modal_close() { } void Tree::_text_editor_gui_input(const Ref<InputEvent> &p_event) { + if (popup_edit_commited) { + return; // Already processed by _text_editor_popup_modal_close + } + + if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) { + return; // ESC pressed, app focus lost, or forced close from code. + } + if (p_event->is_action_pressed("ui_text_newline_blank", true)) { accept_event(); } else if (p_event->is_action_pressed("ui_text_newline")) { + popup_edit_commited = true; // End edit popup processing. popup_editor->hide(); _apply_multiline_edit(); accept_event(); @@ -3206,6 +3218,15 @@ void Tree::_apply_multiline_edit() { } void Tree::_line_editor_submit(String p_text) { + if (popup_edit_commited) { + return; // Already processed by _text_editor_popup_modal_close + } + + if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) { + return; // ESC pressed, app focus lost, or forced close from code. + } + + popup_edit_commited = true; // End edit popup processing. popup_editor->hide(); if (!popup_edited_item) { @@ -3428,7 +3449,7 @@ Rect2 Tree::_get_content_rect() const { const real_t v_size = v_scroll->is_visible() ? (v_scroll->get_combined_minimum_size().x + theme_cache.scrollbar_h_separation) : 0; const real_t h_size = h_scroll->is_visible() ? (h_scroll->get_combined_minimum_size().y + theme_cache.scrollbar_v_separation) : 0; const Point2 scroll_begin = _get_scrollbar_layout_rect().get_end() - Vector2(v_size, h_size); - const Size2 offset = (content_rect.get_end() - scroll_begin).max(Vector2(0, 0)); + const Size2 offset = (content_rect.get_end() - scroll_begin).maxf(0); return content_rect.grow_individual(0, 0, -offset.x, -offset.y); } @@ -3778,7 +3799,12 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); warp_mouse(range_drag_capture_pos); } else { - Rect2 rect = get_selected()->get_meta("__focus_rect"); + Rect2 rect; + if (select_mode == SELECT_ROW) { + rect = get_selected()->get_meta("__focus_col_" + itos(selected_col)); + } else { + rect = get_selected()->get_meta("__focus_rect"); + } Point2 mpos = mb->get_position(); int icon_size_x = 0; Ref<Texture2D> icon = get_selected()->get_icon(selected_col); @@ -3868,6 +3894,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } if (!root || (!root->get_first_child() && hide_root)) { + emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), mb->get_button_index()); break; } @@ -3986,7 +4013,14 @@ bool Tree::edit_selected(bool p_force_edit) { return false; } - Rect2 rect = s->get_meta("__focus_rect"); + float popup_scale = popup_editor->is_embedded() ? 1.0 : popup_editor->get_parent_visible_window()->get_content_scale_factor(); + Rect2 rect; + if (select_mode == SELECT_ROW) { + rect = s->get_meta("__focus_col_" + itos(selected_col)); + } else { + rect = s->get_meta("__focus_rect"); + } + rect.position *= popup_scale; popup_edited_item = s; popup_edited_item_col = col; @@ -4029,7 +4063,7 @@ bool Tree::edit_selected(bool p_force_edit) { popup_rect.size = rect.size; // Account for icon. - Size2 icon_size = _get_cell_icon_size(c); + Size2 icon_size = _get_cell_icon_size(c) * popup_scale; popup_rect.position.x += icon_size.x; popup_rect.size.x -= icon_size.x; @@ -4056,7 +4090,11 @@ bool Tree::edit_selected(bool p_force_edit) { } popup_editor->set_position(popup_rect.position); - popup_editor->set_size(popup_rect.size); + popup_editor->set_size(popup_rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } + popup_edit_commited = false; // Start edit popup processing. popup_editor->popup(); popup_editor->child_controls_changed(); @@ -4072,7 +4110,11 @@ bool Tree::edit_selected(bool p_force_edit) { text_editor->show(); popup_editor->set_position(get_screen_position() + rect.position); - popup_editor->set_size(rect.size); + popup_editor->set_size(rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } + popup_edit_commited = false; // Start edit popup processing. popup_editor->popup(); popup_editor->child_controls_changed(); @@ -4325,7 +4367,7 @@ void Tree::_notification(int p_what) { } default: { - text_pos.x += sb->get_offset().x + (tbrect.size.width - columns[i].text_buf->get_size().x) / 2; + text_pos.x += (tbrect.size.width - columns[i].text_buf->get_size().x) / 2; break; } } @@ -4375,17 +4417,23 @@ void Tree::_update_all() { } Size2 Tree::get_minimum_size() const { - if (h_scroll_enabled && v_scroll_enabled) { - return Size2(); - } else { - Vector2 min_size = get_internal_min_size(); - Ref<StyleBox> bg = theme_cache.panel_style; - if (bg.is_valid()) { - min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); - min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM); - } - return Vector2(h_scroll_enabled ? 0 : min_size.x, v_scroll_enabled ? 0 : min_size.y); + Vector2 min_size = Vector2(0, _get_title_button_height()); + + if (theme_cache.panel_style.is_valid()) { + min_size += theme_cache.panel_style->get_minimum_size(); } + + Vector2 content_min_size = get_internal_min_size(); + if (h_scroll_enabled) { + content_min_size.x = 0; + min_size.y += h_scroll->get_combined_minimum_size().height; + } + if (v_scroll_enabled) { + min_size.x += v_scroll->get_combined_minimum_size().width; + content_min_size.y = 0; + } + + return min_size + content_min_size; } TreeItem *Tree::create_item(TreeItem *p_parent, int p_index) { @@ -4563,6 +4611,7 @@ void Tree::set_hide_root(bool p_enabled) { hide_root = p_enabled; queue_redraw(); + update_minimum_size(); } bool Tree::is_root_hidden() const { @@ -4707,34 +4756,37 @@ int Tree::get_column_minimum_width(int p_column) const { // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(theme_cache.font->get_string_size(columns[p_column].xl_title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT), min_width); - } - - if (!columns[p_column].clip_content) { - int depth = 0; - TreeItem *next; - for (TreeItem *item = get_root(); item; item = next) { - next = item->get_next_visible(); - // Compute the depth in tree. - if (next && p_column == 0) { - if (next->get_parent() == item) { - depth += 1; - } else { - TreeItem *common_parent = item->get_parent(); - while (common_parent != next->get_parent() && common_parent) { - common_parent = common_parent->get_parent(); - depth -= 1; + const float padding = theme_cache.title_button->get_margin(SIDE_LEFT) + theme_cache.title_button->get_margin(SIDE_RIGHT); + min_width = MAX(theme_cache.font->get_string_size(columns[p_column].xl_title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + padding, min_width); + } + + if (root && !columns[p_column].clip_content) { + int depth = 1; + + TreeItem *last = nullptr; + TreeItem *first = hide_root ? root->get_next_visible() : root; + for (TreeItem *item = first; item; last = item, item = item->get_next_visible()) { + // Get column indentation. + int indent; + if (p_column == 0) { + if (last) { + if (item->parent == last) { + depth += 1; + } else if (item->parent != last->parent) { + depth = hide_root ? 0 : 1; + for (TreeItem *iter = item->parent; iter; iter = iter->parent) { + depth += 1; + } } } + indent = theme_cache.item_margin * depth; + } else { + indent = theme_cache.h_separation; } // Get the item minimum size. Size2 item_size = item->get_minimum_size(p_column); - if (p_column == 0) { - item_size.width += theme_cache.item_margin * depth; - } else { - item_size.width += theme_cache.h_separation; - } + item_size.width += indent; // Check if the item is wider. min_width = MAX(min_width, item_size.width); @@ -4948,6 +5000,7 @@ void Tree::set_column_titles_visible(bool p_show) { show_column_titles = p_show; queue_redraw(); + update_minimum_size(); } bool Tree::are_column_titles_visible() const { @@ -5246,6 +5299,86 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_ return nullptr; } +// When on a button, r_index is valid. +// When on an item, both r_item and r_column are valid. +// Otherwise, all output arguments are invalid. +void Tree::_find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const { + r_item = nullptr; + r_column = -1; + r_index = -1; + + if (!root) { + return; + } + + Point2 pos = p_pos - theme_cache.panel_style->get_offset(); + pos.y -= _get_title_button_height(); + if (pos.y < 0) { + return; + } + + if (cache.rtl) { + pos.x = get_size().width - pos.x; + } + pos += theme_cache.offset; // Scrolling. + + int col, h, section; + TreeItem *it = _find_item_at_pos(root, pos, col, h, section); + if (!it) { + return; + } + + r_item = it; + r_column = col; + + const TreeItem::Cell &c = it->cells[col]; + if (c.buttons.is_empty()) { + return; + } + + int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width + theme_cache.offset.x; + if (v_scroll->is_visible_in_tree()) { + x_limit -= v_scroll->get_minimum_size().width; + } + + for (int i = 0; i < col; i++) { + const int col_w = get_column_width(i) + theme_cache.h_separation; + pos.x -= col_w; + x_limit -= col_w; + } + + int x_check; + if (cache.rtl) { + x_check = get_column_width(col); + } else { + // Right edge of the buttons area, relative to the start of the column. + int buttons_area_min = 0; + if (col == 0) { + // Content of column 0 should take indentation into account. + for (TreeItem *current = it; current && (current != root || !hide_root); current = current->parent) { + buttons_area_min += theme_cache.item_margin; + } + } + for (int i = c.buttons.size() - 1; i >= 0; i--) { + Ref<Texture2D> b = c.buttons[i].texture; + buttons_area_min += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin; + } + + x_check = MAX(buttons_area_min, MIN(get_column_width(col), x_limit)); + } + + for (int i = c.buttons.size() - 1; i >= 0; i--) { + Ref<Texture2D> b = c.buttons[i].texture; + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); + if (pos.x > x_check - size.width) { + x_limit -= theme_cache.item_margin; + r_index = i; + return; + } + x_check -= size.width + theme_cache.button_margin; + } +} + int Tree::get_column_at_position(const Point2 &p_pos) const { if (root) { Point2 pos = p_pos; @@ -5337,84 +5470,37 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const { } int Tree::get_button_id_at_position(const Point2 &p_pos) const { - if (root) { - Point2 pos = p_pos; - pos -= theme_cache.panel_style->get_offset(); - pos.y -= _get_title_button_height(); - if (pos.y < 0) { - return -1; - } - - if (h_scroll->is_visible_in_tree()) { - pos.x += h_scroll->get_value(); - } - if (v_scroll->is_visible_in_tree()) { - pos.y += v_scroll->get_value(); - } - - int col, h, section; - TreeItem *it = _find_item_at_pos(root, pos, col, h, section); - - if (it) { - const TreeItem::Cell &c = it->cells[col]; - int col_width = get_column_width(col); + TreeItem *it; + int col, index; + _find_button_at_pos(p_pos, it, col, index); - for (int i = 0; i < col; i++) { - pos.x -= get_column_width(i); - } - - for (int j = c.buttons.size() - 1; j >= 0; j--) { - Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); - if (pos.x > col_width - size.width) { - return c.buttons[j].id; - } - col_width -= size.width; - } - } + if (index == -1) { + return -1; } - - return -1; + return it->cells[col].buttons[index].id; } String Tree::get_tooltip(const Point2 &p_pos) const { - if (root) { - Point2 pos = p_pos; - pos -= theme_cache.panel_style->get_offset(); - pos.y -= _get_title_button_height(); - if (pos.y < 0) { - return Control::get_tooltip(p_pos); - } + Point2 pos = p_pos - theme_cache.panel_style->get_offset(); + pos.y -= _get_title_button_height(); + if (pos.y < 0) { + return Control::get_tooltip(p_pos); + } - Point2 button_pos = pos; - if (h_scroll->is_visible_in_tree()) { - pos.x += h_scroll->get_value(); - } - if (v_scroll->is_visible_in_tree()) { - pos.y += v_scroll->get_value(); - } + TreeItem *it; + int col, index; + _find_button_at_pos(p_pos, it, col, index); - int col, h, section; - TreeItem *it = _find_item_at_pos(root, pos, col, h, section); + if (index != -1) { + return it->cells[col].buttons[index].tooltip; + } - if (it) { - const TreeItem::Cell &c = it->cells[col]; - for (int j = c.buttons.size() - 1; j >= 0; j--) { - if (c.buttons[j].rect.has_point(button_pos)) { - String tooltip = c.buttons[j].tooltip; - if (!tooltip.is_empty()) { - return tooltip; - } - } - } - String ret; - if (it->get_tooltip_text(col) == "") { - ret = it->get_text(col); - } else { - ret = it->get_tooltip_text(col); - } - return ret; + if (it) { + const String item_tooltip = it->get_tooltip_text(col); + if (item_tooltip.is_empty()) { + return it->get_text(col); } + return item_tooltip; } return Control::get_tooltip(p_pos); @@ -5592,8 +5678,8 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("item_selected")); ADD_SIGNAL(MethodInfo("cell_selected")); ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::BOOL, "selected"))); - ADD_SIGNAL(MethodInfo("item_mouse_selected", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index"))); - ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index"))); + ADD_SIGNAL(MethodInfo("item_mouse_selected", PropertyInfo(Variant::VECTOR2, "mouse_position"), PropertyInfo(Variant::INT, "mouse_button_index"))); + ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "click_position"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("item_edited")); ADD_SIGNAL(MethodInfo("custom_item_clicked", PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("item_icon_double_clicked")); @@ -5733,7 +5819,7 @@ Tree::Tree() { h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved)); v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved)); line_editor->connect("text_submitted", callable_mp(this, &Tree::_line_editor_submit)); - text_editor->connect("gui_input", callable_mp(this, &Tree::_text_editor_gui_input)); + text_editor->connect(SceneStringName(gui_input), callable_mp(this, &Tree::_text_editor_gui_input)); popup_editor->connect("popup_hide", callable_mp(this, &Tree::_text_editor_popup_modal_close)); popup_menu->connect("id_pressed", callable_mp(this, &Tree::popup_select)); value_editor->connect("value_changed", callable_mp(this, &Tree::value_editor_changed)); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 21696d8216..e9c93c6e03 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -108,7 +108,6 @@ private: Ref<Texture2D> texture; Color color = Color(1, 1, 1, 1); String tooltip; - Rect2i rect; }; Vector<Button> buttons; @@ -480,6 +479,7 @@ private: VBoxContainer *popup_editor_vb = nullptr; + bool popup_edit_commited = true; Popup *popup_editor = nullptr; LineEdit *line_editor = nullptr; TextEdit *text_editor = nullptr; @@ -646,6 +646,8 @@ private: TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int §ion) const; + void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const; + /* float drag_speed; float drag_accum; diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index 687a9e46a0..0b521f926d 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -31,7 +31,6 @@ #include "video_stream_player.h" #include "core/os/os.h" -#include "scene/scene_string_names.h" #include "servers/audio_server.h" int VideoStreamPlayer::sp_get_channel_count() const { @@ -163,7 +162,7 @@ void VideoStreamPlayer::_notification(int p_notification) { play(); return; } - emit_signal(SceneStringNames::get_singleton()->finished); + emit_signal(SceneStringName(finished)); } } break; @@ -460,7 +459,7 @@ StringName VideoStreamPlayer::get_bus() const { return bus; } } - return SceneStringNames::get_singleton()->Master; + return SceneStringName(Master); } void VideoStreamPlayer::_validate_property(PropertyInfo &p_property) const { diff --git a/scene/main/canvas_item.compat.inc b/scene/main/canvas_item.compat.inc new file mode 100644 index 0000000000..9bc3d01a69 --- /dev/null +++ b/scene/main/canvas_item.compat.inc @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* canvas_item.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void CanvasItem::_draw_circle_bind_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color) { + draw_circle(p_pos, p_radius, p_color, true, -1.0, false); +} + +void CanvasItem::_draw_rect_bind_compat_84523(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width) { + draw_rect(p_rect, p_color, p_filled, p_width, false); +} + +void CanvasItem::_draw_dashed_line_bind_compat_84523(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned) { + draw_dashed_line(p_from, p_to, p_color, p_width, p_dash, p_aligned, false); +} + +void CanvasItem::_draw_multiline_bind_compat_84523(const Vector<Point2> &p_points, const Color &p_color, real_t p_width) { + draw_multiline(p_points, p_color, p_width, false); +} + +void CanvasItem::_draw_multiline_colors_bind_compat_84523(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width) { + draw_multiline_colors(p_points, p_colors, p_width, false); +} + +void CanvasItem::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::_draw_circle_bind_compat_84472); + ClassDB::bind_compatibility_method(D_METHOD("draw_rect", "rect", "color", "filled", "width"), &CanvasItem::_draw_rect_bind_compat_84523, DEFVAL(true), DEFVAL(-1.0)); + ClassDB::bind_compatibility_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash", "aligned"), &CanvasItem::_draw_dashed_line_bind_compat_84523, DEFVAL(-1.0), DEFVAL(2.0), DEFVAL(true)); + ClassDB::bind_compatibility_method(D_METHOD("draw_multiline", "points", "color", "width"), &CanvasItem::_draw_multiline_bind_compat_84523, DEFVAL(-1.0)); + ClassDB::bind_compatibility_method(D_METHOD("draw_multiline_colors", "points", "colors", "width"), &CanvasItem::_draw_multiline_colors_bind_compat_84523, DEFVAL(-1.0)); +} + +#endif diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 2d0da075ba..7ed1f130c8 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "canvas_item.h" +#include "canvas_item.compat.inc" #include "scene/2d/canvas_group.h" #include "scene/main/canvas_layer.h" @@ -38,7 +39,6 @@ #include "scene/resources/multimesh.h" #include "scene/resources/style_box.h" #include "scene/resources/world_2d.h" -#include "scene/scene_string_names.h" #define ERR_DRAW_GUARD \ ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside this node's `_draw()`, functions connected to its `draw` signal, or when it receives NOTIFICATION_DRAW.") @@ -94,7 +94,7 @@ void CanvasItem::_handle_visibility_change(bool p_visible) { if (p_visible) { queue_redraw(); } else { - emit_signal(SceneStringNames::get_singleton()->hidden); + emit_signal(SceneStringName(hidden)); } _block(); @@ -140,7 +140,7 @@ void CanvasItem::_redraw_callback() { drawing = true; current_item_drawn = this; notification(NOTIFICATION_DRAW); - emit_signal(SceneStringNames::get_singleton()->draw); + emit_signal(SceneStringName(draw)); GDVIRTUAL_CALL(_draw); current_item_drawn = nullptr; drawing = false; @@ -308,7 +308,7 @@ void CanvasItem::_notification(int p_what) { window = Object::cast_to<Window>(viewport); if (window) { - window->connect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed)); + window->connect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItem::_window_visibility_changed)); parent_visible_in_tree = window->is_visible(); } else { parent_visible_in_tree = true; @@ -336,6 +336,19 @@ void CanvasItem::_notification(int p_what) { get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::canvas_parent_mark_dirty).bind(get_parent()), CONNECT_REFERENCE_COUNTED); } + // If using physics interpolation, reset for this node only, + // as a helper, as in most cases, users will want items reset when + // adding to the tree. + // In cases where they move immediately after adding, + // there will be little cost in having two resets as these are cheap, + // and it is worth it for convenience. + // Do not propagate to children, as each child of an added branch + // receives its own NOTIFICATION_ENTER_TREE, and this would + // cause unnecessary duplicate resets. + if (is_physics_interpolated_and_enabled()) { + notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + } + } break; case NOTIFICATION_EXIT_TREE: { ERR_MAIN_THREAD_GUARD; @@ -349,7 +362,7 @@ void CanvasItem::_notification(int p_what) { C = nullptr; } if (window) { - window->disconnect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed)); + window->disconnect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItem::_window_visibility_changed)); window = nullptr; } _set_global_invalid(true); @@ -360,10 +373,16 @@ void CanvasItem::_notification(int p_what) { } } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_visible_in_tree() && is_physics_interpolated()) { + RenderingServer::get_singleton()->canvas_item_reset_physics_interpolation(canvas_item); + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { ERR_MAIN_THREAD_GUARD; - emit_signal(SceneStringNames::get_singleton()->visibility_changed); + emit_signal(SceneStringName(visibility_changed)); } break; case NOTIFICATION_WORLD_2D_CHANGED: { ERR_MAIN_THREAD_GUARD; @@ -535,7 +554,7 @@ void CanvasItem::item_rect_changed(bool p_size_changed) { if (p_size_changed) { queue_redraw(); } - emit_signal(SceneStringNames::get_singleton()->item_rect_changed); + emit_signal(SceneStringName(item_rect_changed)); } void CanvasItem::set_z_index(int p_z) { @@ -589,7 +608,7 @@ bool CanvasItem::is_y_sort_enabled() const { return y_sort_enabled; } -void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned) { +void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned, bool p_antialiased) { ERR_THREAD_GUARD; ERR_DRAW_GUARD; ERR_FAIL_COND(p_dash <= 0.0); @@ -598,7 +617,7 @@ void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, cons Vector2 step = p_dash * (p_to - p_from).normalized(); if (length < p_dash || step == Vector2()) { - RenderingServer::get_singleton()->canvas_item_add_line(canvas_item, p_from, p_to, p_color, p_width); + RenderingServer::get_singleton()->canvas_item_add_line(canvas_item, p_from, p_to, p_color, p_width, p_antialiased); return; } @@ -622,7 +641,7 @@ void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, cons Vector<Color> colors = { p_color }; - RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, points, colors, p_width); + RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, points, colors, p_width, p_antialiased); } void CanvasItem::draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, bool p_antialiased) { @@ -663,22 +682,22 @@ void CanvasItem::draw_arc(const Vector2 &p_center, real_t p_radius, real_t p_sta draw_polyline(points, p_color, p_width, p_antialiased); } -void CanvasItem::draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width) { +void CanvasItem::draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width, bool p_antialiased) { ERR_THREAD_GUARD; ERR_DRAW_GUARD; Vector<Color> colors = { p_color }; - RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, colors, p_width); + RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, colors, p_width, p_antialiased); } -void CanvasItem::draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width) { +void CanvasItem::draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width, bool p_antialiased) { ERR_THREAD_GUARD; ERR_DRAW_GUARD; - RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, p_colors, p_width); + RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, p_colors, p_width, p_antialiased); } -void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width) { +void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width, bool p_antialiased) { ERR_THREAD_GUARD; ERR_DRAW_GUARD; @@ -689,9 +708,9 @@ void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_fil WARN_PRINT("The draw_rect() \"width\" argument has no effect when \"filled\" is \"true\"."); } - RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect, p_color); + RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect, p_color, p_antialiased); } else if (p_width >= rect.size.width || p_width >= rect.size.height) { - RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect.grow(0.5f * p_width), p_color); + RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect.grow(0.5f * p_width), p_color, p_antialiased); } else { Vector<Vector2> points; points.resize(5); @@ -703,15 +722,44 @@ void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_fil Vector<Color> colors = { p_color }; - RenderingServer::get_singleton()->canvas_item_add_polyline(canvas_item, points, colors, p_width); + RenderingServer::get_singleton()->canvas_item_add_polyline(canvas_item, points, colors, p_width, p_antialiased); } } -void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color) { +void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color, bool p_filled, real_t p_width, bool p_antialiased) { ERR_THREAD_GUARD; ERR_DRAW_GUARD; - RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color); + if (p_filled) { + if (p_width != -1.0) { + WARN_PRINT("The draw_circle() \"width\" argument has no effect when \"filled\" is \"true\"."); + } + + RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color, p_antialiased); + } else if (p_width >= 2.0 * p_radius) { + RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius + 0.5 * p_width, p_color, p_antialiased); + } else { + // Tessellation count is hardcoded. Keep in sync with the same variable in `RendererCanvasCull::canvas_item_add_circle()`. + const int circle_segments = 64; + + Vector<Vector2> points; + points.resize(circle_segments + 1); + + Vector2 *points_ptr = points.ptrw(); + const real_t circle_point_step = Math_TAU / circle_segments; + + for (int i = 0; i < circle_segments; i++) { + float angle = i * circle_point_step; + points_ptr[i].x = Math::cos(angle) * p_radius; + points_ptr[i].y = Math::sin(angle) * p_radius; + points_ptr[i] += p_pos; + } + points_ptr[circle_segments] = points_ptr[0]; + + Vector<Color> colors = { p_color }; + + RenderingServer::get_singleton()->canvas_item_add_polyline(canvas_item, points, colors, p_width, p_antialiased); + } } void CanvasItem::draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate) { @@ -921,6 +969,10 @@ void CanvasItem::_notify_transform(CanvasItem *p_node) { } } +void CanvasItem::_physics_interpolated_changed() { + RenderingServer::get_singleton()->canvas_item_set_interpolated(canvas_item, is_physics_interpolated()); +} + Rect2 CanvasItem::get_viewport_rect() const { ERR_READ_THREAD_GUARD_V(Rect2()); ERR_FAIL_COND_V(!is_inside_tree(), Rect2()); @@ -1133,14 +1185,14 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("is_draw_behind_parent_enabled"), &CanvasItem::is_draw_behind_parent_enabled); ClassDB::bind_method(D_METHOD("draw_line", "from", "to", "color", "width", "antialiased"), &CanvasItem::draw_line, DEFVAL(-1.0), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash", "aligned"), &CanvasItem::draw_dashed_line, DEFVAL(-1.0), DEFVAL(2.0), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash", "aligned", "antialiased"), &CanvasItem::draw_dashed_line, DEFVAL(-1.0), DEFVAL(2.0), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_polyline", "points", "color", "width", "antialiased"), &CanvasItem::draw_polyline, DEFVAL(-1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_polyline_colors", "points", "colors", "width", "antialiased"), &CanvasItem::draw_polyline_colors, DEFVAL(-1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_arc", "center", "radius", "start_angle", "end_angle", "point_count", "color", "width", "antialiased"), &CanvasItem::draw_arc, DEFVAL(-1.0), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("draw_multiline", "points", "color", "width"), &CanvasItem::draw_multiline, DEFVAL(-1.0)); - ClassDB::bind_method(D_METHOD("draw_multiline_colors", "points", "colors", "width"), &CanvasItem::draw_multiline_colors, DEFVAL(-1.0)); - ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled", "width"), &CanvasItem::draw_rect, DEFVAL(true), DEFVAL(-1.0)); - ClassDB::bind_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::draw_circle); + ClassDB::bind_method(D_METHOD("draw_multiline", "points", "color", "width", "antialiased"), &CanvasItem::draw_multiline, DEFVAL(-1.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("draw_multiline_colors", "points", "colors", "width", "antialiased"), &CanvasItem::draw_multiline_colors, DEFVAL(-1.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled", "width", "antialiased"), &CanvasItem::draw_rect, DEFVAL(true), DEFVAL(-1.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("draw_circle", "position", "radius", "color", "filled", "width", "antialiased"), &CanvasItem::draw_circle, DEFVAL(true), DEFVAL(-1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_texture", "texture", "position", "modulate"), &CanvasItem::draw_texture, DEFVAL(Color(1, 1, 1, 1))); ClassDB::bind_method(D_METHOD("draw_texture_rect", "texture", "rect", "tile", "modulate", "transpose"), &CanvasItem::draw_texture_rect, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_texture_rect_region", "texture", "rect", "src_rect", "modulate", "transpose", "clip_uv"), &CanvasItem::draw_texture_rect_region, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false), DEFVAL(true)); @@ -1378,14 +1430,17 @@ void CanvasItem::_refresh_texture_filter_cache() const { } } +void CanvasItem::_update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter) { + RS::get_singleton()->canvas_item_set_default_texture_filter(get_canvas_item(), p_texture_filter); + queue_redraw(); +} + void CanvasItem::_update_texture_filter_changed(bool p_propagate) { if (!is_inside_tree()) { return; } _refresh_texture_filter_cache(); - - RS::get_singleton()->canvas_item_set_default_texture_filter(get_canvas_item(), texture_filter_cache); - queue_redraw(); + _update_self_texture_filter(texture_filter_cache); if (p_propagate) { for (CanvasItem *E : children_items) { @@ -1429,14 +1484,18 @@ void CanvasItem::_refresh_texture_repeat_cache() const { } } +void CanvasItem::_update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat) { + RS::get_singleton()->canvas_item_set_default_texture_repeat(get_canvas_item(), p_texture_repeat); + queue_redraw(); +} + void CanvasItem::_update_texture_repeat_changed(bool p_propagate) { if (!is_inside_tree()) { return; } _refresh_texture_repeat_cache(); + _update_self_texture_repeat(texture_repeat_cache); - RS::get_singleton()->canvas_item_set_default_texture_repeat(get_canvas_item(), texture_repeat_cache); - queue_redraw(); if (p_propagate) { for (CanvasItem *E : children_items) { if (!E->top_level && E->texture_repeat == TEXTURE_REPEAT_PARENT_NODE) { diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index 03b01f7ef7..028c2cb2cf 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -140,6 +140,8 @@ private: void _notify_transform(CanvasItem *p_node); + virtual void _physics_interpolated_changed() override; + static CanvasItem *current_item_drawn; friend class Viewport; void _refresh_texture_repeat_cache() const; @@ -150,6 +152,9 @@ private: void _notify_transform_deferred(); protected: + virtual void _update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat); + virtual void _update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter); + _FORCE_INLINE_ void _notify_transform() { _notify_transform(this); if (is_inside_tree() && !block_transform_notify && notify_local_transform) { @@ -161,6 +166,16 @@ protected: void _notification(int p_what); static void _bind_methods(); + +#ifndef DISABLE_DEPRECATED + void _draw_circle_bind_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color); + void _draw_rect_bind_compat_84523(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width); + void _draw_dashed_line_bind_compat_84523(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned); + void _draw_multiline_bind_compat_84523(const Vector<Point2> &p_points, const Color &p_color, real_t p_width); + void _draw_multiline_colors_bind_compat_84523(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width); + static void _bind_compatibility_methods(); +#endif + void _validate_property(PropertyInfo &p_property) const; _FORCE_INLINE_ void set_hide_clip_children(bool p_value) { hide_clip_children = p_value; } @@ -260,15 +275,15 @@ public: /* DRAWING API */ - void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, real_t p_dash = 2.0, bool p_aligned = true); + void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, real_t p_dash = 2.0, bool p_aligned = true, bool p_antialiased = false); void draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false); void draw_polyline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false); void draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false); void draw_arc(const Vector2 &p_center, real_t p_radius, real_t p_start_angle, real_t p_end_angle, int p_point_count, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false); - void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0); - void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0); - void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0); - void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color); + void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false); + void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false); + void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0, bool p_antialiased = false); + void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color, bool p_filled = true, real_t p_width = -1.0, bool p_antialiased = false); void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1)); void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false); void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false); diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index c9cca76dea..11d074f014 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -52,7 +52,7 @@ void CanvasLayer::set_visible(bool p_visible) { } visible = p_visible; - emit_signal(SNAME("visibility_changed")); + emit_signal(SceneStringName(visibility_changed)); for (int i = 0; i < get_child_count(); i++) { CanvasItem *c = Object::cast_to<CanvasItem>(get_child(i)); diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 0f89784369..3469b806a6 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -239,7 +239,7 @@ bool HTTPRequest::_handle_response(bool *ret_value) { String new_request; for (const String &E : rheaders) { - if (E.findn("Location: ") != -1) { + if (E.containsn("Location: ")) { new_request = E.substr(9, E.length()).strip_edges(); } } diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index fe23ca1800..f36bbe9395 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -88,16 +88,16 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene if (!ps.is_valid()) { return nullptr; } - Node *scene = ps->instantiate(); - if (!scene) { + Node *instance = ps->instantiate(); + if (!instance) { return nullptr; } - scene->set_name(get_name()); - scene->set_multiplayer_authority(get_multiplayer_authority()); + instance->set_name(get_name()); + instance->set_multiplayer_authority(get_multiplayer_authority()); int pos = get_index(); for (const PropSet &E : stored_values) { - scene->set(E.name, E.value); + set_value_on_instance(this, instance, E); } if (p_replace) { @@ -105,10 +105,125 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene base->remove_child(this); } - base->add_child(scene); - base->move_child(scene, pos); + base->add_child(instance); + base->move_child(instance, pos); - return scene; + return instance; +} + +// This method will attempt to set the correct values on the placeholder instance +// for regular types this is trivial and unnecessary. +// For nodes however this becomes a bit tricky because they might now have existed until the instantiation, +// so this method will try to find the correct nodes and resolve them. +void InstancePlaceholder::set_value_on_instance(InstancePlaceholder *p_placeholder, Node *p_instance, const PropSet &p_set) { + bool is_valid; + + // If we don't have any info, we can't do anything, + // so try setting the value directly. + Variant current = p_instance->get(p_set.name, &is_valid); + if (!is_valid) { + p_instance->set(p_set.name, p_set.value, &is_valid); + return; + } + + Variant::Type current_type = current.get_type(); + Variant::Type placeholder_type = p_set.value.get_type(); + + // Arrays are a special case, because their containing type might be different. + if (current_type != Variant::Type::ARRAY) { + // Check if the variant types match. + if (Variant::evaluate(Variant::OP_EQUAL, current_type, placeholder_type)) { + p_instance->set(p_set.name, p_set.value, &is_valid); + if (is_valid) { + return; + } + // Types match but setting failed? This is strange, so let's print a warning! + WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name())); + return; + } + } else { + // We are dealing with an Array. + // Let's check if the subtype of the array matches first. + // This is needed because the set method of ScriptInstance checks for type, + // but the ClassDB set method doesn't! So we cannot reliably know what actually happens. + Array current_array = current; + Array placeholder_array = p_set.value; + if (current_array.is_same_typed(placeholder_array)) { + p_instance->set(p_set.name, p_set.value, &is_valid); + if (is_valid) { + return; + } + // Internal array types match but setting failed? This is strange, so let's print a warning! + WARN_PRINT(vformat("Array Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(Variant::Type(current_array.get_typed_builtin())), p_placeholder->get_name())); + } + // Arrays are not the same internal type. This should be happening because we have a NodePath Array, + // but the instance wants a Node Array. + } + + switch (current_type) { + case Variant::Type::NIL: + if (placeholder_type != Variant::Type::NODE_PATH) { + break; + } + // If it's nil but we have a NodePath, we guess what works. + + p_instance->set(p_set.name, p_set.value, &is_valid); + if (is_valid) { + break; + } + + p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value), &is_valid); + break; + case Variant::Type::OBJECT: + if (placeholder_type != Variant::Type::NODE_PATH) { + break; + } + // Easiest case, we want a node, but we have a deferred NodePath. + p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value)); + break; + case Variant::Type::ARRAY: { + // If we have reached here it means our array types don't match, + // so we will convert the placeholder array into the correct type + // and resolve nodes if necessary. + Array current_array = current; + Array converted_array; + Array placeholder_array = p_set.value; + converted_array = current_array.duplicate(); + converted_array.resize(placeholder_array.size()); + + if (Variant::evaluate(Variant::OP_EQUAL, current_array.get_typed_builtin(), Variant::Type::NODE_PATH)) { + // We want a typed NodePath array. + for (int i = 0; i < placeholder_array.size(); i++) { + converted_array.set(i, placeholder_array[i]); + } + } else { + // We want Nodes, convert NodePaths. + for (int i = 0; i < placeholder_array.size(); i++) { + converted_array.set(i, try_get_node(p_placeholder, p_instance, placeholder_array[i])); + } + } + + p_instance->set(p_set.name, converted_array, &is_valid); + if (!is_valid) { + WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name())); + } + break; + } + default: + WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name())); + break; + } +} + +Node *InstancePlaceholder::try_get_node(InstancePlaceholder *p_placeholder, Node *p_instance, const NodePath &p_path) { + // First try to resolve internally, + // if that fails try resolving externally. + Node *node = p_instance->get_node_or_null(p_path); + if (node == nullptr) { + node = p_placeholder->get_node_or_null(p_path); + } + + return node; } Dictionary InstancePlaceholder::get_stored_values(bool p_with_order) { diff --git a/scene/main/instance_placeholder.h b/scene/main/instance_placeholder.h index 480474d0bd..ccf1e63a16 100644 --- a/scene/main/instance_placeholder.h +++ b/scene/main/instance_placeholder.h @@ -46,6 +46,10 @@ class InstancePlaceholder : public Node { List<PropSet> stored_values; +private: + void set_value_on_instance(InstancePlaceholder *p_placeholder, Node *p_instance, const PropSet &p_set); + Node *try_get_node(InstancePlaceholder *p_placeholder, Node *p_instance, const NodePath &p_path); + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; diff --git a/scene/main/multiplayer_api.cpp b/scene/main/multiplayer_api.cpp index 35e4302bb7..bd097ec2d0 100644 --- a/scene/main/multiplayer_api.cpp +++ b/scene/main/multiplayer_api.cpp @@ -266,10 +266,6 @@ Error MultiplayerAPI::decode_and_decompress_variants(Vector<Variant> &r_variants return OK; } - Vector<Variant> args; - Vector<const Variant *> argp; - args.resize(argc); - for (int i = 0; i < argc; i++) { ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small."); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 906c397b5c..884fc6de07 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -31,7 +31,6 @@ #include "node.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "core/io/resource_loader.h" #include "core/object/message_queue.h" #include "core/object/script_language.h" @@ -42,7 +41,6 @@ #include "scene/main/multiplayer_api.h" #include "scene/main/window.h" #include "scene/resources/packed_scene.h" -#include "scene/scene_string_names.h" #include "viewport.h" #include <stdint.h> @@ -99,6 +97,14 @@ void Node::_notification(int p_notification) { } } + if (data.physics_interpolation_mode == PHYSICS_INTERPOLATION_MODE_INHERIT) { + bool interpolate = true; // Root node default is for interpolation to be on. + if (data.parent) { + interpolate = data.parent->is_physics_interpolated(); + } + _propagate_physics_interpolated(interpolate); + } + // Update auto translate mode. if (data.auto_translate_mode == AUTO_TRANSLATE_MODE_INHERIT && !data.parent) { ERR_PRINT("The root node can't be set to Inherit auto translate mode, reverting to Always instead."); @@ -258,7 +264,7 @@ void Node::_propagate_ready() { if (data.ready_first) { data.ready_first = false; notification(NOTIFICATION_READY); - emit_signal(SceneStringNames::get_singleton()->ready); + emit_signal(SceneStringName(ready)); } } @@ -287,7 +293,7 @@ void Node::_propagate_enter_tree() { GDVIRTUAL_CALL(_enter_tree); - emit_signal(SceneStringNames::get_singleton()->tree_entered); + emit_signal(SceneStringName(tree_entered)); data.tree->node_added(this); @@ -342,7 +348,7 @@ void Node::_propagate_after_exit_tree() { data.blocked--; - emit_signal(SceneStringNames::get_singleton()->tree_exited); + emit_signal(SceneStringName(tree_exited)); } void Node::_propagate_exit_tree() { @@ -364,7 +370,7 @@ void Node::_propagate_exit_tree() { GDVIRTUAL_CALL(_exit_tree); - emit_signal(SceneStringNames::get_singleton()->tree_exiting); + emit_signal(SceneStringName(tree_exiting)); notification(NOTIFICATION_EXIT_TREE, true); if (data.tree) { @@ -395,6 +401,36 @@ void Node::_propagate_exit_tree() { data.depth = -1; } +void Node::_propagate_physics_interpolated(bool p_interpolated) { + switch (data.physics_interpolation_mode) { + case PHYSICS_INTERPOLATION_MODE_INHERIT: + // Keep the parent p_interpolated. + break; + case PHYSICS_INTERPOLATION_MODE_OFF: { + p_interpolated = false; + } break; + case PHYSICS_INTERPOLATION_MODE_ON: { + p_interpolated = true; + } break; + } + + // No change? No need to propagate further. + if (data.physics_interpolated == p_interpolated) { + return; + } + + data.physics_interpolated = p_interpolated; + + // Allow a call to the RenderingServer etc. in derived classes. + _physics_interpolated_changed(); + + data.blocked++; + for (KeyValue<StringName, Node *> &K : data.children) { + K.value->_propagate_physics_interpolated(p_interpolated); + } + data.blocked--; +} + void Node::move_child(Node *p_child, int p_index) { ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index)."); ERR_FAIL_NULL(p_child); @@ -507,6 +543,8 @@ void Node::move_child_notify(Node *p_child) { void Node::owner_changed_notify() { } +void Node::_physics_interpolated_changed() {} + void Node::set_physics_process(bool p_process) { ERR_THREAD_GUARD if (data.physics_process == p_process) { @@ -821,6 +859,42 @@ bool Node::_can_process(bool p_paused) const { } } +void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) { + ERR_THREAD_GUARD + if (data.physics_interpolation_mode == p_mode) { + return; + } + + data.physics_interpolation_mode = p_mode; + + bool interpolate = true; // Default for root node. + + switch (p_mode) { + case PHYSICS_INTERPOLATION_MODE_INHERIT: { + if (is_inside_tree() && data.parent) { + interpolate = data.parent->is_physics_interpolated(); + } + } break; + case PHYSICS_INTERPOLATION_MODE_OFF: { + interpolate = false; + } break; + case PHYSICS_INTERPOLATION_MODE_ON: { + interpolate = true; + } break; + } + + // If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset. + if (is_physics_interpolated() && !interpolate) { + reset_physics_interpolation(); + } + + _propagate_physics_interpolated(interpolate); +} + +void Node::reset_physics_interpolation() { + propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); +} + bool Node::_is_enabled() const { ProcessMode process_mode; @@ -1662,12 +1736,10 @@ Node *Node::get_node_or_null(const NodePath &p_path) const { StringName name = p_path.get_name(i); Node *next = nullptr; - if (name == SceneStringNames::get_singleton()->dot) { // . - + if (name == SNAME(".")) { next = current; - } else if (name == SceneStringNames::get_singleton()->doubledot) { // .. - + } else if (name == SNAME("..")) { if (current == nullptr || !current->data.parent) { return nullptr; } @@ -1679,23 +1751,14 @@ Node *Node::get_node_or_null(const NodePath &p_path) const { } } else if (name.is_node_unique_name()) { - if (current->data.owned_unique_nodes.size()) { - // Has unique nodes in ownership - Node **unique = current->data.owned_unique_nodes.getptr(name); - if (!unique) { - return nullptr; - } - next = *unique; - } else if (current->data.owner) { - Node **unique = current->data.owner->data.owned_unique_nodes.getptr(name); - if (!unique) { - return nullptr; - } - next = *unique; - } else { + Node **unique = current->data.owned_unique_nodes.getptr(name); + if (!unique && current->data.owner) { + unique = current->data.owner->data.owned_unique_nodes.getptr(name); + } + if (!unique) { return nullptr; } - + next = *unique; } else { next = nullptr; const Node *const *node = current->data.children.getptr(name); @@ -1823,7 +1886,7 @@ void Node::reparent(Node *p_parent, bool p_keep_global_transform) { Node *check = to_visit[to_visit.size() - 1]; to_visit.resize(to_visit.size() - 1); - for (int i = 0; i < check->get_child_count(); i++) { + for (int i = 0; i < check->get_child_count(false); i++) { Node *child = check->get_child(i, false); to_visit.push_back(child); if (child->data.owner == owner_temp) { @@ -2600,8 +2663,6 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c node->data.editable_instance = data.editable_instance; } - StringName script_property_name = CoreStringNames::get_singleton()->_script; - List<const Node *> hidden_roots; List<const Node *> node_tree; node_tree.push_front(this); @@ -2703,63 +2764,6 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c parent->move_child(dup, pos); } } - - for (List<const Node *>::Element *N = node_tree.front(); N; N = N->next()) { - Node *current_node = node->get_node(get_path_to(N->get())); - ERR_CONTINUE(!current_node); - - if (p_flags & DUPLICATE_SCRIPTS) { - bool is_valid = false; - Variant scr = N->get()->get(script_property_name, &is_valid); - if (is_valid) { - current_node->set(script_property_name, scr); - } - } - - List<PropertyInfo> plist; - N->get()->get_property_list(&plist); - - for (const PropertyInfo &E : plist) { - if (!(E.usage & PROPERTY_USAGE_STORAGE)) { - continue; - } - String name = E.name; - if (name == script_property_name) { - continue; - } - - Variant value = N->get()->get(name).duplicate(true); - - if (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE) { - Resource *res = Object::cast_to<Resource>(value); - if (res) { // Duplicate only if it's a resource - current_node->set(name, res->duplicate()); - } - - } else { - // If property points to a node which is owned by a node we are duplicating, update its path. - if (value.get_type() == Variant::OBJECT) { - Node *property_node = Object::cast_to<Node>(value); - if (property_node && is_ancestor_of(property_node)) { - value = current_node->get_node_or_null(get_path_to(property_node)); - } - } else if (value.get_type() == Variant::ARRAY) { - Array arr = value; - if (arr.get_typed_builtin() == Variant::OBJECT) { - for (int i = 0; i < arr.size(); i++) { - Node *property_node = Object::cast_to<Node>(arr[i]); - if (property_node && is_ancestor_of(property_node)) { - arr[i] = current_node->get_node_or_null(get_path_to(property_node)); - } - } - value = arr; - } - } - current_node->set(name, value); - } - } - } - return node; } @@ -2771,6 +2775,8 @@ Node *Node::duplicate(int p_flags) const { _duplicate_signals(this, dupe); } + _duplicate_properties(this, this, dupe, p_flags); + return dupe; } @@ -2780,7 +2786,8 @@ Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) con } Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const { - Node *dupe = _duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANTIATION | DUPLICATE_FROM_EDITOR, &r_duplimap); + int flags = DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANTIATION | DUPLICATE_FROM_EDITOR; + Node *dupe = _duplicate(flags, &r_duplimap); // This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated. if (!p_resource_remap.is_empty()) { @@ -2792,6 +2799,8 @@ Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, con // if the emitter node comes later in tree order than the receiver _duplicate_signals(this, dupe); + _duplicate_properties(this, this, dupe, flags); + return dupe; } @@ -2844,6 +2853,70 @@ void Node::remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Re } #endif +// Duplicate node's properties. +// This has to be called after nodes have been duplicated since there might be properties +// of type Node that can be updated properly only if duplicated node tree is complete. +void Node::_duplicate_properties(const Node *p_root, const Node *p_original, Node *p_copy, int p_flags) const { + List<PropertyInfo> props; + p_original->get_property_list(&props); + const StringName &script_property_name = CoreStringName(script); + if (p_flags & DUPLICATE_SCRIPTS) { + bool is_valid = false; + Variant scr = p_original->get(script_property_name, &is_valid); + if (is_valid) { + p_copy->set(script_property_name, scr); + } + } + for (const PropertyInfo &E : props) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + const StringName name = E.name; + + if (name == script_property_name) { + continue; + } + + Variant value = p_original->get(name).duplicate(true); + + if (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE) { + Resource *res = Object::cast_to<Resource>(value); + if (res) { // Duplicate only if it's a resource + p_copy->set(name, res->duplicate()); + } + } else { + if (value.get_type() == Variant::OBJECT) { + Node *property_node = Object::cast_to<Node>(value); + Variant out_value = value; + if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) { + out_value = p_copy->get_node_or_null(p_original->get_path_to(property_node)); + } + p_copy->set(name, out_value); + } else if (value.get_type() == Variant::ARRAY) { + Array arr = value; + if (arr.get_typed_builtin() == Variant::OBJECT) { + for (int i = 0; i < arr.size(); i++) { + Node *property_node = Object::cast_to<Node>(arr[i]); + if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) { + arr[i] = p_copy->get_node_or_null(p_original->get_path_to(property_node)); + } + } + value = arr; + p_copy->set(name, value); + } + } else { + p_copy->set(name, value); + } + } + } + + for (int i = 0; i < p_original->get_child_count(); i++) { + Node *copy_child = p_copy->get_child(i); + ERR_FAIL_NULL_MSG(copy_child, "Child node disappeared while duplicating."); + _duplicate_properties(p_root, p_original->get_child(i), copy_child, p_flags); + } +} + // Duplication of signals must happen after all the node descendants have been copied, // because re-targeting of connections from some descendant to another is not possible // if the emitter node comes later in tree order than the receiver @@ -3140,7 +3213,7 @@ void Node::print_orphan_nodes() { ObjectDB::debug_objects(_print_orphan_nodes_routine); for (const KeyValue<ObjectID, List<String>> &E : _print_orphan_nodes_map) { - print_line(itos(E.key) + " - Stray Node: " + E.value[0] + " (Type: " + E.value[1] + ")"); + print_line(itos(E.key) + " - Stray Node: " + E.value.get(0) + " (Type: " + E.value.get(1) + ")"); } // Flush it after use. @@ -3230,7 +3303,7 @@ void Node::update_configuration_warnings() { return; } if (get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root() == this || get_tree()->get_edited_scene_root()->is_ancestor_of(this))) { - get_tree()->emit_signal(SceneStringNames::get_singleton()->node_configuration_warning_changed, this); + get_tree()->emit_signal(SceneStringName(node_configuration_warning_changed), this); } #endif } @@ -3429,6 +3502,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource); ClassDB::bind_method(D_METHOD("is_inside_tree"), &Node::is_inside_tree); + ClassDB::bind_method(D_METHOD("is_part_of_edited_scene"), &Node::is_part_of_edited_scene); ClassDB::bind_method(D_METHOD("is_ancestor_of", "node"), &Node::is_ancestor_of); ClassDB::bind_method(D_METHOD("is_greater_than", "node"), &Node::is_greater_than); ClassDB::bind_method(D_METHOD("get_path"), &Node::get_path); @@ -3489,6 +3563,12 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_physics_process_internal", "enable"), &Node::set_physics_process_internal); ClassDB::bind_method(D_METHOD("is_physics_processing_internal"), &Node::is_physics_processing_internal); + ClassDB::bind_method(D_METHOD("set_physics_interpolation_mode", "mode"), &Node::set_physics_interpolation_mode); + ClassDB::bind_method(D_METHOD("get_physics_interpolation_mode"), &Node::get_physics_interpolation_mode); + ClassDB::bind_method(D_METHOD("is_physics_interpolated"), &Node::is_physics_interpolated); + ClassDB::bind_method(D_METHOD("is_physics_interpolated_and_enabled"), &Node::is_physics_interpolated_and_enabled); + ClassDB::bind_method(D_METHOD("reset_physics_interpolation"), &Node::reset_physics_interpolation); + ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "mode"), &Node::set_auto_translate_mode); ClassDB::bind_method(D_METHOD("get_auto_translate_mode"), &Node::get_auto_translate_mode); @@ -3594,6 +3674,7 @@ void Node::_bind_methods() { BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE); BIND_CONSTANT(NOTIFICATION_DISABLED); BIND_CONSTANT(NOTIFICATION_ENABLED); + BIND_CONSTANT(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); BIND_CONSTANT(NOTIFICATION_EDITOR_PRE_SAVE); BIND_CONSTANT(NOTIFICATION_EDITOR_POST_SAVE); @@ -3633,6 +3714,10 @@ void Node::_bind_methods() { BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_PHYSICS); BIND_BITFIELD_FLAG(FLAG_PROCESS_THREAD_MESSAGES_ALL); + BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_INHERIT); + BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_ON); + BIND_ENUM_CONSTANT(PHYSICS_INTERPOLATION_MODE_OFF); + BIND_ENUM_CONSTANT(DUPLICATE_SIGNALS); BIND_ENUM_CONSTANT(DUPLICATE_GROUPS); BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS); @@ -3674,6 +3759,9 @@ void Node::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_group_order"), "set_process_thread_group_order", "get_process_thread_group_order"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_thread_messages", PROPERTY_HINT_FLAGS, "Process,Physics Process"), "set_process_thread_messages", "get_process_thread_messages"); + ADD_GROUP("Physics Interpolation", "physics_interpolation_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_mode", PROPERTY_HINT_ENUM, "Inherit,On,Off"), "set_physics_interpolation_mode", "get_physics_interpolation_mode"); + ADD_GROUP("Auto Translate", "auto_translate_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "auto_translate_mode", PROPERTY_HINT_ENUM, "Inherit,Always,Disabled"), "set_auto_translate_mode", "get_auto_translate_mode"); @@ -3708,6 +3796,35 @@ String Node::_get_name_num_separator() { Node::Node() { orphan_node_count++; + + // Default member initializer for bitfield is a C++20 extension, so: + + data.process_mode = PROCESS_MODE_INHERIT; + data.physics_interpolation_mode = PHYSICS_INTERPOLATION_MODE_INHERIT; + + data.physics_process = false; + data.process = false; + + data.physics_process_internal = false; + data.process_internal = false; + + data.input = false; + data.shortcut_input = false; + data.unhandled_input = false; + data.unhandled_key_input = false; + + data.physics_interpolated = true; + + data.parent_owned = false; + data.in_constructor = true; + data.use_placeholder = false; + + data.display_folded = false; + data.editable_instance = false; + + data.inside_tree = false; + data.ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification. + data.ready_first = true; } Node::~Node() { diff --git a/scene/main/node.h b/scene/main/node.h index 57a7278e73..6b93724478 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -35,6 +35,7 @@ #include "core/templates/rb_map.h" #include "core/variant/typed_array.h" #include "scene/main/scene_tree.h" +#include "scene/scene_string_names.h" class Viewport; class Window; @@ -66,7 +67,9 @@ protected: }; public: - enum ProcessMode { + // N.B. Any enum stored as a bitfield should be specified as UNSIGNED to work around + // some compilers trying to store it as signed, and requiring 1 more bit than necessary. + enum ProcessMode : unsigned int { PROCESS_MODE_INHERIT, // same as parent node PROCESS_MODE_PAUSABLE, // process only if not paused PROCESS_MODE_WHEN_PAUSED, // process only if paused @@ -86,6 +89,12 @@ public: FLAG_PROCESS_THREAD_MESSAGES_ALL = 3, }; + enum PhysicsInterpolationMode : unsigned int { + PHYSICS_INTERPOLATION_MODE_INHERIT, + PHYSICS_INTERPOLATION_MODE_ON, + PHYSICS_INTERPOLATION_MODE_OFF, + }; + enum DuplicateFlags { DUPLICATE_SIGNALS = 1, DUPLICATE_GROUPS = 2, @@ -170,9 +179,7 @@ private: int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. StringName name; SceneTree *tree = nullptr; - bool inside_tree = false; - bool ready_notified = false; // This is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification. - bool ready_first = true; + #ifdef TOOLS_ENABLED NodePath import_path; // Path used when imported, used by scene editors to keep tracking. #endif @@ -184,7 +191,6 @@ private: List<Node *>::Element *OW = nullptr; // Owned element. List<Node *> owned; - ProcessMode process_mode = PROCESS_MODE_INHERIT; Node *process_owner = nullptr; ProcessThreadGroup process_thread_group = PROCESS_THREAD_GROUP_INHERIT; Node *process_thread_group_owner = nullptr; @@ -196,26 +202,39 @@ private: Variant rpc_config; // Variables used to properly sort the node when processing, ignored otherwise. - // TODO: Should move all the stuff below to bits. - bool physics_process = false; - bool process = false; int process_priority = 0; int physics_process_priority = 0; - bool physics_process_internal = false; - bool process_internal = false; + // Keep bitpacked values together to get better packing. + ProcessMode process_mode : 3; + PhysicsInterpolationMode physics_interpolation_mode : 2; + + bool physics_process : 1; + bool process : 1; + + bool physics_process_internal : 1; + bool process_internal : 1; + + bool input : 1; + bool shortcut_input : 1; + bool unhandled_input : 1; + bool unhandled_key_input : 1; - bool input = false; - bool shortcut_input = false; - bool unhandled_input = false; - bool unhandled_key_input = false; + // Physics interpolation can be turned on and off on a per node basis. + // This only takes effect when the SceneTree (or project setting) physics interpolation + // is switched on. + bool physics_interpolated : 1; - bool parent_owned = false; - bool in_constructor = true; - bool use_placeholder = false; + bool parent_owned : 1; + bool in_constructor : 1; + bool use_placeholder : 1; - bool display_folded = false; - bool editable_instance = false; + bool display_folded : 1; + bool editable_instance : 1; + + bool inside_tree : 1; + bool ready_notified : 1; + bool ready_first : 1; AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT; mutable bool is_auto_translating = true; @@ -243,10 +262,12 @@ private: void _propagate_ready(); void _propagate_exit_tree(); void _propagate_after_exit_tree(); + void _propagate_physics_interpolated(bool p_interpolated); void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification); void _propagate_groups_dirty(); Array _get_node_and_resource(const NodePath &p_path); + void _duplicate_properties(const Node *p_root, const Node *p_original, Node *p_copy, int p_flags) const; void _duplicate_signals(const Node *p_original, Node *p_copy) const; Node *_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap = nullptr) const; @@ -295,6 +316,8 @@ protected: void _notification(int p_notification); + virtual void _physics_interpolated_changed(); + virtual void add_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child); virtual void move_child_notify(Node *p_child); @@ -339,7 +362,7 @@ protected: public: enum { - // you can make your own, but don't use the same numbers as other notifications in other nodes + // You can make your own, but don't use the same numbers as other notifications in other nodes. NOTIFICATION_ENTER_TREE = 10, NOTIFICATION_EXIT_TREE = 11, NOTIFICATION_MOVED_IN_PARENT = 12, @@ -360,8 +383,8 @@ public: NOTIFICATION_POST_ENTER_TREE = 27, NOTIFICATION_DISABLED = 28, NOTIFICATION_ENABLED = 29, - //keep these linked to node - + NOTIFICATION_RESET_PHYSICS_INTERPOLATION = 2001, // A GodotSpace Odyssey. + // Keep these linked to Node. NOTIFICATION_WM_MOUSE_ENTER = 1002, NOTIFICATION_WM_MOUSE_EXIT = 1003, NOTIFICATION_WM_WINDOW_FOCUS_IN = 1004, @@ -503,6 +526,8 @@ public: bool is_property_pinned(const StringName &p_property) const; virtual StringName get_property_store_alias(const StringName &p_property) const; bool is_part_of_edited_scene() const; +#else + bool is_part_of_edited_scene() const { return false; } #endif void get_storable_properties(HashSet<StringName> &r_storable_properties) const; @@ -613,6 +638,13 @@ public: ProcessMode get_process_mode() const; bool can_process() const; bool can_process_notification(int p_what) const; + + void set_physics_interpolation_mode(PhysicsInterpolationMode p_mode); + PhysicsInterpolationMode get_physics_interpolation_mode() const { return data.physics_interpolation_mode; } + _FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; } + _FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); } + void reset_physics_interpolation(); + bool is_enabled() const; bool is_ready() const; @@ -742,6 +774,7 @@ VARIANT_ENUM_CAST(Node::ProcessMode); VARIANT_ENUM_CAST(Node::ProcessThreadGroup); VARIANT_BITFIELD_CAST(Node::ProcessThreadMessages); VARIANT_ENUM_CAST(Node::InternalMode); +VARIANT_ENUM_CAST(Node::PhysicsInterpolationMode); VARIANT_ENUM_CAST(Node::AutoTranslateMode); typedef HashSet<Node *, Node::Comparator> NodeSet; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index cb16f425b5..ced6d9aaa6 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -55,7 +55,6 @@ #include "scene/resources/mesh.h" #include "scene/resources/packed_scene.h" #include "scene/resources/world_2d.h" -#include "scene/scene_string_names.h" #include "servers/display_server.h" #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" @@ -120,7 +119,6 @@ void SceneTreeTimer::release_connections() { SceneTreeTimer::SceneTreeTimer() {} void SceneTree::tree_changed() { - tree_version++; emit_signal(tree_changed_name); } @@ -153,7 +151,6 @@ SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_nod ERR_FAIL_COND_V_MSG(E->value.nodes.has(p_node), &E->value, "Already in group: " + p_group + "."); E->value.nodes.push_back(p_node); - //E->value.last_tree_version=0; E->value.changed = true; return &E->value; } @@ -451,9 +448,31 @@ void SceneTree::initialize() { root->_set_tree(this); } -bool SceneTree::physics_process(double p_time) { - root_lock++; +void SceneTree::set_physics_interpolation_enabled(bool p_enabled) { + // We never want interpolation in the editor. + if (Engine::get_singleton()->is_editor_hint()) { + p_enabled = false; + } + + if (p_enabled == _physics_interpolation_enabled) { + return; + } + _physics_interpolation_enabled = p_enabled; + RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled); +} + +bool SceneTree::is_physics_interpolation_enabled() const { + return _physics_interpolation_enabled; +} + +void SceneTree::iteration_prepare() { + if (_physics_interpolation_enabled) { + RenderingServer::get_singleton()->tick(); + } +} + +bool SceneTree::physics_process(double p_time) { current_frame++; flush_transform_notifications(); @@ -477,7 +496,6 @@ bool SceneTree::physics_process(double p_time) { process_tweens(p_time, true); flush_transform_notifications(); - root_lock--; _flush_delete_queue(); _call_idle_callbacks(); @@ -486,8 +504,6 @@ bool SceneTree::physics_process(double p_time) { } bool SceneTree::process(double p_time) { - root_lock++; - if (MainLoop::process(p_time)) { _quit = true; } @@ -513,8 +529,6 @@ bool SceneTree::process(double p_time) { MessageQueue::get_singleton()->flush(); //small little hack flush_transform_notifications(); //transforms after world update, to avoid unnecessary enter/exit notifications - root_lock--; - _flush_delete_queue(); if (unlikely(pending_new_scene)) { @@ -714,13 +728,6 @@ void SceneTree::set_quit_on_go_back(bool p_enable) { quit_on_go_back = p_enable; } -#ifdef TOOLS_ENABLED - -bool SceneTree::is_node_being_edited(const Node *p_node) const { - return Engine::get_singleton()->is_editor_hint() && edited_scene_root && (edited_scene_root->is_ancestor_of(p_node) || edited_scene_root == p_node); -} -#endif - #ifdef DEBUG_ENABLED void SceneTree::set_debug_collisions_hint(bool p_enabled) { debug_collisions_hint = p_enabled; @@ -1606,6 +1613,9 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_frame"), &SceneTree::get_frame); ClassDB::bind_method(D_METHOD("quit", "exit_code"), &SceneTree::quit, DEFVAL(EXIT_SUCCESS)); + ClassDB::bind_method(D_METHOD("set_physics_interpolation_enabled", "enabled"), &SceneTree::set_physics_interpolation_enabled); + ClassDB::bind_method(D_METHOD("is_physics_interpolation_enabled"), &SceneTree::is_physics_interpolation_enabled); + ClassDB::bind_method(D_METHOD("queue_delete", "obj"), &SceneTree::queue_delete); MethodInfo mi; @@ -1657,6 +1667,7 @@ void SceneTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_current_scene", "get_current_scene"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "", "get_root"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled"); ADD_SIGNAL(MethodInfo("tree_changed")); ADD_SIGNAL(MethodInfo("tree_process_mode_changed")); //editor only signal, but due to API hash it can't be removed in run-time @@ -1733,7 +1744,7 @@ SceneTree::SceneTree() { root = memnew(Window); root->set_min_size(Size2i(64, 64)); // Define a very small minimum window size to prevent bugs such as GH-37242. root->set_process_mode(Node::PROCESS_MODE_PAUSABLE); - root->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_ALWAYS); + root->set_auto_translate_mode(GLOBAL_GET("internationalization/rendering/root_node_auto_translate") ? Node::AUTO_TRANSLATE_MODE_ALWAYS : Node::AUTO_TRANSLATE_MODE_DISABLED); root->set_name("root"); root->set_title(GLOBAL_GET("application/config/name")); @@ -1748,6 +1759,8 @@ SceneTree::SceneTree() { root->set_as_audio_listener_3d(true); #endif // _3D_DISABLED + set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false)); + // Initialize network state. set_multiplayer(MultiplayerAPI::create_default_interface()); @@ -1862,7 +1875,7 @@ SceneTree::SceneTree() { root->connect("close_requested", callable_mp(this, &SceneTree::_main_window_close)); root->connect("go_back_requested", callable_mp(this, &SceneTree::_main_window_go_back)); - root->connect("focus_entered", callable_mp(this, &SceneTree::_main_window_focus_in)); + root->connect(SceneStringName(focus_entered), callable_mp(this, &SceneTree::_main_window_focus_in)); #ifdef TOOLS_ENABLED edited_scene_root = nullptr; diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index a7515f0243..6f0a61ec51 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -122,7 +122,6 @@ private: Window *root = nullptr; - uint64_t tree_version = 1; double physics_process_time = 0.0; double process_time = 0.0; bool accept_quit = true; @@ -134,11 +133,12 @@ private: bool debug_navigation_hint = false; #endif bool paused = false; - int root_lock = 0; HashMap<StringName, Group> group_map; bool _quit = false; + bool _physics_interpolation_enabled = false; + StringName tree_changed_name = "tree_changed"; StringName node_added_name = "node_added"; StringName node_removed_name = "node_removed"; @@ -163,7 +163,6 @@ private: // Safety for when a node is deleted while a group is being called. - bool processing = false; int nodes_removed_on_group_call_lock = 0; HashSet<Node *> nodes_removed_on_group_call; // Skip erased nodes. @@ -313,6 +312,8 @@ public: virtual void initialize() override; + virtual void iteration_prepare() override; + virtual bool physics_process(double p_time) override; virtual bool process(double p_time) override; @@ -329,12 +330,6 @@ public: _FORCE_INLINE_ double get_physics_process_time() const { return physics_process_time; } _FORCE_INLINE_ double get_process_time() const { return process_time; } -#ifdef TOOLS_ENABLED - bool is_node_being_edited(const Node *p_node) const; -#else - bool is_node_being_edited(const Node *p_node) const { return false; } -#endif - void set_pause(bool p_enabled); bool is_paused() const; @@ -425,6 +420,9 @@ public: void set_disable_node_threading(bool p_disable); //default texture settings + void set_physics_interpolation_enabled(bool p_enabled); + bool is_physics_interpolation_enabled() const; + SceneTree(); ~SceneTree(); }; diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp index 091e6249bf..41e0aa739e 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -31,7 +31,6 @@ #include "shader_globals_override.h" #include "scene/main/node.h" -#include "scene/scene_string_names.h" StringName *ShaderGlobalsOverride::_remap(const StringName &p_name) const { StringName *r = param_remaps.getptr(p_name); @@ -223,11 +222,11 @@ 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); + get_tree()->get_nodes_in_group(SceneStringName(shader_overrides_group_active), &nodes); if (nodes.size() == 0) { //good we are the only override, enable all active = true; - add_to_group(SceneStringNames::get_singleton()->shader_overrides_group_active); + add_to_group(SceneStringName(shader_overrides_group_active)); for (const KeyValue<StringName, Override> &E : overrides) { const Override *o = &E.value; @@ -248,7 +247,7 @@ void ShaderGlobalsOverride::_activate() { void ShaderGlobalsOverride::_notification(int p_what) { switch (p_what) { case Node::NOTIFICATION_ENTER_TREE: { - add_to_group(SceneStringNames::get_singleton()->shader_overrides_group); + add_to_group(SceneStringName(shader_overrides_group)); _activate(); } break; @@ -263,9 +262,9 @@ void ShaderGlobalsOverride::_notification(int p_what) { } } - remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group_active); - remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group); - get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->shader_overrides_group, "_activate"); //another may want to activate when this is removed + remove_from_group(SceneStringName(shader_overrides_group_active)); + remove_from_group(SceneStringName(shader_overrides_group)); + get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(shader_overrides_group), "_activate"); //another may want to activate when this is removed active = false; } break; } diff --git a/scene/main/status_indicator.cpp b/scene/main/status_indicator.cpp index ae58bc0b18..f0ba5db1a0 100644 --- a/scene/main/status_indicator.cpp +++ b/scene/main/status_indicator.cpp @@ -30,6 +30,8 @@ #include "status_indicator.h" +#include "scene/gui/popup_menu.h" + void StatusIndicator::_notification(int p_what) { ERR_MAIN_THREAD_GUARD; #ifdef TOOLS_ENABLED @@ -43,12 +45,22 @@ void StatusIndicator::_notification(int p_what) { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) { if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) { iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback)); + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + RID menu_rid = pm->bind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid); + } } } } break; case NOTIFICATION_EXIT_TREE: { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) { if (iid != DisplayServer::INVALID_INDICATOR_ID) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + pm->unbind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID()); + } DisplayServer::get_singleton()->delete_status_indicator(iid); iid = DisplayServer::INVALID_INDICATOR_ID; } @@ -66,19 +78,23 @@ void StatusIndicator::_bind_methods() { ClassDB::bind_method(D_METHOD("get_icon"), &StatusIndicator::get_icon); ClassDB::bind_method(D_METHOD("set_visible", "visible"), &StatusIndicator::set_visible); ClassDB::bind_method(D_METHOD("is_visible"), &StatusIndicator::is_visible); + ClassDB::bind_method(D_METHOD("set_menu", "menu"), &StatusIndicator::set_menu); + ClassDB::bind_method(D_METHOD("get_menu"), &StatusIndicator::get_menu); + ClassDB::bind_method(D_METHOD("get_rect"), &StatusIndicator::get_rect); - ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "position"))); + ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "mouse_position"))); ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "get_tooltip"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_icon", "get_icon"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_icon", "get_icon"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "menu", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PopupMenu"), "set_menu", "get_menu"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); } void StatusIndicator::_callback(MouseButton p_index, const Point2i &p_pos) { - emit_signal(SNAME("pressed"), p_index, p_pos); + emit_signal(SceneStringName(pressed), p_index, p_pos); } -void StatusIndicator::set_icon(const Ref<Image> &p_icon) { +void StatusIndicator::set_icon(const Ref<Texture2D> &p_icon) { ERR_MAIN_THREAD_GUARD; icon = p_icon; if (iid != DisplayServer::INVALID_INDICATOR_ID) { @@ -86,7 +102,7 @@ void StatusIndicator::set_icon(const Ref<Image> &p_icon) { } } -Ref<Image> StatusIndicator::get_icon() const { +Ref<Texture2D> StatusIndicator::get_icon() const { return icon; } @@ -102,6 +118,30 @@ String StatusIndicator::get_tooltip() const { return tooltip; } +void StatusIndicator::set_menu(const NodePath &p_menu) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + pm->unbind_global_menu(); + if (iid != DisplayServer::INVALID_INDICATOR_ID) { + DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID()); + } + } + + menu = p_menu; + + pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + if (iid != DisplayServer::INVALID_INDICATOR_ID) { + RID menu_rid = pm->bind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid); + } + } +} + +NodePath StatusIndicator::get_menu() const { + return menu; +} + void StatusIndicator::set_visible(bool p_visible) { ERR_MAIN_THREAD_GUARD; if (visible == p_visible) { @@ -122,8 +162,18 @@ void StatusIndicator::set_visible(bool p_visible) { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) { if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) { iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback)); + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + RID menu_rid = pm->bind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid); + } } if (!visible && iid != DisplayServer::INVALID_INDICATOR_ID) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu)); + if (pm) { + pm->unbind_global_menu(); + DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID()); + } DisplayServer::get_singleton()->delete_status_indicator(iid); iid = DisplayServer::INVALID_INDICATOR_ID; } @@ -133,3 +183,10 @@ void StatusIndicator::set_visible(bool p_visible) { bool StatusIndicator::is_visible() const { return visible; } + +Rect2 StatusIndicator::get_rect() const { + if (iid == DisplayServer::INVALID_INDICATOR_ID) { + return Rect2(); + } + return DisplayServer::get_singleton()->status_indicator_get_rect(iid); +} diff --git a/scene/main/status_indicator.h b/scene/main/status_indicator.h index aa3aa68d78..cd38da6e6c 100644 --- a/scene/main/status_indicator.h +++ b/scene/main/status_indicator.h @@ -37,10 +37,11 @@ class StatusIndicator : public Node { GDCLASS(StatusIndicator, Node); - Ref<Image> icon; + Ref<Texture2D> icon; String tooltip; bool visible = true; DisplayServer::IndicatorID iid = DisplayServer::INVALID_INDICATOR_ID; + NodePath menu; protected: void _notification(int p_what); @@ -49,14 +50,19 @@ protected: void _callback(MouseButton p_index, const Point2i &p_pos); public: - void set_icon(const Ref<Image> &p_icon); - Ref<Image> get_icon() const; + void set_icon(const Ref<Texture2D> &p_icon); + Ref<Texture2D> get_icon() const; void set_tooltip(const String &p_tooltip); String get_tooltip() const; + void set_menu(const NodePath &p_menu); + NodePath get_menu() const; + void set_visible(bool p_visible); bool is_visible() const; + + Rect2 get_rect() const; }; #endif // STATUS_INDICATOR_H diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index c8d2d71c2a..26128a08ab 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -54,7 +54,6 @@ #include "scene/resources/mesh.h" #include "scene/resources/text_line.h" #include "scene/resources/world_2d.h" -#include "scene/scene_string_names.h" #include "servers/audio_server.h" #include "servers/rendering/rendering_server_globals.h" @@ -81,7 +80,7 @@ void ViewportTexture::setup_local_to_scene() { if (loc_scene->is_ready()) { _setup_local_to_scene(loc_scene); } else { - loc_scene->connect(SNAME("ready"), callable_mp(this, &ViewportTexture::_setup_local_to_scene).bind(loc_scene), CONNECT_ONE_SHOT); + loc_scene->connect(SceneStringName(ready), callable_mp(this, &ViewportTexture::_setup_local_to_scene).bind(loc_scene), CONNECT_ONE_SHOT); vp_pending = true; } } @@ -688,6 +687,18 @@ void Viewport::_process_picking() { physics_picking_events.clear(); return; } +#ifndef _3D_DISABLED + if (use_xr) { + if (XRServer::get_singleton() != nullptr) { + Ref<XRInterface> xr_interface = XRServer::get_singleton()->get_primary_interface(); + if (xr_interface.is_valid() && xr_interface->is_initialized() && xr_interface->get_view_count() > 1) { + WARN_PRINT_ONCE("Object picking can't be used when stereo rendering, this will be turned off!"); + physics_object_picking = false; // don't try again. + return; + } + } + } +#endif _drop_physics_mouseover(true); @@ -856,9 +867,10 @@ void Viewport::_process_picking() { if (send_event) { co->_input_event_call(this, ev, res[i].shape); - if (physics_object_picking_first_only) { - break; - } + } + + if (physics_object_picking_first_only) { + break; } } } @@ -970,7 +982,7 @@ void Viewport::_set_size(const Size2i &p_size, const Size2i &p_size_2d_override, stretch_transform_new.scale(scale); } - Size2i new_size = p_size.max(Size2i(2, 2)); + Size2i new_size = p_size.maxi(2); if (size == new_size && size_allocated == p_allocated && stretch_transform == stretch_transform_new && p_size_2d_override == size_2d_override) { return; } @@ -1452,7 +1464,7 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_label->set_theme_type_variation(SNAME("TooltipLabel")); gui.tooltip_label->set_text(gui.tooltip_text); base_tooltip = gui.tooltip_label; - panel->connect("mouse_entered", callable_mp(this, &Viewport::_gui_cancel_tooltip)); + panel->connect(SceneStringName(mouse_entered), callable_mp(this, &Viewport::_gui_cancel_tooltip)); } base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); @@ -1461,6 +1473,8 @@ void Viewport::_gui_show_tooltip() { panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_flag(Window::FLAG_POPUP, false); panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true); + // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport. + panel->set_flag(Window::FLAG_TRANSPARENT, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); panel->gui_parent = this; @@ -1469,17 +1483,25 @@ void Viewport::_gui_show_tooltip() { tooltip_owner->add_child(gui.tooltip_popup); + Window *window = Object::cast_to<Window>(gui.tooltip_popup->get_embedder()); + if (!window) { // Not embedded. + window = gui.tooltip_popup->get_parent_visible_window(); + } + float win_scale = window->content_scale_factor; Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset"); + if (!gui.tooltip_popup->is_embedded()) { + tooltip_offset *= win_scale; + } Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size()); - r.size = r.size.min(panel->get_max_size()); - - Window *window = gui.tooltip_popup->get_parent_visible_window(); Rect2i vr; if (gui.tooltip_popup->is_embedded()) { vr = gui.tooltip_popup->get_embedder()->get_visible_rect(); } else { + panel->content_scale_factor = win_scale; + r.size *= win_scale; vr = window->get_usable_parent_rect(); } + r.size = r.size.min(panel->get_max_size()); if (r.size.x + r.position.x > vr.size.x + vr.position.x) { // Place it in the opposite direction. If it fails, just hug the border. @@ -1705,12 +1727,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Point2 mpos = mb->get_position(); if (mb->is_pressed()) { - if (!gui.mouse_focus_mask.is_empty()) { - // Do not steal mouse focus and stuff while a focus mask exists. - gui.mouse_focus_mask.set_flag(mouse_button_to_mask(mb->get_button_index())); + MouseButtonMask button_mask = mouse_button_to_mask(mb->get_button_index()); + if (!gui.mouse_focus_mask.is_empty() && !gui.mouse_focus_mask.has_flag(button_mask)) { + // Do not steal mouse focus and stuff while a focus mask without the current mouse button exists. + gui.mouse_focus_mask.set_flag(button_mask); } else { gui.mouse_focus = gui_find_control(mpos); - gui.last_mouse_focus = gui.mouse_focus; if (!gui.mouse_focus) { return; @@ -2295,6 +2317,7 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control * gui.dragging = true; gui.drag_data = p_data; gui.mouse_focus = nullptr; + gui.mouse_focus_mask.clear(); if (p_control) { _gui_set_drag_preview(p_base, p_control); @@ -2350,7 +2373,7 @@ void Viewport::_gui_hide_control(Control *p_control) { if (gui.key_focus == p_control) { gui_release_focus(); } - if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) { _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { @@ -2367,13 +2390,10 @@ void Viewport::_gui_remove_control(Control *p_control) { gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); } - if (gui.last_mouse_focus == p_control) { - gui.last_mouse_focus = nullptr; - } if (gui.key_focus == p_control) { gui.key_focus = nullptr; } - if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) { _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { @@ -2747,8 +2767,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { Size2i min_size = gui.currently_dragged_subwindow->get_min_size(); Size2i min_size_clamped = gui.currently_dragged_subwindow->get_clamped_minimum_size(); - min_size_clamped.x = MAX(min_size_clamped.x, 1); - min_size_clamped.y = MAX(min_size_clamped.y, 1); + min_size_clamped = min_size_clamped.maxi(1); Rect2i r = gui.subwindow_resize_from_rect; @@ -2809,8 +2828,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { Size2i max_size = gui.currently_dragged_subwindow->get_max_size(); if ((max_size.x > 0 || max_size.y > 0) && (max_size.x >= min_size.x && max_size.y >= min_size.y)) { - max_size.x = MAX(max_size.x, 1); - max_size.y = MAX(max_size.y, 1); + max_size = max_size.maxi(1); if (r.size.x > max_size.x) { r.size.x = max_size.x; @@ -3569,6 +3587,13 @@ bool Viewport::gui_is_drag_successful() const { return gui.drag_successful; } +void Viewport::gui_cancel_drag() { + ERR_MAIN_THREAD_GUARD; + if (gui_is_dragging()) { + _perform_drop(); + } +} + void Viewport::set_input_as_handled() { ERR_MAIN_THREAD_GUARD; if (!handle_input_locally) { @@ -3709,6 +3734,28 @@ Viewport::VRSMode Viewport::get_vrs_mode() const { return vrs_mode; } +void Viewport::set_vrs_update_mode(VRSUpdateMode p_vrs_update_mode) { + ERR_MAIN_THREAD_GUARD; + + vrs_update_mode = p_vrs_update_mode; + switch (p_vrs_update_mode) { + case VRS_UPDATE_ONCE: { + RS::get_singleton()->viewport_set_vrs_update_mode(viewport, RS::VIEWPORT_VRS_UPDATE_ONCE); + } break; + case VRS_UPDATE_ALWAYS: { + RS::get_singleton()->viewport_set_vrs_update_mode(viewport, RS::VIEWPORT_VRS_UPDATE_ALWAYS); + } break; + default: { + RS::get_singleton()->viewport_set_vrs_update_mode(viewport, RS::VIEWPORT_VRS_UPDATE_DISABLED); + } break; + } +} + +Viewport::VRSUpdateMode Viewport::get_vrs_update_mode() const { + ERR_READ_THREAD_GUARD_V(VRS_UPDATE_DISABLED); + return vrs_update_mode; +} + void Viewport::set_vrs_texture(Ref<Texture2D> p_texture) { ERR_MAIN_THREAD_GUARD; vrs_texture = p_texture; @@ -4488,6 +4535,10 @@ void Viewport::set_use_xr(bool p_use_xr) { } else { RS::get_singleton()->viewport_set_size(viewport, 0, 0); } + + // Reset render target override textures. + RID rt = RS::get_singleton()->viewport_get_render_target(viewport); + RSG::texture_storage->render_target_set_override(rt, RID(), RID(), RID()); } } } @@ -4745,6 +4796,9 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vrs_mode", "mode"), &Viewport::set_vrs_mode); ClassDB::bind_method(D_METHOD("get_vrs_mode"), &Viewport::get_vrs_mode); + ClassDB::bind_method(D_METHOD("set_vrs_update_mode", "mode"), &Viewport::set_vrs_update_mode); + ClassDB::bind_method(D_METHOD("get_vrs_update_mode"), &Viewport::get_vrs_update_mode); + ClassDB::bind_method(D_METHOD("set_vrs_texture", "texture"), &Viewport::set_vrs_texture); ClassDB::bind_method(D_METHOD("get_vrs_texture"), &Viewport::get_vrs_texture); @@ -4777,6 +4831,7 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fsr_sharpness", PROPERTY_HINT_RANGE, "0,2,0.1"), "set_fsr_sharpness", "get_fsr_sharpness"); ADD_GROUP("Variable Rate Shading", "vrs_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vrs_mode", PROPERTY_HINT_ENUM, "Disabled,Texture,Depth buffer,XR"), "set_vrs_mode", "get_vrs_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vrs_update_mode", PROPERTY_HINT_ENUM, "Disabled,Once,Always"), "set_vrs_update_mode", "get_vrs_update_mode"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "vrs_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_vrs_texture", "get_vrs_texture"); #endif ADD_GROUP("Canvas Items", "canvas_item_"); @@ -4843,6 +4898,7 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_VISIBLE); BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_SHADOW); + BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_CANVAS); BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_MAX); BIND_ENUM_CONSTANT(DEBUG_DRAW_DISABLED); @@ -4899,12 +4955,21 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(VRS_TEXTURE); BIND_ENUM_CONSTANT(VRS_XR); BIND_ENUM_CONSTANT(VRS_MAX); + + BIND_ENUM_CONSTANT(VRS_UPDATE_DISABLED); + BIND_ENUM_CONSTANT(VRS_UPDATE_ONCE); + BIND_ENUM_CONSTANT(VRS_UPDATE_ALWAYS); + BIND_ENUM_CONSTANT(VRS_UPDATE_MAX); } void Viewport::_validate_property(PropertyInfo &p_property) const { if (vrs_mode != VRS_TEXTURE && (p_property.name == "vrs_texture")) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } + + if (vrs_mode == VRS_DISABLED && (p_property.name == "vrs_update_mode")) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } Viewport::Viewport() { @@ -4940,7 +5005,7 @@ Viewport::Viewport() { unhandled_key_input_group = "_vp_unhandled_key_input" + id; // Window tooltip. - gui.tooltip_delay = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5); + gui.tooltip_delay = GLOBAL_GET("gui/timers/tooltip_delay_sec"); #ifndef _3D_DISABLED set_scaling_3d_mode((Viewport::Scaling3DMode)(int)GLOBAL_GET("rendering/scaling_3d/mode")); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 29ccdc5426..2474b890b0 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -138,6 +138,7 @@ public: enum RenderInfoType { RENDER_INFO_TYPE_VISIBLE, RENDER_INFO_TYPE_SHADOW, + RENDER_INFO_TYPE_CANVAS, RENDER_INFO_TYPE_MAX }; @@ -212,6 +213,13 @@ public: VRS_MAX }; + enum VRSUpdateMode { + VRS_UPDATE_DISABLED, + VRS_UPDATE_ONCE, + VRS_UPDATE_ALWAYS, + VRS_UPDATE_MAX + }; + private: friend class ViewportTexture; @@ -336,6 +344,7 @@ private: // VRS VRSMode vrs_mode = VRS_DISABLED; + VRSUpdateMode vrs_update_mode = VRS_UPDATE_ONCE; Ref<Texture2D> vrs_texture; struct GUI { @@ -344,7 +353,6 @@ private: bool key_event_accepted = false; HashMap<int, ObjectID> touch_focus; Control *mouse_focus = nullptr; - Control *last_mouse_focus = nullptr; Control *mouse_click_grabber = nullptr; BitField<MouseButtonMask> mouse_focus_mask; Control *key_focus = nullptr; @@ -615,6 +623,7 @@ public: bool gui_is_dragging() const; bool gui_is_drag_successful() const; + void gui_cancel_drag(); Control *gui_find_control(const Point2 &p_global); @@ -635,6 +644,9 @@ public: void set_vrs_mode(VRSMode p_vrs_mode); VRSMode get_vrs_mode() const; + void set_vrs_update_mode(VRSUpdateMode p_vrs_update_mode); + VRSUpdateMode get_vrs_update_mode() const; + void set_vrs_texture(Ref<Texture2D> p_texture); Ref<Texture2D> get_vrs_texture() const; @@ -843,6 +855,7 @@ VARIANT_ENUM_CAST(Viewport::DebugDraw); VARIANT_ENUM_CAST(Viewport::SDFScale); VARIANT_ENUM_CAST(Viewport::SDFOversize); VARIANT_ENUM_CAST(Viewport::VRSMode); +VARIANT_ENUM_CAST(Viewport::VRSUpdateMode); VARIANT_ENUM_CAST(SubViewport::ClearMode); VARIANT_ENUM_CAST(Viewport::RenderInfo); VARIANT_ENUM_CAST(Viewport::RenderInfoType); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 6632dd9033..addbd6078a 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -37,7 +37,6 @@ #include "core/string/translation.h" #include "core/variant/variant_parser.h" #include "scene/gui/control.h" -#include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" #include "scene/theme/theme_owner.h" @@ -307,10 +306,21 @@ String Window::get_title() const { return title; } +void Window::_settings_changed() { + if (visible && initial_position != WINDOW_INITIAL_POSITION_ABSOLUTE && is_in_edited_scene_root()) { + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + position = (screen_size - size) / 2; + if (embedder) { + embedder->_sub_window_update(this); + } + } +} + void Window::set_initial_position(Window::WindowInitialPosition p_initial_position) { ERR_MAIN_THREAD_GUARD; initial_position = p_initial_position; + _settings_changed(); notify_property_list_changed(); } @@ -415,7 +425,7 @@ Size2i Window::_clamp_limit_size(const Size2i &p_limit_size) { if (max_window_size != Size2i()) { return p_limit_size.clamp(Vector2i(), max_window_size); } else { - return p_limit_size.max(Vector2i()); + return p_limit_size.maxi(0); } } @@ -724,6 +734,9 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { if (!is_inside_tree()) { return; } + // Ensure keeping the order of input events and window events when input events are buffered or accumulated. + Input::get_singleton()->flush_buffered_events(); + Window *root = get_tree()->get_root(); if (!root->gui.windowmanager_window_over) { #ifdef DEV_ENABLED @@ -739,13 +752,13 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { case DisplayServer::WINDOW_EVENT_FOCUS_IN: { focused = true; _propagate_window_notification(this, NOTIFICATION_WM_WINDOW_FOCUS_IN); - emit_signal(SNAME("focus_entered")); + emit_signal(SceneStringName(focus_entered)); } break; case DisplayServer::WINDOW_EVENT_FOCUS_OUT: { focused = false; _propagate_window_notification(this, NOTIFICATION_WM_WINDOW_FOCUS_OUT); - emit_signal(SNAME("focus_exited")); + emit_signal(SceneStringName(focus_exited)); } break; case DisplayServer::WINDOW_EVENT_CLOSE_REQUEST: { if (exclusive_child != nullptr) { @@ -827,7 +840,12 @@ void Window::set_visible(bool p_visible) { if (visible) { embedder = embedder_vp; if (initial_position != WINDOW_INITIAL_POSITION_ABSOLUTE) { - position = (embedder->get_visible_rect().size - size) / 2; + if (is_in_edited_scene_root()) { + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + position = (screen_size - size) / 2; + } else { + position = (embedder->get_visible_rect().size - size) / 2; + } } embedder->_sub_window_register(this); RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_PARENT_VISIBLE); @@ -843,7 +861,7 @@ void Window::set_visible(bool p_visible) { focused = false; } notification(NOTIFICATION_VISIBILITY_CHANGED); - emit_signal(SceneStringNames::get_singleton()->visibility_changed); + emit_signal(SceneStringName(visibility_changed)); RS::get_singleton()->viewport_set_active(get_viewport_rid(), visible); @@ -1033,8 +1051,7 @@ void Window::_update_window_size() { } if (embedder) { - size.x = MAX(size.x, 1); - size.y = MAX(size.y, 1); + size = size.maxi(1); embedder->_sub_window_update(this); } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { @@ -1213,10 +1230,10 @@ void Window::set_force_native(bool p_force_native) { if (force_native == p_force_native) { return; } - force_native = p_force_native; - if (is_visible()) { - WARN_PRINT("Can't change \"force_native\" while a window is displayed. Consider hiding window before changing this value."); + if (is_visible() && !is_in_edited_scene_root()) { + ERR_FAIL_MSG("Can't change \"force_native\" while a window is displayed. Consider hiding window before changing this value."); } + force_native = p_force_native; } bool Window::get_force_native() const { @@ -1225,7 +1242,7 @@ bool Window::get_force_native() const { Viewport *Window::get_embedder() const { ERR_READ_THREAD_GUARD_V(nullptr); - if (force_native && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SUBWINDOWS)) { + if (force_native && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SUBWINDOWS) && !is_in_edited_scene_root()) { return nullptr; } @@ -1264,6 +1281,12 @@ void Window::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { + if (is_in_edited_scene_root()) { + if (!ProjectSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &Window::_settings_changed))) { + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Window::_settings_changed)); + } + } + bool embedded = false; { embedder = get_embedder(); @@ -1279,7 +1302,12 @@ void Window::_notification(int p_what) { // Create as embedded. if (embedder) { if (initial_position != WINDOW_INITIAL_POSITION_ABSOLUTE) { - position = (embedder->get_visible_rect().size - size) / 2; + if (is_in_edited_scene_root()) { + Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + position = (screen_size - size) / 2; + } else { + position = (embedder->get_visible_rect().size - size) / 2; + } } embedder->_sub_window_register(this); RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_PARENT_VISIBLE); @@ -1319,7 +1347,7 @@ void Window::_notification(int p_what) { } if (visible) { notification(NOTIFICATION_VISIBILITY_CHANGED); - emit_signal(SceneStringNames::get_singleton()->visibility_changed); + emit_signal(SceneStringName(visibility_changed)); RS::get_singleton()->viewport_set_active(get_viewport_rid(), true); } @@ -1335,7 +1363,7 @@ void Window::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { - emit_signal(SceneStringNames::get_singleton()->theme_changed); + emit_signal(SceneStringName(theme_changed)); _invalidate_theme_cache(); _update_theme_item_cache(); } break; @@ -1376,6 +1404,10 @@ void Window::_notification(int p_what) { } break; case NOTIFICATION_EXIT_TREE: { + if (ProjectSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &Window::_settings_changed))) { + ProjectSettings::get_singleton()->disconnect("settings_changed", callable_mp(this, &Window::_settings_changed)); + } + set_theme_context(nullptr, false); if (transient) { @@ -1402,11 +1434,11 @@ void Window::_notification(int p_what) { } break; case NOTIFICATION_VP_MOUSE_ENTER: { - emit_signal(SceneStringNames::get_singleton()->mouse_entered); + emit_signal(SceneStringName(mouse_entered)); } break; case NOTIFICATION_VP_MOUSE_EXIT: { - emit_signal(SceneStringNames::get_singleton()->mouse_exited); + emit_signal(SceneStringName(mouse_exited)); } break; } } @@ -1545,8 +1577,7 @@ Size2 Window::_get_contents_minimum_size() const { Point2i pos = c->get_position(); Size2i min = c->get_combined_minimum_size(); - max.x = MAX(pos.x + min.x, max.x); - max.y = MAX(pos.y + min.y, max.y); + max = max.max(pos + min); } } @@ -1578,6 +1609,7 @@ bool Window::_can_consume_input_events() const { } void Window::_window_input(const Ref<InputEvent> &p_ev) { + ERR_MAIN_THREAD_GUARD; if (EngineDebugger::is_active()) { // Quit from game window using the stop shortcut (F8 by default). // The custom shortcut is provided via environment variable when running from the editor. @@ -1620,7 +1652,7 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) { _input_from_window(p_ev); if (p_ev->get_device() != InputEvent::DEVICE_ID_INTERNAL && is_inside_tree()) { - emit_signal(SceneStringNames::get_singleton()->window_input, p_ev); + emit_signal(SceneStringName(window_input), p_ev); } if (is_inside_tree()) { @@ -1703,7 +1735,7 @@ void Window::popup_centered_clamped(const Size2i &p_size, float p_fallback_ratio Vector2i size_ratio = parent_rect.size * p_fallback_ratio; Rect2i popup_rect; - popup_rect.size = Vector2i(MIN(size_ratio.x, expected_size.x), MIN(size_ratio.y, expected_size.y)); + popup_rect.size = size_ratio.min(expected_size); popup_rect.size = _clamp_window_size(popup_rect.size); if (parent_rect != Rect2()) { @@ -2719,9 +2751,6 @@ void Window::_update_mouse_over(Vector2 p_pos) { if (is_embedded()) { mouse_in_window = true; _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER); - } else { - // Prevent update based on delayed InputEvents from DisplayServer. - return; } } diff --git a/scene/main/window.h b/scene/main/window.h index ffcf50ccdd..33d593711f 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -214,6 +214,8 @@ private: int resize_margin = 0; } theme_cache; + void _settings_changed(); + Viewport *embedder = nullptr; Transform2D window_transform; diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp index d9a80011b0..152ecaf89d 100644 --- a/scene/property_list_helper.cpp +++ b/scene/property_list_helper.cpp @@ -31,53 +31,104 @@ #include "property_list_helper.h" const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const { - const Vector<String> components = p_property.split("/", true, 2); + const Vector<String> components = p_property.rsplit("/", true, 1); if (components.size() < 2 || !components[0].begins_with(prefix)) { return nullptr; } - { - const String index_string = components[0].trim_prefix(prefix); - if (!index_string.is_valid_int()) { - return nullptr; - } - *r_index = index_string.to_int(); + const String index_string = components[0].trim_prefix(prefix); + if (!index_string.is_valid_int()) { + return nullptr; } + int index = index_string.to_int(); + if (index < 0 || index >= _call_array_length_getter()) { + return nullptr; + } + + *r_index = index; return property_list.getptr(components[1]); } void PropertyListHelper::_call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const { + DEV_ASSERT(p_setter); Variant args[] = { p_index, p_value }; const Variant *argptrs[] = { &args[0], &args[1] }; Callable::CallError ce; p_setter->call(object, argptrs, 2, ce); } -Variant PropertyListHelper::_call_getter(const MethodBind *p_getter, int p_index) const { +Variant PropertyListHelper::_call_getter(const Property *p_property, int p_index) const { + if (!p_property->getter) { + return object->get(prefix + itos(p_index) + "/" + p_property->info.name); + } + Callable::CallError ce; Variant args[] = { p_index }; const Variant *argptrs[] = { &args[0] }; - return p_getter->call(object, argptrs, 1, ce); + return p_property->getter->call(object, argptrs, 1, ce); +} + +int PropertyListHelper::_call_array_length_getter() const { + Callable::CallError ce; + return array_length_getter->call(object, nullptr, 0, ce); } void PropertyListHelper::set_prefix(const String &p_prefix) { prefix = p_prefix; } +void PropertyListHelper::register_property(const PropertyInfo &p_info, const Variant &p_default) { + Property property; + property.info = p_info; + property.default_value = p_default; + + property_list[p_info.name] = property; +} + +bool PropertyListHelper::is_initialized() const { + return !property_list.is_empty(); +} + void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) { + DEV_ASSERT(!p_base.prefix.is_empty()); + DEV_ASSERT(p_base.array_length_getter != nullptr); + DEV_ASSERT(!p_base.property_list.is_empty()); + DEV_ASSERT(p_object != nullptr); + prefix = p_base.prefix; + array_length_getter = p_base.array_length_getter; property_list = p_base.property_list; object = p_object; } +bool PropertyListHelper::is_property_valid(const String &p_property, int *r_index) const { + const Vector<String> components = p_property.rsplit("/", true, 1); + if (components.size() < 2 || !components[0].begins_with(prefix)) { + return false; + } + + { + const String index_string = components[0].trim_prefix(prefix); + if (!index_string.is_valid_int()) { + return false; + } + + if (r_index) { + *r_index = index_string.to_int(); + } + } + + return property_list.has(components[1]); +} + void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_count) const { for (int i = 0; i < p_count; i++) { for (const KeyValue<String, Property> &E : property_list) { const Property &property = E.value; PropertyInfo info = property.info; - if (_call_getter(property.getter, i) == property.default_value) { + if (_call_getter(&property, i) == property.default_value) { info.usage &= (~PROPERTY_USAGE_STORAGE); } @@ -92,7 +143,7 @@ bool PropertyListHelper::property_get_value(const String &p_property, Variant &r const Property *property = _get_property(p_property, &index); if (property) { - r_ret = _call_getter(property->getter, index); + r_ret = _call_getter(property, index); return true; } return false; @@ -110,8 +161,7 @@ bool PropertyListHelper::property_set_value(const String &p_property, const Vari } bool PropertyListHelper::property_can_revert(const String &p_property) const { - int index; - return _get_property(p_property, &index) != nullptr; + return is_property_valid(p_property); } bool PropertyListHelper::property_get_revert(const String &p_property, Variant &r_value) const { @@ -129,8 +179,10 @@ PropertyListHelper::~PropertyListHelper() { // No object = it's the main helper. Do a cleanup. if (!object) { for (const KeyValue<String, Property> &E : property_list) { - memdelete(E.value.setter); - memdelete(E.value.getter); + if (E.value.setter) { + memdelete(E.value.setter); + memdelete(E.value.getter); + } } } } diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h index 6c1ad21a05..e19e7cd22e 100644 --- a/scene/property_list_helper.h +++ b/scene/property_list_helper.h @@ -43,15 +43,24 @@ class PropertyListHelper { }; String prefix; + MethodBind *array_length_getter = nullptr; HashMap<String, Property> property_list; Object *object = nullptr; const Property *_get_property(const String &p_property, int *r_index) const; void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const; - Variant _call_getter(const MethodBind *p_getter, int p_index) const; + Variant _call_getter(const Property *p_property, int p_index) const; + int _call_array_length_getter() const; public: void set_prefix(const String &p_prefix); + template <typename G> + void set_array_length_getter(G p_array_length_getter) { + array_length_getter = create_method_bind(p_array_length_getter); + } + + // Register property without setter/getter. Only use when you don't need PropertyListHelper for _set/_get logic. + void register_property(const PropertyInfo &p_info, const Variant &p_default); template <typename S, typename G> void register_property(const PropertyInfo &p_info, const Variant &p_default, S p_setter, G p_getter) { @@ -64,7 +73,9 @@ public: property_list[p_info.name] = property; } + bool is_initialized() const; void setup_for_instance(const PropertyListHelper &p_base, Object *p_object); + bool is_property_valid(const String &p_property, int *r_index = nullptr) const; void get_property_list(List<PropertyInfo> *p_list, int p_count) const; bool property_get_value(const String &p_property, Variant &r_ret) const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 3583dace2a..aa8ff75c6a 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -33,52 +33,6 @@ #include "core/config/project_settings.h" #include "core/object/class_db.h" #include "core/os/os.h" -#include "scene/2d/animated_sprite_2d.h" -#include "scene/2d/audio_listener_2d.h" -#include "scene/2d/audio_stream_player_2d.h" -#include "scene/2d/back_buffer_copy.h" -#include "scene/2d/camera_2d.h" -#include "scene/2d/canvas_group.h" -#include "scene/2d/canvas_modulate.h" -#include "scene/2d/cpu_particles_2d.h" -#include "scene/2d/gpu_particles_2d.h" -#include "scene/2d/light_2d.h" -#include "scene/2d/light_occluder_2d.h" -#include "scene/2d/line_2d.h" -#include "scene/2d/marker_2d.h" -#include "scene/2d/mesh_instance_2d.h" -#include "scene/2d/multimesh_instance_2d.h" -#include "scene/2d/navigation_agent_2d.h" -#include "scene/2d/navigation_link_2d.h" -#include "scene/2d/navigation_obstacle_2d.h" -#include "scene/2d/navigation_region_2d.h" -#include "scene/2d/parallax_2d.h" -#include "scene/2d/parallax_background.h" -#include "scene/2d/parallax_layer.h" -#include "scene/2d/path_2d.h" -#include "scene/2d/physics/animatable_body_2d.h" -#include "scene/2d/physics/area_2d.h" -#include "scene/2d/physics/character_body_2d.h" -#include "scene/2d/physics/collision_polygon_2d.h" -#include "scene/2d/physics/collision_shape_2d.h" -#include "scene/2d/physics/joints/damped_spring_joint_2d.h" -#include "scene/2d/physics/joints/groove_joint_2d.h" -#include "scene/2d/physics/joints/joint_2d.h" -#include "scene/2d/physics/joints/pin_joint_2d.h" -#include "scene/2d/physics/kinematic_collision_2d.h" -#include "scene/2d/physics/physical_bone_2d.h" -#include "scene/2d/physics/physics_body_2d.h" -#include "scene/2d/physics/ray_cast_2d.h" -#include "scene/2d/physics/rigid_body_2d.h" -#include "scene/2d/physics/shape_cast_2d.h" -#include "scene/2d/physics/static_body_2d.h" -#include "scene/2d/polygon_2d.h" -#include "scene/2d/remote_transform_2d.h" -#include "scene/2d/skeleton_2d.h" -#include "scene/2d/sprite_2d.h" -#include "scene/2d/tile_map.h" -#include "scene/2d/touch_screen_button.h" -#include "scene/2d/visible_on_screen_notifier_2d.h" #include "scene/animation/animation_blend_space_1d.h" #include "scene/animation/animation_blend_space_2d.h" #include "scene/animation/animation_blend_tree.h" @@ -103,6 +57,7 @@ #include "scene/gui/file_dialog.h" #include "scene/gui/flow_container.h" #include "scene/gui/graph_edit.h" +#include "scene/gui/graph_frame.h" #include "scene/gui/graph_node.h" #include "scene/gui/grid_container.h" #include "scene/gui/item_list.h" @@ -144,29 +99,11 @@ #include "scene/main/multiplayer_api.h" #include "scene/main/resource_preloader.h" #include "scene/main/scene_tree.h" +#include "scene/main/shader_globals_override.h" #include "scene/main/status_indicator.h" #include "scene/main/timer.h" #include "scene/main/viewport.h" #include "scene/main/window.h" -#include "scene/resources/2d/capsule_shape_2d.h" -#include "scene/resources/2d/circle_shape_2d.h" -#include "scene/resources/2d/concave_polygon_shape_2d.h" -#include "scene/resources/2d/convex_polygon_shape_2d.h" -#include "scene/resources/2d/polygon_path_finder.h" -#include "scene/resources/2d/rectangle_shape_2d.h" -#include "scene/resources/2d/segment_shape_2d.h" -#include "scene/resources/2d/separation_ray_shape_2d.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d_lookat.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.h" -#include "scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h" -#include "scene/resources/2d/skeleton/skeleton_modification_stack_2d.h" -#include "scene/resources/2d/tile_set.h" -#include "scene/resources/2d/world_boundary_shape_2d.h" #include "scene/resources/animated_texture.h" #include "scene/resources/animation_library.h" #include "scene/resources/atlas_texture.h" @@ -191,9 +128,6 @@ #include "scene/resources/mesh_texture.h" #include "scene/resources/multimesh.h" #include "scene/resources/navigation_mesh.h" -#include "scene/resources/navigation_mesh_source_geometry_data_2d.h" -#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" -#include "scene/resources/navigation_polygon.h" #include "scene/resources/packed_scene.h" #include "scene/resources/particle_process_material.h" #include "scene/resources/physics_material.h" @@ -221,10 +155,77 @@ #include "scene/resources/visual_shader_particle_nodes.h" #include "scene/resources/visual_shader_sdf_nodes.h" #include "scene/resources/world_2d.h" -#include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" -#include "scene/main/shader_globals_override.h" +// 2D +#include "scene/2d/animated_sprite_2d.h" +#include "scene/2d/audio_listener_2d.h" +#include "scene/2d/audio_stream_player_2d.h" +#include "scene/2d/back_buffer_copy.h" +#include "scene/2d/camera_2d.h" +#include "scene/2d/canvas_group.h" +#include "scene/2d/canvas_modulate.h" +#include "scene/2d/cpu_particles_2d.h" +#include "scene/2d/gpu_particles_2d.h" +#include "scene/2d/light_2d.h" +#include "scene/2d/light_occluder_2d.h" +#include "scene/2d/line_2d.h" +#include "scene/2d/marker_2d.h" +#include "scene/2d/mesh_instance_2d.h" +#include "scene/2d/multimesh_instance_2d.h" +#include "scene/2d/navigation_agent_2d.h" +#include "scene/2d/navigation_link_2d.h" +#include "scene/2d/navigation_obstacle_2d.h" +#include "scene/2d/navigation_region_2d.h" +#include "scene/2d/parallax_2d.h" +#include "scene/2d/parallax_background.h" +#include "scene/2d/parallax_layer.h" +#include "scene/2d/path_2d.h" +#include "scene/2d/physics/animatable_body_2d.h" +#include "scene/2d/physics/area_2d.h" +#include "scene/2d/physics/character_body_2d.h" +#include "scene/2d/physics/collision_polygon_2d.h" +#include "scene/2d/physics/collision_shape_2d.h" +#include "scene/2d/physics/joints/damped_spring_joint_2d.h" +#include "scene/2d/physics/joints/groove_joint_2d.h" +#include "scene/2d/physics/joints/joint_2d.h" +#include "scene/2d/physics/joints/pin_joint_2d.h" +#include "scene/2d/physics/kinematic_collision_2d.h" +#include "scene/2d/physics/physical_bone_2d.h" +#include "scene/2d/physics/physics_body_2d.h" +#include "scene/2d/physics/ray_cast_2d.h" +#include "scene/2d/physics/rigid_body_2d.h" +#include "scene/2d/physics/shape_cast_2d.h" +#include "scene/2d/physics/static_body_2d.h" +#include "scene/2d/polygon_2d.h" +#include "scene/2d/remote_transform_2d.h" +#include "scene/2d/skeleton_2d.h" +#include "scene/2d/sprite_2d.h" +#include "scene/2d/tile_map.h" +#include "scene/2d/tile_map_layer.h" +#include "scene/2d/touch_screen_button.h" +#include "scene/2d/visible_on_screen_notifier_2d.h" +#include "scene/resources/2d/capsule_shape_2d.h" +#include "scene/resources/2d/circle_shape_2d.h" +#include "scene/resources/2d/concave_polygon_shape_2d.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/2d/navigation_polygon.h" +#include "scene/resources/2d/polygon_path_finder.h" +#include "scene/resources/2d/rectangle_shape_2d.h" +#include "scene/resources/2d/segment_shape_2d.h" +#include "scene/resources/2d/separation_ray_shape_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_lookat.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h" +#include "scene/resources/2d/skeleton/skeleton_modification_stack_2d.h" +#include "scene/resources/2d/tile_set.h" +#include "scene/resources/2d/world_boundary_shape_2d.h" #ifndef _3D_DISABLED #include "scene/3d/audio_listener_3d.h" @@ -251,6 +252,7 @@ #include "scene/3d/node_3d.h" #include "scene/3d/occluder_instance_3d.h" #include "scene/3d/path_3d.h" +#include "scene/3d/physical_bone_simulator_3d.h" #include "scene/3d/physics/animatable_body_3d.h" #include "scene/3d/physics/area_3d.h" #include "scene/3d/physics/character_body_3d.h" @@ -275,6 +277,7 @@ #include "scene/3d/remote_transform_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_ik_3d.h" +#include "scene/3d/skeleton_modifier_3d.h" #include "scene/3d/soft_body_3d.h" #include "scene/3d/sprite_3d.h" #include "scene/3d/visible_on_screen_notifier_3d.h" @@ -294,6 +297,7 @@ #include "scene/resources/3d/height_map_shape_3d.h" #include "scene/resources/3d/importer_mesh.h" #include "scene/resources/3d/mesh_library.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" #include "scene/resources/3d/primitive_meshes.h" #include "scene/resources/3d/separation_ray_shape_3d.h" #include "scene/resources/3d/sky_material.h" @@ -472,6 +476,7 @@ void register_scene_types() { GDREGISTER_CLASS(GraphElement); GDREGISTER_CLASS(GraphNode); + GDREGISTER_CLASS(GraphFrame); GDREGISTER_CLASS(GraphEdit); OS::get_singleton()->yield(); // may take time to init @@ -539,7 +544,7 @@ void register_scene_types() { GDREGISTER_CLASS(Camera3D); GDREGISTER_CLASS(AudioListener3D); GDREGISTER_CLASS(XRCamera3D); - GDREGISTER_ABSTRACT_CLASS(XRNode3D); + GDREGISTER_CLASS(XRNode3D); GDREGISTER_CLASS(XRController3D); GDREGISTER_CLASS(XRAnchor3D); GDREGISTER_CLASS(XROrigin3D); @@ -583,6 +588,7 @@ void register_scene_types() { GDREGISTER_CLASS(CPUParticles3D); GDREGISTER_CLASS(Marker3D); GDREGISTER_CLASS(RootMotionView); + GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D); OS::get_singleton()->yield(); // may take time to init @@ -595,6 +601,7 @@ void register_scene_types() { GDREGISTER_CLASS(CharacterBody3D); GDREGISTER_CLASS(SpringArm3D); + GDREGISTER_CLASS(PhysicalBoneSimulator3D); GDREGISTER_CLASS(PhysicalBone3D); GDREGISTER_CLASS(SoftBody3D); @@ -648,7 +655,10 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeGroupBase); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeConstant); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVectorBase); - GDREGISTER_CLASS(VisualShaderNodeComment); + GDREGISTER_CLASS(VisualShaderNodeFrame); +#ifndef DISABLE_DEPRECATED + GDREGISTER_CLASS(VisualShaderNodeComment); // Deprecated, just for compatibility. +#endif GDREGISTER_CLASS(VisualShaderNodeFloatConstant); GDREGISTER_CLASS(VisualShaderNodeIntConstant); GDREGISTER_CLASS(VisualShaderNodeUIntConstant); @@ -733,6 +743,7 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVarying); GDREGISTER_CLASS(VisualShaderNodeVaryingSetter); GDREGISTER_CLASS(VisualShaderNodeVaryingGetter); + GDREGISTER_CLASS(VisualShaderNodeReroute); GDREGISTER_CLASS(VisualShaderNodeSDFToScreenUV); GDREGISTER_CLASS(VisualShaderNodeScreenUVToSDF); @@ -813,7 +824,7 @@ void register_scene_types() { GDREGISTER_CLASS(TileMapPattern); GDREGISTER_CLASS(TileData); GDREGISTER_CLASS(TileMap); - GDREGISTER_ABSTRACT_CLASS(TileMapLayerGroup); + GDREGISTER_CLASS(TileMapLayer); GDREGISTER_CLASS(Parallax2D); GDREGISTER_CLASS(ParallaxBackground); GDREGISTER_CLASS(ParallaxLayer); @@ -851,6 +862,7 @@ void register_scene_types() { GDREGISTER_CLASS(MeshDataTool); #ifndef _3D_DISABLED + GDREGISTER_CLASS(AudioStreamPlayer3D); GDREGISTER_VIRTUAL_CLASS(PrimitiveMesh); GDREGISTER_CLASS(BoxMesh); GDREGISTER_CLASS(CapsuleMesh); @@ -874,6 +886,7 @@ void register_scene_types() { BaseMaterial3D::init_shaders(); GDREGISTER_CLASS(MeshLibrary); + GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D); OS::get_singleton()->yield(); // may take time to init @@ -962,7 +975,6 @@ void register_scene_types() { GDREGISTER_CLASS(StyleBoxLine); GDREGISTER_CLASS(Theme); - GDREGISTER_CLASS(PolygonPathFinder); GDREGISTER_CLASS(BitMap); GDREGISTER_CLASS(Gradient); @@ -973,16 +985,13 @@ void register_scene_types() { OS::get_singleton()->yield(); // may take time to init GDREGISTER_CLASS(AudioStreamPlayer); - GDREGISTER_CLASS(AudioStreamPlayer2D); -#ifndef _3D_DISABLED - GDREGISTER_CLASS(AudioStreamPlayer3D); -#endif GDREGISTER_CLASS(AudioStreamWAV); GDREGISTER_CLASS(AudioStreamPolyphonic); GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackPolyphonic); OS::get_singleton()->yield(); // may take time to init + GDREGISTER_CLASS(AudioStreamPlayer2D); GDREGISTER_ABSTRACT_CLASS(Shape2D); GDREGISTER_CLASS(WorldBoundaryShape2D); GDREGISTER_CLASS(SegmentShape2D); @@ -995,10 +1004,10 @@ void register_scene_types() { GDREGISTER_CLASS(Curve2D); GDREGISTER_CLASS(Path2D); GDREGISTER_CLASS(PathFollow2D); + GDREGISTER_CLASS(PolygonPathFinder); GDREGISTER_CLASS(NavigationMesh); GDREGISTER_CLASS(NavigationMeshSourceGeometryData2D); - GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D); GDREGISTER_CLASS(NavigationPolygon); GDREGISTER_CLASS(NavigationRegion2D); GDREGISTER_CLASS(NavigationAgent2D); diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp index 7c33aa9e38..aee743ccf2 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp +++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp @@ -35,6 +35,12 @@ void NavigationMeshSourceGeometryData2D::clear() { traversable_outlines.clear(); obstruction_outlines.clear(); + clear_projected_obstructions(); +} + +void NavigationMeshSourceGeometryData2D::clear_projected_obstructions() { + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.clear(); } void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) { @@ -91,6 +97,24 @@ TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_ return typed_array_obstruction_outlines; } +void NavigationMeshSourceGeometryData2D::append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines) { + RWLockWrite write_lock(geometry_rwlock); + int traversable_outlines_size = traversable_outlines.size(); + traversable_outlines.resize(traversable_outlines_size + p_traversable_outlines.size()); + for (int i = traversable_outlines_size; i < p_traversable_outlines.size(); i++) { + traversable_outlines.write[i] = p_traversable_outlines[i]; + } +} + +void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) { + RWLockWrite write_lock(geometry_rwlock); + int obstruction_outlines_size = obstruction_outlines.size(); + obstruction_outlines.resize(obstruction_outlines_size + p_obstruction_outlines.size()); + for (int i = obstruction_outlines_size; i < p_obstruction_outlines.size(); i++) { + obstruction_outlines.write[i] = p_obstruction_outlines[i]; + } +} + void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) { if (p_shape_outline.size() > 1) { Vector<Vector2> traversable_outline; @@ -114,9 +138,114 @@ void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVec } void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry) { + ERR_FAIL_NULL(p_other_geometry); + // No need to worry about `root_node_transform` here as the data is already xformed. traversable_outlines.append_array(p_other_geometry->traversable_outlines); obstruction_outlines.append_array(p_other_geometry->obstruction_outlines); + + if (p_other_geometry->_projected_obstructions.size() > 0) { + RWLockWrite write_lock(geometry_rwlock); + + for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) { + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size()); + + const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr(); + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) { + obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j]; + } + + projected_obstruction.carve = other_projected_obstruction.carve; + + _projected_obstructions.push_back(projected_obstruction); + } + } +} + +void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve) { + ERR_FAIL_COND(p_vertices.size() < 2); + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(p_vertices.size() * 2); + projected_obstruction.carve = p_carve; + + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + int vertex_index = 0; + for (const Vector2 &vertex : p_vertices) { + obstruction_vertices_ptrw[vertex_index++] = vertex.x; + obstruction_vertices_ptrw[vertex_index++] = vertex.y; + } + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); +} + +void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array &p_array) { + clear_projected_obstructions(); + + for (int i = 0; i < p_array.size(); i++) { + Dictionary data = p_array[i]; + ERR_FAIL_COND(!data.has("version")); + + uint32_t po_version = data["version"]; + + if (po_version == 1) { + ERR_FAIL_COND(!data.has("vertices")); + ERR_FAIL_COND(!data.has("carve")); + } + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices = Vector<float>(data["vertices"]); + projected_obstruction.carve = data["carve"]; + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); + } +} + +Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> NavigationMeshSourceGeometryData2D::_get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + return _projected_obstructions; +} + +Array NavigationMeshSourceGeometryData2D::get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + + Array ret; + ret.resize(_projected_obstructions.size()); + + for (int i = 0; i < _projected_obstructions.size(); i++) { + const ProjectedObstruction &projected_obstruction = _projected_obstructions[i]; + + Dictionary data; + data["version"] = (int)ProjectedObstruction::VERSION; + data["vertices"] = projected_obstruction.vertices; + data["carve"] = projected_obstruction.carve; + + ret[i] = data; + } + + return ret; +} + +bool NavigationMeshSourceGeometryData2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "projected_obstructions") { + set_projected_obstructions(p_value); + return true; + } + return false; +} + +bool NavigationMeshSourceGeometryData2D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "projected_obstructions") { + r_ret = get_projected_obstructions(); + return true; + } + return false; } void NavigationMeshSourceGeometryData2D::_bind_methods() { @@ -129,11 +258,20 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::set_obstruction_outlines); ClassDB::bind_method(D_METHOD("get_obstruction_outlines"), &NavigationMeshSourceGeometryData2D::get_obstruction_outlines); + ClassDB::bind_method(D_METHOD("append_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::append_traversable_outlines); + ClassDB::bind_method(D_METHOD("append_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::append_obstruction_outlines); + ClassDB::bind_method(D_METHOD("add_traversable_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_traversable_outline); ClassDB::bind_method(D_METHOD("add_obstruction_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_obstruction_outline); ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData2D::merge); + ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "carve"), &NavigationMeshSourceGeometryData2D::add_projected_obstruction); + ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData2D::clear_projected_obstructions); + ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData2D::set_projected_obstructions); + ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData2D::get_projected_obstructions); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions"); } diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.h b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h index 4accdbc1f4..aaa02ab40e 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.h +++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h @@ -31,19 +31,36 @@ #ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H #define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H +#include "core/os/rw_lock.h" #include "scene/2d/node_2d.h" -#include "scene/resources/navigation_polygon.h" +#include "scene/resources/2d/navigation_polygon.h" class NavigationMeshSourceGeometryData2D : public Resource { GDCLASS(NavigationMeshSourceGeometryData2D, Resource); + RWLock geometry_rwlock; Vector<Vector<Vector2>> traversable_outlines; Vector<Vector<Vector2>> obstruction_outlines; +public: + struct ProjectedObstruction; + +private: + Vector<ProjectedObstruction> _projected_obstructions; + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; static void _bind_methods(); public: + struct ProjectedObstruction { + static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility. + + Vector<float> vertices; + bool carve = false; + }; + void _set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines); const Vector<Vector<Vector2>> &_get_traversable_outlines() const { return traversable_outlines; } @@ -65,11 +82,21 @@ public: void set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines); TypedArray<Vector<Vector2>> get_obstruction_outlines() const; + void append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines); + void append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines); + void add_traversable_outline(const PackedVector2Array &p_shape_outline); void add_obstruction_outline(const PackedVector2Array &p_shape_outline); bool has_data() { return traversable_outlines.size(); }; void clear(); + void clear_projected_obstructions(); + + void add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve); + Vector<ProjectedObstruction> _get_projected_obstructions() const; + + void set_projected_obstructions(const Array &p_array); + Array get_projected_obstructions() const; void merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry); diff --git a/scene/resources/navigation_polygon.cpp b/scene/resources/2d/navigation_polygon.cpp index e830153330..274b13a487 100644 --- a/scene/resources/navigation_polygon.cpp +++ b/scene/resources/2d/navigation_polygon.cpp @@ -251,8 +251,7 @@ void NavigationPolygon::make_polygons_from_outlines() { } const Vector2 *r = ol.ptr(); for (int j = 0; j < olsize; j++) { - outside_point.x = MAX(r[j].x, outside_point.x); - outside_point.y = MAX(r[j].y, outside_point.y); + outside_point = outside_point.max(r[j]); } } diff --git a/scene/resources/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h index b9816f900c..b9816f900c 100644 --- a/scene/resources/navigation_polygon.h +++ b/scene/resources/2d/navigation_polygon.h diff --git a/scene/resources/2d/polygon_path_finder.cpp b/scene/resources/2d/polygon_path_finder.cpp index 617a53f0a3..3aa292431d 100644 --- a/scene/resources/2d/polygon_path_finder.cpp +++ b/scene/resources/2d/polygon_path_finder.cpp @@ -64,8 +64,7 @@ void PolygonPathFinder::setup(const Vector<Vector2> &p_points, const Vector<int> points.write[i].pos = p_points[i]; points.write[i].penalty = 0; - outside_point.x = i == 0 ? p_points[0].x : (MAX(p_points[i].x, outside_point.x)); - outside_point.y = i == 0 ? p_points[0].y : (MAX(p_points[i].y, outside_point.y)); + outside_point = i == 0 ? p_points[0] : p_points[i].max(outside_point); if (i == 0) { bounds.position = points[i].pos; diff --git a/scene/resources/2d/shape_2d.cpp b/scene/resources/2d/shape_2d.cpp index 2de9b70f53..e94280fe3d 100644 --- a/scene/resources/2d/shape_2d.cpp +++ b/scene/resources/2d/shape_2d.cpp @@ -111,11 +111,6 @@ void Shape2D::_bind_methods() { } bool Shape2D::is_collision_outline_enabled() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return true; - } -#endif return GLOBAL_GET("debug/shapes/collision/draw_2d_outlines"); } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp index 4e7563fdc3..5450f544c3 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp @@ -233,6 +233,11 @@ void SkeletonModification2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode"); } +void SkeletonModification2D::reset_state() { + stack = nullptr; + is_setup = false; +} + SkeletonModification2D::SkeletonModification2D() { stack = nullptr; is_setup = false; diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.h b/scene/resources/2d/skeleton/skeleton_modification_2d.h index 413b860a99..6a6f1bb39b 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d.h +++ b/scene/resources/2d/skeleton/skeleton_modification_2d.h @@ -57,6 +57,8 @@ protected: bool _print_execution_error(bool p_condition, String p_message); + virtual void reset_state() override; + GDVIRTUAL1(_execute, double) GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack2D>) GDVIRTUAL0(_draw_editor_gizmo) diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp index 1ad8d0eccc..051c4eabc0 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp @@ -266,7 +266,9 @@ void SkeletonModification2DCCDIK::_draw_editor_gizmo() { void SkeletonModification2DCCDIK::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -287,7 +289,9 @@ void SkeletonModification2DCCDIK::update_target_cache() { void SkeletonModification2DCCDIK::update_tip_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); + } return; } @@ -309,7 +313,9 @@ void SkeletonModification2DCCDIK::update_tip_cache() { void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); + } return; } @@ -390,7 +396,6 @@ void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, in ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp index dd1c4a91d5..16a6166878 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp @@ -289,13 +289,21 @@ void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack if (stack != nullptr) { is_setup = true; + + if (stack->skeleton) { + for (int i = 0; i < fabrik_data_chain.size(); i++) { + fabrik_joint_update_bone2d_cache(i); + } + } update_target_cache(); } } void SkeletonModification2DFABRIK::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -317,7 +325,9 @@ void SkeletonModification2DFABRIK::update_target_cache() { void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); + } return; } @@ -389,7 +399,6 @@ void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp index 2ace9577e4..b7200b49c4 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp @@ -254,6 +254,8 @@ void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx); jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position(); } + + jiggle_joint_update_bone2d_cache(i); } } @@ -263,7 +265,9 @@ void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack void SkeletonModification2DJiggle::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -285,7 +289,9 @@ void SkeletonModification2DJiggle::update_target_cache() { void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); + } return; } @@ -425,7 +431,6 @@ void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp index 8f6f6bc4ae..cd4ca8e090 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp @@ -200,7 +200,9 @@ void SkeletonModification2DLookAt::_draw_editor_gizmo() { void SkeletonModification2DLookAt::update_bone2d_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); + } return; } @@ -256,7 +258,6 @@ void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { bone_idx = p_bone_idx; } } else { - WARN_PRINT("Cannot verify the bone index for this modification..."); bone_idx = p_bone_idx; } @@ -265,7 +266,9 @@ void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { void SkeletonModification2DLookAt::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp index 61e5aed150..001000fa17 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp @@ -153,7 +153,7 @@ void SkeletonModification2DPhysicalBones::_setup_modification(SkeletonModificati void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) { ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!"); if (!is_setup || !stack) { - if (!stack) { + if (is_setup) { ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!"); } return; @@ -194,7 +194,7 @@ void SkeletonModification2DPhysicalBones::fetch_physical_bones() { node_queue.push_back(stack->skeleton); while (node_queue.size() > 0) { - Node *node_to_process = node_queue[0]; + Node *node_to_process = node_queue.front()->get(); node_queue.pop_front(); if (node_to_process != nullptr) { diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp index c3366d5c36..41e4ea828e 100644 --- a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp @@ -250,7 +250,9 @@ void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { void SkeletonModification2DTwoBoneIK::update_target_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + } return; } @@ -271,7 +273,9 @@ void SkeletonModification2DTwoBoneIK::update_target_cache() { void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); + } return; } @@ -299,7 +303,9 @@ void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() { if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); + if (is_setup) { + ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); + } return; } @@ -400,7 +406,6 @@ void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { joint_one_bone_idx = p_bone_idx; } } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); joint_one_bone_idx = p_bone_idx; } @@ -425,7 +430,6 @@ void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { joint_two_bone_idx = p_bone_idx; } } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); joint_two_bone_idx = p_bone_idx; } diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp index d09723d765..6c3356a205 100644 --- a/scene/resources/2d/tile_set.cpp +++ b/scene/resources/2d/tile_set.cpp @@ -74,7 +74,7 @@ void TileMapPattern::_set_tile_data(const Vector<int> &p_data) { uint16_t alternative_tile = decode_uint16(&local[10]); set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); } - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Vector<int> TileMapPattern::_get_tile_data() const { @@ -4650,7 +4650,7 @@ Ref<Texture2D> TileSetAtlasSource::get_texture() const { void TileSetAtlasSource::set_margins(Vector2i p_margins) { if (p_margins.x < 0 || p_margins.y < 0) { WARN_PRINT("Atlas source margins should be positive."); - margins = Vector2i(MAX(0, p_margins.x), MAX(0, p_margins.y)); + margins = p_margins.maxi(0); } else { margins = p_margins; } @@ -4666,7 +4666,7 @@ Vector2i TileSetAtlasSource::get_margins() const { void TileSetAtlasSource::set_separation(Vector2i p_separation) { if (p_separation.x < 0 || p_separation.y < 0) { WARN_PRINT("Atlas source separation should be positive."); - separation = Vector2i(MAX(0, p_separation.x), MAX(0, p_separation.y)); + separation = p_separation.maxi(0); } else { separation = p_separation; } @@ -4682,7 +4682,7 @@ Vector2i TileSetAtlasSource::get_separation() const { void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { if (p_tile_size.x <= 0 || p_tile_size.y <= 0) { WARN_PRINT("Atlas source tile_size should be strictly positive."); - texture_region_size = Vector2i(MAX(1, p_tile_size.x), MAX(1, p_tile_size.y)); + texture_region_size = p_tile_size.maxi(1); } else { texture_region_size = p_tile_size; } @@ -4969,7 +4969,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector tad.alternatives[0] = memnew(TileData); tad.alternatives[0]->set_tile_set(tile_set); tad.alternatives[0]->set_allow_transform(false); - tad.alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); + tad.alternatives[0]->connect(CoreStringName(changed), callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); tad.alternatives[0]->notify_property_list_changed(); tad.alternatives_ids.push_back(0); @@ -5353,7 +5353,7 @@ int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, i tiles[p_atlas_coords].alternatives[new_alternative_id] = memnew(TileData); tiles[p_atlas_coords].alternatives[new_alternative_id]->set_tile_set(tile_set); tiles[p_atlas_coords].alternatives[new_alternative_id]->set_allow_transform(true); - tiles[p_atlas_coords].alternatives[new_alternative_id]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); + tiles[p_atlas_coords].alternatives[new_alternative_id]->connect(CoreStringName(changed), callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); tiles[p_atlas_coords].alternatives[new_alternative_id]->notify_property_list_changed(); tiles[p_atlas_coords].alternatives_ids.push_back(new_alternative_id); tiles[p_atlas_coords].alternatives_ids.sort(); @@ -5591,6 +5591,11 @@ Ref<ImageTexture> TileSetAtlasSource::_create_padded_image_texture(const Ref<Tex ret.instantiate(); return ret; } + if (src_image->is_compressed()) { + src_image = src_image->duplicate(); + Error err = src_image->decompress(); + ERR_FAIL_COND_V_MSG(err != OK, Ref<ImageTexture>(), "Unable to decompress image."); + } Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2)); Ref<Image> image = Image::create_empty(size.x, size.y, false, src_image->get_format()); @@ -5933,7 +5938,7 @@ void TileData::notify_tile_data_properties_should_change() { } notify_property_list_changed(); - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } void TileData::add_occlusion_layer(int p_to_pos) { @@ -6134,7 +6139,7 @@ TileData *TileData::duplicate() { void TileData::set_flip_h(bool p_flip_h) { ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); flip_h = p_flip_h; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } bool TileData::get_flip_h() const { return flip_h; @@ -6143,7 +6148,7 @@ bool TileData::get_flip_h() const { void TileData::set_flip_v(bool p_flip_v) { ERR_FAIL_COND_MSG(!allow_transform && p_flip_v, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); flip_v = p_flip_v; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } bool TileData::get_flip_v() const { @@ -6153,7 +6158,7 @@ bool TileData::get_flip_v() const { void TileData::set_transpose(bool p_transpose) { ERR_FAIL_COND_MSG(!allow_transform && p_transpose, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); transpose = p_transpose; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } bool TileData::get_transpose() const { return transpose; @@ -6161,7 +6166,7 @@ bool TileData::get_transpose() const { void TileData::set_texture_origin(Vector2i p_texture_origin) { texture_origin = p_texture_origin; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Vector2i TileData::get_texture_origin() const { @@ -6170,7 +6175,7 @@ Vector2i TileData::get_texture_origin() const { void TileData::set_material(Ref<Material> p_material) { material = p_material; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Ref<Material> TileData::get_material() const { return material; @@ -6178,7 +6183,7 @@ Ref<Material> TileData::get_material() const { void TileData::set_modulate(Color p_modulate) { modulate = p_modulate; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Color TileData::get_modulate() const { return modulate; @@ -6186,7 +6191,7 @@ Color TileData::get_modulate() const { void TileData::set_z_index(int p_z_index) { z_index = p_z_index; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } int TileData::get_z_index() const { return z_index; @@ -6194,7 +6199,7 @@ int TileData::get_z_index() const { void TileData::set_y_sort_origin(int p_y_sort_origin) { y_sort_origin = p_y_sort_origin; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } int TileData::get_y_sort_origin() const { return y_sort_origin; @@ -6204,7 +6209,7 @@ void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_po ERR_FAIL_INDEX(p_layer_id, occluders.size()); occluders.write[p_layer_id].occluder = p_occluder_polygon; occluders.write[p_layer_id].transformed_occluders.clear(); - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const { @@ -6237,7 +6242,7 @@ Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, boo void TileData::set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity) { ERR_FAIL_INDEX(p_layer_id, physics.size()); physics.write[p_layer_id].linear_velocity = p_velocity; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const { @@ -6248,7 +6253,7 @@ Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const { void TileData::set_constant_angular_velocity(int p_layer_id, real_t p_velocity) { ERR_FAIL_INDEX(p_layer_id, physics.size()); physics.write[p_layer_id].angular_velocity = p_velocity; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } real_t TileData::get_constant_angular_velocity(int p_layer_id) const { @@ -6264,7 +6269,7 @@ void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count } physics.write[p_layer_id].polygons.resize(p_polygons_count); notify_property_list_changed(); - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } int TileData::get_collision_polygons_count(int p_layer_id) const { @@ -6275,14 +6280,14 @@ int TileData::get_collision_polygons_count(int p_layer_id) const { void TileData::add_collision_polygon(int p_layer_id) { ERR_FAIL_INDEX(p_layer_id, physics.size()); physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData()); - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } void TileData::remove_collision_polygon(int p_layer_id, int p_polygon_index) { ERR_FAIL_INDEX(p_layer_id, physics.size()); ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); physics.write[p_layer_id].polygons.remove_at(p_polygon_index); - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon) { @@ -6309,7 +6314,7 @@ void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, } polygon_shape_tile_data.transformed_shapes.clear(); polygon_shape_tile_data.polygon = p_polygon; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Vector<Vector2> TileData::get_collision_polygon_points(int p_layer_id, int p_polygon_index) const { @@ -6322,7 +6327,7 @@ void TileData::set_collision_polygon_one_way(int p_layer_id, int p_polygon_index ERR_FAIL_INDEX(p_layer_id, physics.size()); ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); physics.write[p_layer_id].polygons.write[p_polygon_index].one_way = p_one_way; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } bool TileData::is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const { @@ -6335,7 +6340,7 @@ void TileData::set_collision_polygon_one_way_margin(int p_layer_id, int p_polygo ERR_FAIL_INDEX(p_layer_id, physics.size()); ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); physics.write[p_layer_id].polygons.write[p_polygon_index].one_way_margin = p_one_way_margin; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } float TileData::get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const { @@ -6373,7 +6378,7 @@ Ref<ConvexPolygonShape2D> TileData::get_collision_polygon_shape(int p_layer_id, for (int i = 0; i < size; i++) { Ref<ConvexPolygonShape2D> transformed_polygon; transformed_polygon.instantiate(); - transformed_polygon->set_points(get_transformed_vertices(shapes_data.shapes[shape_index]->get_points(), p_flip_h, p_flip_v, p_transpose)); + transformed_polygon->set_points(get_transformed_vertices(shapes_data.shapes[i]->get_points(), p_flip_h, p_flip_v, p_transpose)); shapes_data.transformed_shapes[key][i] = transformed_polygon; } return shapes_data.transformed_shapes[key][shape_index]; @@ -6397,7 +6402,7 @@ void TileData::set_terrain_set(int p_terrain_set) { } terrain_set = p_terrain_set; notify_property_list_changed(); - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } int TileData::get_terrain_set() const { @@ -6411,7 +6416,7 @@ void TileData::set_terrain(int p_terrain) { ERR_FAIL_COND(p_terrain >= tile_set->get_terrains_count(terrain_set)); } terrain = p_terrain; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } int TileData::get_terrain() const { @@ -6427,7 +6432,7 @@ void TileData::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int ERR_FAIL_COND(!is_valid_terrain_peering_bit(p_peering_bit)); } terrain_peering_bits[p_peering_bit] = p_terrain_index; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } int TileData::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { @@ -6459,7 +6464,7 @@ void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_n ERR_FAIL_INDEX(p_layer_id, navigation.size()); navigation.write[p_layer_id].navigation_polygon = p_navigation_polygon; navigation.write[p_layer_id].transformed_navigation_polygon.clear(); - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const { @@ -6507,7 +6512,7 @@ Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id, bool p_f void TileData::set_probability(float p_probability) { ERR_FAIL_COND(p_probability < 0.0); probability = p_probability; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } float TileData::get_probability() const { return probability; @@ -6531,7 +6536,7 @@ Variant TileData::get_custom_data(String p_layer_name) const { void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) { ERR_FAIL_INDEX(p_layer_id, custom_data.size()); custom_data.write[p_layer_id] = p_value; - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const { @@ -6805,8 +6810,20 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const { // Physics layers. p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Physics", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int i = 0; i < physics.size(); i++) { - p_list->push_back(PropertyInfo(Variant::VECTOR2, vformat("physics_layer_%d/%s", i, PNAME("linear_velocity")), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/%s", i, PNAME("angular_velocity")), PROPERTY_HINT_NONE)); + // physics_layer_%d/linear_velocity + property_info = PropertyInfo(Variant::VECTOR2, vformat("physics_layer_%d/%s", i, PNAME("linear_velocity")), PROPERTY_HINT_NONE); + if (physics[i].linear_velocity == Vector2()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + + // physics_layer_%d/angular_velocity + property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/%s", i, PNAME("angular_velocity")), PROPERTY_HINT_NONE); + if (physics[i].angular_velocity == 0.0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); for (int j = 0; j < physics[i].polygons.size(); j++) { @@ -6918,6 +6935,7 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("get_terrain"), &TileData::get_terrain); ClassDB::bind_method(D_METHOD("set_terrain_peering_bit", "peering_bit", "terrain"), &TileData::set_terrain_peering_bit); ClassDB::bind_method(D_METHOD("get_terrain_peering_bit", "peering_bit"), &TileData::get_terrain_peering_bit); + ClassDB::bind_method(D_METHOD("is_valid_terrain_peering_bit", "peering_bit"), &TileData::is_valid_terrain_peering_bit); // Navigation ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon); diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h index f1a8d42b95..e6d3f7e15d 100644 --- a/scene/resources/2d/tile_set.h +++ b/scene/resources/2d/tile_set.h @@ -38,8 +38,8 @@ #include "scene/2d/light_occluder_2d.h" #include "scene/main/canvas_item.h" #include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/2d/navigation_polygon.h" #include "scene/resources/image_texture.h" -#include "scene/resources/navigation_polygon.h" #include "scene/resources/packed_scene.h" #include "scene/resources/physics_material.h" @@ -619,6 +619,8 @@ public: TRANSFORM_TRANSPOSE = 1 << 14, }; + static const int16_t UNTRANSFORM_MASK = ~(TileSetAtlasSource::TRANSFORM_FLIP_H + TileSetAtlasSource::TRANSFORM_FLIP_V + TileSetAtlasSource::TRANSFORM_TRANSPOSE); + private: struct TileAlternativesData { Vector2i size_in_atlas = Vector2i(1, 1); diff --git a/scene/resources/3d/height_map_shape_3d.cpp b/scene/resources/3d/height_map_shape_3d.cpp index 35c905bd43..5b55b66152 100644 --- a/scene/resources/3d/height_map_shape_3d.cpp +++ b/scene/resources/3d/height_map_shape_3d.cpp @@ -30,6 +30,7 @@ #include "height_map_shape_3d.h" +#include "core/io/image.h" #include "servers/physics_server_3d.h" Vector<Vector3> HeightMapShape3D::get_debug_mesh_lines() const { @@ -187,6 +188,104 @@ real_t HeightMapShape3D::get_max_height() const { return max_height; } +void HeightMapShape3D::update_map_data_from_image(const Ref<Image> &p_image, real_t p_height_min, real_t p_height_max) { + ERR_FAIL_COND_MSG(p_image.is_null(), "Heightmap update image requires a valid Image reference."); + ERR_FAIL_COND_MSG(p_image->get_format() != Image::FORMAT_RF && p_image->get_format() != Image::FORMAT_RH && p_image->get_format() != Image::FORMAT_R8, "Heightmap update image requires Image in format FORMAT_RF (32 bit), FORMAT_RH (16 bit), or FORMAT_R8 (8 bit)."); + ERR_FAIL_COND_MSG(p_image->get_width() < 2, "Heightmap update image requires a minimum Image width of 2."); + ERR_FAIL_COND_MSG(p_image->get_height() < 2, "Heightmap update image requires a minimum Image height of 2."); + ERR_FAIL_COND_MSG(p_height_min > p_height_max, "Heightmap update image requires height_max to be greater than height_min."); + + map_width = p_image->get_width(); + map_depth = p_image->get_height(); + map_data.resize(map_width * map_depth); + + real_t new_min_height = FLT_MAX; + real_t new_max_height = -FLT_MAX; + + float remap_height_min = float(p_height_min); + float remap_height_max = float(p_height_max); + + real_t *map_data_ptrw = map_data.ptrw(); + + switch (p_image->get_format()) { + case Image::FORMAT_RF: { + const float *image_data_ptr = (float *)p_image->get_data().ptr(); + + for (int i = 0; i < map_data.size(); i++) { + float pixel_value = image_data_ptr[i]; + + DEV_ASSERT(pixel_value >= 0.0 && pixel_value <= 1.0); + + real_t height_value = Math::remap(pixel_value, 0.0f, 1.0f, remap_height_min, remap_height_max); + + if (height_value < new_min_height) { + new_min_height = height_value; + } + if (height_value > new_max_height) { + new_max_height = height_value; + } + + map_data_ptrw[i] = height_value; + } + + } break; + + case Image::FORMAT_RH: { + const uint16_t *image_data_ptr = (uint16_t *)p_image->get_data().ptr(); + + for (int i = 0; i < map_data.size(); i++) { + float pixel_value = Math::half_to_float(image_data_ptr[i]); + + DEV_ASSERT(pixel_value >= 0.0 && pixel_value <= 1.0); + + real_t height_value = Math::remap(pixel_value, 0.0f, 1.0f, remap_height_min, remap_height_max); + + if (height_value < new_min_height) { + new_min_height = height_value; + } + if (height_value > new_max_height) { + new_max_height = height_value; + } + + map_data_ptrw[i] = height_value; + } + + } break; + + case Image::FORMAT_R8: { + const uint8_t *image_data_ptr = (uint8_t *)p_image->get_data().ptr(); + + for (int i = 0; i < map_data.size(); i++) { + float pixel_value = float(image_data_ptr[i] / 255.0); + + DEV_ASSERT(pixel_value >= 0.0 && pixel_value <= 1.0); + + real_t height_value = Math::remap(pixel_value, 0.0f, 1.0f, remap_height_min, remap_height_max); + + if (height_value < new_min_height) { + new_min_height = height_value; + } + if (height_value > new_max_height) { + new_max_height = height_value; + } + + map_data_ptrw[i] = height_value; + } + + } break; + + default: { + return; + } + } + + min_height = new_min_height; + max_height = new_max_height; + + _update_shape(); + emit_changed(); +} + void HeightMapShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_map_width", "width"), &HeightMapShape3D::set_map_width); ClassDB::bind_method(D_METHOD("get_map_width"), &HeightMapShape3D::get_map_width); @@ -197,6 +296,8 @@ void HeightMapShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_min_height"), &HeightMapShape3D::get_min_height); ClassDB::bind_method(D_METHOD("get_max_height"), &HeightMapShape3D::get_max_height); + ClassDB::bind_method(D_METHOD("update_map_data_from_image", "image", "height_min", "height_max"), &HeightMapShape3D::update_map_data_from_image); + ADD_PROPERTY(PropertyInfo(Variant::INT, "map_width", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_width", "get_map_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "map_depth", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_depth", "get_map_depth"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "map_data"), "set_map_data", "get_map_data"); diff --git a/scene/resources/3d/height_map_shape_3d.h b/scene/resources/3d/height_map_shape_3d.h index 363d9ec0e9..33ba9c4472 100644 --- a/scene/resources/3d/height_map_shape_3d.h +++ b/scene/resources/3d/height_map_shape_3d.h @@ -33,6 +33,8 @@ #include "scene/resources/3d/shape_3d.h" +class Image; + class HeightMapShape3D : public Shape3D { GDCLASS(HeightMapShape3D, Shape3D); @@ -57,6 +59,8 @@ public: real_t get_min_height() const; real_t get_max_height() const; + void update_map_data_from_image(const Ref<Image> &p_image, real_t p_height_min, real_t p_height_max); + virtual Vector<Vector3> get_debug_mesh_lines() const override; virtual real_t get_enclosing_radius() const override; diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp index 952e99608d..375efcb227 100644 --- a/scene/resources/3d/importer_mesh.cpp +++ b/scene/resources/3d/importer_mesh.cpp @@ -1139,7 +1139,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, s.material = get_surface_material(i); s.name = get_surface_name(i); - SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); + SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format); PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; int vc = rvertices.size(); diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp index 43fb592bba..3f3f8b44fd 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp +++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp @@ -35,12 +35,33 @@ void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_ver } void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indices) { + ERR_FAIL_COND(vertices.size() < p_indices.size()); indices = p_indices; } +void NavigationMeshSourceGeometryData3D::append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices) { + RWLockWrite write_lock(geometry_rwlock); + + const int64_t number_of_vertices_before_merge = vertices.size(); + const int64_t number_of_indices_before_merge = indices.size(); + + vertices.append_array(p_vertices); + indices.append_array(p_indices); + + for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) { + indices.set(i, indices[i] + number_of_vertices_before_merge / 3); + } +} + void NavigationMeshSourceGeometryData3D::clear() { vertices.clear(); indices.clear(); + clear_projected_obstructions(); +} + +void NavigationMeshSourceGeometryData3D::clear_projected_obstructions() { + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.clear(); } void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) { @@ -152,6 +173,15 @@ void NavigationMeshSourceGeometryData3D::_add_faces(const PackedVector3Array &p_ void NavigationMeshSourceGeometryData3D::add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform) { ERR_FAIL_COND(!p_mesh.is_valid()); + +#ifdef DEBUG_ENABLED + if (!Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT_ONCE("Source geometry parsing for navigation mesh baking had to parse RenderingServer meshes at runtime.\n\ + This poses a significant performance issues as visual meshes store geometry data on the GPU and transferring this data back to the CPU blocks the rendering.\n\ + For runtime (re)baking navigation meshes use and parse collision shapes as source geometry or create geometry data procedurally in scripts."); + } +#endif + _add_mesh(p_mesh, root_node_transform * p_xform); } @@ -166,16 +196,126 @@ void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_f } void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) { - // No need to worry about `root_node_transform` here as the vertices are already xformed. - const int64_t number_of_vertices_before_merge = vertices.size(); - const int64_t number_of_indices_before_merge = indices.size(); - vertices.append_array(p_other_geometry->vertices); - indices.append_array(p_other_geometry->indices); - for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) { - indices.set(i, indices[i] + number_of_vertices_before_merge / 3); + ERR_FAIL_NULL(p_other_geometry); + + append_arrays(p_other_geometry->vertices, p_other_geometry->indices); + + if (p_other_geometry->_projected_obstructions.size() > 0) { + RWLockWrite write_lock(geometry_rwlock); + + for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) { + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size()); + + const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr(); + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) { + obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j]; + } + + projected_obstruction.elevation = other_projected_obstruction.elevation; + projected_obstruction.height = other_projected_obstruction.height; + projected_obstruction.carve = other_projected_obstruction.carve; + + _projected_obstructions.push_back(projected_obstruction); + } + } +} + +void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve) { + ERR_FAIL_COND(p_vertices.size() < 3); + ERR_FAIL_COND(p_height < 0.0); + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(p_vertices.size() * 3); + projected_obstruction.elevation = p_elevation; + projected_obstruction.height = p_height; + projected_obstruction.carve = p_carve; + + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + int vertex_index = 0; + for (const Vector3 &vertex : p_vertices) { + obstruction_vertices_ptrw[vertex_index++] = vertex.x; + obstruction_vertices_ptrw[vertex_index++] = vertex.y; + obstruction_vertices_ptrw[vertex_index++] = vertex.z; + } + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); +} + +void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array &p_array) { + clear_projected_obstructions(); + + for (int i = 0; i < p_array.size(); i++) { + Dictionary data = p_array[i]; + ERR_FAIL_COND(!data.has("version")); + + uint32_t po_version = data["version"]; + + if (po_version == 1) { + ERR_FAIL_COND(!data.has("vertices")); + ERR_FAIL_COND(!data.has("elevation")); + ERR_FAIL_COND(!data.has("height")); + ERR_FAIL_COND(!data.has("carve")); + } + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices = Vector<float>(data["vertices"]); + projected_obstruction.elevation = data["elevation"]; + projected_obstruction.height = data["height"]; + projected_obstruction.carve = data["carve"]; + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); } } +Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> NavigationMeshSourceGeometryData3D::_get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + return _projected_obstructions; +} + +Array NavigationMeshSourceGeometryData3D::get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + + Array ret; + ret.resize(_projected_obstructions.size()); + + for (int i = 0; i < _projected_obstructions.size(); i++) { + const ProjectedObstruction &projected_obstruction = _projected_obstructions[i]; + + Dictionary data; + data["version"] = (int)ProjectedObstruction::VERSION; + data["vertices"] = projected_obstruction.vertices; + data["elevation"] = projected_obstruction.elevation; + data["height"] = projected_obstruction.height; + data["carve"] = projected_obstruction.carve; + + ret[i] = data; + } + + return ret; +} + +bool NavigationMeshSourceGeometryData3D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "projected_obstructions") { + set_projected_obstructions(p_value); + return true; + } + return false; +} + +bool NavigationMeshSourceGeometryData3D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "projected_obstructions") { + r_ret = get_projected_obstructions(); + return true; + } + return false; +} + void NavigationMeshSourceGeometryData3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationMeshSourceGeometryData3D::set_vertices); ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationMeshSourceGeometryData3D::get_vertices); @@ -183,6 +323,8 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_indices", "indices"), &NavigationMeshSourceGeometryData3D::set_indices); ClassDB::bind_method(D_METHOD("get_indices"), &NavigationMeshSourceGeometryData3D::get_indices); + ClassDB::bind_method(D_METHOD("append_arrays", "vertices", "indices"), &NavigationMeshSourceGeometryData3D::append_arrays); + ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData3D::clear); ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData3D::has_data); @@ -191,6 +333,12 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() { ClassDB::bind_method(D_METHOD("add_faces", "faces", "xform"), &NavigationMeshSourceGeometryData3D::add_faces); ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData3D::merge); + ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "elevation", "height", "carve"), &NavigationMeshSourceGeometryData3D::add_projected_obstruction); + ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData3D::clear_projected_obstructions); + ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData3D::set_projected_obstructions); + ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData3D::get_projected_obstructions); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions"); } diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.h b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h index 981f20a74b..6c1ca760ea 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.h +++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h @@ -31,15 +31,25 @@ #ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H #define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H +#include "core/os/rw_lock.h" #include "scene/resources/mesh.h" class NavigationMeshSourceGeometryData3D : public Resource { GDCLASS(NavigationMeshSourceGeometryData3D, Resource); + RWLock geometry_rwlock; Vector<float> vertices; Vector<int> indices; +public: + struct ProjectedObstruction; + +private: + Vector<ProjectedObstruction> _projected_obstructions; + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; static void _bind_methods(); private: @@ -49,6 +59,15 @@ private: void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform); public: + struct ProjectedObstruction { + static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility. + + Vector<float> vertices; + float elevation = 0.0; + float height = 0.0; + bool carve = false; + }; + // kept root node transform here on the geometry data // if we add this transform to all exposed functions we need to break comp on all functions later // when navmesh changes from global transform to relative to navregion @@ -61,8 +80,11 @@ public: void set_indices(const Vector<int> &p_indices); const Vector<int> &get_indices() const { return indices; } + void append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices); + bool has_data() { return vertices.size() && indices.size(); }; void clear(); + void clear_projected_obstructions(); void add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform); void add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform); @@ -70,6 +92,12 @@ public: void merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry); + void add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve); + Vector<ProjectedObstruction> _get_projected_obstructions() const; + + void set_projected_obstructions(const Array &p_array); + Array get_projected_obstructions() const; + NavigationMeshSourceGeometryData3D() {} ~NavigationMeshSourceGeometryData3D() { clear(); } }; diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp index c2b2a6d68b..ee772f960a 100644 --- a/scene/resources/3d/primitive_meshes.cpp +++ b/scene/resources/3d/primitive_meshes.cpp @@ -2845,10 +2845,8 @@ void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph for (int j = 0; j < gl_data.contours[i].size(); j++) { int next = (j + 1 == gl_data.contours[i].size()) ? 0 : (j + 1); - gl_data.min_p.x = MIN(gl_data.min_p.x, gl_data.contours[i][j].point.x); - gl_data.min_p.y = MIN(gl_data.min_p.y, gl_data.contours[i][j].point.y); - gl_data.max_p.x = MAX(gl_data.max_p.x, gl_data.contours[i][j].point.x); - gl_data.max_p.y = MAX(gl_data.max_p.y, gl_data.contours[i][j].point.y); + gl_data.min_p = gl_data.min_p.min(gl_data.contours[i][j].point); + gl_data.max_p = gl_data.max_p.max(gl_data.contours[i][j].point); length += (gl_data.contours[i][next].point - gl_data.contours[i][j].point).length(); inp.GetPoint(j) = gl_data.contours[i][j].point; diff --git a/scene/resources/3d/world_3d.cpp b/scene/resources/3d/world_3d.cpp index 7948a8bfd5..b743b24262 100644 --- a/scene/resources/3d/world_3d.cpp +++ b/scene/resources/3d/world_3d.cpp @@ -35,7 +35,6 @@ #include "scene/3d/visible_on_screen_notifier_3d.h" #include "scene/resources/camera_attributes.h" #include "scene/resources/environment.h" -#include "scene/scene_string_names.h" #include "servers/navigation_server_3d.h" void World3D::_register_camera(Camera3D *p_camera) { diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index cd530f100e..a3bfa987c6 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -33,7 +33,6 @@ #include "core/io/marshalls.h" #include "core/math/geometry_3d.h" -#include "scene/scene_string_names.h" bool Animation::_set(const StringName &p_name, const Variant &p_value) { String prop_name = p_name; @@ -247,6 +246,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } vt->update_mode = UpdateMode(um); } + capture_included = capture_included || (vt->update_mode == UPDATE_CAPTURE); Vector<real_t> times = d["times"]; Array values = d["values"]; @@ -966,6 +966,28 @@ void Animation::remove_track(int p_track) { memdelete(t); tracks.remove_at(p_track); emit_changed(); + _check_capture_included(); +} + +void Animation::set_capture_included(bool p_capture_included) { + capture_included = p_capture_included; +} + +bool Animation::is_capture_included() const { + return capture_included; +} + +void Animation::_check_capture_included() { + capture_included = false; + for (int i = 0; i < tracks.size(); i++) { + if (tracks[i]->type == TYPE_VALUE) { + ValueTrack *vt = static_cast<ValueTrack *>(tracks[i]); + if (vt->update_mode == UPDATE_CAPTURE) { + capture_included = true; + break; + } + } + } } int Animation::get_track_count() const { @@ -2681,6 +2703,8 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) { ValueTrack *vt = static_cast<ValueTrack *>(t); vt->update_mode = p_mode; + + _check_capture_included(); emit_changed(); } @@ -3870,9 +3894,13 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("compress", "page_size", "fps", "split_tolerance"), &Animation::compress, DEFVAL(8192), DEFVAL(120), DEFVAL(4.0)); + ClassDB::bind_method(D_METHOD("_set_capture_included", "capture_included"), &Animation::set_capture_included); + ClassDB::bind_method(D_METHOD("is_capture_included"), &Animation::is_capture_included); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001,suffix:s"), "set_length", "get_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001,suffix:s"), "set_step", "get_step"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_included", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_NO_EDITOR), "_set_capture_included", "is_capture_included"); BIND_ENUM_CONSTANT(TYPE_VALUE); BIND_ENUM_CONSTANT(TYPE_POSITION_3D); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index d3df8d03e5..cc7bbae8a3 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -75,6 +75,7 @@ public: LOOP_PINGPONG, }; + // LoopedFlag is used in Animataion to "process the keys at both ends correct". enum LoopedFlag { LOOPED_FLAG_NONE, LOOPED_FLAG_END, @@ -187,6 +188,7 @@ private: }; /* BEZIER TRACK */ + struct BezierKey { Vector2 in_handle; // Relative (x always <0) Vector2 out_handle; // Relative (x always >0) @@ -223,7 +225,7 @@ private: } }; - /* AUDIO TRACK */ + /* ANIMATION TRACK */ struct AnimationTrack : public Track { Vector<TKey<StringName>> values; @@ -264,8 +266,10 @@ private: _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const; double length = 1.0; - real_t step = 0.1; + real_t step = 1.0 / 30; LoopMode loop_mode = LOOP_NONE; + bool capture_included = false; + void _check_capture_included(); void _track_update_hash(int p_track); @@ -390,6 +394,9 @@ public: int add_track(TrackType p_type, int p_at_pos = -1); void remove_track(int p_track); + void set_capture_included(bool p_capture_included); + bool is_capture_included() const; + int get_track_count() const; TrackType track_get_type(int p_track) const; diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp index 79d06c4d66..22666876ae 100644 --- a/scene/resources/animation_library.cpp +++ b/scene/resources/animation_library.cpp @@ -30,6 +30,8 @@ #include "animation_library.h" +#include "scene/scene_string_names.h" + bool AnimationLibrary::is_valid_animation_name(const String &p_name) { return !(p_name.is_empty() || p_name.contains("/") || p_name.contains(":") || p_name.contains(",") || p_name.contains("[")); } @@ -106,7 +108,7 @@ TypedArray<StringName> AnimationLibrary::_get_animation_list() const { } void AnimationLibrary::_animation_changed(const StringName &p_name) { - emit_signal(SNAME("animation_changed"), p_name); + emit_signal(SceneStringName(animation_changed), p_name); } void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const { diff --git a/scene/resources/atlas_texture.cpp b/scene/resources/atlas_texture.cpp index 6aed68849b..ef2f1eb135 100644 --- a/scene/resources/atlas_texture.cpp +++ b/scene/resources/atlas_texture.cpp @@ -30,8 +30,6 @@ #include "atlas_texture.h" -#include "core/core_string_names.h" - int AtlasTexture::get_width() const { if (region.size.width == 0) { if (atlas.is_valid()) { diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index 0185c6ef85..db2564af22 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -86,15 +86,15 @@ void AudioStreamPlaybackWAV::seek(double p_time) { offset = uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS; } -template <typename Depth, bool is_stereo, bool is_ima_adpcm> -void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm) { +template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa> +void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa) { // this function will be compiled branchless by any decent compiler - int32_t final, final_r, next, next_r; + int32_t final = 0, final_r = 0, next = 0, next_r = 0; while (p_amount) { p_amount--; int64_t pos = p_offset >> MIX_FRAC_BITS; - if (is_stereo && !is_ima_adpcm) { + if (is_stereo && !is_ima_adpcm && !is_qoa) { pos <<= 1; } @@ -175,32 +175,77 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, } } else { - final = p_src[pos]; - if (is_stereo) { - final_r = p_src[pos + 1]; - } + if (is_qoa) { + if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates. + for (int i = 0; i < 2; i++) { + // Sign operations prevent triple decoding on backward loops, maxing prevents pop. + uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1); + uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len; + + if (p_qoa->data_ofs != new_data_ofs) { + p_qoa->data_ofs = new_data_ofs; + const uint8_t *src_ptr = (const uint8_t *)base->data; + src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD; + qoa_decode_frame(src_ptr, p_qoa->frame_len, p_qoa->desc, p_qoa->dec, &p_qoa->dec_len); + } - if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */ - final <<= 8; + uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels; + + if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) { + final = p_qoa->dec[dec_idx]; + p_qoa->cache[0] = final; + if (is_stereo) { + final_r = p_qoa->dec[dec_idx + 1]; + p_qoa->cache_r[0] = final_r; + } + } else { + next = p_qoa->dec[dec_idx]; + p_qoa->cache[1] = next; + if (is_stereo) { + next_r = p_qoa->dec[dec_idx + 1]; + p_qoa->cache_r[1] = next_r; + } + } + } + p_qoa->cache_pos = pos; + } else { + final = p_qoa->cache[0]; + if (is_stereo) { + final_r = p_qoa->cache_r[0]; + } + + next = p_qoa->cache[1]; + if (is_stereo) { + next_r = p_qoa->cache_r[1]; + } + } + } else { + final = p_src[pos]; if (is_stereo) { - final_r <<= 8; + final_r = p_src[pos + 1]; } - } - if (is_stereo) { - next = p_src[pos + 2]; - next_r = p_src[pos + 3]; - } else { - next = p_src[pos + 1]; - } + if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */ + final <<= 8; + if (is_stereo) { + final_r <<= 8; + } + } - if constexpr (sizeof(Depth) == 1) { - next <<= 8; if (is_stereo) { - next_r <<= 8; + next = p_src[pos + 2]; + next_r = p_src[pos + 3]; + } else { + next = p_src[pos + 1]; } - } + if constexpr (sizeof(Depth) == 1) { + next <<= 8; + if (is_stereo) { + next_r <<= 8; + } + } + } int32_t frac = int64_t(p_offset & MIX_FRAC_MASK); final = final + ((next - final) * frac >> MIX_FRAC_BITS); @@ -240,6 +285,9 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ case AudioStreamWAV::FORMAT_IMA_ADPCM: len *= 2; break; + case AudioStreamWAV::FORMAT_QOA: + len = qoa.desc->samples * qoa.desc->channels; + break; } if (base->stereo) { @@ -368,27 +416,34 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ switch (base->format) { case AudioStreamWAV::FORMAT_8_BITS: { if (is_stereo) { - do_resample<int8_t, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, true, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } else { - do_resample<int8_t, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, false, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } } break; case AudioStreamWAV::FORMAT_16_BITS: { if (is_stereo) { - do_resample<int16_t, true, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int16_t, true, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } else { - do_resample<int16_t, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int16_t, false, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } } break; case AudioStreamWAV::FORMAT_IMA_ADPCM: { if (is_stereo) { - do_resample<int8_t, true, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, true, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } else { - do_resample<int8_t, false, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm); + do_resample<int8_t, false, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); } } break; + case AudioStreamWAV::FORMAT_QOA: { + if (is_stereo) { + do_resample<uint8_t, true, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); + } else { + do_resample<uint8_t, false, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa); + } + } break; } dst_buff += target; @@ -412,6 +467,16 @@ void AudioStreamPlaybackWAV::tag_used_streams() { AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {} +AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() { + if (qoa.desc) { + memfree(qoa.desc); + } + + if (qoa.dec) { + memfree(qoa.dec); + } +} + ///////////////////// void AudioStreamWAV::set_format(Format p_format) { @@ -475,6 +540,10 @@ double AudioStreamWAV::get_length() const { case AudioStreamWAV::FORMAT_IMA_ADPCM: len *= 2; break; + case AudioStreamWAV::FORMAT_QOA: + qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } }; + qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc); + len = desc.samples * desc.channels; } if (stereo) { @@ -526,8 +595,8 @@ Vector<uint8_t> AudioStreamWAV::get_data() const { } Error AudioStreamWAV::save_to_wav(const String &p_path) { - if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) { - WARN_PRINT("Saving IMA_ADPC samples are not supported yet"); + if (format == AudioStreamWAV::FORMAT_IMA_ADPCM || format == AudioStreamWAV::FORMAT_QOA) { + WARN_PRINT("Saving IMA_ADPCM and QOA samples is not supported yet"); return ERR_UNAVAILABLE; } @@ -548,6 +617,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) { byte_pr_sample = 1; break; case AudioStreamWAV::FORMAT_16_BITS: + case AudioStreamWAV::FORMAT_QOA: byte_pr_sample = 2; break; case AudioStreamWAV::FORMAT_IMA_ADPCM: @@ -590,6 +660,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) { } break; case AudioStreamWAV::FORMAT_16_BITS: + case AudioStreamWAV::FORMAT_QOA: for (unsigned int i = 0; i < data_bytes / 2; i++) { uint16_t data_point = decode_uint16(&read_data[i * 2]); file->store_16(data_point); @@ -607,6 +678,17 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { Ref<AudioStreamPlaybackWAV> sample; sample.instantiate(); sample->base = Ref<AudioStreamWAV>(this); + + if (format == AudioStreamWAV::FORMAT_QOA) { + sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc)); + uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc); + ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>()); + sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc); + int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples); + int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t); + sample->qoa.dec = (int16_t *)memalloc(alloc_len); + } + return sample; } @@ -639,7 +721,7 @@ void AudioStreamWAV::_bind_methods() { ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav); ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end"); @@ -649,6 +731,7 @@ void AudioStreamWAV::_bind_methods() { BIND_ENUM_CONSTANT(FORMAT_8_BITS); BIND_ENUM_CONSTANT(FORMAT_16_BITS); BIND_ENUM_CONSTANT(FORMAT_IMA_ADPCM); + BIND_ENUM_CONSTANT(FORMAT_QOA); BIND_ENUM_CONSTANT(LOOP_DISABLED); BIND_ENUM_CONSTANT(LOOP_FORWARD); diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 959d1ceca0..146142d8a4 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -31,7 +31,11 @@ #ifndef AUDIO_STREAM_WAV_H #define AUDIO_STREAM_WAV_H +#define QOA_IMPLEMENTATION +#define QOA_NO_STDIO + #include "servers/audio/audio_stream.h" +#include "thirdparty/misc/qoa.h" class AudioStreamWAV; @@ -54,14 +58,25 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { int32_t window_ofs = 0; } ima_adpcm[2]; + struct QOA_State { + qoa_desc *desc = nullptr; + uint32_t data_ofs = 0; + uint32_t frame_len = 0; + int16_t *dec = nullptr; + uint32_t dec_len = 0; + int64_t cache_pos = -1; + int16_t cache[2] = { 0, 0 }; + int16_t cache_r[2] = { 0, 0 }; + } qoa; + int64_t offset = 0; int sign = 1; bool active = false; friend class AudioStreamWAV; Ref<AudioStreamWAV> base; - template <typename Depth, bool is_stereo, bool is_ima_adpcm> - void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm); + template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa> + void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa); public: virtual void start(double p_from_pos = 0.0) override; @@ -78,6 +93,7 @@ public: virtual void tag_used_streams() override; AudioStreamPlaybackWAV(); + ~AudioStreamPlaybackWAV(); }; class AudioStreamWAV : public AudioStream { @@ -88,7 +104,8 @@ public: enum Format { FORMAT_8_BITS, FORMAT_16_BITS, - FORMAT_IMA_ADPCM + FORMAT_IMA_ADPCM, + FORMAT_QOA, }; // Keep the ResourceImporterWAV `edit/loop_mode` enum hint in sync with these options. diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp index 31c8e68ea5..76e99aca92 100644 --- a/scene/resources/canvas_item_material.cpp +++ b/scene/resources/canvas_item_material.cpp @@ -33,13 +33,11 @@ #include "core/version.h" Mutex CanvasItemMaterial::material_mutex; -SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr; +SelfList<CanvasItemMaterial>::List CanvasItemMaterial::dirty_materials; HashMap<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData, CanvasItemMaterial::MaterialKey> CanvasItemMaterial::shader_map; CanvasItemMaterial::ShaderNames *CanvasItemMaterial::shader_names = nullptr; void CanvasItemMaterial::init_shaders() { - dirty_materials = memnew(SelfList<CanvasItemMaterial>::List); - shader_names = memnew(ShaderNames); shader_names->particles_anim_h_frames = "particles_anim_h_frames"; @@ -48,14 +46,13 @@ void CanvasItemMaterial::init_shaders() { } void CanvasItemMaterial::finish_shaders() { - memdelete(dirty_materials); + dirty_materials.clear(); + memdelete(shader_names); - dirty_materials = nullptr; + shader_names = nullptr; } void CanvasItemMaterial::_update_shader() { - dirty_materials->remove(&element); - MaterialKey mk = _compute_key(); if (mk.key == current_key.key) { return; //no update required in the end @@ -153,8 +150,9 @@ void CanvasItemMaterial::_update_shader() { void CanvasItemMaterial::flush_changes() { MutexLock lock(material_mutex); - while (dirty_materials->first()) { - dirty_materials->first()->self()->_update_shader(); + while (dirty_materials.first()) { + dirty_materials.first()->self()->_update_shader(); + dirty_materials.first()->remove_from_list(); } } @@ -162,16 +160,10 @@ void CanvasItemMaterial::_queue_shader_change() { MutexLock lock(material_mutex); if (_is_initialized() && !element.in_list()) { - dirty_materials->add(&element); + dirty_materials.add(&element); } } -bool CanvasItemMaterial::_is_shader_dirty() const { - MutexLock lock(material_mutex); - - return element.in_list(); -} - void CanvasItemMaterial::set_blend_mode(BlendMode p_blend_mode) { blend_mode = p_blend_mode; _queue_shader_change(); @@ -288,7 +280,7 @@ CanvasItemMaterial::CanvasItemMaterial() : current_key.invalid_key = 1; - _mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change)); + _mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change), callable_mp(this, &CanvasItemMaterial::_update_shader)); } CanvasItemMaterial::~CanvasItemMaterial() { diff --git a/scene/resources/canvas_item_material.h b/scene/resources/canvas_item_material.h index 7dddd74a31..ef498c2ff6 100644 --- a/scene/resources/canvas_item_material.h +++ b/scene/resources/canvas_item_material.h @@ -98,12 +98,11 @@ private: } static Mutex material_mutex; - static SelfList<CanvasItemMaterial>::List *dirty_materials; + static SelfList<CanvasItemMaterial>::List dirty_materials; SelfList<CanvasItemMaterial> element; void _update_shader(); _FORCE_INLINE_ void _queue_shader_change(); - _FORCE_INLINE_ bool _is_shader_dirty() const; BlendMode blend_mode = BLEND_MODE_MIX; LightMode light_mode = LIGHT_MODE_NORMAL; diff --git a/scene/resources/compressed_texture.h b/scene/resources/compressed_texture.h index 5297d79cfe..439f7c097e 100644 --- a/scene/resources/compressed_texture.h +++ b/scene/resources/compressed_texture.h @@ -113,10 +113,10 @@ public: class ResourceFormatLoaderCompressedTexture2D : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class CompressedTextureLayered : public TextureLayered { @@ -178,10 +178,10 @@ public: class ResourceFormatLoaderCompressedTextureLayered : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class CompressedTexture2DArray : public CompressedTextureLayered { @@ -264,10 +264,10 @@ public: class ResourceFormatLoaderCompressedTexture3D : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; #endif // COMPRESSED_TEXTURE_H diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 2b54acef75..8926eb1d51 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -2029,6 +2029,10 @@ Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const { return nearest; } +PackedVector3Array Curve3D::get_points() const { + return _get_data()["points"]; +} + real_t Curve3D::get_closest_offset(const Vector3 &p_to_point) const { // Brute force method. diff --git a/scene/resources/curve.h b/scene/resources/curve.h index e085dfedbd..6da337a93f 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -345,6 +345,7 @@ public: PackedVector3Array get_baked_up_vectors() const; Vector3 get_closest_point(const Vector3 &p_to_point) const; real_t get_closest_offset(const Vector3 &p_to_point) const; + PackedVector3Array get_points() const; PackedVector3Array tessellate(int p_max_stages = 5, real_t p_tolerance = 4) const; // Useful for display. PackedVector3Array tessellate_even_length(int p_max_stages = 5, real_t p_length = 0.2) const; // Useful for baking. diff --git a/scene/resources/curve_texture.cpp b/scene/resources/curve_texture.cpp index 488a527bbb..4ba5393110 100644 --- a/scene/resources/curve_texture.cpp +++ b/scene/resources/curve_texture.cpp @@ -30,8 +30,6 @@ #include "curve_texture.h" -#include "core/core_string_names.h" - void CurveTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width); diff --git a/scene/resources/gradient_texture.cpp b/scene/resources/gradient_texture.cpp index 327a99c6ad..6ec9422d2d 100644 --- a/scene/resources/gradient_texture.cpp +++ b/scene/resources/gradient_texture.cpp @@ -30,7 +30,6 @@ #include "gradient_texture.h" -#include "core/core_string_names.h" #include "core/math/geometry_2d.h" GradientTexture1D::GradientTexture1D() { @@ -137,6 +136,7 @@ void GradientTexture1D::_update() { texture = RS::get_singleton()->texture_2d_create(image); } } + RS::get_singleton()->texture_set_path(texture, get_path()); } void GradientTexture1D::set_width(int p_width) { @@ -276,6 +276,7 @@ void GradientTexture2D::_update() { } else { texture = RS::get_singleton()->texture_2d_create(image); } + RS::get_singleton()->texture_set_path(texture, get_path()); } float GradientTexture2D::_get_gradient_offset_at(int x, int y) const { diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 0f438c8464..8e49a8b56f 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -35,7 +35,6 @@ #include "core/error/error_macros.h" #include "core/version.h" #include "scene/main/scene_tree.h" -#include "scene/scene_string_names.h" void Material::set_next_pass(const Ref<Material> &p_pass) { for (Ref<Material> pass_child = p_pass; pass_child != nullptr; pass_child = pass_child->get_next_pass()) { @@ -82,24 +81,25 @@ void Material::_validate_property(PropertyInfo &p_property) const { } } -void Material::_mark_initialized(const Callable &p_queue_shader_change_callable) { +void Material::_mark_ready() { + init_state = INIT_STATE_INITIALIZING; +} + +void Material::_mark_initialized(const Callable &p_add_to_dirty_list, const Callable &p_update_shader) { // If this is happening as part of resource loading, it is not safe to queue the update - // as an addition to the dirty list, unless the load is happening on the main thread. - if (ResourceLoader::is_within_load() && Thread::get_caller_id() != Thread::get_main_id()) { + // as an addition to the dirty list. It would be if the load is happening on the main thread, + // but even so we'd rather perform the update directly instead of using the dirty list. + if (ResourceLoader::is_within_load()) { DEV_ASSERT(init_state != INIT_STATE_READY); if (init_state == INIT_STATE_UNINITIALIZED) { // Prevent queueing twice. - // Let's mark this material as being initialized. init_state = INIT_STATE_INITIALIZING; - // Knowing that the ResourceLoader will eventually feed deferred calls into the main message queue, let's do these: - // 1. Queue setting the init state to INIT_STATE_READY finally. - callable_mp(this, &Material::_mark_initialized).bind(p_queue_shader_change_callable).call_deferred(); - // 2. Queue an individual update of this material. - p_queue_shader_change_callable.call_deferred(); + callable_mp(this, &Material::_mark_ready).call_deferred(); + p_update_shader.call_deferred(); } } else { // Straightforward conditions. init_state = INIT_STATE_READY; - p_queue_shader_change_callable.callv(Array()); + p_add_to_dirty_list.call(); } } @@ -603,8 +603,6 @@ void BaseMaterial3D::finish_shaders() { } void BaseMaterial3D::_update_shader() { - dirty_materials.remove(&element); - MaterialKey mk = _compute_key(); if (mk == current_key) { return; //no update required in the end @@ -688,6 +686,9 @@ void BaseMaterial3D::_update_shader() { case BLEND_MODE_MUL: code += "blend_mul"; break; + case BLEND_MODE_PREMULT_ALPHA: + code += "blend_premul_alpha"; + break; case BLEND_MODE_MAX: break; // Internal value, skip. } @@ -1826,6 +1827,11 @@ void fragment() {)"; vec3 detail = mix(ALBEDO.rgb, ALBEDO.rgb * detail_tex.rgb, detail_tex.a); )"; } break; + case BLEND_MODE_PREMULT_ALPHA: { + // This is unlikely to ever be used for detail textures, and in order for it to function in the editor, another bit must be used in MaterialKey, + // but there are only 5 bits left, so I'm going to leave this disabled unless it's actually requested. + //code += "\tvec3 detail = (1.0-detail_tex.a)*ALBEDO.rgb+detail_tex.rgb;\n"; + } break; case BLEND_MODE_MAX: break; // Internal value, skip. } @@ -1854,6 +1860,7 @@ void BaseMaterial3D::flush_changes() { while (dirty_materials.first()) { dirty_materials.first()->self()->_update_shader(); + dirty_materials.first()->remove_from_list(); } } @@ -1865,12 +1872,6 @@ void BaseMaterial3D::_queue_shader_change() { } } -bool BaseMaterial3D::_is_shader_dirty() const { - MutexLock lock(material_mutex); - - return element.in_list(); -} - void BaseMaterial3D::set_albedo(const Color &p_albedo) { albedo = p_albedo; @@ -2823,7 +2824,7 @@ BaseMaterial3D::EmissionOperator BaseMaterial3D::get_emission_operator() const { RID BaseMaterial3D::get_shader_rid() const { MutexLock lock(material_mutex); - if (element.in_list()) { // _is_shader_dirty() would create anoder mutex lock + if (element.in_list()) { ((BaseMaterial3D *)this)->_update_shader(); } ERR_FAIL_COND_V(!shader_map.has(current_key), RID()); @@ -3047,7 +3048,7 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_hash_scale", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_alpha_hash_scale", "get_alpha_hash_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_antialiasing_mode", PROPERTY_HINT_ENUM, "Disabled,Alpha Edge Blend,Alpha Edge Clip"), "set_alpha_antialiasing", "get_alpha_antialiasing"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_antialiasing_edge", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_alpha_antialiasing_edge", "get_alpha_antialiasing_edge"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply"), "set_blend_mode", "get_blend_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply,Premultiplied Alpha"), "set_blend_mode", "get_blend_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mode", PROPERTY_HINT_ENUM, "Back,Front,Disabled"), "set_cull_mode", "get_cull_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "depth_draw_mode", PROPERTY_HINT_ENUM, "Opaque Only,Always,Never"), "set_depth_draw_mode", "get_depth_draw_mode"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_flag", "get_flag", FLAG_DISABLE_DEPTH_TEST); @@ -3276,6 +3277,7 @@ void BaseMaterial3D::_bind_methods() { BIND_ENUM_CONSTANT(BLEND_MODE_ADD); BIND_ENUM_CONSTANT(BLEND_MODE_SUB); BIND_ENUM_CONSTANT(BLEND_MODE_MUL); + BIND_ENUM_CONSTANT(BLEND_MODE_PREMULT_ALPHA); BIND_ENUM_CONSTANT(ALPHA_ANTIALIASING_OFF); BIND_ENUM_CONSTANT(ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE); @@ -3410,7 +3412,7 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : current_key.invalid_key = 1; - _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change)); + _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change), callable_mp(this, &BaseMaterial3D::_update_shader)); } BaseMaterial3D::~BaseMaterial3D() { diff --git a/scene/resources/material.h b/scene/resources/material.h index 073403f71e..50a774e961 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -62,7 +62,8 @@ protected: void _validate_property(PropertyInfo &p_property) const; - void _mark_initialized(const Callable &p_queue_shader_change_callable); + void _mark_ready(); + void _mark_initialized(const Callable &p_add_to_dirty_list, const Callable &p_update_shader); bool _is_initialized() { return init_state == INIT_STATE_READY; } GDVIRTUAL0RC(RID, _get_shader_rid) @@ -219,6 +220,7 @@ public: BLEND_MODE_ADD, BLEND_MODE_SUB, BLEND_MODE_MUL, + BLEND_MODE_PREMULT_ALPHA, BLEND_MODE_MAX }; @@ -465,7 +467,6 @@ private: void _update_shader(); _FORCE_INLINE_ void _queue_shader_change(); - _FORCE_INLINE_ bool _is_shader_dirty() const; bool orm; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index f766d1d2c7..8b5e438aea 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -1880,7 +1880,7 @@ void ArrayMesh::set_blend_shape_name(int p_index, const StringName &p_name) { do { shape_name = String(p_name) + " " + itos(count); count++; - } while (blend_shapes.find(shape_name) != -1); + } while (blend_shapes.has(shape_name)); } blend_shapes.write[p_index] = shape_name; @@ -2086,7 +2086,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo Array arrays = surface_get_arrays(i); s.material = surface_get_material(i); - SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); + SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format); PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; int vc = rvertices.size(); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 94a031947a..0c57c6b7ba 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -32,7 +32,6 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" -#include "core/core_string_names.h" #include "core/io/missing_resource.h" #include "core/io/resource_loader.h" #include "core/templates/local_vector.h" @@ -191,6 +190,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { Node *node = nullptr; MissingNode *missing_node = nullptr; + bool is_inherited_scene = false; if (i == 0 && base_scene_idx >= 0) { // Scene inheritance on root node. @@ -201,7 +201,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { if (p_edit_state != GEN_EDIT_STATE_DISABLED) { node->set_scene_inherited_state(sdata->get_state()); } - + is_inherited_scene = true; } else if (n.instance >= 0) { // Instance a scene into this node. if (n.instance & FLAG_INSTANCE_IS_PLACEHOLDER) { @@ -314,6 +314,16 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr); if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) { + if (!Engine::get_singleton()->is_editor_hint() && node->get_scene_instance_load_placeholder()) { + // We cannot know if the referenced nodes exist yet, so instead of deferring, we write the NodePaths directly. + + uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); + ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); + + node->set(snames[name_idx], props[nprops[j].value], &valid); + continue; + } + uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); @@ -327,7 +337,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); - if (snames[nprops[j].name] == CoreStringNames::get_singleton()->_script) { + if (snames[nprops[j].name] == CoreStringName(script)) { //work around to avoid old script variables from disappearing, should be the proper fix to: //https://github.com/godotengine/godot/issues/2958 @@ -346,6 +356,12 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } else { Variant value = props[nprops[j].value]; + // Making sure that instances of inherited scenes don't share the same + // reference between them. + if (is_inherited_scene) { + value = value.duplicate(true); + } + if (value.get_type() == Variant::OBJECT) { //handle resources that are local to scene by duplicating them if needed Ref<Resource> res = value; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 5acb08de14..01d26a8bed 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -33,14 +33,12 @@ #include "core/version.h" Mutex ParticleProcessMaterial::material_mutex; -SelfList<ParticleProcessMaterial>::List *ParticleProcessMaterial::dirty_materials = nullptr; +SelfList<ParticleProcessMaterial>::List ParticleProcessMaterial::dirty_materials; HashMap<ParticleProcessMaterial::MaterialKey, ParticleProcessMaterial::ShaderData, ParticleProcessMaterial::MaterialKey> ParticleProcessMaterial::shader_map; RBSet<String> ParticleProcessMaterial::min_max_properties; ParticleProcessMaterial::ShaderNames *ParticleProcessMaterial::shader_names = nullptr; void ParticleProcessMaterial::init_shaders() { - dirty_materials = memnew(SelfList<ParticleProcessMaterial>::List); - shader_names = memnew(ShaderNames); shader_names->direction = "direction"; @@ -140,15 +138,13 @@ void ParticleProcessMaterial::init_shaders() { } void ParticleProcessMaterial::finish_shaders() { - memdelete(dirty_materials); - dirty_materials = nullptr; + dirty_materials.clear(); memdelete(shader_names); + shader_names = nullptr; } void ParticleProcessMaterial::_update_shader() { - dirty_materials->remove(&element); - MaterialKey mk = _compute_key(); if (mk == current_key) { return; //no update required in the end @@ -634,10 +630,10 @@ void ParticleProcessMaterial::_update_shader() { if (emission_shape == EMISSION_SHAPE_RING) { code += " \n"; code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n"; - code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n"; - code += " vec3 axis = normalize(emission_ring_axis);\n"; + code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n"; + code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n"; code += " vec3 ortho_axis = vec3(0.0);\n"; - code += " if (axis == vec3(1.0, 0.0, 0.0)) {\n"; + code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n"; code += " ortho_axis = cross(axis, vec3(0.0, 1.0, 0.0));\n"; code += " } else {\n"; code += " ortho_axis = cross(axis, vec3(1.0, 0.0, 0.0));\n"; @@ -741,26 +737,33 @@ void ParticleProcessMaterial::_update_shader() { code += "vec3 get_random_direction_from_spread(inout uint alt_seed, float spread_angle){\n"; code += " float pi = 3.14159;\n"; code += " float degree_to_rad = pi / 180.0;\n"; - code += " vec3 velocity = vec3(0.);\n"; code += " float spread_rad = spread_angle * degree_to_rad;\n"; - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; - code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; - code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; - code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; - code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; - code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; - code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n"; - code += " // rotate spread to direction\n"; - code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; - code += " if (length(binormal) < 0.0001) {\n"; - code += " // direction is parallel to Y. Choose Z as the binormal.\n"; - code += " binormal = vec3(0.0, 0.0, 1.0);\n"; - code += " }\n"; - code += " binormal = normalize(binormal);\n"; - code += " vec3 normal = cross(binormal, direction_nrm);\n"; - code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; - code += " return spread_direction;\n"; - + if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { + // Spread calculation for 2D. + code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n"; + code += " vec3 spread_direction = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; + code += " return spread_direction;\n"; + } else { + // Spread calculation for 3D. + code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; + code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; + code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; + code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; + code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; + code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n"; + code += " // rotate spread to direction\n"; + code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n"; + code += " if (length(binormal) < 0.0001) {\n"; + code += " // direction is parallel to Y. Choose Z as the binormal.\n"; + code += " binormal = vec3(0.0, 0.0, 1.0);\n"; + code += " }\n"; + code += " binormal = normalize(binormal);\n"; + code += " vec3 normal = cross(binormal, direction_nrm);\n"; + code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n"; + code += " return normalize(spread_direction);\n"; + } code += "}\n"; code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform, float delta){\n"; @@ -988,13 +991,15 @@ void ParticleProcessMaterial::_update_shader() { code += " \n"; if (collision_mode == COLLISION_RIGID) { code += " if (COLLIDED) {\n"; - code += " if (length(VELOCITY) > 3.0) {\n"; - code += " TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n"; - code += " VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n"; - code += " VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n"; - code += " } else {\n"; - code += " VELOCITY = vec3(0.0);\n"; - code += " }\n"; + code += " float collision_response = dot(COLLISION_NORMAL, VELOCITY);\n"; + code += " float slide_to_bounce_trigger = step(2.0/clamp(collision_bounce + 1.0, 1.0, 2.0), abs(collision_response));\n"; + code += " TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n"; + code += " // Remove all components of VELOCITY that is not tangent to COLLISION_NORMAL\n"; + code += " VELOCITY -= COLLISION_NORMAL * collision_response;\n"; + code += " // Apply friction only to VELOCITY across the surface (Effectively decouples friction and bounce behavior).\n"; + code += " VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n"; + code += " // Add bounce velocity to VELOCITY\n"; + code += " VELOCITY -= COLLISION_NORMAL * collision_response * (collision_bounce * slide_to_bounce_trigger);\n"; code += " }\n"; } else if (collision_mode == COLLISION_HIDE_ON_CONTACT) { code += " if (COLLIDED) {\n"; @@ -1129,9 +1134,9 @@ void ParticleProcessMaterial::_update_shader() { code += " if (COLLIDED) emit_count = sub_emitter_amount_at_collision;\n"; } break; case SUB_EMITTER_AT_END: { - code += " float unit_delta = DELTA/LIFETIME;\n"; - code += " float end_time = CUSTOM.w * 0.95;\n"; // if we do at the end we might miss it, as it can just get deactivated by emitter - code += " if (CUSTOM.y < end_time && (CUSTOM.y + unit_delta) >= end_time) emit_count = sub_emitter_amount_at_end;\n"; + code += " if ((CUSTOM.y / CUSTOM.w * LIFETIME) > (LIFETIME - DELTA)) {\n"; + code += " emit_count = sub_emitter_amount_at_end;\n"; + code += " }\n"; } break; default: { } @@ -1163,8 +1168,9 @@ void ParticleProcessMaterial::_update_shader() { void ParticleProcessMaterial::flush_changes() { MutexLock lock(material_mutex); - while (dirty_materials->first()) { - dirty_materials->first()->self()->_update_shader(); + while (dirty_materials.first()) { + dirty_materials.first()->self()->_update_shader(); + dirty_materials.first()->remove_from_list(); } } @@ -1172,16 +1178,10 @@ void ParticleProcessMaterial::_queue_shader_change() { MutexLock lock(material_mutex); if (_is_initialized() && !element.in_list()) { - dirty_materials->add(&element); + dirty_materials.add(&element); } } -bool ParticleProcessMaterial::_is_shader_dirty() const { - MutexLock lock(material_mutex); - - return element.in_list(); -} - bool ParticleProcessMaterial::has_min_max_property(const String &p_name) { return min_max_properties.has(p_name); } @@ -2323,7 +2323,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() : current_key.invalid_key = 1; - _mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change)); + _mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change), callable_mp(this, &ParticleProcessMaterial::_update_shader)); } ParticleProcessMaterial::~ParticleProcessMaterial() { diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index 94b2009654..25046b51cd 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -186,7 +186,7 @@ private: } static Mutex material_mutex; - static SelfList<ParticleProcessMaterial>::List *dirty_materials; + static SelfList<ParticleProcessMaterial>::List dirty_materials; struct ShaderNames { StringName direction; @@ -293,7 +293,6 @@ private: void _update_shader(); _FORCE_INLINE_ void _queue_shader_change(); - _FORCE_INLINE_ bool _is_shader_dirty() const; Vector3 direction; float spread = 0.0f; diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp index 918b5c0b41..002db30379 100644 --- a/scene/resources/portable_compressed_texture.cpp +++ b/scene/resources/portable_compressed_texture.cpp @@ -153,14 +153,14 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { Vector<uint8_t> data; if (p_compression_mode == COMPRESSION_MODE_LOSSY) { - data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + data = Image::webp_lossy_packer(i ? p_image->get_image_from_mipmap(i) : p_image, p_lossy_quality); encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2); } else { if (use_webp) { - data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + data = Image::webp_lossless_packer(i ? p_image->get_image_from_mipmap(i) : p_image); encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2); } else { - data = Image::png_packer(p_image->get_image_from_mipmap(i)); + data = Image::png_packer(i ? p_image->get_image_from_mipmap(i) : p_image); encode_uint16(DATA_FORMAT_PNG, buffer.ptrw() + 2); } } diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 6d0796f1b9..2e27ac9198 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -37,9 +37,12 @@ #include "core/object/script_language.h" #include "core/version.h" -// Version 2: changed names for Basis, AABB, Vectors, etc. -// Version 3: new string ID for ext/subresources, breaks forward compat. -#define FORMAT_VERSION 3 +// Version 2: Changed names for Basis, AABB, Vectors, etc. +// Version 3: New string ID for ext/subresources, breaks forward compat. +// Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added. +#define FORMAT_VERSION 4 +// For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray. +#define FORMAT_VERSION_COMPAT 3 #define BINARY_FORMAT_VERSION 4 @@ -273,8 +276,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars if (next_tag.fields.has("groups")) { Array groups = next_tag.fields["groups"]; - for (int i = 0; i < groups.size(); i++) { - packed_scene->get_state()->add_node_group(node_id, packed_scene->get_state()->add_name(groups[i])); + for (const Variant &group : groups) { + packed_scene->get_state()->add_node_group(node_id, packed_scene->get_state()->add_name(group)); } } @@ -352,8 +355,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars } Vector<int> bind_ints; - for (int i = 0; i < binds.size(); i++) { - bind_ints.push_back(packed_scene->get_state()->add_value(binds[i])); + for (const Variant &bind : binds) { + bind_ints.push_back(packed_scene->get_state()->add_value(bind)); } packed_scene->get_state()->add_connection( @@ -844,7 +847,7 @@ void ResourceLoaderText::set_translation_remapped(bool p_remapped) { } ResourceLoaderText::ResourceLoaderText() : - stream(false) {} + stream(false), format_version(FORMAT_VERSION) {} void ResourceLoaderText::get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types) { open(p_f); @@ -953,13 +956,13 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String } if (is_scene) { - fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n"); + fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(format_version) + uid_text + "]\n"); } else { String script_res_text; if (!script_class.is_empty()) { script_res_text = "script_class=\"" + script_class + "\" "; } - fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n"); + fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(format_version) + uid_text + "]\n"); } } @@ -1062,13 +1065,15 @@ void ResourceLoaderText::open(Ref<FileAccess> p_f, bool p_skip_first_tag) { } if (tag.fields.has("format")) { - int fmt = tag.fields["format"]; - if (fmt > FORMAT_VERSION) { + format_version = tag.fields["format"]; + if (format_version > FORMAT_VERSION) { error_text = "Saved with newer format version"; _printerr(); - error = ERR_PARSE_ERROR; + error = ERR_FILE_UNRECOGNIZED; return; } + } else { + format_version = FORMAT_VERSION; } if (tag.name == "gd_scene") { @@ -1951,10 +1956,9 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, } break; case Variant::ARRAY: { Array varray = p_variant; - int len = varray.size(); - for (int i = 0; i < len; i++) { - const Variant &v = varray.get(i); - _find_resources(v); + _find_resources(varray.get_typed_script()); + for (const Variant &var : varray) { + _find_resources(var); } } break; @@ -1970,6 +1974,15 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, _find_resources(v); } } break; + case Variant::PACKED_BYTE_ARRAY: { + // Balance between compatibility and performance. + if (use_compat && p_variant.operator PackedByteArray().size() > 64) { + use_compat = false; + } + } break; + case Variant::PACKED_VECTOR4_ARRAY: { + use_compat = false; + } break; default: { } } @@ -2005,6 +2018,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso } // Save resources. + use_compat = true; // _find_resources() changes this. _find_resources(p_resource, true); if (packed_scene.is_valid()) { @@ -2037,7 +2051,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (load_steps > 1) { title += "load_steps=" + itos(load_steps) + " "; } - title += "format=" + itos(FORMAT_VERSION) + ""; + title += "format=" + itos(use_compat ? FORMAT_VERSION_COMPAT : FORMAT_VERSION) + ""; ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true); @@ -2223,7 +2237,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso } String vars; - VariantWriter::write_to_string(value, vars, _write_resources, this); + VariantWriter::write_to_string(value, vars, _write_resources, this, use_compat); f->store_string(name.property_name_encode() + " = " + vars + "\n"); } } @@ -2287,14 +2301,14 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (!instance_placeholder.is_empty()) { String vars; f->store_string(" instance_placeholder="); - VariantWriter::write_to_string(instance_placeholder, vars, _write_resources, this); + VariantWriter::write_to_string(instance_placeholder, vars, _write_resources, this, use_compat); f->store_string(vars); } if (instance.is_valid()) { String vars; f->store_string(" instance="); - VariantWriter::write_to_string(instance, vars, _write_resources, this); + VariantWriter::write_to_string(instance, vars, _write_resources, this, use_compat); f->store_string(vars); } @@ -2302,7 +2316,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso for (int j = 0; j < state->get_node_property_count(i); j++) { String vars; - VariantWriter::write_to_string(state->get_node_property_value(i, j), vars, _write_resources, this); + VariantWriter::write_to_string(state->get_node_property_value(i, j), vars, _write_resources, this, use_compat); f->store_string(String(state->get_node_property_name(i, j)).property_name_encode() + " = " + vars + "\n"); } @@ -2336,7 +2350,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso f->store_string(connstr); if (binds.size()) { String vars; - VariantWriter::write_to_string(binds, vars, _write_resources, this); + VariantWriter::write_to_string(binds, vars, _write_resources, this, use_compat); f->store_string(" binds= " + vars); } @@ -2368,14 +2382,14 @@ Error ResourceLoaderText::set_uid(Ref<FileAccess> p_f, ResourceUID::ID p_uid) { fw = FileAccess::open(local_path + ".uidren", FileAccess::WRITE); if (is_scene) { - fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); + fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(format_version) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); } else { String script_res_text; if (!script_class.is_empty()) { script_res_text = "script_class=\"" + script_class + "\" "; } - fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); + fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(format_version) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]"); } uint8_t c = f->get_8(); diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index 02898ce984..41363fd975 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -54,6 +54,7 @@ class ResourceLoaderText { }; bool is_scene = false; + int format_version; String res_type; bool ignore_resource_parsing = false; @@ -139,17 +140,17 @@ public: class ResourceFormatLoaderText : public ResourceFormatLoader { public: static ResourceFormatLoaderText *singleton; - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes); - - virtual String get_resource_type(const String &p_path) const; - virtual String get_resource_script_class(const String &p_path) const; - virtual ResourceUID::ID get_resource_uid(const String &p_path) const; - virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); - virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override; + + virtual String get_resource_type(const String &p_path) const override; + virtual String get_resource_script_class(const String &p_path) const override; + virtual ResourceUID::ID get_resource_uid(const String &p_path) const override; + virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override; + virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) override; static Error convert_file_to_binary(const String &p_src_path, const String &p_dst_path); @@ -178,6 +179,7 @@ class ResourceFormatSaverTextInstance { List<Ref<Resource>> saved_resources; HashMap<Ref<Resource>, String> external_resources; HashMap<Ref<Resource>, String> internal_resources; + bool use_compat = true; struct ResourceSort { Ref<Resource> resource; @@ -199,10 +201,10 @@ public: class ResourceFormatSaverText : public ResourceFormatSaver { public: static ResourceFormatSaverText *singleton; - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid); - virtual bool recognize(const Ref<Resource> &p_resource) const; - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid) override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; ResourceFormatSaverText(); }; diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 5b375905cc..dfe5bd4a47 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -31,12 +31,20 @@ #include "shader.h" #include "core/io/file_access.h" -#include "scene/scene_string_names.h" #include "servers/rendering/shader_language.h" #include "servers/rendering/shader_preprocessor.h" #include "servers/rendering_server.h" #include "texture.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_help.h" + +#include "modules/modules_enabled.gen.h" // For regex. +#ifdef MODULE_REGEX_ENABLED +#include "modules/regex/regex.h" +#endif +#endif + Shader::Mode Shader::get_mode() const { return mode; } @@ -121,6 +129,12 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr List<PropertyInfo> local; RenderingServer::get_singleton()->get_shader_parameter_list(shader, &local); +#ifdef TOOLS_ENABLED + DocData::ClassDoc class_doc; + class_doc.name = get_path(); + class_doc.is_script_doc = true; +#endif + for (PropertyInfo &pi : local) { bool is_group = pi.usage == PROPERTY_USAGE_GROUP || pi.usage == PROPERTY_USAGE_SUBGROUP; if (!p_get_groups && is_group) { @@ -136,9 +150,33 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr if (pi.type == Variant::RID) { pi.type = Variant::OBJECT; } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + DocData::PropertyDoc prop_doc; + prop_doc.name = "shader_parameter/" + pi.name; +#ifdef MODULE_REGEX_ENABLED + const RegEx pattern("/\\*\\*\\s([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+/\\s*uniform\\s+\\w+\\s+" + pi.name + "(?=[\\s:;=])"); + Ref<RegExMatch> pattern_ref = pattern.search(code); + if (pattern_ref != nullptr) { + RegExMatch *match = pattern_ref.ptr(); + const RegEx pattern_tip("\\/\\*\\*([\\s\\S]*?)\\*/"); + Ref<RegExMatch> pattern_tip_ref = pattern_tip.search(match->get_string(0)); + RegExMatch *match_tip = pattern_tip_ref.ptr(); + const RegEx pattern_stripped("\\n\\s*\\*\\s*"); + prop_doc.description = pattern_stripped.sub(match_tip->get_string(1), "\n", true); + } +#endif + class_doc.properties.push_back(prop_doc); + } +#endif p_params->push_back(pi); } } +#ifdef TOOLS_ENABLED + if (EditorHelp::get_doc_data() != nullptr && Engine::get_singleton()->is_editor_hint() && !class_doc.name.is_empty() && p_params) { + EditorHelp::get_doc_data()->add_doc(class_doc); + } +#endif } RID Shader::get_rid() const { diff --git a/scene/resources/shader.h b/scene/resources/shader.h index ca889940ef..921143c219 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -96,17 +96,17 @@ VARIANT_ENUM_CAST(Shader::Mode); class ResourceFormatLoaderShader : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class ResourceFormatSaverShader : public ResourceFormatSaver { public: - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; }; #endif // SHADER_H diff --git a/scene/resources/shader_include.h b/scene/resources/shader_include.h index a8949b327e..9fb4271623 100644 --- a/scene/resources/shader_include.h +++ b/scene/resources/shader_include.h @@ -58,17 +58,17 @@ public: class ResourceFormatLoaderShaderInclude : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class ResourceFormatSaverShaderInclude : public ResourceFormatSaver { public: - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; }; #endif // SHADER_INCLUDE_H diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp index 24ed480289..2c1d3d4a4c 100644 --- a/scene/resources/skeleton_profile.cpp +++ b/scene/resources/skeleton_profile.cpp @@ -68,7 +68,7 @@ bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) { } else if (what == "group") { set_group(which, p_value); } else if (what == "require") { - set_require(which, p_value); + set_required(which, p_value); } else { return false; } @@ -113,7 +113,7 @@ bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const { } else if (what == "group") { r_ret = get_group(which); } else if (what == "require") { - r_ret = is_require(which); + r_ret = is_required(which); } else { return false; } @@ -299,7 +299,7 @@ SkeletonProfile::TailDirection SkeletonProfile::get_tail_direction(int p_bone_id return bones[p_bone_idx].tail_direction; } -void SkeletonProfile::set_tail_direction(int p_bone_idx, const TailDirection p_tail_direction) { +void SkeletonProfile::set_tail_direction(int p_bone_idx, TailDirection p_tail_direction) { if (is_read_only) { return; } @@ -328,7 +328,7 @@ Transform3D SkeletonProfile::get_reference_pose(int p_bone_idx) const { return bones[p_bone_idx].reference_pose; } -void SkeletonProfile::set_reference_pose(int p_bone_idx, const Transform3D p_reference_pose) { +void SkeletonProfile::set_reference_pose(int p_bone_idx, const Transform3D &p_reference_pose) { if (is_read_only) { return; } @@ -342,7 +342,7 @@ Vector2 SkeletonProfile::get_handle_offset(int p_bone_idx) const { return bones[p_bone_idx].handle_offset; } -void SkeletonProfile::set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset) { +void SkeletonProfile::set_handle_offset(int p_bone_idx, const Vector2 &p_handle_offset) { if (is_read_only) { return; } @@ -365,17 +365,17 @@ void SkeletonProfile::set_group(int p_bone_idx, const StringName &p_group) { emit_signal("profile_updated"); } -bool SkeletonProfile::is_require(int p_bone_idx) const { +bool SkeletonProfile::is_required(int p_bone_idx) const { ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), false); - return bones[p_bone_idx].require; + return bones[p_bone_idx].required; } -void SkeletonProfile::set_require(int p_bone_idx, const bool p_require) { +void SkeletonProfile::set_required(int p_bone_idx, bool p_required) { if (is_read_only) { return; } ERR_FAIL_INDEX(p_bone_idx, bones.size()); - bones.write[p_bone_idx].require = p_require; + bones.write[p_bone_idx].required = p_required; emit_signal("profile_updated"); } @@ -432,6 +432,9 @@ void SkeletonProfile::_bind_methods() { ClassDB::bind_method(D_METHOD("get_group", "bone_idx"), &SkeletonProfile::get_group); ClassDB::bind_method(D_METHOD("set_group", "bone_idx", "group"), &SkeletonProfile::set_group); + ClassDB::bind_method(D_METHOD("is_required", "bone_idx"), &SkeletonProfile::is_required); + ClassDB::bind_method(D_METHOD("set_required", "bone_idx", "required"), &SkeletonProfile::set_required); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "root_bone", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_root_bone", "get_root_bone"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "scale_base_bone", PROPERTY_HINT_ENUM_SUGGESTION, ""), "set_scale_base_bone", "get_scale_base_bone"); @@ -478,14 +481,14 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[1].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0); bones.write[1].handle_offset = Vector2(0.5, 0.5); bones.write[1].group = "Body"; - bones.write[1].require = true; + bones.write[1].required = true; bones.write[2].bone_name = "Spine"; bones.write[2].bone_parent = "Hips"; bones.write[2].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); bones.write[2].handle_offset = Vector2(0.5, 0.43); bones.write[2].group = "Body"; - bones.write[2].require = true; + bones.write[2].required = true; bones.write[3].bone_name = "Chest"; bones.write[3].bone_parent = "Spine"; @@ -506,7 +509,7 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[5].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); bones.write[5].handle_offset = Vector2(0.5, 0.23); bones.write[5].group = "Body"; - bones.write[5].require = false; + bones.write[5].required = false; bones.write[6].bone_name = "Head"; bones.write[6].bone_parent = "Neck"; @@ -514,7 +517,7 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[6].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0); bones.write[6].handle_offset = Vector2(0.5, 0.18); bones.write[6].group = "Body"; - bones.write[6].require = true; + bones.write[6].required = true; bones.write[7].bone_name = "LeftEye"; bones.write[7].bone_parent = "Head"; @@ -539,21 +542,21 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[10].reference_pose = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.05, 0.1, 0); bones.write[10].handle_offset = Vector2(0.55, 0.235); bones.write[10].group = "Body"; - bones.write[10].require = true; + bones.write[10].required = true; bones.write[11].bone_name = "LeftUpperArm"; bones.write[11].bone_parent = "LeftShoulder"; bones.write[11].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.05, 0); bones.write[11].handle_offset = Vector2(0.6, 0.24); bones.write[11].group = "Body"; - bones.write[11].require = true; + bones.write[11].required = true; bones.write[12].bone_name = "LeftLowerArm"; bones.write[12].bone_parent = "LeftUpperArm"; bones.write[12].reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0, 0.25, 0); bones.write[12].handle_offset = Vector2(0.7, 0.24); bones.write[12].group = "Body"; - bones.write[12].require = true; + bones.write[12].required = true; bones.write[13].bone_name = "LeftHand"; bones.write[13].bone_parent = "LeftLowerArm"; @@ -562,7 +565,7 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[13].reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 0.25, 0); bones.write[13].handle_offset = Vector2(0.82, 0.235); bones.write[13].group = "Body"; - bones.write[13].require = true; + bones.write[13].required = true; bones.write[14].bone_name = "LeftThumbMetacarpal"; bones.write[14].bone_parent = "LeftHand"; @@ -659,21 +662,21 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[29].reference_pose = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.05, 0.1, 0); bones.write[29].handle_offset = Vector2(0.45, 0.235); bones.write[29].group = "Body"; - bones.write[29].require = true; + bones.write[29].required = true; bones.write[30].bone_name = "RightUpperArm"; bones.write[30].bone_parent = "RightShoulder"; bones.write[30].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.05, 0); bones.write[30].handle_offset = Vector2(0.4, 0.24); bones.write[30].group = "Body"; - bones.write[30].require = true; + bones.write[30].required = true; bones.write[31].bone_name = "RightLowerArm"; bones.write[31].bone_parent = "RightUpperArm"; bones.write[31].reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 0.25, 0); bones.write[31].handle_offset = Vector2(0.3, 0.24); bones.write[31].group = "Body"; - bones.write[31].require = true; + bones.write[31].required = true; bones.write[32].bone_name = "RightHand"; bones.write[32].bone_parent = "RightLowerArm"; @@ -682,7 +685,7 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[32].reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0, 0.25, 0); bones.write[32].handle_offset = Vector2(0.18, 0.235); bones.write[32].group = "Body"; - bones.write[32].require = true; + bones.write[32].required = true; bones.write[33].bone_name = "RightThumbMetacarpal"; bones.write[33].bone_parent = "RightHand"; @@ -779,21 +782,21 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[48].reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.1, 0, 0); bones.write[48].handle_offset = Vector2(0.549, 0.49); bones.write[48].group = "Body"; - bones.write[48].require = true; + bones.write[48].required = true; bones.write[49].bone_name = "LeftLowerLeg"; bones.write[49].bone_parent = "LeftUpperLeg"; bones.write[49].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.375, 0); bones.write[49].handle_offset = Vector2(0.548, 0.683); bones.write[49].group = "Body"; - bones.write[49].require = true; + bones.write[49].required = true; bones.write[50].bone_name = "LeftFoot"; bones.write[50].bone_parent = "LeftLowerLeg"; bones.write[50].reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0.375, 0); bones.write[50].handle_offset = Vector2(0.545, 0.9); bones.write[50].group = "Body"; - bones.write[50].require = true; + bones.write[50].required = true; bones.write[51].bone_name = "LeftToes"; bones.write[51].bone_parent = "LeftFoot"; @@ -806,21 +809,21 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[52].reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.1, 0, 0); bones.write[52].handle_offset = Vector2(0.451, 0.49); bones.write[52].group = "Body"; - bones.write[52].require = true; + bones.write[52].required = true; bones.write[53].bone_name = "RightLowerLeg"; bones.write[53].bone_parent = "RightUpperLeg"; bones.write[53].reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.375, 0); bones.write[53].handle_offset = Vector2(0.452, 0.683); bones.write[53].group = "Body"; - bones.write[53].require = true; + bones.write[53].required = true; bones.write[54].bone_name = "RightFoot"; bones.write[54].bone_parent = "RightLowerLeg"; bones.write[54].reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0.375, 0); bones.write[54].handle_offset = Vector2(0.455, 0.9); bones.write[54].group = "Body"; - bones.write[54].require = true; + bones.write[54].required = true; bones.write[55].bone_name = "RightToes"; bones.write[55].bone_parent = "RightFoot"; diff --git a/scene/resources/skeleton_profile.h b/scene/resources/skeleton_profile.h index 143f495c61..b5a3bce940 100644 --- a/scene/resources/skeleton_profile.h +++ b/scene/resources/skeleton_profile.h @@ -61,7 +61,7 @@ protected: Transform3D reference_pose; Vector2 handle_offset; StringName group; - bool require = false; + bool required = false; }; StringName root_bone; @@ -104,22 +104,22 @@ public: void set_bone_parent(int p_bone_idx, const StringName &p_bone_parent); TailDirection get_tail_direction(int p_bone_idx) const; - void set_tail_direction(int p_bone_idx, const TailDirection p_tail_direction); + void set_tail_direction(int p_bone_idx, TailDirection p_tail_direction); StringName get_bone_tail(int p_bone_idx) const; void set_bone_tail(int p_bone_idx, const StringName &p_bone_tail); Transform3D get_reference_pose(int p_bone_idx) const; - void set_reference_pose(int p_bone_idx, const Transform3D p_reference_pose); + void set_reference_pose(int p_bone_idx, const Transform3D &p_reference_pose); Vector2 get_handle_offset(int p_bone_idx) const; - void set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset); + void set_handle_offset(int p_bone_idx, const Vector2 &p_handle_offset); StringName get_group(int p_bone_idx) const; void set_group(int p_bone_idx, const StringName &p_group); - bool is_require(int p_bone_idx) const; - void set_require(int p_bone_idx, const bool p_require); + bool is_required(int p_bone_idx) const; + void set_required(int p_bone_idx, bool p_required); bool has_bone(const StringName &p_bone_name); diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index 42ffa2d25a..6e43ea9b17 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -277,5 +277,5 @@ void SpriteFrames::_bind_methods() { } SpriteFrames::SpriteFrames() { - add_animation(SceneStringNames::get_singleton()->_default); + add_animation(SceneStringName(default_)); } diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 06d53e4e2f..b83be8b6ef 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -802,7 +802,9 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local const uint32_t SurfaceTool::custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 }; const uint32_t SurfaceTool::custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; -void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) { +void SurfaceTool::create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) { + ERR_FAIL_INDEX(RS::ARRAY_WEIGHTS, p_arrays.size()); + ret.clear(); Vector<Vector3> varr = p_arrays[RS::ARRAY_VERTEX]; @@ -932,7 +934,7 @@ void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays } void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) { - create_vertex_array_from_triangle_arrays(arr, *r_vertex, &lformat); + create_vertex_array_from_arrays(arr, *r_vertex, &lformat); ERR_FAIL_COND(r_vertex->size() == 0); //indices @@ -949,9 +951,9 @@ void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_ver } } -void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { +void SurfaceTool::create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type) { clear(); - primitive = Mesh::PRIMITIVE_TRIANGLES; + primitive = p_primitive_type; _create_list_from_arrays(p_arrays, &vertex_array, &index_array, format); for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) { @@ -961,6 +963,10 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { } } +void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { + create_from_arrays(p_arrays, Mesh::PRIMITIVE_TRIANGLES); +} + void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh"); @@ -1377,6 +1383,7 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &SurfaceTool::clear); ClassDB::bind_method(D_METHOD("create_from", "existing", "surface"), &SurfaceTool::create_from); + ClassDB::bind_method(D_METHOD("create_from_arrays", "arrays", "primitive_type"), &SurfaceTool::create_from_arrays, DEFVAL(Mesh::PRIMITIVE_TRIANGLES)); ClassDB::bind_method(D_METHOD("create_from_blend_shape", "existing", "surface", "blend_shape"), &SurfaceTool::create_from_blend_shape); ClassDB::bind_method(D_METHOD("append_from", "existing", "surface", "transform"), &SurfaceTool::append_from); ClassDB::bind_method(D_METHOD("commit", "existing", "flags"), &SurfaceTool::commit, DEFVAL(Variant()), DEFVAL(0)); diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index 9dfb298b9e..a072df5bee 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -219,7 +219,8 @@ public: LocalVector<Vertex> &get_vertex_array() { return vertex_array; } void create_from_triangle_arrays(const Array &p_arrays); - static void create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr); + void create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type = Mesh::PRIMITIVE_TRIANGLES); + static void create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr); Array commit_to_arrays(); void create_from(const Ref<Mesh> &p_existing, int p_surface); void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name); diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index 441c8859cf..6da4215031 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -303,7 +303,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { } // Check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation. - if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'f' || str[j] == 'e') && !in_word && prev_is_number && !is_number) { + if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'f' || str[j] == 'e' || (uint_suffix_enabled && str[j] == 'u')) && !in_word && prev_is_number && !is_number) { is_number = true; is_a_symbol = false; is_char = false; @@ -313,7 +313,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { } } - if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) { + if (!in_word && (is_ascii_alphabet_char(str[j]) || is_underscore(str[j])) && !is_number) { in_word = true; } @@ -617,6 +617,10 @@ void CodeHighlighter::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "color_regions"), "set_color_regions", "get_color_regions"); } +void CodeHighlighter::set_uint_suffix_enabled(bool p_enabled) { + uint_suffix_enabled = p_enabled; +} + void CodeHighlighter::set_number_color(Color p_color) { number_color = p_color; clear_highlighting_cache(); diff --git a/scene/resources/syntax_highlighter.h b/scene/resources/syntax_highlighter.h index cac2807ee2..02afd9045e 100644 --- a/scene/resources/syntax_highlighter.h +++ b/scene/resources/syntax_highlighter.h @@ -93,6 +93,8 @@ private: Color symbol_color; Color number_color; + bool uint_suffix_enabled = false; + protected: static void _bind_methods(); @@ -139,6 +141,8 @@ public: void set_member_variable_color(Color p_color); Color get_member_variable_color() const; + + void set_uint_suffix_enabled(bool p_enabled); }; #endif // SYNTAX_HIGHLIGHTER_H diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index c7c2ddbb18..601e8c52a4 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -348,14 +348,22 @@ void VisualShaderNode::set_disabled(bool p_disabled) { disabled = p_disabled; } -bool VisualShaderNode::is_closable() const { +bool VisualShaderNode::is_deletable() const { return closable; } -void VisualShaderNode::set_closable(bool p_closable) { +void VisualShaderNode::set_deletable(bool p_closable) { closable = p_closable; } +void VisualShaderNode::set_frame(int p_node) { + linked_parent_graph_frame = p_node; +} + +int VisualShaderNode::get_frame() const { + return linked_parent_graph_frame; +} + Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { return Vector<VisualShader::DefaultTextureParam>(); } @@ -433,9 +441,13 @@ void VisualShaderNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_input_values", "values"), &VisualShaderNode::set_default_input_values); ClassDB::bind_method(D_METHOD("get_default_input_values"), &VisualShaderNode::get_default_input_values); + ClassDB::bind_method(D_METHOD("set_frame", "frame"), &VisualShaderNode::set_frame); + ClassDB::bind_method(D_METHOD("get_frame"), &VisualShaderNode::get_frame); + ADD_PROPERTY(PropertyInfo(Variant::INT, "output_port_for_preview"), "set_output_port_for_preview", "get_output_port_for_preview"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_default_input_values", "get_default_input_values"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "expanded_output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_output_ports_expanded", "_get_output_ports_expanded"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "linked_parent_graph_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_frame", "get_frame"); BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR); BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR_INT); @@ -573,12 +585,12 @@ int VisualShaderNodeCustom::get_input_port_count() const { VisualShaderNodeCustom::PortType VisualShaderNodeCustom::get_input_port_type(int p_port) const { ERR_FAIL_INDEX_V(p_port, input_ports.size(), PORT_TYPE_SCALAR); - return (PortType)input_ports[p_port].type; + return (PortType)input_ports.get(p_port).type; } String VisualShaderNodeCustom::get_input_port_name(int p_port) const { ERR_FAIL_INDEX_V(p_port, input_ports.size(), ""); - return input_ports[p_port].name; + return input_ports.get(p_port).name; } int VisualShaderNodeCustom::get_default_input_port(PortType p_type) const { @@ -593,12 +605,12 @@ int VisualShaderNodeCustom::get_output_port_count() const { VisualShaderNodeCustom::PortType VisualShaderNodeCustom::get_output_port_type(int p_port) const { ERR_FAIL_INDEX_V(p_port, output_ports.size(), PORT_TYPE_SCALAR); - return (PortType)output_ports[p_port].type; + return (PortType)output_ports.get(p_port).type; } String VisualShaderNodeCustom::get_output_port_name(int p_port) const { ERR_FAIL_INDEX_V(p_port, output_ports.size(), ""); - return output_ports[p_port].name; + return output_ports.get(p_port).name; } String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { @@ -853,7 +865,7 @@ int VisualShader::get_varyings_count() const { const VisualShader::Varying *VisualShader::get_varying_by_index(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, varyings_list.size(), nullptr); - return &varyings_list[p_idx]; + return &varyings_list.get(p_idx); } void VisualShader::set_varying_mode(const String &p_name, VaryingMode p_mode) { @@ -937,6 +949,9 @@ Vector2 VisualShader::get_node_position(Type p_type, int p_id) const { Ref<VisualShaderNode> VisualShader::get_node(Type p_type, int p_id) const { ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Ref<VisualShaderNode>()); const Graph *g = &graph[p_type]; + if (!g->nodes.has(p_id)) { + return Ref<VisualShaderNode>(); + } ERR_FAIL_COND_V(!g->nodes.has(p_id), Ref<VisualShaderNode>()); return g->nodes[p_id].node; } @@ -1086,6 +1101,42 @@ bool VisualShader::is_nodes_connected_relatively(const Graph *p_graph, int p_nod return result; } +bool VisualShader::_check_reroute_subgraph(Type p_type, int p_target_port_type, int p_reroute_node, List<int> *r_visited_reroute_nodes) const { + const Graph *g = &graph[p_type]; + + // BFS to check whether connecting to the given subgraph (rooted at p_reroute_node) is valid. + List<int> queue; + queue.push_back(p_reroute_node); + if (r_visited_reroute_nodes != nullptr) { + r_visited_reroute_nodes->push_back(p_reroute_node); + } + while (!queue.is_empty()) { + int current_node_id = queue.front()->get(); + VisualShader::Node current_node = g->nodes[current_node_id]; + queue.pop_front(); + for (const int &next_node_id : current_node.next_connected_nodes) { + Ref<VisualShaderNodeReroute> next_vsnode = g->nodes[next_node_id].node; + if (next_vsnode.is_valid()) { + queue.push_back(next_node_id); + if (r_visited_reroute_nodes != nullptr) { + r_visited_reroute_nodes->push_back(next_node_id); + } + continue; + } + // Check whether all ports connected with the reroute node are compatible. + for (const Connection &c : g->connections) { + VisualShaderNode::PortType to_port_type = g->nodes[next_node_id].node->get_input_port_type(c.to_port); + if (c.from_node == current_node_id && + c.to_node == next_node_id && + !is_port_types_compatible(p_target_port_type, to_port_type)) { + return false; + } + } + } + } + return true; +} + bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { ERR_FAIL_INDEX_V(p_type, TYPE_MAX, false); const Graph *g = &graph[p_type]; @@ -1113,7 +1164,12 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port); VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port); - if (!is_port_types_compatible(from_port_type, to_port_type)) { + Ref<VisualShaderNodeReroute> to_node_reroute = g->nodes[p_to_node].node; + if (to_node_reroute.is_valid()) { + if (!_check_reroute_subgraph(p_type, from_port_type, p_to_node)) { + return false; + } + } else if (!is_port_types_compatible(from_port_type, to_port_type)) { return false; } @@ -1126,7 +1182,6 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po if (is_nodes_connected_relatively(g, p_from_node, p_to_node)) { return false; } - return true; } @@ -1134,6 +1189,58 @@ bool VisualShader::is_port_types_compatible(int p_a, int p_b) const { return MAX(0, p_a - (int)VisualShaderNode::PORT_TYPE_BOOLEAN) == (MAX(0, p_b - (int)VisualShaderNode::PORT_TYPE_BOOLEAN)); } +void VisualShader::attach_node_to_frame(Type p_type, int p_node, int p_frame) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + ERR_FAIL_COND(p_frame < 0); + Graph *g = &graph[p_type]; + + ERR_FAIL_COND(!g->nodes.has(p_node)); + + g->nodes[p_node].node->set_frame(p_frame); + + Ref<VisualShaderNodeFrame> vsnode_frame = g->nodes[p_frame].node; + if (vsnode_frame.is_valid()) { + vsnode_frame->add_attached_node(p_node); + } +} + +void VisualShader::detach_node_from_frame(Type p_type, int p_node) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Graph *g = &graph[p_type]; + + ERR_FAIL_COND(!g->nodes.has(p_node)); + + int parent_frame_id = g->nodes[p_node].node->get_frame(); + Ref<VisualShaderNodeFrame> vsnode_frame = g->nodes[parent_frame_id].node; + if (vsnode_frame.is_valid()) { + vsnode_frame->remove_attached_node(p_node); + } + + g->nodes[p_node].node->set_frame(-1); +} + +String VisualShader::get_reroute_parameter_name(Type p_type, int p_reroute_node) const { + ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ""); + const Graph *g = &graph[p_type]; + + ERR_FAIL_COND_V(!g->nodes.has(p_reroute_node), ""); + + const VisualShader::Node *node = &g->nodes[p_reroute_node]; + while (node->prev_connected_nodes.size() > 0) { + int connected_node_id = node->prev_connected_nodes[0]; + node = &g->nodes[connected_node_id]; + Ref<VisualShaderNodeParameter> parameter_node = node->node; + if (parameter_node.is_valid() && parameter_node->get_output_port_type(0) == VisualShaderNode::PORT_TYPE_SAMPLER) { + return parameter_node->get_parameter_name(); + } + Ref<VisualShaderNodeInput> input_node = node->node; + if (input_node.is_valid() && input_node->get_output_port_type(0) == VisualShaderNode::PORT_TYPE_SAMPLER) { + return input_node->get_input_real_name(); + } + } + return ""; +} + void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { ERR_FAIL_INDEX(p_type, TYPE_MAX); Graph *g = &graph[p_type]; @@ -1172,10 +1279,30 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, ERR_FAIL_COND_V(!g->nodes.has(p_to_node), ERR_INVALID_PARAMETER); ERR_FAIL_INDEX_V(p_to_port, g->nodes[p_to_node].node->get_input_port_count(), ERR_INVALID_PARAMETER); + Ref<VisualShaderNodeReroute> from_node_reroute = g->nodes[p_from_node].node; + Ref<VisualShaderNodeReroute> to_node_reroute = g->nodes[p_to_node].node; + + // Allow connection with incompatible port types only if the reroute node isn't connected to anything. VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port); VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port); + bool port_types_are_compatible = is_port_types_compatible(from_port_type, to_port_type); + + if (to_node_reroute.is_valid()) { + List<int> visited_reroute_nodes; + port_types_are_compatible = _check_reroute_subgraph(p_type, from_port_type, p_to_node, &visited_reroute_nodes); + if (port_types_are_compatible) { + // Set the port type of all reroute nodes. + for (const int &E : visited_reroute_nodes) { + Ref<VisualShaderNodeReroute> reroute_node = g->nodes[E].node; + reroute_node->_set_port_type(from_port_type); + } + } + } else if (from_node_reroute.is_valid() && !from_node_reroute->is_input_port_connected(0)) { + from_node_reroute->_set_port_type(to_port_type); + port_types_are_compatible = true; + } - ERR_FAIL_COND_V_MSG(!is_port_types_compatible(from_port_type, to_port_type), ERR_INVALID_PARAMETER, "Incompatible port types (scalar/vec/bool) with transform."); + ERR_FAIL_COND_V_MSG(!port_types_are_compatible, ERR_INVALID_PARAMETER, "Incompatible port types."); for (const Connection &E : g->connections) { if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) { @@ -1418,7 +1545,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN return String(); } - while (port_name.length() && !is_ascii_char(port_name[0])) { + while (port_name.length() && !is_ascii_alphabet_char(port_name[0])) { port_name = port_name.substr(1, port_name.length() - 1); } @@ -1463,7 +1590,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN String VisualShader::validate_parameter_name(const String &p_name, const Ref<VisualShaderNodeParameter> &p_parameter) const { String param_name = p_name; //validate name first - while (param_name.length() && !is_ascii_char(param_name[0])) { + while (param_name.length() && !is_ascii_alphabet_char(param_name[0])) { param_name = param_name.substr(1, param_name.length() - 1); } if (!param_name.is_empty()) { @@ -1859,12 +1986,18 @@ Error VisualShader::_write_node(Type type, StringBuilder *p_global_code, StringB if (in_type == VisualShaderNode::PORT_TYPE_SAMPLER && out_type == VisualShaderNode::PORT_TYPE_SAMPLER) { VisualShaderNode *ptr = const_cast<VisualShaderNode *>(graph[type].nodes[from_node].node.ptr()); + // FIXME: This needs to be refactored at some point. if (ptr->has_method("get_input_real_name")) { inputs[i] = ptr->call("get_input_real_name"); } else if (ptr->has_method("get_parameter_name")) { inputs[i] = ptr->call("get_parameter_name"); } else { - inputs[i] = ""; + Ref<VisualShaderNodeReroute> reroute = graph[type].nodes[from_node].node; + if (reroute.is_valid()) { + inputs[i] = get_reroute_parameter_name(type, from_node); + } else { + inputs[i] = ""; + } } } else if (in_type == out_type) { inputs[i] = src_var; @@ -2433,10 +2566,11 @@ void VisualShader::_update_shader() const { } } - for (int i = 0; i < parameters.size(); i++) { - VisualShaderNodeParameter *parameter = parameters[i]; + int idx = 0; + for (List<VisualShaderNodeParameter *>::Iterator itr = parameters.begin(); itr != parameters.end(); ++itr, ++idx) { + VisualShaderNodeParameter *parameter = *itr; if (used_parameter_names.has(parameter->get_parameter_name())) { - global_code += parameter->generate_global(get_mode(), Type(i), -1); + global_code += parameter->generate_global(get_mode(), Type(idx), -1); const_cast<VisualShaderNodeParameter *>(parameter)->set_global_code_generated(true); } else { const_cast<VisualShaderNodeParameter *>(parameter)->set_global_code_generated(false); @@ -2746,12 +2880,13 @@ void VisualShader::_update_shader() const { const_cast<VisualShader *>(this)->set_code(final_code); for (int i = 0; i < default_tex_params.size(); i++) { - for (int j = 0; j < default_tex_params[i].params.size(); j++) { - const_cast<VisualShader *>(this)->set_default_texture_parameter(default_tex_params[i].name, default_tex_params[i].params[j], j); + int j = 0; + for (List<Ref<Texture2D>>::ConstIterator itr = default_tex_params[i].params.begin(); itr != default_tex_params[i].params.end(); ++itr, ++j) { + const_cast<VisualShader *>(this)->set_default_texture_parameter(default_tex_params[i].name, *itr, j); } } if (previous_code != final_code) { - const_cast<VisualShader *>(this)->emit_signal(SNAME("changed")); + const_cast<VisualShader *>(this)->emit_signal(CoreStringName(changed)); } previous_code = final_code; } @@ -2797,6 +2932,9 @@ void VisualShader::_bind_methods() { ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &VisualShader::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &VisualShader::get_graph_offset); + ClassDB::bind_method(D_METHOD("attach_node_to_frame", "type", "id", "frame"), &VisualShader::attach_node_to_frame); + ClassDB::bind_method(D_METHOD("detach_node_from_frame", "type", "id"), &VisualShader::detach_node_from_frame); + ClassDB::bind_method(D_METHOD("add_varying", "name", "mode", "type"), &VisualShader::add_varying); ClassDB::bind_method(D_METHOD("remove_varying", "name"), &VisualShader::remove_varying); ClassDB::bind_method(D_METHOD("has_varying", "name"), &VisualShader::has_varying); @@ -3636,7 +3774,7 @@ String VisualShaderNodeParameterRef::get_parameter_name_by_index(int p_idx) cons ERR_FAIL_COND_V(!shader_rid.is_valid(), String()); if (p_idx >= 0 && p_idx < parameters[shader_rid].size()) { - return parameters[shader_rid][p_idx].name; + return parameters[shader_rid].get(p_idx).name; } return ""; } @@ -3644,9 +3782,9 @@ String VisualShaderNodeParameterRef::get_parameter_name_by_index(int p_idx) cons VisualShaderNodeParameterRef::ParameterType VisualShaderNodeParameterRef::get_parameter_type_by_name(const String &p_name) const { ERR_FAIL_COND_V(!shader_rid.is_valid(), PARAMETER_TYPE_FLOAT); - for (int i = 0; i < parameters[shader_rid].size(); i++) { - if (parameters[shader_rid][i].name == p_name) { - return parameters[shader_rid][i].type; + for (const VisualShaderNodeParameterRef::Parameter ¶meter : parameters[shader_rid]) { + if (parameter.name == p_name) { + return parameter.type; } } return PARAMETER_TYPE_FLOAT; @@ -3656,7 +3794,7 @@ VisualShaderNodeParameterRef::ParameterType VisualShaderNodeParameterRef::get_pa ERR_FAIL_COND_V(!shader_rid.is_valid(), PARAMETER_TYPE_FLOAT); if (p_idx >= 0 && p_idx < parameters[shader_rid].size()) { - return parameters[shader_rid][p_idx].type; + return parameters[shader_rid].get(p_idx).type; } return PARAMETER_TYPE_FLOAT; } @@ -3665,7 +3803,7 @@ VisualShaderNodeParameterRef::PortType VisualShaderNodeParameterRef::get_port_ty ERR_FAIL_COND_V(!shader_rid.is_valid(), PORT_TYPE_SCALAR); if (p_idx >= 0 && p_idx < parameters[shader_rid].size()) { - switch (parameters[shader_rid][p_idx].type) { + switch (parameters[shader_rid].get(p_idx).type) { case PARAMETER_TYPE_FLOAT: return PORT_TYPE_SCALAR; case PARAMETER_TYPE_INT: @@ -4158,70 +4296,142 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() { set_allow_v_resize(true); } -////////////// Comment +////////////// Frame -String VisualShaderNodeComment::get_caption() const { +String VisualShaderNodeFrame::get_caption() const { return title; } -int VisualShaderNodeComment::get_input_port_count() const { +int VisualShaderNodeFrame::get_input_port_count() const { return 0; } -VisualShaderNodeComment::PortType VisualShaderNodeComment::get_input_port_type(int p_port) const { +VisualShaderNodeFrame::PortType VisualShaderNodeFrame::get_input_port_type(int p_port) const { return PortType::PORT_TYPE_SCALAR; } -String VisualShaderNodeComment::get_input_port_name(int p_port) const { +String VisualShaderNodeFrame::get_input_port_name(int p_port) const { return String(); } -int VisualShaderNodeComment::get_output_port_count() const { +int VisualShaderNodeFrame::get_output_port_count() const { return 0; } -VisualShaderNodeComment::PortType VisualShaderNodeComment::get_output_port_type(int p_port) const { +VisualShaderNodeFrame::PortType VisualShaderNodeFrame::get_output_port_type(int p_port) const { return PortType::PORT_TYPE_SCALAR; } -String VisualShaderNodeComment::get_output_port_name(int p_port) const { +String VisualShaderNodeFrame::get_output_port_name(int p_port) const { return String(); } -void VisualShaderNodeComment::set_title(const String &p_title) { +void VisualShaderNodeFrame::set_title(const String &p_title) { title = p_title; } -String VisualShaderNodeComment::get_title() const { +String VisualShaderNodeFrame::get_title() const { return title; } -void VisualShaderNodeComment::set_description(const String &p_description) { - description = p_description; +void VisualShaderNodeFrame::set_tint_color_enabled(bool p_enabled) { + tint_color_enabled = p_enabled; } -String VisualShaderNodeComment::get_description() const { - return description; +bool VisualShaderNodeFrame::is_tint_color_enabled() const { + return tint_color_enabled; +} + +void VisualShaderNodeFrame::set_tint_color(const Color &p_color) { + tint_color = p_color; +} + +Color VisualShaderNodeFrame::get_tint_color() const { + return tint_color; } -String VisualShaderNodeComment::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { +void VisualShaderNodeFrame::set_autoshrink_enabled(bool p_enable) { + autoshrink = p_enable; +} + +bool VisualShaderNodeFrame::is_autoshrink_enabled() const { + return autoshrink; +} + +String VisualShaderNodeFrame::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { return String(); } -void VisualShaderNodeComment::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_title", "title"), &VisualShaderNodeComment::set_title); - ClassDB::bind_method(D_METHOD("get_title"), &VisualShaderNodeComment::get_title); +void VisualShaderNodeFrame::add_attached_node(int p_node) { + attached_nodes.insert(p_node); +} + +void VisualShaderNodeFrame::remove_attached_node(int p_node) { + attached_nodes.erase(p_node); +} + +void VisualShaderNodeFrame::set_attached_nodes(const PackedInt32Array &p_attached_nodes) { + attached_nodes.clear(); + for (const int &node_id : p_attached_nodes) { + attached_nodes.insert(node_id); + } +} + +PackedInt32Array VisualShaderNodeFrame::get_attached_nodes() const { + PackedInt32Array attached_nodes_arr; + for (const int &node_id : attached_nodes) { + attached_nodes_arr.push_back(node_id); + } + return attached_nodes_arr; +} + +void VisualShaderNodeFrame::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_title", "title"), &VisualShaderNodeFrame::set_title); + ClassDB::bind_method(D_METHOD("get_title"), &VisualShaderNodeFrame::get_title); + + ClassDB::bind_method(D_METHOD("set_tint_color_enabled", "enable"), &VisualShaderNodeFrame::set_tint_color_enabled); + ClassDB::bind_method(D_METHOD("is_tint_color_enabled"), &VisualShaderNodeFrame::is_tint_color_enabled); + ClassDB::bind_method(D_METHOD("set_tint_color", "color"), &VisualShaderNodeFrame::set_tint_color); + ClassDB::bind_method(D_METHOD("get_tint_color"), &VisualShaderNodeFrame::get_tint_color); + + ClassDB::bind_method(D_METHOD("set_autoshrink_enabled", "enable"), &VisualShaderNodeFrame::set_autoshrink_enabled); + ClassDB::bind_method(D_METHOD("is_autoshrink_enabled"), &VisualShaderNodeFrame::is_autoshrink_enabled); + + ClassDB::bind_method(D_METHOD("add_attached_node", "node"), &VisualShaderNodeFrame::add_attached_node); + ClassDB::bind_method(D_METHOD("remove_attached_node", "node"), &VisualShaderNodeFrame::remove_attached_node); + ClassDB::bind_method(D_METHOD("set_attached_nodes", "attached_nodes"), &VisualShaderNodeFrame::set_attached_nodes); + ClassDB::bind_method(D_METHOD("get_attached_nodes"), &VisualShaderNodeFrame::get_attached_nodes); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tint_color_enabled"), "set_tint_color_enabled", "is_tint_color_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_color"), "set_tint_color", "get_tint_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoshrink"), "set_autoshrink_enabled", "is_autoshrink_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "attached_nodes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_attached_nodes", "get_attached_nodes"); +} + +VisualShaderNodeFrame::VisualShaderNodeFrame() { +} + +////////////// Comment (Deprecated) + +#ifndef DISABLE_DEPRECATED +void VisualShaderNodeComment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description); ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description"); } -VisualShaderNodeComment::VisualShaderNodeComment() { +void VisualShaderNodeComment::set_description(const String &p_description) { + description = p_description; } +String VisualShaderNodeComment::get_description() const { + return description; +} +#endif + ////////////// GroupBase void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) { @@ -4710,68 +4920,61 @@ String VisualShaderNodeExpression::get_expression() const { return expression; } +bool VisualShaderNodeExpression::_is_valid_identifier_char(char32_t p_c) const { + return p_c == '_' || (p_c >= 'A' && p_c <= 'Z') || (p_c >= 'a' && p_c <= 'z') || (p_c >= '0' && p_c <= '9'); +} + +String VisualShaderNodeExpression::_replace_port_names(const Vector<Pair<String, String>> &p_pairs, const String &p_expression) const { + String _expression = p_expression; + + for (const Pair<String, String> &pair : p_pairs) { + String from = pair.first; + String to = pair.second; + int search_idx = 0; + int len = from.length(); + + while (true) { + int index = _expression.find(from, search_idx); + if (index == -1) { + break; + } + + int left_index = index - 1; + int right_index = index + len; + bool left_correct = left_index <= 0 || !_is_valid_identifier_char(_expression[left_index]); + bool right_correct = right_index >= _expression.length() || !_is_valid_identifier_char(_expression[right_index]); + + if (left_correct && right_correct) { + _expression = _expression.erase(index, len); + _expression = _expression.insert(index, to); + + search_idx = index + to.length(); + } else { + search_idx = index + len; + } + } + } + + return _expression; +} + String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String _expression = expression; _expression = _expression.insert(0, "\n"); _expression = _expression.replace("\n", "\n "); - static Vector<String> pre_symbols; - if (pre_symbols.is_empty()) { - pre_symbols.push_back(" "); - pre_symbols.push_back(","); - pre_symbols.push_back(";"); - pre_symbols.push_back("{"); - pre_symbols.push_back("["); - pre_symbols.push_back("]"); - pre_symbols.push_back("("); - pre_symbols.push_back(" "); - pre_symbols.push_back("-"); - pre_symbols.push_back("*"); - pre_symbols.push_back("/"); - pre_symbols.push_back("+"); - pre_symbols.push_back("="); - pre_symbols.push_back("&"); - pre_symbols.push_back("|"); - pre_symbols.push_back("!"); - } - - static Vector<String> post_symbols; - if (post_symbols.is_empty()) { - post_symbols.push_back(" "); - post_symbols.push_back("\n"); - post_symbols.push_back(","); - post_symbols.push_back(";"); - post_symbols.push_back("}"); - post_symbols.push_back("["); - post_symbols.push_back("]"); - post_symbols.push_back(")"); - post_symbols.push_back(" "); - post_symbols.push_back("."); - post_symbols.push_back("-"); - post_symbols.push_back("*"); - post_symbols.push_back("/"); - post_symbols.push_back("+"); - post_symbols.push_back("="); - post_symbols.push_back("&"); - post_symbols.push_back("|"); - post_symbols.push_back("!"); - } - + Vector<Pair<String, String>> input_port_names; for (int i = 0; i < get_input_port_count(); i++) { - for (int j = 0; j < pre_symbols.size(); j++) { - for (int k = 0; k < post_symbols.size(); k++) { - _expression = _expression.replace(pre_symbols[j] + get_input_port_name(i) + post_symbols[k], pre_symbols[j] + p_input_vars[i] + post_symbols[k]); - } - } + input_port_names.push_back(Pair<String, String>(get_input_port_name(i), p_input_vars[i])); } + _expression = _replace_port_names(input_port_names, _expression); + + Vector<Pair<String, String>> output_port_names; for (int i = 0; i < get_output_port_count(); i++) { - for (int j = 0; j < pre_symbols.size(); j++) { - for (int k = 0; k < post_symbols.size(); k++) { - _expression = _expression.replace(pre_symbols[j] + get_output_port_name(i) + post_symbols[k], pre_symbols[j] + p_output_vars[i] + post_symbols[k]); - } - } + output_port_names.push_back(Pair<String, String>(get_output_port_name(i), p_output_vars[i])); } + _expression = _replace_port_names(output_port_names, _expression); String output_initializer; @@ -4815,6 +5018,10 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad return code; } +bool VisualShaderNodeExpression::is_output_port_expandable(int p_port) const { + return false; +} + void VisualShaderNodeExpression::_bind_methods() { ClassDB::bind_method(D_METHOD("set_expression", "expression"), &VisualShaderNodeExpression::set_expression); ClassDB::bind_method(D_METHOD("get_expression"), &VisualShaderNodeExpression::get_expression); @@ -4867,15 +5074,15 @@ int VisualShaderNodeVarying::get_varyings_count() const { String VisualShaderNodeVarying::get_varying_name_by_index(int p_idx) const { if (p_idx >= 0 && p_idx < varyings.size()) { - return varyings[p_idx].name; + return varyings.get(p_idx).name; } return ""; } VisualShader::VaryingType VisualShaderNodeVarying::get_varying_type_by_name(const String &p_name) const { - for (int i = 0; i < varyings.size(); i++) { - if (varyings[i].name == p_name) { - return varyings[i].type; + for (const VisualShaderNodeVarying::Varying &varying : varyings) { + if (varying.name == p_name) { + return varying.type; } } return VisualShader::VARYING_TYPE_FLOAT; @@ -4883,15 +5090,15 @@ VisualShader::VaryingType VisualShaderNodeVarying::get_varying_type_by_name(cons VisualShader::VaryingType VisualShaderNodeVarying::get_varying_type_by_index(int p_idx) const { if (p_idx >= 0 && p_idx < varyings.size()) { - return varyings[p_idx].type; + return varyings.get(p_idx).type; } return VisualShader::VARYING_TYPE_FLOAT; } VisualShader::VaryingMode VisualShaderNodeVarying::get_varying_mode_by_name(const String &p_name) const { - for (int i = 0; i < varyings.size(); i++) { - if (varyings[i].name == p_name) { - return varyings[i].mode; + for (const VisualShaderNodeVarying::Varying &varying : varyings) { + if (varying.name == p_name) { + return varying.mode; } } return VisualShader::VARYING_MODE_VERTEX_TO_FRAG_LIGHT; @@ -4899,14 +5106,14 @@ VisualShader::VaryingMode VisualShaderNodeVarying::get_varying_mode_by_name(cons VisualShader::VaryingMode VisualShaderNodeVarying::get_varying_mode_by_index(int p_idx) const { if (p_idx >= 0 && p_idx < varyings.size()) { - return varyings[p_idx].mode; + return varyings.get(p_idx).mode; } return VisualShader::VARYING_MODE_VERTEX_TO_FRAG_LIGHT; } VisualShaderNodeVarying::PortType VisualShaderNodeVarying::get_port_type_by_index(int p_idx) const { if (p_idx >= 0 && p_idx < varyings.size()) { - return get_port_type(varyings[p_idx].type, 0); + return get_port_type(varyings.get(p_idx).type, 0); } return PORT_TYPE_SCALAR; } diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 09ea9a8890..18cdc8342b 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -163,6 +163,8 @@ private: void _input_type_changed(Type p_type, int p_id); bool has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const; + bool _check_reroute_subgraph(Type p_type, int p_target_port_type, int p_reroute_node, List<int> *r_visited_reroute_nodes = nullptr) const; + protected: virtual void _update_shader() const override; static void _bind_methods(); @@ -226,6 +228,11 @@ public: // internal methods void connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); bool is_port_types_compatible(int p_a, int p_b) const; + void attach_node_to_frame(Type p_type, int p_node, int p_frame); + void detach_node_from_frame(Type p_type, int p_node); + + String get_reroute_parameter_name(Type p_type, int p_reroute_node) const; + void rebuild(); void get_node_connections(Type p_type, List<Connection> *r_connections) const; @@ -287,6 +294,7 @@ public: private: int port_preview = -1; + int linked_parent_graph_frame = -1; HashMap<int, bool> connected_input_ports; HashMap<int, int> connected_output_ports; @@ -351,8 +359,11 @@ public: bool is_disabled() const; void set_disabled(bool p_disabled = true); - bool is_closable() const; - void set_closable(bool p_closable = true); + bool is_deletable() const; + void set_deletable(bool p_closable = true); + + void set_frame(int p_node); + int get_frame() const; virtual Vector<StringName> get_editable_properties() const; virtual HashMap<StringName, String> get_editable_properties_names() const; @@ -712,12 +723,15 @@ public: VisualShaderNodeResizableBase(); }; -class VisualShaderNodeComment : public VisualShaderNodeResizableBase { - GDCLASS(VisualShaderNodeComment, VisualShaderNodeResizableBase); +class VisualShaderNodeFrame : public VisualShaderNodeResizableBase { + GDCLASS(VisualShaderNodeFrame, VisualShaderNodeResizableBase); protected: - String title = "Comment"; - String description = ""; + String title = "Title"; + bool tint_color_enabled = false; + Color tint_color = Color(0.3, 0.3, 0.3, 0.75); + bool autoshrink = true; + HashSet<int> attached_nodes; protected: static void _bind_methods(); @@ -738,13 +752,46 @@ public: void set_title(const String &p_title); String get_title() const; + void set_tint_color_enabled(bool p_enable); + bool is_tint_color_enabled() const; + + void set_tint_color(const Color &p_color); + Color get_tint_color() const; + + void set_autoshrink_enabled(bool p_enable); + bool is_autoshrink_enabled() const; + + void add_attached_node(int p_node); + void remove_attached_node(int p_node); + void set_attached_nodes(const PackedInt32Array &p_nodes); + PackedInt32Array get_attached_nodes() const; + + virtual Category get_category() const override { return CATEGORY_NONE; } + + VisualShaderNodeFrame(); +}; + +#ifndef DISABLE_DEPRECATED +// Deprecated, for compatibility only. +class VisualShaderNodeComment : public VisualShaderNodeFrame { + GDCLASS(VisualShaderNodeComment, VisualShaderNodeFrame); + + String description; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override { return "Comment(Deprecated)"; } + + virtual Category get_category() const override { return CATEGORY_NONE; } + void set_description(const String &p_description); String get_description() const; - virtual Category get_category() const override { return CATEGORY_SPECIAL; } - - VisualShaderNodeComment(); + VisualShaderNodeComment() {} }; +#endif class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase { GDCLASS(VisualShaderNodeGroupBase, VisualShaderNodeResizableBase); @@ -819,6 +866,10 @@ public: class VisualShaderNodeExpression : public VisualShaderNodeGroupBase { GDCLASS(VisualShaderNodeExpression, VisualShaderNodeGroupBase); +private: + bool _is_valid_identifier_char(char32_t p_c) const; + String _replace_port_names(const Vector<Pair<String, String>> &p_pairs, const String &p_expression) const; + protected: String expression = ""; @@ -831,6 +882,7 @@ public: String get_expression() const; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + virtual bool is_output_port_expandable(int p_port) const override; VisualShaderNodeExpression(); }; diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index ccd730eef2..d5394c8af5 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -8150,3 +8150,82 @@ VisualShaderNodeRotationByAxis::VisualShaderNodeRotationByAxis() { simple_decl = false; } + +String VisualShaderNodeReroute::get_caption() const { + return "Reroute"; +} + +int VisualShaderNodeReroute::get_input_port_count() const { + return 1; +} + +VisualShaderNodeReroute::PortType VisualShaderNodeReroute::get_input_port_type(int p_port) const { + return input_port_type; +} + +String VisualShaderNodeReroute::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeReroute::get_output_port_count() const { + return 1; +} + +VisualShaderNodeReroute::PortType VisualShaderNodeReroute::get_output_port_type(int p_port) const { + return input_port_type; +} + +String VisualShaderNodeReroute::get_output_port_name(int p_port) const { + return String(); +} + +String VisualShaderNodeReroute::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + String code; + for (int i = 0; i < get_output_port_count(); i++) { + if (input_port_type == PORT_TYPE_SAMPLER) { + continue; + } + + String input = p_input_vars[0]; + if (input.is_empty()) { + code += vformat(" %s;\n", p_output_vars[i]); + continue; + } + code += vformat(" %s = %s;\n", p_output_vars[i], input); + } + return code; +} + +void VisualShaderNodeReroute::_set_port_type(PortType p_type) { + input_port_type = p_type; + switch (p_type) { + case PORT_TYPE_SCALAR: + set_input_port_default_value(0, 0.0); + break; + case PORT_TYPE_VECTOR_2D: + set_input_port_default_value(0, Vector2()); + break; + case PORT_TYPE_VECTOR_3D: + set_input_port_default_value(0, Vector3()); + break; + case PORT_TYPE_VECTOR_4D: + set_input_port_default_value(0, Vector4()); + break; + case PORT_TYPE_TRANSFORM: + set_input_port_default_value(0, Transform3D()); + break; + default: + break; + } +} + +void VisualShaderNodeReroute::_bind_methods() { + ClassDB::bind_method(D_METHOD("_set_port_type", "port_type"), &VisualShaderNodeReroute::_set_port_type); + ClassDB::bind_method(D_METHOD("get_port_type"), &VisualShaderNodeReroute::get_port_type); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "port_type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_port_type", "get_port_type"); +} + +VisualShaderNodeReroute::VisualShaderNodeReroute() { + set_input_port_default_value(0, 0.0); +} diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 0bd0c631b8..a23ae72def 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -3092,4 +3092,35 @@ public: VisualShaderNodeRotationByAxis(); }; +class VisualShaderNodeReroute : public VisualShaderNode { + GDCLASS(VisualShaderNodeReroute, VisualShaderNode); + + PortType input_port_type = PORT_TYPE_SCALAR; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override; + + virtual int get_input_port_count() const override; + virtual PortType get_input_port_type(int p_port) const override; + virtual String get_input_port_name(int p_port) const override; + + virtual int get_output_port_count() const override; + virtual PortType get_output_port_type(int p_port) const override; + virtual String get_output_port_name(int p_port) const override; + virtual bool has_output_port_preview(int p_port) const override { return false; } + virtual bool is_output_port_expandable(int p_port) const override { return false; } + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; + + virtual Category get_category() const override { return CATEGORY_SPECIAL; } + + void _set_port_type(PortType p_type); + PortType get_port_type() const { return input_port_type; } + + VisualShaderNodeReroute(); +}; + #endif // VISUAL_SHADER_NODES_H diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index 5ff1f79a22..cc88876232 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -1641,11 +1641,11 @@ String VisualShaderNodeParticleEmit::generate_code(Shader::Mode p_mode, VisualSh String flags_str; - for (int i = 0; i < flags_arr.size(); i++) { - if (i > 0) { + for (List<String>::ConstIterator itr = flags_arr.begin(); itr != flags_arr.end(); ++itr) { + if (itr != flags_arr.begin()) { flags_str += "|"; } - flags_str += flags_arr[i]; + flags_str += *itr; } if (flags_str.is_empty()) { diff --git a/scene/resources/world_2d.cpp b/scene/resources/world_2d.cpp index e52e5d47bb..ec2c8ddb7b 100644 --- a/scene/resources/world_2d.cpp +++ b/scene/resources/world_2d.cpp @@ -66,6 +66,10 @@ RID World2D::get_navigation_map() const { return navigation_map; } +PhysicsDirectSpaceState2D *World2D::get_direct_space_state() { + return PhysicsServer2D::get_singleton()->space_get_direct_state(get_space()); +} + void World2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_canvas"), &World2D::get_canvas); ClassDB::bind_method(D_METHOD("get_space"), &World2D::get_space); @@ -79,10 +83,6 @@ void World2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState2D", PROPERTY_USAGE_NONE), "", "get_direct_space_state"); } -PhysicsDirectSpaceState2D *World2D::get_direct_space_state() { - return PhysicsServer2D::get_singleton()->space_get_direct_state(get_space()); -} - void World2D::register_viewport(Viewport *p_viewport) { viewports.insert(p_viewport); } diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index cf8ece0d4c..2ee27c95e1 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -33,20 +33,12 @@ SceneStringNames *SceneStringNames::singleton = nullptr; SceneStringNames::SceneStringNames() { - _estimate_cost = StaticCString::create("_estimate_cost"); - _compute_cost = StaticCString::create("_compute_cost"); - resized = StaticCString::create("resized"); - dot = StaticCString::create("."); - doubledot = StaticCString::create(".."); draw = StaticCString::create("draw"); - _draw = StaticCString::create("_draw"); hidden = StaticCString::create("hidden"); visibility_changed = StaticCString::create("visibility_changed"); input_event = StaticCString::create("input_event"); shader = StaticCString::create("shader"); - shader_unshaded = StaticCString::create("shader/unshaded"); - shading_mode = StaticCString::create("shader/shading_mode"); tree_entered = StaticCString::create("tree_entered"); tree_exiting = StaticCString::create("tree_exiting"); tree_exited = StaticCString::create("tree_exited"); @@ -63,7 +55,7 @@ SceneStringNames::SceneStringNames() { RESET = StaticCString::create("RESET"); pose_updated = StaticCString::create("pose_updated"); - bone_pose_changed = StaticCString::create("bone_pose_changed"); + skeleton_updated = StaticCString::create("skeleton_updated"); bone_enabled_changed = StaticCString::create("bone_enabled_changed"); show_rest_only_changed = StaticCString::create("show_rest_only_changed"); @@ -86,103 +78,33 @@ SceneStringNames::SceneStringNames() { area_shape_entered = StaticCString::create("area_shape_entered"); area_shape_exited = StaticCString::create("area_shape_exited"); - _body_inout = StaticCString::create("_body_inout"); - _area_inout = StaticCString::create("_area_inout"); - - idle = StaticCString::create("idle"); - iteration = StaticCString::create("iteration"); update = StaticCString::create("update"); updated = StaticCString::create("updated"); - _physics_process = StaticCString::create("_physics_process"); - _process = StaticCString::create("_process"); - - _enter_tree = StaticCString::create("_enter_tree"); - _exit_tree = StaticCString::create("_exit_tree"); - _enter_world = StaticCString::create("_enter_world"); - _exit_world = StaticCString::create("_exit_world"); _ready = StaticCString::create("_ready"); - _update_scroll = StaticCString::create("_update_scroll"); - _update_xform = StaticCString::create("_update_xform"); - - _structured_text_parser = StaticCString::create("_structured_text_parser"); - - _proxgroup_add = StaticCString::create("_proxgroup_add"); - _proxgroup_remove = StaticCString::create("_proxgroup_remove"); - - grouped = StaticCString::create("grouped"); - ungrouped = StaticCString::create("ungrouped"); - screen_entered = StaticCString::create("screen_entered"); screen_exited = StaticCString::create("screen_exited"); - viewport_entered = StaticCString::create("viewport_entered"); - viewport_exited = StaticCString::create("viewport_exited"); - - camera_entered = StaticCString::create("camera_entered"); - camera_exited = StaticCString::create("camera_exited"); - - _input = StaticCString::create("_input"); - _input_event = StaticCString::create("_input_event"); - gui_input = StaticCString::create("gui_input"); - _gui_input = StaticCString::create("_gui_input"); - - _unhandled_input = StaticCString::create("_unhandled_input"); - _unhandled_key_input = StaticCString::create("_unhandled_key_input"); - - changed = StaticCString::create("changed"); - _shader_changed = StaticCString::create("_shader_changed"); _spatial_editor_group = StaticCString::create("_spatial_editor_group"); _request_gizmo = StaticCString::create("_request_gizmo"); - _set_subgizmo_selection = StaticCString::create("_set_subgizmo_selection"); - _clear_subgizmo_selection = StaticCString::create("_clear_subgizmo_selection"); offset = StaticCString::create("offset"); - unit_offset = StaticCString::create("unit_offset"); rotation_mode = StaticCString::create("rotation_mode"); rotate = StaticCString::create("rotate"); h_offset = StaticCString::create("h_offset"); v_offset = StaticCString::create("v_offset"); - transform_pos = StaticCString::create("position"); - transform_rot = StaticCString::create("rotation"); - transform_scale = StaticCString::create("scale"); - - _update_remote = StaticCString::create("_update_remote"); - _update_pairs = StaticCString::create("_update_pairs"); - - _get_minimum_size = StaticCString::create("_get_minimum_size"); - area_entered = StaticCString::create("area_entered"); area_exited = StaticCString::create("area_exited"); - _has_point = StaticCString::create("_has_point"); - line_separation = StaticCString::create("line_separation"); - _get_drag_data = StaticCString::create("_get_drag_data"); - _drop_data = StaticCString::create("_drop_data"); - _can_drop_data = StaticCString::create("_can_drop_data"); - - baked_light_changed = StaticCString::create("baked_light_changed"); - _baked_light_changed = StaticCString::create("_baked_light_changed"); - - _mouse_enter = StaticCString::create("_mouse_enter"); - _mouse_exit = StaticCString::create("_mouse_exit"); - _mouse_shape_enter = StaticCString::create("_mouse_shape_enter"); - _mouse_shape_exit = StaticCString::create("_mouse_shape_exit"); - - _pressed = StaticCString::create("_pressed"); - _toggled = StaticCString::create("_toggled"); - frame_changed = StaticCString::create("frame_changed"); texture_changed = StaticCString::create("texture_changed"); - playback_speed = StaticCString::create("playback/speed"); - playback_active = StaticCString::create("playback/active"); autoplay = StaticCString::create("autoplay"); blend_times = StaticCString::create("blend_times"); speed = StaticCString::create("speed"); @@ -196,13 +118,9 @@ SceneStringNames::SceneStringNames() { // Audio bus name. Master = StaticCString::create("Master"); - _default = StaticCString::create("default"); + default_ = StaticCString::create("default"); - _window_group = StaticCString::create("_window_group"); - _window_input = StaticCString::create("_window_input"); window_input = StaticCString::create("window_input"); - _window_unhandled_input = StaticCString::create("_window_unhandled_input"); - _get_contents_minimum_size = StaticCString::create("_get_contents_minimum_size"); theme_changed = StaticCString::create("theme_changed"); parameters_base_path = "parameters/"; @@ -210,8 +128,5 @@ SceneStringNames::SceneStringNames() { shader_overrides_group = StaticCString::create("_shader_overrides_group_"); shader_overrides_group_active = StaticCString::create("_shader_overrides_group_active_"); -#ifndef DISABLE_DEPRECATED - use_in_baked_light = StaticCString::create("use_in_baked_light"); - use_dynamic_gi = StaticCString::create("use_dynamic_gi"); -#endif + pressed = StaticCString::create("pressed"); } diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 10b71e2a2a..7ed86cde46 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -51,23 +51,14 @@ class SceneStringNames { public: _FORCE_INLINE_ static SceneStringNames *get_singleton() { return singleton; } - StringName _estimate_cost; - StringName _compute_cost; - StringName resized; - StringName dot; - StringName doubledot; StringName draw; StringName hidden; StringName visibility_changed; StringName input_event; - StringName _input_event; StringName gui_input; - StringName _gui_input; StringName item_rect_changed; StringName shader; - StringName shader_unshaded; - StringName shading_mode; StringName tree_entered; StringName tree_exiting; StringName tree_exited; @@ -75,8 +66,6 @@ public: StringName size_flags_changed; StringName minimum_size_changed; StringName sleeping_state_changed; - StringName idle; - StringName iteration; StringName update; StringName updated; @@ -99,7 +88,7 @@ public: StringName RESET; StringName pose_updated; - StringName bone_pose_changed; + StringName skeleton_updated; StringName bone_enabled_changed; StringName show_rest_only_changed; @@ -111,94 +100,33 @@ public: StringName area_shape_entered; StringName area_shape_exited; - StringName _body_inout; - StringName _area_inout; - - StringName _physics_process; - StringName _process; - StringName _enter_world; - StringName _exit_world; - StringName _enter_tree; - StringName _exit_tree; - StringName _draw; - StringName _input; StringName _ready; - StringName _unhandled_input; - StringName _unhandled_key_input; - - StringName _pressed; - StringName _toggled; - - StringName _update_scroll; - StringName _update_xform; - - StringName _structured_text_parser; - - StringName _proxgroup_add; - StringName _proxgroup_remove; - - StringName grouped; - StringName ungrouped; - - StringName _has_point; - StringName _get_drag_data; - StringName _can_drop_data; - StringName _drop_data; StringName screen_entered; StringName screen_exited; - StringName viewport_entered; - StringName viewport_exited; - StringName camera_entered; - StringName camera_exited; - - StringName changed; - StringName _shader_changed; StringName _spatial_editor_group; StringName _request_gizmo; - StringName _set_subgizmo_selection; - StringName _clear_subgizmo_selection; StringName offset; - StringName unit_offset; StringName rotation_mode; StringName rotate; StringName v_offset; StringName h_offset; - StringName transform_pos; - StringName transform_rot; - StringName transform_scale; - - StringName _update_remote; - StringName _update_pairs; - StringName area_entered; StringName area_exited; - StringName _get_minimum_size; - - StringName baked_light_changed; - StringName _baked_light_changed; - - StringName _mouse_enter; - StringName _mouse_exit; - StringName _mouse_shape_enter; - StringName _mouse_shape_exit; - StringName frame_changed; StringName texture_changed; - StringName playback_speed; - StringName playback_active; StringName autoplay; StringName blend_times; StringName speed; NodePath path_pp; - StringName _default; + StringName default_; // "default", conflict with C++ keyword. StringName node_configuration_warning_changed; @@ -207,21 +135,15 @@ public: StringName Master; StringName parameters_base_path; - - StringName _window_group; - StringName _window_input; - StringName _window_unhandled_input; StringName window_input; - StringName _get_contents_minimum_size; StringName theme_changed; StringName shader_overrides_group; StringName shader_overrides_group_active; -#ifndef DISABLE_DEPRECATED - StringName use_in_baked_light; - StringName use_dynamic_gi; -#endif + StringName pressed; }; +#define SceneStringName(m_name) SceneStringNames::get_singleton()->m_name + #endif // SCENE_STRING_NAMES_H diff --git a/scene/theme/SCsub b/scene/theme/SCsub index 5f62ae4b05..2372d1820a 100644 --- a/scene/theme/SCsub +++ b/scene/theme/SCsub @@ -4,7 +4,6 @@ Import("env") import default_theme_builders - env.add_source_files(env.scene_sources, "*.cpp") SConscript("icons/SCsub") diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index e4c469b752..aa4d669238 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -39,6 +39,7 @@ #include "scene/resources/style_box_flat.h" #include "scene/resources/style_box_line.h" #include "scene/resources/theme.h" +#include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" #include "servers/text_server.h" @@ -160,7 +161,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("normal", "Button", button_normal); theme->set_stylebox("hover", "Button", button_hover); - theme->set_stylebox("pressed", "Button", button_pressed); + theme->set_stylebox(SceneStringName(pressed), "Button", button_pressed); theme->set_stylebox("disabled", "Button", button_disabled); theme->set_stylebox("focus", "Button", focus); @@ -189,7 +190,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // MenuBar theme->set_stylebox("normal", "MenuBar", button_normal); theme->set_stylebox("hover", "MenuBar", button_hover); - theme->set_stylebox("pressed", "MenuBar", button_pressed); + theme->set_stylebox(SceneStringName(pressed), "MenuBar", button_pressed); theme->set_stylebox("disabled", "MenuBar", button_disabled); theme->set_font("font", "MenuBar", Ref<Font>()); @@ -232,7 +233,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("normal", "OptionButton", sb_optbutton_normal); theme->set_stylebox("hover", "OptionButton", sb_optbutton_hover); - theme->set_stylebox("pressed", "OptionButton", sb_optbutton_pressed); + theme->set_stylebox(SceneStringName(pressed), "OptionButton", sb_optbutton_pressed); theme->set_stylebox("disabled", "OptionButton", sb_optbutton_disabled); Ref<StyleBox> sb_optbutton_normal_mirrored = make_flat_stylebox(style_normal_color, 2 * default_margin, default_margin, 2 * default_margin, default_margin); @@ -266,7 +267,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // MenuButton theme->set_stylebox("normal", "MenuButton", button_normal); - theme->set_stylebox("pressed", "MenuButton", button_pressed); + theme->set_stylebox(SceneStringName(pressed), "MenuButton", button_pressed); theme->set_stylebox("hover", "MenuButton", button_hover); theme->set_stylebox("disabled", "MenuButton", button_disabled); theme->set_stylebox("focus", "MenuButton", focus); @@ -292,7 +293,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const cbx_focus->set_content_margin_all(Math::round(4 * scale)); theme->set_stylebox("normal", "CheckBox", cbx_empty); - theme->set_stylebox("pressed", "CheckBox", cbx_empty); + theme->set_stylebox(SceneStringName(pressed), "CheckBox", cbx_empty); theme->set_stylebox("disabled", "CheckBox", cbx_empty); theme->set_stylebox("hover", "CheckBox", cbx_empty); theme->set_stylebox("hover_pressed", "CheckBox", cbx_empty); @@ -328,7 +329,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const cb_empty->set_content_margin_individual(Math::round(6 * scale), Math::round(4 * scale), Math::round(6 * scale), Math::round(4 * scale)); theme->set_stylebox("normal", "CheckButton", cb_empty); - theme->set_stylebox("pressed", "CheckButton", cb_empty); + theme->set_stylebox(SceneStringName(pressed), "CheckButton", cb_empty); theme->set_stylebox("disabled", "CheckButton", cb_empty); theme->set_stylebox("hover", "CheckButton", cb_empty); theme->set_stylebox("hover_pressed", "CheckButton", cb_empty); @@ -373,12 +374,12 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("normal", "FlatButton", flat_button_normal); theme->set_stylebox("hover", "FlatButton", flat_button_normal); - theme->set_stylebox("pressed", "FlatButton", flat_button_pressed); + theme->set_stylebox(SceneStringName(pressed), "FlatButton", flat_button_pressed); theme->set_stylebox("disabled", "FlatButton", flat_button_normal); theme->set_stylebox("normal", "FlatMenuButton", flat_button_normal); theme->set_stylebox("hover", "FlatMenuButton", flat_button_normal); - theme->set_stylebox("pressed", "FlatMenuButton", flat_button_pressed); + theme->set_stylebox(SceneStringName(pressed), "FlatMenuButton", flat_button_pressed); theme->set_stylebox("disabled", "FlatMenuButton", flat_button_normal); // Label @@ -639,7 +640,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Dialogs // AcceptDialog is currently the base dialog, so this defines styles for all extending nodes. - theme->set_stylebox("panel", "AcceptDialog", make_flat_stylebox(style_popup_color, Math::round(8 * scale), Math::round(8 * scale), Math::round(8 * scale), Math::round(8 * scale))); + theme->set_stylebox("panel", "AcceptDialog", make_flat_stylebox(style_popup_color, Math::round(8 * scale), Math::round(8 * scale), Math::round(8 * scale), Math::round(8 * scale), 0)); theme->set_constant("buttons_separation", "AcceptDialog", Math::round(10 * scale)); // File Dialog @@ -757,6 +758,36 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_outline_size", "GraphNodeTitleLabel", Math::round(1 * scale)); theme->set_constant("line_spacing", "GraphNodeTitleLabel", Math::round(3 * scale)); + // GraphFrame + + Ref<StyleBoxFlat> graphframe_sb = make_flat_stylebox(style_pressed_color, 18, 12, 18, 12, 3, true, 2); + graphframe_sb->set_expand_margin(SIDE_TOP, 38 * scale); + graphframe_sb->set_border_color(style_pressed_color); + Ref<StyleBoxFlat> graphframe_sb_selected = graphframe_sb->duplicate(); + graphframe_sb_selected->set_border_color(style_hover_color); + + theme->set_stylebox("panel", "GraphFrame", graphframe_sb); + theme->set_stylebox("panel_selected", "GraphFrame", graphframe_sb_selected); + theme->set_stylebox("titlebar", "GraphFrame", make_empty_stylebox(4, 4, 4, 4)); + theme->set_stylebox("titlebar_selected", "GraphFrame", make_empty_stylebox(4, 4, 4, 4)); + theme->set_icon("resizer", "GraphFrame", icons["resizer_se"]); + theme->set_color("resizer_color", "GraphFrame", control_font_color); + + // GraphFrame's title Label + + theme->set_type_variation("GraphFrameTitleLabel", "Label"); + + theme->set_stylebox("normal", "GraphFrameTitleLabel", memnew(StyleBoxEmpty)); + theme->set_font_size("font_size", "GraphFrameTitleLabel", 22); + theme->set_color("font_color", "GraphFrameTitleLabel", Color(1, 1, 1)); + theme->set_color("font_shadow_color", "GraphFrameTitleLabel", Color(0, 0, 0, 0)); + theme->set_color("font_outline_color", "GraphFrameTitleLabel", Color(1, 1, 1)); + theme->set_constant("shadow_offset_x", "GraphFrameTitleLabel", 1 * scale); + theme->set_constant("shadow_offset_y", "GraphFrameTitleLabel", 1 * scale); + theme->set_constant("outline_size", "GraphFrameTitleLabel", 0); + theme->set_constant("shadow_outline_size", "GraphFrameTitleLabel", 1 * scale); + theme->set_constant("line_spacing", "GraphFrameTitleLabel", 3 * scale); + // Tree theme->set_stylebox("panel", "Tree", make_flat_stylebox(style_normal_color, 4, 4, 4, 5)); @@ -834,7 +865,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("h_separation", "ItemList", Math::round(4 * scale)); theme->set_constant("v_separation", "ItemList", Math::round(4 * scale)); theme->set_constant("icon_margin", "ItemList", Math::round(4 * scale)); - theme->set_constant("line_separation", "ItemList", Math::round(2 * scale)); + theme->set_constant(SceneStringName(line_separation), "ItemList", Math::round(2 * scale)); theme->set_font("font", "ItemList", Ref<Font>()); theme->set_font_size("font_size", "ItemList", -1); @@ -936,8 +967,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("separator", "VSeparator", separator_vertical); theme->set_icon("close", "Icons", icons["close"]); - theme->set_font("normal", "Fonts", Ref<Font>()); - theme->set_font("large", "Fonts", Ref<Font>()); theme->set_constant("separation", "HSeparator", Math::round(4 * scale)); theme->set_constant("separation", "VSeparator", Math::round(4 * scale)); @@ -1022,7 +1051,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("bg", "ColorPickerButton", icons["mini_checkerboard"]); theme->set_stylebox("normal", "ColorPickerButton", button_normal); - theme->set_stylebox("pressed", "ColorPickerButton", button_pressed); + theme->set_stylebox(SceneStringName(pressed), "ColorPickerButton", button_pressed); theme->set_stylebox("hover", "ColorPickerButton", button_hover); theme->set_stylebox("disabled", "ColorPickerButton", button_disabled); theme->set_stylebox("focus", "ColorPickerButton", focus); @@ -1097,7 +1126,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_offset_y", "RichTextLabel", Math::round(1 * scale)); theme->set_constant("shadow_outline_size", "RichTextLabel", Math::round(1 * scale)); - theme->set_constant("line_separation", "RichTextLabel", 0); + theme->set_constant(SceneStringName(line_separation), "RichTextLabel", 0); theme->set_constant("table_h_separation", "RichTextLabel", Math::round(3 * scale)); theme->set_constant("table_v_separation", "RichTextLabel", Math::round(3 * scale)); diff --git a/scene/theme/icons/SCsub b/scene/theme/icons/SCsub index 46133ccceb..1f3b7f6d17 100644 --- a/scene/theme/icons/SCsub +++ b/scene/theme/icons/SCsub @@ -4,7 +4,6 @@ Import("env") import default_theme_icons_builders - env["BUILDERS"]["MakeDefaultThemeIconsBuilder"] = Builder( action=env.Run(default_theme_icons_builders.make_default_theme_icons_action), suffix=".h", diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp index 4c82e5ba5f..7249fd7ba8 100644 --- a/scene/theme/theme_db.cpp +++ b/scene/theme/theme_db.cpp @@ -225,7 +225,7 @@ ThemeContext *ThemeDB::create_theme_context(Node *p_node, List<Ref<Theme>> &p_th theme_contexts[p_node] = context; _propagate_theme_context(p_node, context); - p_node->connect("tree_exited", callable_mp(this, &ThemeDB::destroy_theme_context).bind(p_node)); + p_node->connect(SceneStringName(tree_exited), callable_mp(this, &ThemeDB::destroy_theme_context).bind(p_node)); return context; } @@ -233,7 +233,7 @@ ThemeContext *ThemeDB::create_theme_context(Node *p_node, List<Ref<Theme>> &p_th void ThemeDB::destroy_theme_context(Node *p_node) { ERR_FAIL_COND(!theme_contexts.has(p_node)); - p_node->disconnect("tree_exited", callable_mp(this, &ThemeDB::destroy_theme_context)); + p_node->disconnect(SceneStringName(tree_exited), callable_mp(this, &ThemeDB::destroy_theme_context)); ThemeContext *context = theme_contexts[p_node]; @@ -472,7 +472,7 @@ ThemeDB::~ThemeDB() { } void ThemeContext::_emit_changed() { - emit_signal(SNAME("changed")); + emit_signal(CoreStringName(changed)); } void ThemeContext::set_themes(List<Ref<Theme>> &p_themes) { diff --git a/scene/theme/theme_owner.cpp b/scene/theme/theme_owner.cpp index 8cdffe8c73..a25adddc09 100644 --- a/scene/theme/theme_owner.cpp +++ b/scene/theme/theme_owner.cpp @@ -69,18 +69,18 @@ bool ThemeOwner::has_owner_node() const { void ThemeOwner::set_owner_context(ThemeContext *p_context, bool p_propagate) { ThemeContext *default_context = ThemeDB::get_singleton()->get_default_theme_context(); - if (owner_context && owner_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) { - owner_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed)); - } else if (default_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) { - default_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed)); + if (owner_context && owner_context->is_connected(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) { + owner_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed)); + } else if (default_context->is_connected(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) { + default_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed)); } owner_context = p_context; if (owner_context) { - owner_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed)); + owner_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed)); } else { - default_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed)); + default_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed)); } if (p_propagate) { |