diff options
Diffstat (limited to 'scene')
196 files changed, 5688 insertions, 2692 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 014419573c..1cab29f383 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -463,7 +463,7 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool name = animation; } - ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name)); + ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name)); ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name)); if (frames->get_frame_count(name) == 0) { @@ -541,7 +541,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) { emit_signal(SceneStringName(animation_changed)); - if (frames == nullptr) { + if (frames.is_null()) { animation = StringName(); stop(); ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 8e91dce425..7c60e47e64 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -242,7 +242,7 @@ void AudioStreamPlayer2D::seek(float p_seconds) { void AudioStreamPlayer2D::stop() { setplay.set(-1); - internal->stop(); + internal->stop_basic(); } bool AudioStreamPlayer2D::is_playing() const { @@ -276,10 +276,6 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) { internal->set_playing(p_enable); } -bool AudioStreamPlayer2D::_is_active() const { - return internal->is_active(); -} - void AudioStreamPlayer2D::_validate_property(PropertyInfo &p_property) const { internal->validate_property(p_property); } @@ -385,8 +381,7 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer2D::set_autoplay); ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer2D::is_autoplay_enabled); - ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer2D::_set_playing); - ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer2D::_is_active); + ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer2D::_set_playing); ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &AudioStreamPlayer2D::set_max_distance); ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer2D::get_max_distance); @@ -415,7 +410,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp,suffix:px"), "set_max_distance", "get_max_distance"); @@ -430,7 +425,7 @@ void AudioStreamPlayer2D::_bind_methods() { } AudioStreamPlayer2D::AudioStreamPlayer2D() { - internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer2D::play), true)); + internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer2D::play), callable_mp(this, &AudioStreamPlayer2D::stop), true)); cached_global_panning_strength = GLOBAL_GET("audio/general/2d_panning_strength"); set_hide_clip_children(true); } diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 2c5c6a1a16..dc83775c71 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -114,7 +114,7 @@ Color CanvasModulate::get_color() const { } PackedStringArray CanvasModulate::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_in_canvas && is_visible_in_tree()) { List<Node *> nodes; diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 1d3f1ceada..bfbdb49f22 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -688,6 +688,7 @@ void GPUParticles2D::_notification(int p_what) { RS::get_singleton()->particles_set_speed_scale(particles, 0); } set_process_internal(true); + set_physics_process_internal(true); previous_position = get_global_position(); } break; @@ -711,15 +712,6 @@ void GPUParticles2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - const Vector3 velocity = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) / - get_process_delta_time(); - - if (velocity != previous_velocity) { - RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); - previous_velocity = velocity; - } - previous_position = get_global_position(); - if (one_shot) { time += get_process_delta_time(); if (time > emission_time) { @@ -739,6 +731,19 @@ void GPUParticles2D::_notification(int p_what) { } } } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Update velocity in physics process, so that velocity calculations remain correct + // if the physics tick rate is lower than the rendered framerate (especially without physics interpolation). + const Vector3 velocity = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) / + get_physics_process_delta_time(); + + if (velocity != previous_velocity) { + RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); + previous_velocity = velocity; + } + previous_position = get_global_position(); + } break; } } diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 5ce26b3ed4..50c5873781 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -417,7 +417,7 @@ Vector2 PointLight2D::get_texture_offset() const { } PackedStringArray PointLight2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!texture.is_valid()) { warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property.")); diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 092c987ac0..7c3fb61d04 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -263,7 +263,7 @@ int LightOccluder2D::get_occluder_light_mask() const { } PackedStringArray LightOccluder2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!occluder_polygon.is_valid()) { warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect.")); diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp index 04ba550888..4961e18dc9 100644 --- a/scene/2d/navigation_link_2d.cpp +++ b/scene/2d/navigation_link_2d.cpp @@ -41,6 +41,9 @@ void NavigationLink2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink2D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink2D::is_enabled); + ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationLink2D::set_navigation_map); + ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationLink2D::get_navigation_map); + ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink2D::set_bidirectional); ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink2D::is_bidirectional); @@ -106,12 +109,7 @@ bool NavigationLink2D::_get(const StringName &p_name, Variant &r_ret) const { void NavigationLink2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - if (enabled) { - NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map()); - } - current_global_transform = get_global_transform(); - NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); - NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); + _link_enter_navigation_map(); } break; case NOTIFICATION_TRANSFORM_CHANGED: { @@ -120,36 +118,15 @@ void NavigationLink2D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { set_physics_process_internal(false); - if (is_inside_tree()) { - Transform2D new_global_transform = get_global_transform(); - if (current_global_transform != new_global_transform) { - current_global_transform = new_global_transform; - NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); - NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); - queue_redraw(); - } - } + _link_update_transform(); } break; case NOTIFICATION_EXIT_TREE: { - NavigationServer2D::get_singleton()->link_set_map(link, RID()); + _link_exit_navigation_map(); } break; case NOTIFICATION_DRAW: { #ifdef DEBUG_ENABLED - if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled())) { - Color color; - if (enabled) { - color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_color(); - } else { - color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_disabled_color(); - } - - real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map()); - - draw_line(get_start_position(), get_end_position(), color); - draw_arc(get_start_position(), radius, 0, Math_TAU, 10, color); - draw_arc(get_end_position(), radius, 0, Math_TAU, 10, color); - } + _update_debug_mesh(); #endif // DEBUG_ENABLED } break; } @@ -188,15 +165,32 @@ void NavigationLink2D::set_enabled(bool p_enabled) { enabled = p_enabled; - NavigationServer3D::get_singleton()->link_set_enabled(link, enabled); + NavigationServer2D::get_singleton()->link_set_enabled(link, enabled); #ifdef DEBUG_ENABLED - if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) { - queue_redraw(); - } + queue_redraw(); #endif // DEBUG_ENABLED } +void NavigationLink2D::set_navigation_map(RID p_navigation_map) { + if (map_override == p_navigation_map) { + return; + } + + map_override = p_navigation_map; + + NavigationServer2D::get_singleton()->link_set_map(link, map_override); +} + +RID NavigationLink2D::get_navigation_map() const { + if (map_override.is_valid()) { + return map_override; + } else if (is_inside_tree()) { + return get_world_2d()->get_navigation_map(); + } + return RID(); +} + void NavigationLink2D::set_bidirectional(bool p_bidirectional) { if (bidirectional == p_bidirectional) { return; @@ -255,9 +249,7 @@ void NavigationLink2D::set_start_position(Vector2 p_position) { update_configuration_warnings(); #ifdef DEBUG_ENABLED - if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) { - queue_redraw(); - } + queue_redraw(); #endif // DEBUG_ENABLED } @@ -277,9 +269,7 @@ void NavigationLink2D::set_end_position(Vector2 p_position) { update_configuration_warnings(); #ifdef DEBUG_ENABLED - if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_enabled()) { - queue_redraw(); - } + queue_redraw(); #endif // DEBUG_ENABLED } @@ -338,7 +328,7 @@ void NavigationLink2D::set_travel_cost(real_t p_travel_cost) { } PackedStringArray NavigationLink2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful.")); @@ -347,6 +337,69 @@ PackedStringArray NavigationLink2D::get_configuration_warnings() const { return warnings; } +void NavigationLink2D::_link_enter_navigation_map() { + if (!is_inside_tree()) { + return; + } + + if (map_override.is_valid()) { + NavigationServer2D::get_singleton()->link_set_map(link, map_override); + } else { + NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map()); + } + + current_global_transform = get_global_transform(); + + NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); + NavigationServer2D::get_singleton()->link_set_enabled(link, enabled); + + queue_redraw(); +} + +void NavigationLink2D::_link_exit_navigation_map() { + NavigationServer2D::get_singleton()->link_set_map(link, RID()); +} + +void NavigationLink2D::_link_update_transform() { + if (!is_inside_tree()) { + return; + } + + Transform2D new_global_transform = get_global_transform(); + if (current_global_transform != new_global_transform) { + current_global_transform = new_global_transform; + NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); + queue_redraw(); + } +} + +#ifdef DEBUG_ENABLED +void NavigationLink2D::_update_debug_mesh() { + if (!is_inside_tree()) { + return; + } + + if (!Engine::get_singleton()->is_editor_hint() && !NavigationServer2D::get_singleton()->get_debug_enabled()) { + return; + } + + Color color; + if (enabled) { + color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_color(); + } else { + color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_disabled_color(); + } + + real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map()); + + draw_line(get_start_position(), get_end_position(), color); + draw_arc(get_start_position(), radius, 0, Math_TAU, 10, color); + draw_arc(get_end_position(), radius, 0, Math_TAU, 10, color); +} +#endif // DEBUG_ENABLED + NavigationLink2D::NavigationLink2D() { link = NavigationServer2D::get_singleton()->link_create(); diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h index 2929691c04..c724096607 100644 --- a/scene/2d/navigation_link_2d.h +++ b/scene/2d/navigation_link_2d.h @@ -38,6 +38,7 @@ class NavigationLink2D : public Node2D { bool enabled = true; RID link; + RID map_override; bool bidirectional = true; uint32_t navigation_layers = 1; Vector2 end_position; @@ -47,6 +48,10 @@ class NavigationLink2D : public Node2D { Transform2D current_global_transform; +#ifdef DEBUG_ENABLED + void _update_debug_mesh(); +#endif // DEBUG_ENABLED + protected: static void _bind_methods(); void _notification(int p_what); @@ -66,6 +71,9 @@ public: void set_enabled(bool p_enabled); bool is_enabled() const { return enabled; } + void set_navigation_map(RID p_navigation_map); + RID get_navigation_map() const; + void set_bidirectional(bool p_bidirectional); bool is_bidirectional() const { return bidirectional; } @@ -97,6 +105,11 @@ public: NavigationLink2D(); ~NavigationLink2D(); + +private: + void _link_enter_navigation_map(); + void _link_exit_navigation_map(); + void _link_update_transform(); }; #endif // NAVIGATION_LINK_2D_H diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 9b3c7bb9ea..f65a3c0ecc 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -452,6 +452,7 @@ void NavigationRegion2D::_update_debug_mesh() { const Transform2D region_gt = get_global_transform(); rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas()); + rs->canvas_item_set_z_index(debug_instance_rid, RS::CANVAS_ITEM_Z_MAX - 2); rs->canvas_item_set_transform(debug_instance_rid, region_gt); if (!debug_mesh_dirty) { diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp index 9dd9d4a376..fdb2d2cdd0 100644 --- a/scene/2d/parallax_2d.cpp +++ b/scene/2d/parallax_2d.cpp @@ -47,9 +47,18 @@ void Parallax2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - autoscroll_offset += autoscroll * get_process_delta_time(); - autoscroll_offset = autoscroll_offset.posmodv(repeat_size); + Point2 offset = scroll_offset; + offset += autoscroll * get_process_delta_time(); + if (repeat_size.x) { + offset.x = Math::fposmod(offset.x, repeat_size.x); + } + + if (repeat_size.y) { + offset.y = Math::fposmod(offset.y, repeat_size.y); + } + + scroll_offset = offset; _update_scroll(); } break; @@ -106,14 +115,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 * get_scale().x); + real_t mod = Math::fposmod(scroll_ofs.x - scroll_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 * get_scale().y); + real_t mod = Math::fposmod(scroll_ofs.y - scroll_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; @@ -193,7 +202,6 @@ void Parallax2D::set_autoscroll(const Point2 &p_autoscroll) { } autoscroll = p_autoscroll; - autoscroll_offset = Point2(); _update_process(); _update_scroll(); diff --git a/scene/2d/parallax_2d.h b/scene/2d/parallax_2d.h index 5fbc3a20c8..f15e3fa9ff 100644 --- a/scene/2d/parallax_2d.h +++ b/scene/2d/parallax_2d.h @@ -47,7 +47,6 @@ class Parallax2D : public Node2D { Point2 limit_begin = Point2(-DEFAULT_LIMIT, -DEFAULT_LIMIT); Point2 limit_end = Point2(DEFAULT_LIMIT, DEFAULT_LIMIT); Point2 autoscroll; - Point2 autoscroll_offset; bool follow_viewport = true; bool ignore_camera_scroll = false; diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index dfe321a435..24f261deb6 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -78,13 +78,7 @@ void ParallaxLayer::_update_mirroring() { } void ParallaxLayer::set_mirroring(const Size2 &p_mirroring) { - mirroring = p_mirroring; - if (mirroring.x < 0) { - mirroring.x = 0; - } - if (mirroring.y < 0) { - mirroring.y = 0; - } + mirroring = p_mirroring.maxf(0); _update_mirroring(); } @@ -139,7 +133,7 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s } PackedStringArray ParallaxLayer::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!Object::cast_to<ParallaxBackground>(get_parent())) { warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node.")); diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 282d14da5d..5813ab02e3 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -288,7 +288,7 @@ void PathFollow2D::_validate_property(PropertyInfo &p_property) const { } PackedStringArray PathFollow2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path2D>(get_parent())) { @@ -378,9 +378,10 @@ real_t PathFollow2D::get_progress() const { } void PathFollow2D::set_progress_ratio(real_t p_ratio) { - if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { - set_progress(p_ratio * path->get_curve()->get_baked_length()); - } + ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow2D that is the child of a Path2D which is itself part of the scene tree."); + ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow2D that does not have a Curve."); + ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow2D that has a 0 length curve."); + set_progress(p_ratio * path->get_curve()->get_baked_length()); } real_t PathFollow2D::get_progress_ratio() const { diff --git a/scene/2d/physics/collision_object_2d.cpp b/scene/2d/physics/collision_object_2d.cpp index 00b6085f0c..27ee6b883c 100644 --- a/scene/2d/physics/collision_object_2d.cpp +++ b/scene/2d/physics/collision_object_2d.cpp @@ -582,7 +582,7 @@ void CollisionObject2D::_update_pickable() { } PackedStringArray CollisionObject2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape.")); diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp index a9b47ef4d4..b49badac1f 100644 --- a/scene/2d/physics/collision_polygon_2d.cpp +++ b/scene/2d/physics/collision_polygon_2d.cpp @@ -232,7 +232,7 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl #endif PackedStringArray CollisionPolygon2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); diff --git a/scene/2d/physics/collision_shape_2d.cpp b/scene/2d/physics/collision_shape_2d.cpp index 6fc29ffe63..bdd0d06b5e 100644 --- a/scene/2d/physics/collision_shape_2d.cpp +++ b/scene/2d/physics/collision_shape_2d.cpp @@ -174,7 +174,7 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double } PackedStringArray CollisionShape2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/2d/physics/physical_bone_2d.cpp b/scene/2d/physics/physical_bone_2d.cpp index 77bb8c24b8..19274c8084 100644 --- a/scene/2d/physics/physical_bone_2d.cpp +++ b/scene/2d/physics/physical_bone_2d.cpp @@ -107,7 +107,7 @@ void PhysicalBone2D::_find_joint_child() { } PackedStringArray PhysicalBone2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = RigidBody2D::get_configuration_warnings(); if (!parent_skeleton) { warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); diff --git a/scene/2d/physics/shape_cast_2d.cpp b/scene/2d/physics/shape_cast_2d.cpp index 00be84b622..b92978bcad 100644 --- a/scene/2d/physics/shape_cast_2d.cpp +++ b/scene/2d/physics/shape_cast_2d.cpp @@ -382,7 +382,7 @@ bool ShapeCast2D::is_collide_with_bodies_enabled() const { return collide_with_bodies; } -Array ShapeCast2D::_get_collision_result() const { +Array ShapeCast2D::get_collision_result() const { Array ret; for (int i = 0; i < result.size(); ++i) { @@ -464,7 +464,7 @@ void ShapeCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast2D::set_collide_with_bodies); ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast2D::is_collide_with_bodies_enabled); - ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast2D::_get_collision_result); + ClassDB::bind_method(D_METHOD("get_collision_result"), &ShapeCast2D::get_collision_result); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape"); @@ -473,7 +473,7 @@ void ShapeCast2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:px"), "set_margin", "get_margin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "get_collision_result"); ADD_GROUP("Collide With", "collide_with"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled"); diff --git a/scene/2d/physics/shape_cast_2d.h b/scene/2d/physics/shape_cast_2d.h index 6b8fd5b798..d866dd4edb 100644 --- a/scene/2d/physics/shape_cast_2d.h +++ b/scene/2d/physics/shape_cast_2d.h @@ -60,7 +60,6 @@ class ShapeCast2D : public Node2D { real_t collision_safe_fraction = 1.0; real_t collision_unsafe_fraction = 1.0; - Array _get_collision_result() const; void _shape_changed(); protected: @@ -102,6 +101,7 @@ public: void force_shapecast_update(); bool is_colliding() const; + Array get_collision_result() const; int get_collision_count() const; Object *get_collider(int p_idx) const; RID get_collider_rid(int p_idx) const; diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index 4266060466..42f7a75c0a 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -682,7 +682,7 @@ void Polygon2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "uv"), "set_uv", "get_uv"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "vertex_colors"), "set_vertex_colors", "get_vertex_colors"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons"), "set_polygons", "get_polygons"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_bones", "_get_bones"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_bones", "_get_bones"); ADD_PROPERTY(PropertyInfo(Variant::INT, "internal_vertex_count", PROPERTY_HINT_RANGE, "0,1000"), "set_internal_vertex_count", "get_internal_vertex_count"); } diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index 5ea5098475..1816a3409b 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -114,6 +114,16 @@ void RemoteTransform2D::_notification(int p_what) { _update_cache(); } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (cache.is_valid()) { + _update_remote(); + Node2D *n = Object::cast_to<Node2D>(ObjectDB::get_instance(cache)); + if (n) { + n->reset_physics_interpolation(); + } + } + } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: case NOTIFICATION_TRANSFORM_CHANGED: { if (!is_inside_tree()) { @@ -201,7 +211,7 @@ void RemoteTransform2D::force_update_cache() { } PackedStringArray RemoteTransform2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { warnings.push_back(RTR("Path property must point to a valid Node2D node to work.")); diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 8aa50668eb..90bfb4c84c 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -39,19 +39,17 @@ #endif //TOOLS_ENABLED bool Bone2D::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("auto_calculate_length_and_angle")) { + if (p_path == SNAME("auto_calculate_length_and_angle")) { set_autocalculate_length_and_angle(p_value); - } else if (path.begins_with("length")) { + } else if (p_path == SNAME("length")) { set_length(p_value); - } else if (path.begins_with("bone_angle")) { + } else if (p_path == SNAME("bone_angle")) { set_bone_angle(Math::deg_to_rad(real_t(p_value))); - } else if (path.begins_with("default_length")) { + } else if (p_path == SNAME("default_length")) { set_length(p_value); } #ifdef TOOLS_ENABLED - else if (path.begins_with("editor_settings/show_bone_gizmo")) { + else if (p_path == SNAME("editor_settings/show_bone_gizmo")) { _editor_set_show_bone_gizmo(p_value); } #endif // TOOLS_ENABLED @@ -63,19 +61,17 @@ bool Bone2D::_set(const StringName &p_path, const Variant &p_value) { } bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("auto_calculate_length_and_angle")) { + if (p_path == SNAME("auto_calculate_length_and_angle")) { r_ret = get_autocalculate_length_and_angle(); - } else if (path.begins_with("length")) { + } else if (p_path == SNAME("length")) { r_ret = get_length(); - } else if (path.begins_with("bone_angle")) { + } else if (p_path == SNAME("bone_angle")) { r_ret = Math::rad_to_deg(get_bone_angle()); - } else if (path.begins_with("default_length")) { + } else if (p_path == SNAME("default_length")) { r_ret = get_length(); } #ifdef TOOLS_ENABLED - else if (path.begins_with("editor_settings/show_bone_gizmo")) { + else if (p_path == SNAME("editor_settings/show_bone_gizmo")) { r_ret = _editor_get_show_bone_gizmo(); } #endif // TOOLS_ENABLED @@ -330,9 +326,7 @@ bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p rel = (p_other_bone->get_global_position() - get_global_position()); rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation } else { - real_t angle_to_use = get_rotation() + bone_angle; - rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y)); - rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation + rel = Vector2(Math::cos(bone_angle), Math::sin(bone_angle)) * length * get_global_scale(); } Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width; @@ -418,7 +412,7 @@ int Bone2D::get_index_in_skeleton() const { } PackedStringArray Bone2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!skeleton) { if (parent_bone) { warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node.")); @@ -518,23 +512,19 @@ Bone2D::~Bone2D() { ////////////////////////////////////// bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("modification_stack")) { + if (p_path == SNAME("modification_stack")) { set_modification_stack(p_value); return true; } - return true; + return false; } bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("modification_stack")) { + if (p_path == SNAME("modification_stack")) { r_ret = get_modification_stack(); return true; } - return true; + return false; } void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const { diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 48ade1e5cc..45cfb8cf33 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -532,6 +532,18 @@ TileData *TileMap::get_cell_tile_data(int p_layer, const Vector2i &p_coords, boo } } +bool TileMap::is_cell_flipped_h(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + return get_cell_alternative_tile(p_layer, p_coords, p_use_proxies) & TileSetAtlasSource::TRANSFORM_FLIP_H; +} + +bool TileMap::is_cell_flipped_v(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + return get_cell_alternative_tile(p_layer, p_coords, p_use_proxies) & TileSetAtlasSource::TRANSFORM_FLIP_V; +} + +bool TileMap::is_cell_transposed(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + return get_cell_alternative_tile(p_layer, p_coords, p_use_proxies) & TileSetAtlasSource::TRANSFORM_TRANSPOSE; +} + Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { TILEMAP_CALL_FOR_LAYER_V(p_layer, Ref<TileMapPattern>(), get_pattern, p_coords_array); } @@ -815,7 +827,9 @@ TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) { } PackedStringArray TileMap::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); + + warnings.push_back(RTR("The TileMap node is deprecated as it is superseded by the use of multiple TileMapLayer nodes.\nTo convert a TileMap to a set of TileMapLayer nodes, open the TileMap bottom panel with this node selected, click the toolbox icon in the top-right corner and choose \"Extract TileMap layers as individual TileMapLayer nodes\".")); // Retrieve the set of Z index values with a Y-sorted layer. RBSet<int> y_sorted_z_index; @@ -924,6 +938,10 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "layer", "coords", "use_proxies"), &TileMap::get_cell_alternative_tile, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_cell_tile_data", "layer", "coords", "use_proxies"), &TileMap::get_cell_tile_data, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_cell_flipped_h", "layer", "coords", "use_proxies"), &TileMap::is_cell_flipped_h, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_cell_flipped_v", "layer", "coords", "use_proxies"), &TileMap::is_cell_flipped_v, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_cell_transposed", "layer", "coords", "use_proxies"), &TileMap::is_cell_transposed, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid); ClassDB::bind_method(D_METHOD("get_layer_for_body_rid", "body"), &TileMap::get_layer_for_body_rid); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 690102f730..142dc1193f 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -167,6 +167,10 @@ public: // Helper method to make accessing the data easier. TileData *get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + bool is_cell_flipped_h(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + bool is_cell_flipped_v(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + bool is_cell_transposed(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + // Patterns. Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array); Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern); diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 437790bb99..ebb03e4e73 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -411,7 +411,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) { } // ----------- Occluders processing ----------- - if (forced_cleanup) { + if (forced_cleanup || !occlusion_enabled) { // Clean everything. for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { _rendering_occluders_clear_cell(kv.value); @@ -433,7 +433,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) { // ----------- // Mark the rendering state as up to date. - _rendering_was_cleaned_up = forced_cleanup; + _rendering_was_cleaned_up = forced_cleanup || !occlusion_enabled; } void TileMapLayer::_rendering_notification(int p_what) { @@ -443,13 +443,15 @@ void TileMapLayer::_rendering_notification(int p_what) { Transform2D tilemap_xform = get_global_transform(); 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()) { - continue; + for (const LocalVector<RID> &polygons : cell_data.occluders) { + for (const RID &rid : polygons) { + if (rid.is_null()) { + continue; + } + Transform2D xform(0, tile_set->map_to_local(kv.key)); + rs->canvas_light_occluder_attach_to_canvas(rid, get_canvas()); + rs->canvas_light_occluder_set_transform(rid, tilemap_xform * xform); } - Transform2D xform(0, tile_set->map_to_local(kv.key)); - rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); - rs->canvas_light_occluder_set_transform(occluder, tilemap_xform * xform); } } } @@ -557,8 +559,10 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { RenderingServer *rs = RenderingServer::get_singleton(); // Free the occluders. - for (const RID &rid : r_cell_data.occluders) { - rs->free(rid); + for (const LocalVector<RID> &polygons : r_cell_data.occluders) { + for (const RID &rid : polygons) { + rs->free(rid); + } } r_cell_data.occluders.clear(); } @@ -566,11 +570,12 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { RenderingServer *rs = RenderingServer::get_singleton(); - // Free unused occluders then resize the occluders array. + // Free unused occluders then resize the occluder array. for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) { - RID occluder_id = r_cell_data.occluders[i]; - if (occluder_id.is_valid()) { - rs->free(occluder_id); + for (const RID &occluder_id : r_cell_data.occluders[i]) { + if (occluder_id.is_valid()) { + rs->free(occluder_id); + } } } r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count()); @@ -598,30 +603,42 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { // Create, update or clear occluders. bool needs_set_not_interpolated = is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && !is_physics_interpolated(); for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) { - Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index); - - RID &occluder = r_cell_data.occluders[occlusion_layer_index]; + LocalVector<RID> &occluders = r_cell_data.occluders[occlusion_layer_index]; - if (occluder_polygon.is_valid()) { - // Create or update occluder. - Transform2D xform; - xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); - if (!occluder.is_valid()) { - occluder = rs->canvas_light_occluder_create(); - if (needs_set_not_interpolated) { - rs->canvas_light_occluder_set_interpolated(occluder, false); - } + // Free unused occluders then resize the occluders array. + for (uint32_t i = tile_data->get_occluder_polygons_count(occlusion_layer_index); i < r_cell_data.occluders[occlusion_layer_index].size(); i++) { + RID occluder_id = occluders[i]; + if (occluder_id.is_valid()) { + rs->free(occluder_id); } - rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); - 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()) { - rs->free(occluder); - occluder = RID(); + } + occluders.resize(tile_data->get_occluder_polygons_count(occlusion_layer_index)); + + for (uint32_t occlusion_polygon_index = 0; occlusion_polygon_index < occluders.size(); occlusion_polygon_index++) { + RID &occluder = occluders[occlusion_polygon_index]; + Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index); + if (occluder_polygon.is_valid()) { + // Create or update occluder. + + Transform2D xform; + xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); + if (!occluder.is_valid()) { + occluder = rs->canvas_light_occluder_create(); + if (needs_set_not_interpolated) { + rs->canvas_light_occluder_set_interpolated(occluder, false); + } + } + rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); + rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_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()) { + rs->free(occluder); + occluder = RID(); + } } } } @@ -957,7 +974,7 @@ void TileMapLayer::_navigation_update(bool p_force_cleanup) { // Create a dedicated map for each layer. RID new_layer_map = ns->map_create(); // Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error. - ns->map_set_cell_size(new_layer_map, 1.0); + ns->map_set_cell_size(new_layer_map, NavigationDefaults2D::navmesh_cell_size); ns->map_set_active(new_layer_map, true); navigation_map_override = new_layer_map; } @@ -1709,11 +1726,13 @@ void TileMapLayer::_physics_interpolated_changed() { } for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { - for (const RID &occluder : E.value.occluders) { - if (occluder.is_valid()) { - rs->canvas_light_occluder_set_interpolated(occluder, interpolated); - if (needs_reset) { - rs->canvas_light_occluder_reset_physics_interpolation(occluder); + for (const LocalVector<RID> &polygons : E.value.occluders) { + for (const RID &occluder_id : polygons) { + if (occluder_id.is_valid()) { + rs->canvas_light_occluder_set_interpolated(occluder_id, interpolated); + if (needs_reset) { + rs->canvas_light_occluder_reset_physics_interpolation(occluder_id); + } } } } @@ -1773,6 +1792,10 @@ void TileMapLayer::_bind_methods() { 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("is_cell_flipped_h", "coords"), &TileMapLayer::is_cell_flipped_h); + ClassDB::bind_method(D_METHOD("is_cell_flipped_v", "coords"), &TileMapLayer::is_cell_flipped_v); + ClassDB::bind_method(D_METHOD("is_cell_transposed", "coords"), &TileMapLayer::is_cell_transposed); + 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); @@ -1824,6 +1847,9 @@ void TileMapLayer::_bind_methods() { 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_occlusion_enabled", "enabled"), &TileMapLayer::set_occlusion_enabled); + ClassDB::bind_method(D_METHOD("is_occlusion_enabled"), &TileMapLayer::is_occlusion_enabled); + 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); @@ -1839,6 +1865,7 @@ void TileMapLayer::_bind_methods() { 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::BOOL, "occlusion_enabled"), "set_occlusion_enabled", "is_occlusion_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "x_draw_order_reversed"), "set_x_draw_order_reversed", "is_x_draw_order_reversed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size"), "set_rendering_quadrant_size", "get_rendering_quadrant_size"); @@ -2490,6 +2517,18 @@ Rect2i TileMapLayer::get_used_rect() const { return used_rect_cache; } +bool TileMapLayer::is_cell_flipped_h(const Vector2i &p_coords) const { + return get_cell_alternative_tile(p_coords) & TileSetAtlasSource::TRANSFORM_FLIP_H; +} + +bool TileMapLayer::is_cell_flipped_v(const Vector2i &p_coords) const { + return get_cell_alternative_tile(p_coords) & TileSetAtlasSource::TRANSFORM_FLIP_V; +} + +bool TileMapLayer::is_cell_transposed(const Vector2i &p_coords) const { + return get_cell_alternative_tile(p_coords) & TileSetAtlasSource::TRANSFORM_TRANSPOSE; +} + Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) { ERR_FAIL_COND_V(tile_set.is_null(), nullptr); @@ -2944,6 +2983,20 @@ TileMapLayer::DebugVisibilityMode TileMapLayer::get_collision_visibility_mode() return collision_visibility_mode; } +void TileMapLayer::set_occlusion_enabled(bool p_enabled) { + if (occlusion_enabled == p_enabled) { + return; + } + occlusion_enabled = p_enabled; + dirty.flags[DIRTY_FLAGS_LAYER_OCCLUSION_ENABLED] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); +} + +bool TileMapLayer::is_occlusion_enabled() const { + return occlusion_enabled; +} + void TileMapLayer::set_navigation_enabled(bool p_enabled) { if (navigation_enabled == p_enabled) { return; diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index c71f13d7be..cc0a5b49fb 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -108,7 +108,7 @@ struct CellData { // Rendering. Ref<RenderingQuadrant> rendering_quadrant; SelfList<CellData> rendering_quadrant_list_element; - LocalVector<RID> occluders; + LocalVector<LocalVector<RID>> occluders; // Physics. LocalVector<RID> bodies; @@ -254,6 +254,7 @@ public: DIRTY_FLAGS_LAYER_COLLISION_ENABLED, DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES, DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE, + DIRTY_FLAGS_LAYER_OCCLUSION_ENABLED, DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED, DIRTY_FLAGS_LAYER_NAVIGATION_MAP, DIRTY_FLAGS_LAYER_NAVIGATION_VISIBILITY_MODE, @@ -288,6 +289,8 @@ private: bool use_kinematic_bodies = false; DebugVisibilityMode collision_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT; + bool occlusion_enabled = true; + bool navigation_enabled = true; RID navigation_map_override; DebugVisibilityMode navigation_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT; @@ -438,6 +441,10 @@ public: 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; + bool is_cell_flipped_h(const Vector2i &p_coords) const; + bool is_cell_flipped_v(const Vector2i &p_coords) const; + bool is_cell_transposed(const Vector2i &p_coords) const; + // Patterns. Ref<TileMapPattern> get_pattern(TypedArray<Vector2i> p_coords_array); void set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); @@ -493,6 +500,9 @@ public: void set_collision_visibility_mode(DebugVisibilityMode p_show_collision); DebugVisibilityMode get_collision_visibility_mode() const; + void set_occlusion_enabled(bool p_enabled); + bool is_occlusion_enabled() const; + void set_navigation_enabled(bool p_enabled); bool is_navigation_enabled() const; void set_navigation_map(RID p_map); diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 6888462876..591528b915 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -562,7 +562,7 @@ void AudioStreamPlayer3D::seek(float p_seconds) { void AudioStreamPlayer3D::stop() { setplay.set(-1); - internal->stop(); + internal->stop_basic(); } bool AudioStreamPlayer3D::is_playing() const { @@ -596,10 +596,6 @@ void AudioStreamPlayer3D::_set_playing(bool p_enable) { internal->set_playing(p_enable); } -bool AudioStreamPlayer3D::_is_active() const { - return internal->is_active(); -} - void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const { internal->validate_property(p_property); } @@ -779,8 +775,7 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer3D::set_autoplay); ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::is_autoplay_enabled); - ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer3D::_set_playing); - ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer3D::_is_active); + ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer3D::_set_playing); ClassDB::bind_method(D_METHOD("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance); ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance); @@ -830,7 +825,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance"); @@ -862,7 +857,7 @@ void AudioStreamPlayer3D::_bind_methods() { } AudioStreamPlayer3D::AudioStreamPlayer3D() { - internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer3D::play), true)); + internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer3D::play), callable_mp(this, &AudioStreamPlayer3D::stop), true)); velocity_tracker.instantiate(); set_disable_scale(true); cached_global_panning_strength = GLOBAL_GET("audio/general/3d_panning_strength"); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 4889512037..7fe1b7079a 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -33,7 +33,7 @@ void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "bone_name") { - // Because it is a constant function, we cannot use the _get_skeleton_3d function. + // Because it is a constant function, we cannot use the get_skeleton function. const Skeleton3D *parent = nullptr; if (use_external_skeleton) { if (external_skeleton_node_cache.is_valid()) { @@ -134,7 +134,7 @@ void BoneAttachment3D::_update_external_skeleton_cache() { } void BoneAttachment3D::_check_bind() { - Skeleton3D *sk = _get_skeleton3d(); + Skeleton3D *sk = get_skeleton(); if (sk && !bound) { if (bone_idx <= -1) { @@ -148,7 +148,7 @@ void BoneAttachment3D::_check_bind() { } } -Skeleton3D *BoneAttachment3D::_get_skeleton3d() { +Skeleton3D *BoneAttachment3D::get_skeleton() { if (use_external_skeleton) { if (external_skeleton_node_cache.is_valid()) { return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache)); @@ -166,7 +166,7 @@ Skeleton3D *BoneAttachment3D::_get_skeleton3d() { void BoneAttachment3D::_check_unbind() { if (bound) { - Skeleton3D *sk = _get_skeleton3d(); + Skeleton3D *sk = get_skeleton(); if (sk) { sk->disconnect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update)); @@ -181,7 +181,7 @@ void BoneAttachment3D::_transform_changed() { } if (override_pose && !overriding) { - Skeleton3D *sk = _get_skeleton3d(); + Skeleton3D *sk = get_skeleton(); ERR_FAIL_NULL_MSG(sk, "Cannot override pose: Skeleton not found!"); ERR_FAIL_INDEX_MSG(bone_idx, sk->get_bone_count(), "Cannot override pose: Bone index is out of range!"); @@ -200,7 +200,7 @@ void BoneAttachment3D::_transform_changed() { void BoneAttachment3D::set_bone_name(const String &p_name) { bone_name = p_name; - Skeleton3D *sk = _get_skeleton3d(); + Skeleton3D *sk = get_skeleton(); if (sk) { set_bone_idx(sk->find_bone(bone_name)); } @@ -217,7 +217,7 @@ void BoneAttachment3D::set_bone_idx(const int &p_idx) { bone_idx = p_idx; - Skeleton3D *sk = _get_skeleton3d(); + Skeleton3D *sk = get_skeleton(); if (sk) { if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) { WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!"); @@ -247,7 +247,7 @@ void BoneAttachment3D::set_override_pose(bool p_override) { set_notify_transform(override_pose); set_process_internal(override_pose); if (!override_pose && bone_idx >= 0) { - Skeleton3D *sk = _get_skeleton3d(); + Skeleton3D *sk = get_skeleton(); if (sk) { sk->reset_bone_pose(bone_idx); } @@ -318,7 +318,7 @@ void BoneAttachment3D::on_skeleton_update() { } updating = true; if (bone_idx >= 0) { - Skeleton3D *sk = _get_skeleton3d(); + Skeleton3D *sk = get_skeleton(); if (sk) { if (!override_pose) { if (use_external_skeleton) { @@ -369,6 +369,8 @@ BoneAttachment3D::BoneAttachment3D() { } void BoneAttachment3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_skeleton"), &BoneAttachment3D::get_skeleton); + ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &BoneAttachment3D::set_bone_name); ClassDB::bind_method(D_METHOD("get_bone_name"), &BoneAttachment3D::get_bone_name); diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index e19180b0ea..5435c4ad0c 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -57,7 +57,6 @@ class BoneAttachment3D : public Node3D { bool updating = false; void _transform_changed(); void _update_external_skeleton_cache(); - Skeleton3D *_get_skeleton3d(); protected: void _validate_property(PropertyInfo &p_property) const; @@ -79,6 +78,8 @@ public: virtual PackedStringArray get_configuration_warnings() const override; + Skeleton3D *get_skeleton(); + void set_bone_name(const String &p_name); String get_bone_name() const; diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 8515aacba7..c70fa3ca2e 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -31,7 +31,9 @@ #include "camera_3d.h" #include "core/math/projection.h" +#include "core/math/transform_interpolator.h" #include "scene/main/viewport.h" +#include "servers/rendering/rendering_server_constants.h" void Camera3D::_update_audio_listener_state() { } @@ -88,7 +90,16 @@ void Camera3D::_update_camera() { return; } - RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform()); + if (!is_physics_interpolated_and_enabled()) { + RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform()); + } else { + // Ideally we shouldn't be moving a physics interpolated camera within a frame, + // because it will break smooth interpolation, but it may occur on e.g. level load. + if (!Engine::get_singleton()->is_in_physics_frame() && camera.is_valid()) { + _physics_interpolation_ensure_transform_calculated(true); + RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); + } + } if (is_part_of_edited_scene() || !is_current()) { return; @@ -97,6 +108,64 @@ void Camera3D::_update_camera() { get_viewport()->_camera_3d_transform_changed_notify(); } +void Camera3D::_physics_interpolated_changed() { + _update_process_mode(); +} + +void Camera3D::_physics_interpolation_ensure_data_flipped() { + // The curr -> previous update can either occur + // on the INTERNAL_PHYSICS_PROCESS OR + // on NOTIFICATION_TRANSFORM_CHANGED, + // if NOTIFICATION_TRANSFORM_CHANGED takes place + // earlier than 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; + physics_interpolation_flip_data(); + } +} + +void Camera3D::_physics_interpolation_ensure_transform_calculated(bool p_force) const { + DEV_CHECK_ONCE(!Engine::get_singleton()->is_in_physics_frame()); + + InterpolationData &id = _interpolation_data; + uint64_t frame = Engine::get_singleton()->get_frames_drawn(); + + if (id.last_update_frame != frame || p_force) { + id.last_update_frame = frame; + + TransformInterpolator::interpolate_transform_3d(id.xform_prev, id.xform_curr, id.xform_interpolated, Engine::get_singleton()->get_physics_interpolation_fraction()); + + Transform3D &tr = id.camera_xform_interpolated; + tr = _get_adjusted_camera_transform(id.xform_interpolated); + } +} + +void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) { + _desired_process_internal = p_process_internal; + _desired_physics_process_internal = p_physics_process_internal; + _update_process_mode(); +} + +void Camera3D::_update_process_mode() { + bool process = _desired_process_internal; + bool physics_process = _desired_physics_process_internal; + + if (is_physics_interpolated_and_enabled()) { + if (is_current()) { + process = true; + physics_process = true; + } + } + set_process_internal(process); + set_physics_process_internal(physics_process); +} + void Camera3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { @@ -118,11 +187,58 @@ void Camera3D::_notification(int p_what) { #endif } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (is_physics_interpolated_and_enabled() && camera.is_valid()) { + _physics_interpolation_ensure_transform_calculated(); + +#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION + print_line("\t\tinterpolated Camera3D: " + rtos(_interpolation_data.xform_interpolated.origin.x) + "\t( prev " + rtos(_interpolation_data.xform_prev.origin.x) + ", curr " + rtos(_interpolation_data.xform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames())); +#endif + + RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); + } + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (is_physics_interpolated_and_enabled()) { + _physics_interpolation_ensure_data_flipped(); + _interpolation_data.xform_curr = get_global_transform(); + } + } break; + case NOTIFICATION_TRANSFORM_CHANGED: { + if (is_physics_interpolated_and_enabled()) { + _physics_interpolation_ensure_data_flipped(); + _interpolation_data.xform_curr = get_global_transform(); +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + if (!Engine::get_singleton()->is_in_physics_frame()) { + PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera3D triggered from outside physics process"); + } +#endif + } _request_camera_update(); if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { velocity_tracker->update_position(get_global_transform().origin); } + // Allow auto-reset when first adding to the tree, as a convenience. + if (_is_physics_interpolation_reset_requested() && is_inside_tree()) { + _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + _set_physics_interpolation_reset_requested(false); + } + } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_inside_tree()) { + _interpolation_data.xform_curr = get_global_transform(); + _interpolation_data.xform_prev = _interpolation_data.xform_curr; + } + } break; + + case NOTIFICATION_PAUSED: { + if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) { + _physics_interpolation_ensure_transform_calculated(true); + RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated); + } } break; case NOTIFICATION_EXIT_WORLD: { @@ -151,23 +267,34 @@ void Camera3D::_notification(int p_what) { if (viewport) { viewport->find_world_3d()->_register_camera(this); } + _update_process_mode(); } break; case NOTIFICATION_LOST_CURRENT: { if (viewport) { viewport->find_world_3d()->_remove_camera(this); } + _update_process_mode(); } break; } } -Transform3D Camera3D::get_camera_transform() const { - Transform3D tr = get_global_transform().orthonormalized(); +Transform3D Camera3D::_get_adjusted_camera_transform(const Transform3D &p_xform) const { + Transform3D tr = p_xform.orthonormalized(); tr.origin += tr.basis.get_column(1) * v_offset; tr.origin += tr.basis.get_column(0) * h_offset; return tr; } +Transform3D Camera3D::get_camera_transform() const { + if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) { + _physics_interpolation_ensure_transform_calculated(); + return _interpolation_data.camera_xform_interpolated; + } + + return _get_adjusted_camera_transform(get_global_transform()); +} + Projection Camera3D::_get_camera_projection(real_t p_near) const { Size2 viewport_size = get_viewport()->get_visible_rect().size; Projection cm; @@ -379,6 +506,11 @@ Point2 Camera3D::unproject_position(const Vector3 &p_pos) const { Plane p(get_camera_transform().xform_inv(p_pos), 1.0); p = cm.xform4(p); + + // Prevent divide by zero. + // TODO: Investigate, this was causing NaNs. + ERR_FAIL_COND_V(p.d == 0, Point2()); + p.normal /= p.d; Point2 res; diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h index dbf2ffc1dd..3e9f940ad6 100644 --- a/scene/3d/camera_3d.h +++ b/scene/3d/camera_3d.h @@ -98,7 +98,39 @@ private: RID pyramid_shape; Vector<Vector3> pyramid_shape_points; + /////////////////////////////////////////////////////// + // INTERPOLATION FUNCTIONS + void _physics_interpolation_ensure_transform_calculated(bool p_force = false) const; + void _physics_interpolation_ensure_data_flipped(); + + // These can be set by derived Camera3Ds, if they wish to do processing + // (while still allowing physics interpolation to function). + bool _desired_process_internal = false; + bool _desired_physics_process_internal = false; + + mutable struct InterpolationData { + Transform3D xform_curr; + Transform3D xform_prev; + Transform3D xform_interpolated; + Transform3D camera_xform_interpolated; // After modification according to camera type. + uint32_t last_update_physics_tick = 0; + uint32_t last_update_frame = UINT32_MAX; + } _interpolation_data; + + void _update_process_mode(); + protected: + // Use from derived classes to set process modes instead of setting directly. + // This is because physics interpolation may need to request process modes additionally. + void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal); + + // Opportunity for derived classes to interpolate extra attributes. + virtual void physics_interpolation_flip_data() {} + + virtual void _physics_interpolated_changed() override; + virtual Transform3D _get_adjusted_camera_transform(const Transform3D &p_xform) const; + /////////////////////////////////////////////////////// + void _update_camera(); virtual void _request_camera_update(); void _update_camera_mode(); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 03fe5e1fad..acbc443a93 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -448,6 +448,10 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) { emission_ring_inner_radius = p_radius; } +void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) { + emission_ring_cone_angle = p_angle; +} + void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) { scale_curve_x = p_scale_curve; } @@ -501,6 +505,10 @@ real_t CPUParticles3D::get_emission_ring_inner_radius() const { return emission_ring_inner_radius; } +real_t CPUParticles3D::get_emission_ring_cone_angle() const { + return emission_ring_cone_angle; +} + CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const { return emission_shape; } @@ -878,8 +886,14 @@ void CPUParticles3D::_particles_process(double p_delta) { } } break; case EMISSION_SHAPE_RING: { + real_t radius_clamped = MAX(0.001, emission_ring_radius); + real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0); + real_t y_pos = Math::randf(); + real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5); + y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew); real_t ring_random_angle = Math::randf() * Math_TAU; - 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); + real_t ring_random_radius = Math::sqrt(Math::randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius); + ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos); 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.abs() == Vector3(1.0, 0.0, 0.0)) { @@ -890,7 +904,7 @@ void CPUParticles3D::_particles_process(double p_delta) { ortho_axis = ortho_axis.normalized(); ortho_axis.rotate(axis, ring_random_angle); ortho_axis = ortho_axis.normalized(); - p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis; + p.transform.origin = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis; } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; @@ -1550,6 +1564,9 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius); ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &CPUParticles3D::set_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &CPUParticles3D::get_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity); @@ -1577,9 +1594,10 @@ void CPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle"); ADD_GROUP("Particle Flags", "particle_flag_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y); @@ -1716,6 +1734,7 @@ CPUParticles3D::CPUParticles3D() { set_emission_ring_height(1); set_emission_ring_radius(1); set_emission_ring_inner_radius(0); + set_emission_ring_cone_angle(90); set_gravity(Vector3(0, -9.8, 0)); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 82ea4bbef3..978bb64e71 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -178,6 +178,7 @@ private: real_t emission_ring_height = 0.0; real_t emission_ring_radius = 0.0; real_t emission_ring_inner_radius = 0.0; + real_t emission_ring_cone_angle = 0.0; Ref<Curve> scale_curve_x; Ref<Curve> scale_curve_y; @@ -282,6 +283,7 @@ public: void set_emission_ring_height(real_t p_height); void set_emission_ring_radius(real_t p_radius); void set_emission_ring_inner_radius(real_t p_radius); + void set_emission_ring_cone_angle(real_t p_angle); void set_scale_curve_x(Ref<Curve> p_scale_curve); void set_scale_curve_y(Ref<Curve> p_scale_curve); void set_scale_curve_z(Ref<Curve> p_scale_curve); @@ -297,6 +299,7 @@ public: real_t get_emission_ring_height() const; real_t get_emission_ring_radius() const; real_t get_emission_ring_inner_radius() const; + real_t get_emission_ring_cone_angle() const; Ref<Curve> get_scale_curve_x() const; Ref<Curve> get_scale_curve_y() const; Ref<Curve> get_scale_curve_z() const; diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 485599d0fb..8702b1d3da 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -163,7 +163,7 @@ void Decal::_validate_property(PropertyInfo &p_property) const { } PackedStringArray Decal::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends.")); diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index 54631a8dff..195074ba2f 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -116,7 +116,7 @@ AABB FogVolume::get_aabb() const { } PackedStringArray FogVolume::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment(); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 3771b385e5..2cef607d29 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -459,14 +459,6 @@ void GPUParticles3D::_notification(int p_what) { // Use internal process when emitting and one_shot is on so that when // the shot ends the editor can properly update. case NOTIFICATION_INTERNAL_PROCESS: { - const Vector3 velocity = (get_global_position() - previous_position) / get_process_delta_time(); - - if (velocity != previous_velocity) { - RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); - previous_velocity = velocity; - } - previous_position = get_global_position(); - if (one_shot) { time += get_process_delta_time(); if (time > emission_time) { @@ -487,8 +479,21 @@ void GPUParticles3D::_notification(int p_what) { } } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Update velocity in physics process, so that velocity calculations remain correct + // if the physics tick rate is lower than the rendered framerate (especially without physics interpolation). + const Vector3 velocity = (get_global_position() - previous_position) / get_physics_process_delta_time(); + + if (velocity != previous_velocity) { + RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); + previous_velocity = velocity; + } + previous_position = get_global_position(); + } break; + case NOTIFICATION_ENTER_TREE: { set_process_internal(false); + set_physics_process_internal(false); if (sub_emitter != NodePath()) { _attach_sub_emitter(); } @@ -499,6 +504,7 @@ void GPUParticles3D::_notification(int p_what) { } previous_position = get_global_transform().origin; set_process_internal(true); + set_physics_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index 3a05ec9c9e..9791f23bc3 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -524,7 +524,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { } PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = GPUParticlesCollision3D::get_configuration_warnings(); if (bake_mask == 0) { warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property.")); diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index 54adafbefb..6b3510a72a 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -30,7 +30,7 @@ #include "label_3d.h" -#include "scene/main/viewport.h" +#include "scene/main/window.h" #include "scene/resources/theme.h" #include "scene/theme/theme_db.h" @@ -197,14 +197,14 @@ void Label3D::_notification(int p_what) { if (!pending_update) { _im_update(); } - Viewport *viewport = get_viewport(); - ERR_FAIL_NULL(viewport); - viewport->connect("size_changed", callable_mp(this, &Label3D::_font_changed)); + Window *window = get_window(); + ERR_FAIL_NULL(window); + window->connect("size_changed", callable_mp(this, &Label3D::_font_changed)); } break; case NOTIFICATION_EXIT_TREE: { - Viewport *viewport = get_viewport(); - ERR_FAIL_NULL(viewport); - viewport->disconnect("size_changed", callable_mp(this, &Label3D::_font_changed)); + Window *window = get_window(); + ERR_FAIL_NULL(window); + window->disconnect("size_changed", callable_mp(this, &Label3D::_font_changed)); } break; case NOTIFICATION_TRANSLATION_CHANGED: { String new_text = atr(text); @@ -797,13 +797,13 @@ Ref<Font> Label3D::_get_font_or_default() const { } const StringName theme_name = SceneStringName(font); - List<StringName> theme_types; - ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types); + Vector<StringName> theme_types; + ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), theme_types); ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context(); - List<Ref<Theme>> themes = global_context->get_themes(); + Vector<Ref<Theme>> themes = global_context->get_themes(); if (Engine::get_singleton()->is_editor_hint()) { - themes.push_front(ThemeDB::get_singleton()->get_project_theme()); + themes.insert(0, ThemeDB::get_singleton()->get_project_theme()); } for (const Ref<Theme> &theme : themes) { diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 038a78609f..26a574cd26 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -151,6 +151,14 @@ bool LightmapGIData::is_using_spherical_harmonics() const { return uses_spherical_harmonics; } +void LightmapGIData::_set_uses_packed_directional(bool p_enable) { + _uses_packed_directional = p_enable; +} + +bool LightmapGIData::_is_using_packed_directional() const { + return _uses_packed_directional; +} + void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) { if (p_points.size()) { int pc = p_points.size(); @@ -255,6 +263,9 @@ void LightmapGIData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics); ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics); + ClassDB::bind_method(D_METHOD("_set_uses_packed_directional", "_uses_packed_directional"), &LightmapGIData::_set_uses_packed_directional); + ClassDB::bind_method(D_METHOD("_is_using_packed_directional"), &LightmapGIData::_is_using_packed_directional); + ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user); ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count); ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path); @@ -267,6 +278,7 @@ void LightmapGIData::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "_uses_packed_directional", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uses_packed_directional", "_is_using_packed_directional"); #ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture); @@ -709,7 +721,7 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f const Vector3 *pp = probe_positions.ptr(); bool exists = false; for (int j = 0; j < ppcount; j++) { - if (pp[j].is_equal_approx(real_pos)) { + if (pp[j].distance_to(real_pos) < (p_cell_size * 0.5f)) { exists = true; break; } @@ -1072,6 +1084,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa if (env.is_valid()) { environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 64)); + environment_transform = Basis::from_euler(env->get_sky_rotation()).inverse(); } } } break; @@ -1186,6 +1199,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } gi_data->set_lightmap_textures(textures); + gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically. gi_data->set_uses_spherical_harmonics(directional); for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { @@ -1351,6 +1365,12 @@ void LightmapGI::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POST_ENTER_TREE: { if (light_data.is_valid()) { + ERR_FAIL_COND_MSG( + light_data->is_using_spherical_harmonics() && !light_data->_is_using_packed_directional(), + vformat( + "%s (%s): The directional lightmap textures are stored in a format that isn't supported anymore. Please bake lightmaps again to make lightmaps display from this node again.", + get_light_data()->get_path(), get_name())); + _assign_lightmaps(); } } break; @@ -1580,7 +1600,7 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const { } PackedStringArray LightmapGI::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail.")); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 67480132b6..6377c420d1 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -49,6 +49,8 @@ class LightmapGIData : public Resource { bool uses_spherical_harmonics = false; bool interior = false; + bool _uses_packed_directional = false; + RID lightmap; AABB bounds; float baked_exposure = 1.0; @@ -92,6 +94,9 @@ public: void set_uses_spherical_harmonics(bool p_enable); bool is_using_spherical_harmonics() const; + void _set_uses_packed_directional(bool p_enable); + bool _is_using_packed_directional() const; + bool is_interior() const; float get_baked_exposure() const; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 85bf8846b9..f551cb401c 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -517,12 +517,12 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p 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."); + ERR_FAIL_COND_V_MSG(source_mesh.is_null(), 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(p_existing.is_null(), 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; @@ -671,6 +671,172 @@ Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayM return bake_mesh; } +Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing) { + Ref<ArrayMesh> source_mesh = get_mesh(); + ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh."); + + Ref<ArrayMesh> bake_mesh; + + if (p_existing.is_valid()) { + 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(); + } + + ERR_FAIL_COND_V_MSG(skin_ref.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin."); + ERR_FAIL_COND_V_MSG(skin_internal.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin."); + RID skeleton = skin_ref->get_skeleton(); + ERR_FAIL_COND_V_MSG(!skeleton.is_valid(), Ref<ArrayMesh>(), "The source mesh must have its skin registered with a valid skeleton."); + + const int bone_count = RenderingServer::get_singleton()->skeleton_get_bone_count(skeleton); + ERR_FAIL_COND_V(bone_count <= 0, Ref<ArrayMesh>()); + ERR_FAIL_COND_V(bone_count < skin_internal->get_bind_count(), Ref<ArrayMesh>()); + + LocalVector<Transform3D> bone_transforms; + bone_transforms.resize(bone_count); + for (int bone_index = 0; bone_index < bone_count; bone_index++) { + bone_transforms[bone_index] = RenderingServer::get_singleton()->skeleton_bone_get_transform(skeleton, bone_index); + } + + bake_mesh->clear_surfaces(); + + int mesh_surface_count = source_mesh->get_surface_count(); + + for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) { + ERR_CONTINUE(source_mesh->surface_get_primitive_type(surface_index) != Mesh::PRIMITIVE_TRIANGLES); + + uint32_t surface_format = source_mesh->surface_get_format(surface_index); + + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX)); + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_BONES)); + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_WEIGHTS)); + + unsigned int bones_per_vertex = surface_format & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4; + + surface_format &= ~Mesh::ARRAY_FORMAT_BONES; + surface_format &= ~Mesh::ARRAY_FORMAT_WEIGHTS; + + 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]; + const Vector<int> &source_mesh_bones_array = source_mesh_arrays[Mesh::ARRAY_BONES]; + const Vector<float> &source_mesh_weights_array = source_mesh_arrays[Mesh::ARRAY_WEIGHTS]; + + unsigned int vertex_count = source_mesh_vertex_array.size(); + int expected_bone_array_size = vertex_count * bones_per_vertex; + ERR_CONTINUE(source_mesh_bones_array.size() != expected_bone_array_size); + ERR_CONTINUE(source_mesh_weights_array.size() != expected_bone_array_size); + + 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 || i == Mesh::ARRAY_BONES || i == Mesh::ARRAY_WEIGHTS) { + 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(); + const int *source_bones_ptr = source_mesh_bones_array.ptr(); + const float *source_weights_ptr = source_mesh_weights_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(); + + for (unsigned int vertex_index = 0; vertex_index < vertex_count; vertex_index++) { + Vector3 lerped_vertex; + Vector3 lerped_normal; + Vector3 lerped_tangent; + + const Vector3 &source_vertex = source_vertices_ptr[vertex_index]; + + Vector3 source_normal; + if (use_normal_array) { + source_normal = source_normals_ptr[vertex_index]; + } + + int tangent_index = vertex_index * 4; + Vector4 source_tangent; + Vector3 source_tangent_vec3; + if (use_tangent_array) { + 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]); + + DEV_ASSERT(source_tangent.w == 1.0 || source_tangent.w == -1.0); + + source_tangent_vec3 = Vector3(source_tangent.x, source_tangent.y, source_tangent.z); + } + + for (unsigned int weight_index = 0; weight_index < bones_per_vertex; weight_index++) { + float bone_weight = source_weights_ptr[vertex_index * bones_per_vertex + weight_index]; + if (bone_weight < FLT_EPSILON) { + continue; + } + int vertex_bone_index = source_bones_ptr[vertex_index * bones_per_vertex + weight_index]; + const Transform3D &bone_transform = bone_transforms[vertex_bone_index]; + const Basis bone_basis = bone_transform.basis.orthonormalized(); + + ERR_FAIL_INDEX_V(vertex_bone_index, static_cast<int>(bone_transforms.size()), Ref<ArrayMesh>()); + + lerped_vertex += source_vertex.lerp(bone_transform.xform(source_vertex), bone_weight) - source_vertex; + ; + + if (use_normal_array) { + lerped_normal += source_normal.lerp(bone_basis.xform(source_normal), bone_weight) - source_normal; + } + + if (use_tangent_array) { + lerped_tangent += source_tangent_vec3.lerp(bone_basis.xform(source_tangent_vec3), bone_weight) - source_tangent_vec3; + } + } + + lerped_vertices_ptrw[vertex_index] += lerped_vertex; + + if (use_normal_array) { + lerped_normals_ptrw[vertex_index] = (source_normal + lerped_normal).normalized(); + } + + if (use_tangent_array) { + lerped_tangent = (source_tangent_vec3 + lerped_tangent).normalized(); + 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; + } + } + + 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); @@ -700,6 +866,7 @@ void MeshInstance3D::_bind_methods() { 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>())); + ClassDB::bind_method(D_METHOD("bake_mesh_from_current_skeleton_pose", "existing"), &MeshInstance3D::bake_mesh_from_current_skeleton_pose, DEFVAL(Ref<ArrayMesh>())); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_GROUP("Skeleton", ""); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index 8a7e03c5b3..0eff12762d 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -102,6 +102,7 @@ public: virtual AABB get_aabb() const override; Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>()); + Ref<ArrayMesh> bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>()); MeshInstance3D(); ~MeshInstance3D(); diff --git a/scene/3d/multimesh_instance_3d.cpp b/scene/3d/multimesh_instance_3d.cpp index 55d6e49e6c..2eef1dbbf4 100644 --- a/scene/3d/multimesh_instance_3d.cpp +++ b/scene/3d/multimesh_instance_3d.cpp @@ -30,16 +30,35 @@ #include "multimesh_instance_3d.h" +void MultiMeshInstance3D::_refresh_interpolated() { + if (is_inside_tree() && multimesh.is_valid()) { + bool interpolated = is_physics_interpolated_and_enabled(); + multimesh->set_physics_interpolated(interpolated); + } +} + +void MultiMeshInstance3D::_physics_interpolated_changed() { + VisualInstance3D::_physics_interpolated_changed(); + _refresh_interpolated(); +} + void MultiMeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance3D::set_multimesh); ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance3D::get_multimesh); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh"); } +void MultiMeshInstance3D::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE) { + _refresh_interpolated(); + } +} + void MultiMeshInstance3D::set_multimesh(const Ref<MultiMesh> &p_multimesh) { multimesh = p_multimesh; if (multimesh.is_valid()) { set_base(multimesh->get_rid()); + _refresh_interpolated(); } else { set_base(RID()); } diff --git a/scene/3d/multimesh_instance_3d.h b/scene/3d/multimesh_instance_3d.h index 404f31d1e3..c9507b1047 100644 --- a/scene/3d/multimesh_instance_3d.h +++ b/scene/3d/multimesh_instance_3d.h @@ -39,9 +39,12 @@ class MultiMeshInstance3D : public GeometryInstance3D { Ref<MultiMesh> multimesh; + void _refresh_interpolated(); + protected: + virtual void _physics_interpolated_changed() override; static void _bind_methods(); - // bind helpers + void _notification(int p_what); public: void set_multimesh(const Ref<MultiMesh> &p_multimesh); diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp index dc776ebea2..0cce21b9d0 100644 --- a/scene/3d/navigation_link_3d.cpp +++ b/scene/3d/navigation_link_3d.cpp @@ -152,6 +152,9 @@ void NavigationLink3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink3D::set_enabled); ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink3D::is_enabled); + ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationLink3D::set_navigation_map); + ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationLink3D::get_navigation_map); + ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink3D::set_bidirectional); ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink3D::is_bidirectional); @@ -217,16 +220,7 @@ bool NavigationLink3D::_get(const StringName &p_name, Variant &r_ret) const { void NavigationLink3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - if (enabled) { - NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map()); - } - current_global_transform = get_global_transform(); - NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); - NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); - -#ifdef DEBUG_ENABLED - _update_debug_mesh(); -#endif // DEBUG_ENABLED + _link_enter_navigation_map(); } break; case NOTIFICATION_TRANSFORM_CHANGED: { @@ -235,30 +229,11 @@ void NavigationLink3D::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { set_physics_process_internal(false); - if (is_inside_tree()) { - Transform3D new_global_transform = get_global_transform(); - if (current_global_transform != new_global_transform) { - current_global_transform = new_global_transform; - NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); - NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); -#ifdef DEBUG_ENABLED - if (debug_instance.is_valid()) { - RS::get_singleton()->instance_set_transform(debug_instance, current_global_transform); - } -#endif // DEBUG_ENABLED - } - } + _link_update_transform(); } break; case NOTIFICATION_EXIT_TREE: { - NavigationServer3D::get_singleton()->link_set_map(link, RID()); - -#ifdef DEBUG_ENABLED - if (debug_instance.is_valid()) { - RS::get_singleton()->instance_set_scenario(debug_instance, RID()); - RS::get_singleton()->instance_set_visible(debug_instance, false); - } -#endif // DEBUG_ENABLED + _link_exit_navigation_map(); } break; } } @@ -320,6 +295,25 @@ void NavigationLink3D::set_enabled(bool p_enabled) { update_gizmos(); } +void NavigationLink3D::set_navigation_map(RID p_navigation_map) { + if (map_override == p_navigation_map) { + return; + } + + map_override = p_navigation_map; + + NavigationServer3D::get_singleton()->link_set_map(link, map_override); +} + +RID NavigationLink3D::get_navigation_map() const { + if (map_override.is_valid()) { + return map_override; + } else if (is_inside_tree()) { + return get_world_3d()->get_navigation_map(); + } + return RID(); +} + void NavigationLink3D::set_bidirectional(bool p_bidirectional) { if (bidirectional == p_bidirectional) { return; @@ -459,7 +453,7 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) { } PackedStringArray NavigationLink3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful.")); @@ -467,3 +461,53 @@ PackedStringArray NavigationLink3D::get_configuration_warnings() const { return warnings; } + +void NavigationLink3D::_link_enter_navigation_map() { + if (!is_inside_tree()) { + return; + } + + if (map_override.is_valid()) { + NavigationServer3D::get_singleton()->link_set_map(link, map_override); + } else { + NavigationServer3D::get_singleton()->link_set_map(link, get_world_3d()->get_navigation_map()); + } + + current_global_transform = get_global_transform(); + NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); + NavigationServer3D::get_singleton()->link_set_enabled(link, enabled); + +#ifdef DEBUG_ENABLED + if (NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { + _update_debug_mesh(); + } +#endif // DEBUG_ENABLED +} + +void NavigationLink3D::_link_exit_navigation_map() { + NavigationServer3D::get_singleton()->link_set_map(link, RID()); +#ifdef DEBUG_ENABLED + if (debug_instance.is_valid()) { + RS::get_singleton()->instance_set_visible(debug_instance, false); + } +#endif // DEBUG_ENABLED +} + +void NavigationLink3D::_link_update_transform() { + if (!is_inside_tree()) { + return; + } + + Transform3D new_global_transform = get_global_transform(); + if (current_global_transform != new_global_transform) { + current_global_transform = new_global_transform; + NavigationServer3D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position)); + NavigationServer3D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position)); +#ifdef DEBUG_ENABLED + if (NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { + _update_debug_mesh(); + } +#endif // DEBUG_ENABLED + } +} diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h index 1867082811..e894761f40 100644 --- a/scene/3d/navigation_link_3d.h +++ b/scene/3d/navigation_link_3d.h @@ -38,6 +38,7 @@ class NavigationLink3D : public Node3D { bool enabled = true; RID link; + RID map_override; bool bidirectional = true; uint32_t navigation_layers = 1; Vector3 end_position; @@ -72,6 +73,9 @@ public: void set_enabled(bool p_enabled); bool is_enabled() const { return enabled; } + void set_navigation_map(RID p_navigation_map); + RID get_navigation_map() const; + void set_bidirectional(bool p_bidirectional); bool is_bidirectional() const { return bidirectional; } @@ -100,6 +104,11 @@ public: real_t get_travel_cost() const { return travel_cost; } PackedStringArray get_configuration_warnings() const override; + +private: + void _link_enter_navigation_map(); + void _link_exit_navigation_map(); + void _link_update_transform(); }; #endif // NAVIGATION_LINK_3D_H diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 40e04f0fb4..c0c254e7ed 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -271,7 +271,7 @@ bool NavigationRegion3D::is_baking() const { } PackedStringArray NavigationRegion3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!navigation_mesh.is_valid()) { @@ -686,6 +686,8 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() { Vector<Vector3> vertex_array; vertex_array.resize(connections_count * 6); + Vector3 *vertex_array_ptrw = vertex_array.ptrw(); + int vertex_array_index = 0; for (int i = 0; i < connections_count; i++) { Vector3 connection_pathway_start = NavigationServer3D::get_singleton()->region_get_connection_pathway_start(region, i); @@ -705,13 +707,12 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() { Vector3 left_end_pos = connection_pathway_end + (end_right_dir * half_edge_connection_margin); Vector3 right_end_pos = connection_pathway_end + (end_left_dir * half_edge_connection_margin); - vertex_array.push_back(right_end_pos); - vertex_array.push_back(left_start_pos); - vertex_array.push_back(right_start_pos); - - vertex_array.push_back(left_end_pos); - vertex_array.push_back(right_end_pos); - vertex_array.push_back(right_start_pos); + vertex_array_ptrw[vertex_array_index++] = connection_pathway_start; + vertex_array_ptrw[vertex_array_index++] = connection_pathway_end; + vertex_array_ptrw[vertex_array_index++] = left_start_pos; + vertex_array_ptrw[vertex_array_index++] = right_start_pos; + vertex_array_ptrw[vertex_array_index++] = left_end_pos; + vertex_array_ptrw[vertex_array_index++] = right_end_pos; } if (vertex_array.size() == 0) { @@ -724,7 +725,7 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() { mesh_array.resize(Mesh::ARRAY_MAX); mesh_array[Mesh::ARRAY_VERTEX] = vertex_array; - debug_edge_connections_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, mesh_array); + debug_edge_connections_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, mesh_array); debug_edge_connections_mesh->surface_set_material(0, edge_connections_material); RS::get_singleton()->instance_set_base(debug_edge_connections_instance, debug_edge_connections_mesh->get_rid()); diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 2e08afb30d..86ce8a881a 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -30,6 +30,7 @@ #include "node_3d.h" +#include "core/math/transform_interpolator.h" #include "scene/3d/visual_instance_3d.h" #include "scene/main/viewport.h" #include "scene/property_utils.h" @@ -176,6 +177,7 @@ void Node3D::_notification(int p_what) { data.parent = nullptr; data.C = nullptr; _update_visibility_parent(true); + _disable_client_physics_interpolation(); } break; case NOTIFICATION_ENTER_WORLD: { @@ -226,6 +228,12 @@ void Node3D::_notification(int p_what) { } #endif } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (data.client_physics_interpolation_data) { + data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr; + } + } break; } } @@ -341,6 +349,119 @@ Transform3D Node3D::get_transform() const { return data.local_transform; } +// Return false to timeout and remove from the client interpolation list. +bool Node3D::update_client_physics_interpolation_data() { + if (!is_inside_tree() || !_is_physics_interpolated_client_side()) { + return false; + } + + ERR_FAIL_NULL_V(data.client_physics_interpolation_data, false); + ClientPhysicsInterpolationData &pid = *data.client_physics_interpolation_data; + + uint64_t tick = Engine::get_singleton()->get_physics_frames(); + + // Has this update been done already this tick? + // (For instance, get_global_transform_interpolated() could be called multiple times.) + if (pid.current_physics_tick != tick) { + // Timeout? + if (tick >= pid.timeout_physics_tick) { + return false; + } + + if (pid.current_physics_tick == (tick - 1)) { + // Normal interpolation situation, there is a continuous flow of data + // from one tick to the next... + pid.global_xform_prev = pid.global_xform_curr; + } else { + // There has been a gap, we cannot sensibly offer interpolation over + // a multitick gap, so we will teleport. + pid.global_xform_prev = get_global_transform(); + } + pid.current_physics_tick = tick; + } + + pid.global_xform_curr = get_global_transform(); + return true; +} + +void Node3D::_disable_client_physics_interpolation() { + // Disable any current client side interpolation. + // (This can always restart as normal if you later re-attach the node to the SceneTree.) + if (data.client_physics_interpolation_data) { + memdelete(data.client_physics_interpolation_data); + data.client_physics_interpolation_data = nullptr; + + SceneTree *tree = get_tree(); + if (tree && _client_physics_interpolation_node_3d_list.in_list()) { + tree->client_physics_interpolation_remove_node_3d(&_client_physics_interpolation_node_3d_list); + } + } + _set_physics_interpolated_client_side(false); +} + +Transform3D Node3D::_get_global_transform_interpolated(real_t p_interpolation_fraction) { + ERR_FAIL_COND_V(!is_inside_tree(), Transform3D()); + + // Set in motion the mechanisms for client side interpolation if not already active. + if (!_is_physics_interpolated_client_side()) { + _set_physics_interpolated_client_side(true); + + ERR_FAIL_COND_V(data.client_physics_interpolation_data != nullptr, Transform3D()); + data.client_physics_interpolation_data = memnew(ClientPhysicsInterpolationData); + data.client_physics_interpolation_data->global_xform_curr = get_global_transform(); + data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr; + data.client_physics_interpolation_data->current_physics_tick = Engine::get_singleton()->get_physics_frames(); + } + + // Storing the last tick we requested client interpolation allows us to timeout + // and remove client interpolated nodes from the list to save processing. + // We use some arbitrary timeout here, but this could potentially be user defined. + + // Note: This timeout has to be larger than the number of ticks in a frame, otherwise the interpolated + // data will stop flowing before the next frame is drawn. This should only be relevant at high tick rates. + // We could alternatively do this by frames rather than ticks and avoid this problem, but then the behavior + // would be machine dependent. + data.client_physics_interpolation_data->timeout_physics_tick = Engine::get_singleton()->get_physics_frames() + 256; + + // Make sure data is up to date. + update_client_physics_interpolation_data(); + + // Interpolate the current data. + const Transform3D &xform_curr = data.client_physics_interpolation_data->global_xform_curr; + const Transform3D &xform_prev = data.client_physics_interpolation_data->global_xform_prev; + + Transform3D res; + TransformInterpolator::interpolate_transform_3d(xform_prev, xform_curr, res, p_interpolation_fraction); + + SceneTree *tree = get_tree(); + + // This should not happen, as is_inside_tree() is checked earlier. + ERR_FAIL_NULL_V(tree, res); + if (!_client_physics_interpolation_node_3d_list.in_list()) { + tree->client_physics_interpolation_add_node_3d(&_client_physics_interpolation_node_3d_list); + } + + return res; +} + +Transform3D Node3D::get_global_transform_interpolated() { + // Pass through if physics interpolation is switched off. + // This is a convenience, as it allows you to easy turn off interpolation + // without changing any code. + if (!is_physics_interpolated_and_enabled()) { + return get_global_transform(); + } + + // If we are in the physics frame, the interpolated global transform is meaningless. + // However, there is an exception, we may want to use this as a means of starting off the client + // interpolation pump if not already started (when _is_physics_interpolated_client_side() is false). + if (Engine::get_singleton()->is_in_physics_frame() && _is_physics_interpolated_client_side()) { + return get_global_transform(); + } + + return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction()); +} + Transform3D Node3D::get_global_transform() const { ERR_FAIL_COND_V(!is_inside_tree(), Transform3D()); @@ -1140,6 +1261,7 @@ void Node3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform); ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform); + ClassDB::bind_method(D_METHOD("get_global_transform_interpolated"), &Node3D::get_global_transform_interpolated); ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node3D::set_global_position); ClassDB::bind_method(D_METHOD("get_global_position"), &Node3D::get_global_position); ClassDB::bind_method(D_METHOD("set_global_basis", "basis"), &Node3D::set_global_basis); @@ -1236,4 +1358,27 @@ void Node3D::_bind_methods() { } Node3D::Node3D() : - xform_change(this) {} + xform_change(this), _client_physics_interpolation_node_3d_list(this) { + // Default member initializer for bitfield is a C++20 extension, so: + + data.top_level = false; + data.inside_world = false; + + data.ignore_notification = false; + data.notify_local_transform = false; + data.notify_transform = false; + + data.visible = true; + data.disable_scale = false; + data.vi_visible = true; + +#ifdef TOOLS_ENABLED + data.gizmos_disabled = false; + data.gizmos_dirty = false; + data.transform_gizmo_visible = true; +#endif +} + +Node3D::~Node3D() { + _disable_client_physics_interpolation(); +} diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index c1667221df..217ee28cf1 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -85,7 +85,15 @@ private: DIRTY_GLOBAL_TRANSFORM = 4 }; + struct ClientPhysicsInterpolationData { + Transform3D global_xform_curr; + Transform3D global_xform_prev; + uint64_t current_physics_tick = 0; + uint64_t timeout_physics_tick = 0; + }; + mutable SelfList<Node> xform_change; + SelfList<Node3D> _client_physics_interpolation_node_3d_list; // This Data struct is to avoid namespace pollution in derived classes. @@ -101,8 +109,19 @@ private: Viewport *viewport = nullptr; - bool top_level = false; - bool inside_world = false; + bool top_level : 1; + bool inside_world : 1; + + // This is cached, and only currently kept up to date in visual instances. + // This is set if a visual instance is (a) in the tree AND (b) visible via is_visible_in_tree() call. + bool vi_visible : 1; + + bool ignore_notification : 1; + bool notify_local_transform : 1; + bool notify_transform : 1; + + bool visible : 1; + bool disable_scale : 1; RID visibility_parent; @@ -110,18 +129,13 @@ private: List<Node3D *> children; List<Node3D *>::Element *C = nullptr; - bool ignore_notification = false; - bool notify_local_transform = false; - bool notify_transform = false; - - bool visible = true; - bool disable_scale = false; + ClientPhysicsInterpolationData *client_physics_interpolation_data = nullptr; #ifdef TOOLS_ENABLED Vector<Ref<Node3DGizmo>> gizmos; - bool gizmos_disabled = false; - bool gizmos_dirty = false; - bool transform_gizmo_visible = true; + bool gizmos_disabled : 1; + bool gizmos_dirty : 1; + bool transform_gizmo_visible : 1; #endif } data; @@ -150,6 +164,11 @@ protected: _FORCE_INLINE_ void _update_local_transform() const; _FORCE_INLINE_ void _update_rotation_and_scale() const; + void _set_vi_visible(bool p_visible) { data.vi_visible = p_visible; } + bool _is_vi_visible() const { return data.vi_visible; } + Transform3D _get_global_transform_interpolated(real_t p_interpolation_fraction); + void _disable_client_physics_interpolation(); + void _notification(int p_what); static void _bind_methods(); @@ -208,6 +227,9 @@ public: Quaternion get_quaternion() const; Transform3D get_global_transform() const; + Transform3D get_global_transform_interpolated(); + bool update_client_physics_interpolation_data(); + #ifdef TOOLS_ENABLED virtual Transform3D get_global_gizmo_transform() const; virtual Transform3D get_local_gizmo_transform() const; @@ -279,6 +301,7 @@ public: NodePath get_visibility_parent() const; Node3D(); + ~Node3D(); }; VARIANT_ENUM_CAST(Node3D::RotationEditMode) diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 150771545b..6d88323c76 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -129,9 +129,6 @@ void Occluder3D::_notification(int p_what) { } } -void Occluder3D::_bind_methods() { -} - Occluder3D::Occluder3D() { occluder = RS::get_singleton()->occluder_create(); } @@ -694,7 +691,7 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node, } PackedStringArray OccluderInstance3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) { warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling.")); diff --git a/scene/3d/occluder_instance_3d.h b/scene/3d/occluder_instance_3d.h index 91445710b3..62e9478527 100644 --- a/scene/3d/occluder_instance_3d.h +++ b/scene/3d/occluder_instance_3d.h @@ -49,7 +49,6 @@ protected: void _update(); virtual void _update_arrays(PackedVector3Array &r_vertices, PackedInt32Array &r_indices) = 0; - static void _bind_methods(); void _notification(int p_what); public: diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index 1f8f7cd54c..64259a24b0 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -216,24 +216,7 @@ void Path3D::_bind_methods() { ADD_SIGNAL(MethodInfo("curve_changed")); } -// Update transform, in deferred mode by default to avoid superfluity. -void PathFollow3D::update_transform(bool p_immediate) { - transform_dirty = true; - - if (p_immediate) { - _update_transform(); - } else { - callable_mp(this, &PathFollow3D::_update_transform).call_deferred(); - } -} - -// Update transform immediately . -void PathFollow3D::_update_transform() { - if (!transform_dirty) { - return; - } - transform_dirty = false; - +void PathFollow3D::update_transform() { if (!path) { return; } @@ -286,9 +269,7 @@ void PathFollow3D::_notification(int p_what) { Node *parent = get_parent(); if (parent) { path = Object::cast_to<Path3D>(parent); - if (path) { - update_transform(); - } + update_transform(); } } break; @@ -318,7 +299,7 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const { } PackedStringArray PathFollow3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path3D>(get_parent())) { @@ -414,6 +395,9 @@ void PathFollow3D::_bind_methods() { void PathFollow3D::set_progress(real_t p_progress) { ERR_FAIL_COND(!isfinite(p_progress)); + if (progress == p_progress) { + return; + } progress = p_progress; if (path) { @@ -435,10 +419,11 @@ void PathFollow3D::set_progress(real_t p_progress) { } void PathFollow3D::set_h_offset(real_t p_h_offset) { - h_offset = p_h_offset; - if (path) { - update_transform(); + if (h_offset == p_h_offset) { + return; } + h_offset = p_h_offset; + update_transform(); } real_t PathFollow3D::get_h_offset() const { @@ -446,10 +431,11 @@ real_t PathFollow3D::get_h_offset() const { } void PathFollow3D::set_v_offset(real_t p_v_offset) { - v_offset = p_v_offset; - if (path) { - update_transform(); + if (v_offset == p_v_offset) { + return; } + v_offset = p_v_offset; + update_transform(); } real_t PathFollow3D::get_v_offset() const { @@ -461,9 +447,10 @@ real_t PathFollow3D::get_progress() const { } void PathFollow3D::set_progress_ratio(real_t p_ratio) { - if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { - set_progress(p_ratio * path->get_curve()->get_baked_length()); - } + ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow3D that is the child of a Path3D which is itself part of the scene tree."); + ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow3D that does not have a Curve."); + ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow3D that has a 0 length curve."); + set_progress(p_ratio * path->get_curve()->get_baked_length()); } real_t PathFollow3D::get_progress_ratio() const { @@ -475,6 +462,9 @@ real_t PathFollow3D::get_progress_ratio() const { } void PathFollow3D::set_rotation_mode(RotationMode p_rotation_mode) { + if (rotation_mode == p_rotation_mode) { + return; + } rotation_mode = p_rotation_mode; update_configuration_warnings(); @@ -486,6 +476,9 @@ PathFollow3D::RotationMode PathFollow3D::get_rotation_mode() const { } void PathFollow3D::set_use_model_front(bool p_use_model_front) { + if (use_model_front == p_use_model_front) { + return; + } use_model_front = p_use_model_front; update_transform(); } @@ -495,6 +488,9 @@ bool PathFollow3D::is_using_model_front() const { } void PathFollow3D::set_loop(bool p_loop) { + if (loop == p_loop) { + return; + } loop = p_loop; update_transform(); } @@ -504,6 +500,9 @@ bool PathFollow3D::has_loop() const { } void PathFollow3D::set_tilt_enabled(bool p_enabled) { + if (tilt_enabled == p_enabled) { + return; + } tilt_enabled = p_enabled; update_transform(); } diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h index 0c9111bb8e..fb4f301375 100644 --- a/scene/3d/path_3d.h +++ b/scene/3d/path_3d.h @@ -90,7 +90,6 @@ protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); - void _update_transform(); static void _bind_methods(); @@ -124,7 +123,7 @@ public: PackedStringArray get_configuration_warnings() const override; - void update_transform(bool p_immediate = false); + void update_transform(); static Transform3D correct_posture(Transform3D p_transform, PathFollow3D::RotationMode p_rotation_mode); diff --git a/scene/3d/physical_bone_simulator_3d.cpp b/scene/3d/physical_bone_simulator_3d.cpp index ffe79e0892..8874c9cfc6 100644 --- a/scene/3d/physical_bone_simulator_3d.cpp +++ b/scene/3d/physical_bone_simulator_3d.cpp @@ -73,10 +73,15 @@ void PhysicalBoneSimulator3D::_pose_updated() { } 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); + _bone_pose_updated(skeleton, i); } } +void PhysicalBoneSimulator3D::_bone_pose_updated(Skeleton3D *p_skeleton, int p_bone_id) { + ERR_FAIL_INDEX(p_bone_id, bones.size()); + bones.write[p_bone_id].global_pose = p_skeleton->get_bone_global_pose(p_bone_id); +} + void PhysicalBoneSimulator3D::_set_active(bool p_active) { if (!Engine::get_singleton()->is_editor_hint()) { _reset_physical_bones_state(); @@ -285,11 +290,11 @@ void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_no } void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) { + _pose_updated(); + simulating = true; _reset_physical_bones_state(); - _pose_updated(); - Vector<int> sim_bones; if (p_bones.size() > 0) { sim_bones.resize(p_bones.size()); @@ -357,47 +362,17 @@ void PhysicalBoneSimulator3D::_process_modification() { 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(); - } - } + ERR_FAIL_COND(skeleton->get_bone_count() != bones.size()); + for (int i = 0; i < skeleton->get_bone_count(); i++) { + if (!bones[i].physical_bone) { + continue; } - } else { - ERR_FAIL_COND(skeleton->get_bone_count() != bones.size()); - for (int i = 0; i < skeleton->get_bone_count(); i++) { - if (!bones[i].physical_bone) { - continue; - } + if (bones[i].physical_bone->is_simulating_physics() == false) { + _bone_pose_updated(skeleton, i); + bones[i].physical_bone->reset_to_rest_position(); + } else if (simulating) { skeleton->set_bone_global_pose(i, bones[i].global_pose); } - - // TODO: - // The above method is performance heavy and needs to be improved. - // Ideally, the processing of set_bone_global_pose within Skeleton3D should be improved, - // but the workaround available now is to convert the global pose to a local pose on the SkeletonModifier side. - // However, the follow method needs recursive processing for deformations within PhysicalBoneSimulator3D to account for update order. - /* - 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++) { - if (!bones[i].physical_bone) { - continue; - } - 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()); - } - */ } } diff --git a/scene/3d/physical_bone_simulator_3d.h b/scene/3d/physical_bone_simulator_3d.h index ee900e0e77..24136be2b8 100644 --- a/scene/3d/physical_bone_simulator_3d.h +++ b/scene/3d/physical_bone_simulator_3d.h @@ -41,7 +41,6 @@ class PhysicalBoneSimulator3D : public SkeletonModifier3D { GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D); bool simulating = false; - bool enabled = true; struct SimulatedBone { int parent; @@ -74,6 +73,7 @@ protected: void _bone_list_changed(); void _pose_updated(); + void _bone_pose_updated(Skeleton3D *skeleton, int p_bone_id); virtual void _process_modification() override; diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp index f11aa7012a..f0a5013ca2 100644 --- a/scene/3d/physics/collision_object_3d.cpp +++ b/scene/3d/physics/collision_object_3d.cpp @@ -731,7 +731,7 @@ bool CollisionObject3D::get_capture_input_on_drag() const { } PackedStringArray CollisionObject3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape.")); diff --git a/scene/3d/physics/collision_polygon_3d.cpp b/scene/3d/physics/collision_polygon_3d.cpp index 76cd4db779..bf8dec7b54 100644 --- a/scene/3d/physics/collision_polygon_3d.cpp +++ b/scene/3d/physics/collision_polygon_3d.cpp @@ -169,7 +169,7 @@ void CollisionPolygon3D::set_margin(real_t p_margin) { } PackedStringArray CollisionPolygon3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!Object::cast_to<CollisionObject3D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape.")); diff --git a/scene/3d/physics/collision_shape_3d.cpp b/scene/3d/physics/collision_shape_3d.cpp index f3492a3cf3..304fa74b06 100644 --- a/scene/3d/physics/collision_shape_3d.cpp +++ b/scene/3d/physics/collision_shape_3d.cpp @@ -120,7 +120,7 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) { #endif PackedStringArray CollisionShape3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/3d/physics/shape_cast_3d.cpp b/scene/3d/physics/shape_cast_3d.cpp index ada238c7f2..8ad651fdf5 100644 --- a/scene/3d/physics/shape_cast_3d.cpp +++ b/scene/3d/physics/shape_cast_3d.cpp @@ -157,7 +157,7 @@ void ShapeCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collide_with_bodies", "enable"), &ShapeCast3D::set_collide_with_bodies); ClassDB::bind_method(D_METHOD("is_collide_with_bodies_enabled"), &ShapeCast3D::is_collide_with_bodies_enabled); - ClassDB::bind_method(D_METHOD("_get_collision_result"), &ShapeCast3D::_get_collision_result); + ClassDB::bind_method(D_METHOD("get_collision_result"), &ShapeCast3D::get_collision_result); ClassDB::bind_method(D_METHOD("set_debug_shape_custom_color", "debug_shape_custom_color"), &ShapeCast3D::set_debug_shape_custom_color); ClassDB::bind_method(D_METHOD("get_debug_shape_custom_color"), &ShapeCast3D::get_debug_shape_custom_color); @@ -169,7 +169,7 @@ void ShapeCast3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,100,0.01,suffix:m"), "set_margin", "get_margin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "_get_collision_result"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "collision_result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "get_collision_result"); ADD_GROUP("Collide With", "collide_with"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled"); @@ -475,7 +475,7 @@ bool ShapeCast3D::is_collide_with_bodies_enabled() const { return collide_with_bodies; } -Array ShapeCast3D::_get_collision_result() const { +Array ShapeCast3D::get_collision_result() const { Array ret; for (int i = 0; i < result.size(); ++i) { diff --git a/scene/3d/physics/shape_cast_3d.h b/scene/3d/physics/shape_cast_3d.h index 19b73e3f72..9fc5e71670 100644 --- a/scene/3d/physics/shape_cast_3d.h +++ b/scene/3d/physics/shape_cast_3d.h @@ -73,8 +73,6 @@ class ShapeCast3D : public Node3D { real_t collision_safe_fraction = 1.0; real_t collision_unsafe_fraction = 1.0; - Array _get_collision_result() const; - RID debug_instance; Ref<ArrayMesh> debug_mesh; @@ -123,6 +121,7 @@ public: Ref<StandardMaterial3D> get_debug_material(); + Array get_collision_result() const; int get_collision_count() const; Object *get_collider(int p_idx) const; RID get_collider_rid(int p_idx) const; diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp index c23032d3b9..981e872af2 100644 --- a/scene/3d/physics/vehicle_body_3d.cpp +++ b/scene/3d/physics/vehicle_body_3d.cpp @@ -106,7 +106,7 @@ void VehicleWheel3D::_notification(int p_what) { } PackedStringArray VehicleWheel3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!Object::cast_to<VehicleBody3D>(get_parent())) { warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D.")); @@ -219,6 +219,14 @@ bool VehicleWheel3D::is_in_contact() const { return m_raycastInfo.m_isInContact; } +Vector3 VehicleWheel3D::get_contact_point() const { + return m_raycastInfo.m_contactPointWS; +} + +Vector3 VehicleWheel3D::get_contact_normal() const { + return m_raycastInfo.m_contactNormalWS; +} + Node3D *VehicleWheel3D::get_contact_body() const { return m_raycastInfo.m_groundObject; } @@ -256,6 +264,8 @@ void VehicleWheel3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_in_contact"), &VehicleWheel3D::is_in_contact); ClassDB::bind_method(D_METHOD("get_contact_body"), &VehicleWheel3D::get_contact_body); + ClassDB::bind_method(D_METHOD("get_contact_point"), &VehicleWheel3D::get_contact_point); + ClassDB::bind_method(D_METHOD("get_contact_normal"), &VehicleWheel3D::get_contact_normal); ClassDB::bind_method(D_METHOD("set_roll_influence", "roll_influence"), &VehicleWheel3D::set_roll_influence); ClassDB::bind_method(D_METHOD("get_roll_influence"), &VehicleWheel3D::get_roll_influence); @@ -287,11 +297,11 @@ void VehicleWheel3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_friction_slip"), "set_friction_slip", "get_friction_slip"); ADD_GROUP("Suspension", "suspension_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_travel", PROPERTY_HINT_NONE, "suffix:m"), "set_suspension_travel", "get_suspension_travel"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness"), "set_suspension_stiffness", "get_suspension_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness", PROPERTY_HINT_NONE, U"suffix:N/mm"), "set_suspension_stiffness", "get_suspension_stiffness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_max_force", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_suspension_max_force", "get_suspension_max_force"); ADD_GROUP("Damping", "damping_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression"), "set_damping_compression", "get_damping_compression"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation"), "set_damping_relaxation", "get_damping_relaxation"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_compression", "get_damping_compression"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_relaxation", "get_damping_relaxation"); } void VehicleWheel3D::set_engine_force(real_t p_engine_force) { diff --git a/scene/3d/physics/vehicle_body_3d.h b/scene/3d/physics/vehicle_body_3d.h index def9984440..24f120ed26 100644 --- a/scene/3d/physics/vehicle_body_3d.h +++ b/scene/3d/physics/vehicle_body_3d.h @@ -130,6 +130,10 @@ public: bool is_in_contact() const; + Vector3 get_contact_point() const; + + Vector3 get_contact_normal() const; + Node3D *get_contact_body() const; void set_roll_influence(real_t p_value); diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index 8d6e717132..f970879aa4 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -113,6 +113,16 @@ void RemoteTransform3D::_notification(int p_what) { _update_cache(); } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (cache.is_valid()) { + _update_remote(); + Node3D *n = Object::cast_to<Node3D>(ObjectDB::get_instance(cache)); + if (n) { + n->reset_physics_interpolation(); + } + } + } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: case NOTIFICATION_TRANSFORM_CHANGED: { if (!is_inside_tree()) { @@ -201,7 +211,7 @@ void RemoteTransform3D::force_update_cache() { } PackedStringArray RemoteTransform3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) { warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work.")); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 3d24b3bbe9..db9c4db30d 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -69,13 +69,13 @@ 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")) { + if (p_path == SNAME("animate_physical_bones")) { set_animate_physical_bones(p_value); + return true; } #endif + String path = p_path; if (!path.begins_with("bones/")) { return false; @@ -103,6 +103,8 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { set_bone_pose_rotation(which, p_value); } else if (what == "scale") { set_bone_pose_scale(which, p_value); + } else if (what == "bone_meta") { + set_bone_meta(which, path.get_slicec('/', 3), p_value); #ifndef DISABLE_DEPRECATED } else if (what == "pose" || what == "bound_children") { // Kept for compatibility from 3.x to 4.x. @@ -139,13 +141,13 @@ 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")) { + if (p_path == SNAME("animate_physical_bones")) { r_ret = get_animate_physical_bones(); + return true; } #endif + String path = p_path; if (!path.begins_with("bones/")) { return false; @@ -170,6 +172,8 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const { r_ret = get_bone_pose_rotation(which); } else if (what == "scale") { r_ret = get_bone_pose_scale(which); + } else if (what == "bone_meta") { + r_ret = get_bone_meta(which, path.get_slicec('/', 3)); } else { return false; } @@ -187,6 +191,11 @@ void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + + for (const KeyValue<StringName, Variant> &K : bones[i].metadata) { + PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + PNAME("bone_meta/") + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + p_list->push_back(pi); + } } for (PropertyInfo &E : *p_list) { @@ -300,7 +309,7 @@ void Skeleton3D::setup_simulator() { simulator = sim; sim->is_compat = true; sim->set_active(false); // Don't run unneeded process. - add_child(simulator); + add_child(simulator, false, INTERNAL_MODE_BACK); set_animate_physical_bones(animate_physical_bones); } #endif // _DISABLE_DEPRECATED @@ -531,6 +540,57 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) { version++; } +Variant Skeleton3D::get_bone_meta(int p_bone, const StringName &p_key) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, Variant()); + + if (!bones[p_bone].metadata.has(p_key)) { + return Variant(); + } + return bones[p_bone].metadata[p_key]; +} + +TypedArray<StringName> Skeleton3D::_get_bone_meta_list_bind(int p_bone) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, TypedArray<StringName>()); + + TypedArray<StringName> _metaret; + for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) { + _metaret.push_back(K.key); + } + return _metaret; +} + +void Skeleton3D::get_bone_meta_list(int p_bone, List<StringName> *p_list) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + + for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) { + p_list->push_back(K.key); + } +} + +bool Skeleton3D::has_bone_meta(int p_bone, const StringName &p_key) const { + const int bone_size = bones.size(); + ERR_FAIL_INDEX_V(p_bone, bone_size, false); + + return bones[p_bone].metadata.has(p_key); +} + +void Skeleton3D::set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + + if (p_value.get_type() == Variant::NIL) { + if (bones.write[p_bone].metadata.has(p_key)) { + bones.write[p_bone].metadata.erase(p_key); + } + return; + } + + bones.write[p_bone].metadata.insert(p_key, p_value, false); +} + bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const { int parent_of_bone = get_bone_parent(p_bone); @@ -1014,6 +1074,11 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name); ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "name"), &Skeleton3D::set_bone_name); + ClassDB::bind_method(D_METHOD("get_bone_meta", "bone_idx", "key"), &Skeleton3D::get_bone_meta); + ClassDB::bind_method(D_METHOD("get_bone_meta_list", "bone_idx"), &Skeleton3D::_get_bone_meta_list_bind); + ClassDB::bind_method(D_METHOD("has_bone_meta", "bone_idx", "key"), &Skeleton3D::has_bone_meta); + ClassDB::bind_method(D_METHOD("set_bone_meta", "bone_idx", "key", "value"), &Skeleton3D::set_bone_meta); + ClassDB::bind_method(D_METHOD("get_concatenated_bone_names"), &Skeleton3D::get_concatenated_bone_names); ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &Skeleton3D::get_bone_parent); diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index a009383f45..07bdeccf2f 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -116,6 +116,8 @@ private: } } + HashMap<StringName, Variant> metadata; + #ifndef DISABLE_DEPRECATED Transform3D pose_global_no_override; real_t global_pose_override_amount = 0.0; @@ -193,6 +195,7 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); + TypedArray<StringName> _get_bone_meta_list_bind(int p_bone) const; static void _bind_methods(); virtual void add_child_notify(Node *p_child) override; @@ -238,6 +241,12 @@ public: void set_motion_scale(float p_motion_scale); float get_motion_scale() const; + // bone metadata + Variant get_bone_meta(int p_bone, const StringName &p_key) const; + void get_bone_meta_list(int p_bone, List<StringName> *p_list) const; + bool has_bone_meta(int p_bone, const StringName &p_key) const; + void set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value); + // Posing API Transform3D get_bone_pose(int p_bone) const; Vector3 get_bone_pose_position(int p_bone) const; diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 0d6316ee35..2476e7d5cd 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -503,7 +503,11 @@ Transform3D SkeletonIK3D::_get_target_transform() { Node3D *target_node_override = cast_to<Node3D>(target_node_override_ref.get_validated_object()); if (target_node_override && target_node_override->is_inside_tree()) { - return target_node_override->get_global_transform(); + // Make sure to use the interpolated transform as target. + // When physics interpolation is off this will pass through to get_global_transform(). + // When using interpolation, ensure that the target matches the interpolated visual position + // of the target when updating the IK each frame. + return target_node_override->get_global_transform_interpolated(); } else { return target; } diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp index 9851214194..d5c603112e 100644 --- a/scene/3d/skeleton_modifier_3d.cpp +++ b/scene/3d/skeleton_modifier_3d.cpp @@ -37,7 +37,7 @@ 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.")); + warnings.push_back(RTR("Skeleton3D node not set! SkeletonModifier3D must be child of Skeleton3D.")); } return warnings; } diff --git a/scene/main/window.compat.inc b/scene/3d/soft_body_3d.compat.inc index 0bba01bb1b..0b01bfeb1f 100644 --- a/scene/main/window.compat.inc +++ b/scene/3d/soft_body_3d.compat.inc @@ -1,5 +1,5 @@ /**************************************************************************/ -/* window.compat.inc */ +/* soft_body_3d.compat.inc */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,19 +30,12 @@ #ifndef DISABLE_DEPRECATED -void Window::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Window::get_theme_icon, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Window::get_theme_stylebox, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Window::get_theme_font, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Window::get_theme_font_size, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Window::get_theme_color, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Window::get_theme_constant, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Window::has_theme_icon, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Window::has_theme_stylebox, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Window::has_theme_font, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Window::has_theme_font_size, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Window::has_theme_color, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Window::has_theme_constant, DEFVAL("")); +void SoftBody3D::_pin_point_bind_compat_94684(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path) { + pin_point(p_point_index, pin, p_spatial_attachment_path, -1); } -#endif +void SoftBody3D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::_pin_point_bind_compat_94684, DEFVAL(NodePath())); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index f02cd9b700..b0fc94d75f 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "soft_body_3d.h" +#include "soft_body_3d.compat.inc" #include "scene/3d/physics/physics_body_3d.h" @@ -200,12 +201,18 @@ bool SoftBody3D::_set_property_pinned_points_indices(const Array &p_indices) { int point_index; for (int i = 0; i < p_indices_size; ++i) { point_index = p_indices.get(i); - if (w[i].point_index != point_index) { - if (-1 != w[i].point_index) { + if (w[i].point_index != point_index || pinned_points.size() < p_indices_size) { + bool insert = false; + if (w[i].point_index != -1 && p_indices.find(w[i].point_index) == -1) { pin_point(w[i].point_index, false); + insert = true; } w[i].point_index = point_index; - pin_point(w[i].point_index, true); + if (insert) { + pin_point(w[i].point_index, true, NodePath(), i); + } else { + pin_point(w[i].point_index, true); + } } } return true; @@ -218,7 +225,13 @@ bool SoftBody3D::_set_property_pinned_points_attachment(int p_item, const String if ("spatial_attachment_path" == p_what) { PinnedPoint *w = pinned_points.ptrw(); - callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value); + + if (is_inside_tree()) { + callable_mp(this, &SoftBody3D::_pin_point_deferred).call_deferred(Variant(w[p_item].point_index), true, p_value); + } else { + pin_point(w[p_item].point_index, true, p_value); + _make_cache_dirty(); + } } else if ("offset" == p_what) { PinnedPoint *w = pinned_points.ptrw(); w[p_item].offset = p_value; @@ -350,7 +363,7 @@ void SoftBody3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_transform", "point_index"), &SoftBody3D::get_point_transform); - ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path"), &SoftBody3D::pin_point, DEFVAL(NodePath())); + ClassDB::bind_method(D_METHOD("set_point_pinned", "point_index", "pinned", "attachment_path", "insert_at"), &SoftBody3D::pin_point, DEFVAL(NodePath()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("is_point_pinned", "point_index"), &SoftBody3D::is_point_pinned); ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &SoftBody3D::set_ray_pickable); @@ -377,7 +390,7 @@ void SoftBody3D::_bind_methods() { } PackedStringArray SoftBody3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = MeshInstance3D::get_configuration_warnings(); if (mesh.is_null()) { warnings.push_back(RTR("This body will be ignored until you set a mesh.")); @@ -662,10 +675,11 @@ void SoftBody3D::pin_point_toggle(int p_point_index) { pin_point(p_point_index, !(-1 != _has_pinned_point(p_point_index))); } -void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path) { +void SoftBody3D::pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path, int p_insert_at) { + ERR_FAIL_COND_MSG(p_insert_at < -1 || p_insert_at >= pinned_points.size(), "Invalid index for pin point insertion position."); _pin_point_on_physics_server(p_point_index, pin); if (pin) { - _add_pinned_point(p_point_index, p_spatial_attachment_path); + _add_pinned_point(p_point_index, p_spatial_attachment_path, p_insert_at); } else { _remove_pinned_point(p_point_index); } @@ -724,7 +738,7 @@ void SoftBody3D::_pin_point_on_physics_server(int p_point_index, bool pin) { PhysicsServer3D::get_singleton()->soft_body_pin_point(physics_rid, p_point_index, pin); } -void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path) { +void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path, int p_insert_at) { SoftBody3D::PinnedPoint *pinned_point; if (-1 == _get_pinned_point(p_point_index, pinned_point)) { // Create new @@ -737,7 +751,11 @@ void SoftBody3D::_add_pinned_point(int p_point_index, const NodePath &p_spatial_ pp.offset = (pp.spatial_attachment->get_global_transform().affine_inverse() * get_global_transform()).xform(PhysicsServer3D::get_singleton()->soft_body_get_point_global_position(physics_rid, pp.point_index)); } - pinned_points.push_back(pp); + if (p_insert_at != -1) { + pinned_points.insert(p_insert_at, pp); + } else { + pinned_points.push_back(pp); + } } else { pinned_point->point_index = p_point_index; diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h index ab30f7e654..b01d462d9f 100644 --- a/scene/3d/soft_body_3d.h +++ b/scene/3d/soft_body_3d.h @@ -126,6 +126,11 @@ protected: void _notification(int p_what); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _pin_point_bind_compat_94684(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath()); + static void _bind_compatibility_methods(); +#endif + PackedStringArray get_configuration_warnings() const override; public: @@ -177,7 +182,7 @@ public: Vector3 get_point_transform(int p_point_index); void pin_point_toggle(int p_point_index); - void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath()); + void pin_point(int p_point_index, bool pin, const NodePath &p_spatial_attachment_path = NodePath(), int p_insert_at = -1); bool is_point_pinned(int p_point_index) const; void _pin_point_deferred(int p_point_index, bool pin, const NodePath p_spatial_attachment_path); @@ -193,7 +198,7 @@ private: void _update_cache_pin_points_datas(); void _pin_point_on_physics_server(int p_point_index, bool pin); - void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path); + void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path, int p_insert_at = -1); void _reset_points_offsets(); void _remove_pinned_point(int p_point_index); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 50218a6d86..42460eec4c 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -132,7 +132,7 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, // Properly setup UVs for impostor textures (AtlasTexture). Ref<AtlasTexture> atlas_tex = p_texture; - if (atlas_tex != nullptr) { + if (atlas_tex.is_valid()) { src_tsize[0] = atlas_tex->get_atlas()->get_width(); src_tsize[1] = atlas_tex->get_atlas()->get_height(); } @@ -1324,7 +1324,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool name = animation; } - ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name)); + ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name)); ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name)); if (frames->get_frame_count(name) == 0) { @@ -1402,7 +1402,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) { emit_signal(SceneStringName(animation_changed)); - if (frames == nullptr) { + if (frames.is_null()) { animation = StringName(); stop(); ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index f14ae3a285..a59754c8cc 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -30,6 +30,8 @@ #include "visual_instance_3d.h" +#include "core/config/project_settings.h" + AABB VisualInstance3D::get_aabb() const { AABB ret; GDVIRTUAL_CALL(_get_aabb, ret); @@ -41,7 +43,38 @@ void VisualInstance3D::_update_visibility() { return; } - RS::get_singleton()->instance_set_visible(get_instance(), is_visible_in_tree()); + bool already_visible = _is_vi_visible(); + bool visible = is_visible_in_tree(); + _set_vi_visible(visible); + + // If making visible, make sure the rendering server is up to date with the transform. + if (visible && !already_visible) { + if (!_is_using_identity_transform()) { + Transform3D gt = get_global_transform(); + RS::get_singleton()->instance_set_transform(instance, gt); + } + } + + RS::get_singleton()->instance_set_visible(instance, visible); +} + +void VisualInstance3D::_physics_interpolated_changed() { + RenderingServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated()); +} + +void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) { + // Prevent sending instance transforms when using global coordinates. + _set_use_identity_transform(p_enable); + + if (is_inside_tree()) { + if (p_enable) { + // Want to make sure instance is using identity transform. + RS::get_singleton()->instance_set_transform(instance, Transform3D()); + } else { + // Want to make sure instance is up to date. + RS::get_singleton()->instance_set_transform(instance, get_global_transform()); + } + } } void VisualInstance3D::_notification(int p_what) { @@ -53,13 +86,52 @@ void VisualInstance3D::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { - Transform3D gt = get_global_transform(); - RenderingServer::get_singleton()->instance_set_transform(instance, gt); + if (_is_vi_visible() || is_physics_interpolated_and_enabled()) { + if (!_is_using_identity_transform()) { + RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); + + // For instance when first adding to the tree, when the previous transform is + // unset, to prevent streaking from the origin. + if (_is_physics_interpolation_reset_requested() && is_physics_interpolated_and_enabled() && is_inside_tree()) { + if (_is_vi_visible()) { + _notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + } + _set_physics_interpolation_reset_requested(false); + } + } + } + } break; + + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (_is_vi_visible() && is_physics_interpolated() && is_inside_tree()) { + // We must ensure the RenderingServer transform is up to date before resetting. + // This is because NOTIFICATION_TRANSFORM_CHANGED is deferred, + // and cannot be relied to be called in order before NOTIFICATION_RESET_PHYSICS_INTERPOLATION. + if (!_is_using_identity_transform()) { + RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); + } + + RenderingServer::get_singleton()->instance_reset_physics_interpolation(instance); + } +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + else if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) { + + String node_name = is_inside_tree() ? String(get_path()) : String(get_name()); + if (!_is_vi_visible()) { + WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with unhidden nodes: \"" + node_name + "\"."); + } + if (!is_physics_interpolated()) { + WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with interpolated nodes: \"" + node_name + "\"."); + } + } +#endif + } break; case NOTIFICATION_EXIT_WORLD: { RenderingServer::get_singleton()->instance_set_scenario(instance, RID()); RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID()); + _set_vi_visible(false); } break; case NOTIFICATION_VISIBILITY_CHANGED: { @@ -425,7 +497,7 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() { } PackedStringArray GeometryInstance3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) { warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance.")); diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index 59ede26ac1..9b02c928b7 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -45,6 +45,9 @@ class VisualInstance3D : public Node3D { protected: void _update_visibility(); + virtual void _physics_interpolated_changed() override; + void set_instance_use_identity_transform(bool p_enable); + void _notification(int p_what); static void _bind_methods(); void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index ffca856fba..80ff176a98 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -518,7 +518,7 @@ AABB VoxelGI::get_aabb() const { } PackedStringArray VoxelGI::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("VoxelGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h index 6ea1cfdbb0..08d018eee9 100644 --- a/scene/3d/voxelizer.h +++ b/scene/3d/voxelizer.h @@ -35,9 +35,8 @@ class Voxelizer { private: - enum { + enum : uint32_t { CHILD_EMPTY = 0xFFFFFFFF - }; struct Cell { diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp index baaa9eee48..3b533da701 100644 --- a/scene/3d/xr_hand_modifier_3d.cpp +++ b/scene/3d/xr_hand_modifier_3d.cpp @@ -30,6 +30,7 @@ #include "xr_hand_modifier_3d.h" +#include "core/config/project_settings.h" #include "servers/xr/xr_pose.h" #include "servers/xr_server.h" @@ -207,6 +208,11 @@ void XRHandModifier3D::_process_modification() { // Apply previous relative transforms if they are stored. for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) { + const int bone = joints[joint].bone; + if (bone == -1) { + continue; + } + if (bone_update == BONE_UPDATE_FULL) { skeleton->set_bone_pose_position(joints[joint].bone, previous_relative_transforms[joint].origin); } @@ -278,6 +284,17 @@ void XRHandModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { _get_joint_data(); } +PackedStringArray XRHandModifier3D::get_configuration_warnings() const { + PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings(); + + // Detect OpenXR without the Hand Tracking extension. + if (GLOBAL_GET("xr/openxr/enabled") && !GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { + warnings.push_back("XRHandModifier3D requires the OpenXR Hand Tracking extension to be enabled."); + } + + return warnings; +} + void XRHandModifier3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h index 3d78f32b64..d58ccd0adf 100644 --- a/scene/3d/xr_hand_modifier_3d.h +++ b/scene/3d/xr_hand_modifier_3d.h @@ -55,6 +55,8 @@ public: void set_bone_update(BoneUpdate p_bone_update); BoneUpdate get_bone_update() const; + PackedStringArray get_configuration_warnings() const override; + void _notification(int p_what); protected: diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index 3f4b962641..214c1f77ca 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -77,7 +77,7 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) { } PackedStringArray XRCamera3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Camera3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // Warn if the node has a parent which isn't an XROrigin3D! @@ -303,6 +303,8 @@ StringName XRNode3D::get_pose_name() const { void XRNode3D::set_show_when_tracked(bool p_show) { show_when_tracked = p_show; + + _update_visibility(); } bool XRNode3D::get_show_when_tracked() const { @@ -361,6 +363,9 @@ void XRNode3D::_bind_tracker() { if (pose.is_valid()) { set_transform(pose->get_adjusted_transform()); _set_has_tracking_data(pose->get_has_tracking_data()); + } else { + // Pose has been invalidated or was never set. + _set_has_tracking_data(false); } } } @@ -407,6 +412,10 @@ void XRNode3D::_pose_lost_tracking(const Ref<XRPose> &p_pose) { } void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) { + // Always update our visibility, we may have set our tracking data + // when conditions weren't right. + _update_visibility(); + // Ignore if the has_tracking_data state isn't changing. if (p_has_tracking_data == has_tracking_data) { return; @@ -415,10 +424,19 @@ 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); +} +void XRNode3D::_update_visibility() { // If configured, show or hide the node based on tracking data. if (show_when_tracked) { - set_visible(has_tracking_data); + // Only react to this if we have a primary interface. + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server != nullptr) { + Ref<XRInterface> xr_interface = xr_server->get_primary_interface(); + if (xr_interface.is_valid()) { + set_visible(has_tracking_data); + } + } } } @@ -443,7 +461,7 @@ XRNode3D::~XRNode3D() { } PackedStringArray XRNode3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // Warn if the node has a parent which isn't an XROrigin3D! @@ -626,7 +644,7 @@ Plane XRAnchor3D::get_plane() const { Vector<XROrigin3D *> XROrigin3D::origin_nodes; PackedStringArray XROrigin3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { bool has_camera = false; diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index a42f6d470f..94c3923433 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -95,6 +95,8 @@ protected: void _pose_lost_tracking(const Ref<XRPose> &p_pose); void _set_has_tracking_data(bool p_has_tracking_data); + void _update_visibility(); + public: void _validate_property(PropertyInfo &p_property) const; void set_tracker(const StringName &p_tracker_name); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 59ebf38253..cdc85d2b2d 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -138,15 +138,11 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe bool p_seek = p_playback_info.seeked; bool p_is_external_seeking = p_playback_info.is_external_seeking; - bool is_just_looped = false; - // 1. Progress for AnimationNode. + bool will_end = Animation::is_greater_or_equal_approx(cur_time + cur_delta, cur_len); if (cur_loop_mode != Animation::LOOP_NONE) { if (cur_loop_mode == Animation::LOOP_LINEAR) { if (!Math::is_zero_approx(cur_len)) { - if (Animation::is_less_or_equal_approx(prev_time, cur_len) && Animation::is_greater_approx(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; @@ -156,7 +152,6 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe backward = !backward; } else if (Animation::is_less_or_equal_approx(prev_time, cur_len) && Animation::is_greater_approx(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); } @@ -190,7 +185,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe nti.position = cur_time; nti.delta = cur_delta; nti.loop_mode = cur_loop_mode; - nti.is_just_looped = is_just_looped; + nti.will_end = will_end; // 3. Progress for Animation. double prev_playback_time = prev_time + start_offset; @@ -242,6 +237,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe } // Finished. if (Animation::is_less_approx(prev_playback_time, anim_size) && Animation::is_greater_or_equal_approx(cur_playback_time, anim_size)) { + cur_playback_time = anim_size; process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_finished), animation); } } @@ -758,9 +754,6 @@ AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::Pl return nti; } -void AnimationNodeAdd2::_bind_methods() { -} - AnimationNodeAdd2::AnimationNodeAdd2() { add_input("in"); add_input("add"); @@ -804,9 +797,6 @@ AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::Pl return nti; } -void AnimationNodeAdd3::_bind_methods() { -} - AnimationNodeAdd3::AnimationNodeAdd3() { add_input("-add"); add_input("in"); @@ -849,9 +839,6 @@ bool AnimationNodeBlend2::has_filter() const { return true; } -void AnimationNodeBlend2::_bind_methods() { -} - AnimationNodeBlend2::AnimationNodeBlend2() { add_input("in"); add_input("blend"); @@ -891,9 +878,6 @@ AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer:: return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough. } -void AnimationNodeBlend3::_bind_methods() { -} - AnimationNodeBlend3::AnimationNodeBlend3() { add_input("-blend"); add_input("in"); @@ -936,9 +920,6 @@ AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::Pl return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); } -void AnimationNodeSub2::_bind_methods() { -} - AnimationNodeSub2::AnimationNodeSub2() { add_input("in"); add_input("sub"); @@ -976,9 +957,6 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixe return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); } -void AnimationNodeTimeScale::_bind_methods() { -} - AnimationNodeTimeScale::AnimationNodeTimeScale() { add_input("in"); } @@ -1018,9 +996,6 @@ AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); } -void AnimationNodeTimeSeek::_bind_methods() { -} - AnimationNodeTimeSeek::AnimationNodeTimeSeek() { add_input("in"); } diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index c7ef7ed624..2add35d009 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -200,9 +200,6 @@ class AnimationNodeAdd2 : public AnimationNodeSync { StringName add_amount = PNAME("add_amount"); -protected: - static void _bind_methods(); - public: void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -220,9 +217,6 @@ class AnimationNodeAdd3 : public AnimationNodeSync { StringName add_amount = PNAME("add_amount"); -protected: - static void _bind_methods(); - public: void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -240,9 +234,6 @@ class AnimationNodeBlend2 : public AnimationNodeSync { StringName blend_amount = PNAME("blend_amount"); -protected: - static void _bind_methods(); - public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -259,9 +250,6 @@ class AnimationNodeBlend3 : public AnimationNodeSync { StringName blend_amount = PNAME("blend_amount"); -protected: - static void _bind_methods(); - public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -277,9 +265,6 @@ class AnimationNodeSub2 : public AnimationNodeSync { StringName sub_amount = PNAME("sub_amount"); -protected: - static void _bind_methods(); - public: void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -297,9 +282,6 @@ class AnimationNodeTimeScale : public AnimationNode { StringName scale = PNAME("scale"); -protected: - static void _bind_methods(); - public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -316,9 +298,6 @@ class AnimationNodeTimeSeek : public AnimationNode { StringName seek_pos_request = PNAME("seek_request"); -protected: - static void _bind_methods(); - public: virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 20dd12f8c3..664302d45e 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -81,9 +81,8 @@ bool AnimationMixer::_set(const StringName &p_name, const Variant &p_value) { List<Variant> keys; d.get_key_list(&keys); for (const Variant &K : keys) { - StringName lib_name = K; - Ref<AnimationLibrary> lib = d[lib_name]; - add_animation_library(lib_name, lib); + Ref<AnimationLibrary> lib = d[K]; + add_animation_library(K, lib); } emit_signal(SNAME("animation_libraries_updated")); @@ -947,14 +946,6 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) { clear_animation_instances(); } -Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) { - Variant res; - if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) { - return res; - } - return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx); -} - Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) { #ifndef _3D_DISABLED switch (p_anim->track_get_type(p_track)) { @@ -974,6 +965,17 @@ Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, in return p_value; } +Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) { + if (is_GDVIRTUAL_CALL_post_process_key_value) { + Variant res; + if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) { + return res; + } + is_GDVIRTUAL_CALL_post_process_key_value = false; + } + return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx); +} + void AnimationMixer::_blend_init() { // Check all tracks, see if they need modification. root_motion_position = Vector3(0, 0, 0); @@ -1080,22 +1082,26 @@ void AnimationMixer::_blend_calc_total_weight() { for (const AnimationInstance &ai : animation_instances) { Ref<Animation> a = ai.animation_data.animation; real_t weight = ai.playback_info.weight; - Vector<real_t> track_weights = ai.playback_info.track_weights; - Vector<Animation::TypeHash> processed_hashes; - for (int i = 0; i < a->get_track_count(); i++) { - if (!a->track_is_enabled(i)) { + const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr(); + int track_weights_count = ai.playback_info.track_weights.size(); + static LocalVector<Animation::TypeHash> processed_hashes; + processed_hashes.clear(); + const Vector<Animation::Track *> tracks = a->get_tracks(); + for (const Animation::Track *animation_track : tracks) { + if (!animation_track->enabled) { continue; } - Animation::TypeHash thash = a->track_get_type_hash(i); - if (!track_cache.has(thash) || processed_hashes.has(thash)) { + Animation::TypeHash thash = animation_track->thash; + TrackCache **track_ptr = track_cache.getptr(thash); + if (track_ptr == nullptr || processed_hashes.has(thash)) { // No path, but avoid error spamming. // Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly. continue; } - TrackCache *track = track_cache[thash]; + TrackCache *track = *track_ptr; int blend_idx = track_map[track->path]; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); - real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight; + real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; track->total_weight += blend; processed_hashes.push_back(thash); } @@ -1115,26 +1121,33 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag; bool is_external_seeking = ai.playback_info.is_external_seeking; real_t weight = ai.playback_info.weight; - Vector<real_t> track_weights = ai.playback_info.track_weights; + const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr(); + int track_weights_count = ai.playback_info.track_weights.size(); bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. bool seeked_backward = signbit(p_delta); #ifndef _3D_DISABLED bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED - - for (int i = 0; i < a->get_track_count(); i++) { - if (!a->track_is_enabled(i)) { + const Vector<Animation::Track *> tracks = a->get_tracks(); + Animation::Track *const *tracks_ptr = tracks.ptr(); + real_t a_length = a->get_length(); + int count = tracks.size(); + for (int i = 0; i < count; i++) { + const Animation::Track *animation_track = tracks_ptr[i]; + if (!animation_track->enabled) { continue; } - Animation::TypeHash thash = a->track_get_type_hash(i); - if (!track_cache.has(thash)) { + Animation::TypeHash thash = animation_track->thash; + TrackCache **track_ptr = track_cache.getptr(thash); + if (track_ptr == nullptr) { continue; // No path, but avoid error spamming. } - TrackCache *track = track_cache[thash]; - ERR_CONTINUE(!track_map.has(track->path)); - int blend_idx = track_map[track->path]; + TrackCache *track = *track_ptr; + int *blend_idx_ptr = track_map.getptr(track->path); + ERR_CONTINUE(blend_idx_ptr == nullptr); + int blend_idx = *blend_idx_ptr; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); - real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight; + real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; if (!deterministic) { // If non-deterministic, do normalization. // It would be better to make this if statement outside the for loop, but come here since too much code... @@ -1143,8 +1156,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } blend = blend / track->total_weight; } - Animation::TrackType ttype = a->track_get_type(i); - track->root_motion = root_motion_track == a->track_get_path(i); + Animation::TrackType ttype = animation_track->type; + track->root_motion = root_motion_track == animation_track->path; switch (ttype) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED @@ -1161,26 +1174,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { prev_time = 0; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a->get_length())) { + if (Animation::is_greater_approx(prev_time, (double)a_length)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; @@ -1195,7 +1208,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]); + a->try_position_track_interpolate(i, (double)a_length, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = 0; @@ -1210,7 +1223,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_position_track_interpolate(i, 0, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } } Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); @@ -1221,7 +1234,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_position_track_interpolate(i, time, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); + prev_time = !backward ? 0 : (double)a_length; } { Vector3 loc; @@ -1249,26 +1262,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { prev_time = 0; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a->get_length())) { + if (Animation::is_greater_approx(prev_time, (double)a_length)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; @@ -1283,7 +1296,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); + a->try_rotation_track_interpolate(i, (double)a_length, &rot[1]); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = 0; @@ -1297,7 +1310,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); a->try_rotation_track_interpolate(i, 0, &rot[1]); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } } Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); @@ -1308,7 +1321,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_rotation_track_interpolate(i, time, &rot[1]); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = !backward ? 0 : (double)a->get_length(); + prev_time = !backward ? 0 : (double)a_length; } { Quaternion rot; @@ -1336,26 +1349,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { prev_time = 0; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a->get_length())) { + if (Animation::is_greater_approx(prev_time, (double)a_length)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; @@ -1370,7 +1383,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx); - a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]); + a->try_scale_track_interpolate(i, (double)a_length, &scale[1]); root_motion_cache.scale += (scale[1] - scale[0]) * blend; scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); prev_time = 0; @@ -1385,7 +1398,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_scale_track_interpolate(i, 0, &scale[1]); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } } Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); @@ -1396,7 +1409,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_scale_track_interpolate(i, time, &scale[1]); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); + prev_time = !backward ? 0 : (double)a_length; } { Vector3 scale; @@ -1560,7 +1573,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } PlayingAudioTrackInfo &track_info = t->playing_streams[oid]; - track_info.length = a->get_length(); + track_info.length = a_length; track_info.time = time; track_info.volume += blend; track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE; @@ -1623,6 +1636,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } if (t_obj->call(SNAME("get_is_sample"))) { + if (t->audio_stream_playback->get_sample_playback().is_valid()) { + AudioServer::get_singleton()->stop_sample_playback(t->audio_stream_playback->get_sample_playback()); + } Ref<AudioSamplePlayback> sample_playback; sample_playback.instantiate(); sample_playback->stream = stream; @@ -1679,7 +1695,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { 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()); + at_anim_pos = Math::pingpong(time - pos, (double)a_length); } break; default: break; @@ -1717,6 +1733,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } } + is_GDVIRTUAL_CALL_post_process_key_value = true; } void AnimationMixer::_blend_apply() { diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index c029c68ae1..5482197fbd 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -48,6 +48,7 @@ class AnimationMixer : public Node { #endif // TOOLS_ENABLED bool reset_on_save = true; + bool is_GDVIRTUAL_CALL_post_process_key_value = true; public: enum AnimationCallbackModeProcess { diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index ecf4054e23..c3c5399a6b 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -804,7 +804,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St pi.weight = 0; 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); + _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only); } // Check current node existence. @@ -881,7 +881,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St } // Find next and see when to transition. - bool will_end = _transition_to_next_recursive(tree, p_state_machine, p_test_only) || current == AnimationNodeStateMachine::END_NODE; + bool will_end = _transition_to_next_recursive(tree, p_state_machine, p_delta, p_test_only) || current == AnimationNodeStateMachine::END_NODE; // Predict remaining time. if (will_end || ((p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) && !p_state_machine->has_transition_from(current))) { @@ -899,10 +899,11 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const St return current_nti; } -bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) { +bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, double p_delta, bool p_test_only) { _reset_request_for_fading_from = false; AnimationMixer::PlaybackInfo pi; + pi.delta = p_delta; NextInfo next; Vector<StringName> transition_path; transition_path.push_back(current); diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index b58ff4d224..648e96b138 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -306,7 +306,7 @@ class AnimationNodeStateMachinePlayback : public Resource { 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); + bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, double p_delta, bool p_test_only); NextInfo _find_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const; Ref<AnimationNodeStateMachineTransition> _check_group_transition(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const AnimationNodeStateMachine::Transition &p_transition, Ref<AnimationNodeStateMachine> &r_state_machine, bool &r_bypass) const; bool _can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only); diff --git a/scene/animation/animation_player.compat.inc b/scene/animation/animation_player.compat.inc index 39efacc4ca..974eb2a7d8 100644 --- a/scene/animation/animation_player.compat.inc +++ b/scene/animation/animation_player.compat.inc @@ -58,14 +58,6 @@ void AnimationPlayer::_seek_bind_compat_80813(double p_time, bool p_update) { seek(p_time, p_update, false); } -void AnimationPlayer::_play_compat_84906(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { - play(p_name, p_custom_blend, p_custom_scale, p_from_end); -} - -void AnimationPlayer::_play_backwards_compat_84906(const StringName &p_name, double p_custom_blend) { - play_backwards(p_name, p_custom_blend); -} - void AnimationPlayer::_bind_compatibility_methods() { ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::_set_process_callback_bind_compat_80813); ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::_get_process_callback_bind_compat_80813); @@ -74,8 +66,6 @@ void AnimationPlayer::_bind_compatibility_methods() { ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::_set_root_bind_compat_80813); ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::_get_root_bind_compat_80813); ClassDB::bind_compatibility_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::_seek_bind_compat_80813, DEFVAL(false)); - ClassDB::bind_compatibility_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::_play_compat_84906, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false)); - ClassDB::bind_compatibility_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::_play_backwards_compat_84906, DEFVAL(""), DEFVAL(-1)); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 19229f405a..a4aa383a9d 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -204,19 +204,20 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f } double prev_pos = cd.pos; // The animation may be changed during process, so it is safer that the state is changed before process. - cd.pos = next_pos; // End detection. if (p_is_current) { if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) { if (!backwards && Animation::is_less_or_equal_approx(prev_pos, len) && Math::is_equal_approx(next_pos, len)) { // Playback finished. + next_pos = len; // Snap to the edge. end_reached = true; end_notify = Animation::is_less_approx(prev_pos, len); // Notify only if not already at the end. p_blend = 1.0; } if (backwards && Animation::is_greater_or_equal_approx(prev_pos, 0) && Math::is_equal_approx(next_pos, 0)) { // Playback finished. + next_pos = 0; // Snap to the edge. end_reached = true; end_notify = Animation::is_greater_approx(prev_pos, 0); // Notify only if not already at the beginning. p_blend = 1.0; @@ -224,6 +225,8 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f } } + cd.pos = next_pos; + PlaybackInfo pi; if (p_started) { pi.time = prev_pos; @@ -901,7 +904,7 @@ void AnimationPlayer::_bind_methods() { 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_transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Quad,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"); diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 69287478db..19080e61de 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -46,9 +46,9 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { } } - 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)); + r_list->push_back(PropertyInfo(Variant::FLOAT, current_length, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY)); + r_list->push_back(PropertyInfo(Variant::FLOAT, current_position, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY)); + r_list->push_back(PropertyInfo(Variant::FLOAT, current_delta, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY)); } Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { @@ -203,10 +203,11 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node } for (const KeyValue<NodePath, bool> &E : filter) { - if (!process_state->track_map.has(E.key)) { + const HashMap<NodePath, int> &map = *process_state->track_map; + if (!map.has(E.key)) { continue; } - int idx = process_state->track_map[E.key]; + int idx = map[E.key]; blendw[idx] = 1.0; // Filtered goes to one. } @@ -618,7 +619,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const process_state.valid = true; process_state.invalid_reasons = ""; process_state.last_pass = process_pass; - process_state.track_map = p_track_map; + process_state.track_map = &p_track_map; // Init node state for root AnimationNode. root_animation_node->node_state.track_weights.resize(p_track_count); @@ -680,7 +681,7 @@ uint64_t AnimationTree::get_last_process_pass() const { } PackedStringArray AnimationTree::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = AnimationMixer::get_configuration_warnings(); if (!root_animation_node.is_valid()) { warnings.push_back(RTR("No root AnimationNode for the graph is set.")); } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 6698427233..d4b7bf31c9 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -74,7 +74,7 @@ public: // 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 will_end = false; // For breaking loop, it is true when just looped. bool is_infinity = false; // For unpredictable state machine's end. bool is_looping() { @@ -84,7 +84,7 @@ public: if ((is_looping() && !p_break_loop) || is_infinity) { return HUGE_LENGTH; } - if (p_break_loop && is_just_looped) { + if (is_looping() && p_break_loop && will_end) { return 0; } double remain = length - position; @@ -106,7 +106,7 @@ public: // Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree. struct ProcessState { AnimationTree *tree = nullptr; - HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks? + const HashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks? bool is_testing = false; bool valid = false; String invalid_reasons; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 6a61e8693d..e1fd8abede 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -61,6 +61,11 @@ Ref<Tween> Tweener::_get_tween() { return Ref<Tween>(ObjectDB::get_instance(tween_id)); } +void Tweener::_finish() { + finished = true; + emit_signal(SceneStringName(finished)); +} + void Tweener::_bind_methods() { ADD_SIGNAL(MethodInfo("finished")); } @@ -574,6 +579,7 @@ bool PropertyTweener::step(double &r_delta) { Object *target_instance = ObjectDB::get_instance(target); if (!target_instance) { + _finish(); return false; } elapsed_time += r_delta; @@ -612,9 +618,8 @@ bool PropertyTweener::step(double &r_delta) { return true; } else { target_instance->set_indexed(property, final_val); - finished = true; r_delta = elapsed_time - delay - duration; - emit_signal(SceneStringName(finished)); + _finish(); return false; } } @@ -672,9 +677,8 @@ bool IntervalTweener::step(double &r_delta) { r_delta = 0; return true; } else { - finished = true; r_delta = elapsed_time - duration; - emit_signal(SceneStringName(finished)); + _finish(); return false; } } @@ -703,6 +707,7 @@ bool CallbackTweener::step(double &r_delta) { } if (!callback.is_valid()) { + _finish(); return false; } @@ -715,9 +720,8 @@ bool CallbackTweener::step(double &r_delta) { ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce) + "."); } - finished = true; r_delta = elapsed_time - delay; - emit_signal(SceneStringName(finished)); + _finish(); return false; } @@ -768,6 +772,7 @@ bool MethodTweener::step(double &r_delta) { } if (!callback.is_valid()) { + _finish(); return false; } @@ -801,9 +806,8 @@ bool MethodTweener::step(double &r_delta) { r_delta = 0; return true; } else { - finished = true; r_delta = elapsed_time - delay - duration; - emit_signal(SceneStringName(finished)); + _finish(); return false; } } diff --git a/scene/animation/tween.h b/scene/animation/tween.h index f5ae5e9776..40e1da0ad3 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -50,6 +50,7 @@ protected: static void _bind_methods(); Ref<Tween> _get_tween(); + void _finish(); double elapsed_time = 0; bool finished = false; diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index e90c1aa245..d4b44a8b69 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -112,7 +112,7 @@ void AudioStreamPlayer::seek(float p_seconds) { } void AudioStreamPlayer::stop() { - internal->stop(); + internal->stop_basic(); } bool AudioStreamPlayer::is_playing() const { @@ -154,10 +154,6 @@ void AudioStreamPlayer::_set_playing(bool p_enable) { internal->set_playing(p_enable); } -bool AudioStreamPlayer::_is_active() const { - return internal->is_active(); -} - void AudioStreamPlayer::set_stream_paused(bool p_pause) { internal->set_stream_paused(p_pause); } @@ -249,8 +245,7 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mix_target", "mix_target"), &AudioStreamPlayer::set_mix_target); ClassDB::bind_method(D_METHOD("get_mix_target"), &AudioStreamPlayer::get_mix_target); - ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer::_set_playing); - ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer::_is_active); + ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer::_set_playing); ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); @@ -267,7 +262,7 @@ void AudioStreamPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); @@ -283,7 +278,7 @@ void AudioStreamPlayer::_bind_methods() { } AudioStreamPlayer::AudioStreamPlayer() { - internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer::play), false)); + internal = memnew(AudioStreamPlayerInternal(this, callable_mp(this, &AudioStreamPlayer::play), callable_mp(this, &AudioStreamPlayer::stop), false)); } AudioStreamPlayer::~AudioStreamPlayer() { diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp index 36c14e03d5..7d1ed56ca8 100644 --- a/scene/audio/audio_stream_player_internal.cpp +++ b/scene/audio/audio_stream_player_internal.cpp @@ -132,7 +132,7 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() { } ERR_FAIL_COND_V_MSG(!node->is_inside_tree(), stream_playback, "Playback can only happen when a node is inside the scene tree"); if (stream->is_monophonic() && is_playing()) { - stop(); + stop_callable.call(); } stream_playback = stream->instantiate_playback(); ERR_FAIL_COND_V_MSG(stream_playback.is_null(), stream_playback, "Failed to instantiate playback."); @@ -152,6 +152,7 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() { Ref<AudioSamplePlayback> sample_playback; sample_playback.instantiate(); sample_playback->stream = stream; + sample_playback->pitch_scale = pitch_scale; stream_playback->set_sample_playback(sample_playback); } } else if (!stream->is_meta_stream()) { @@ -242,7 +243,7 @@ void AudioStreamPlayerInternal::set_stream(Ref<AudioStream> p_stream) { if (stream.is_valid()) { stream->disconnect(SNAME("parameter_list_changed"), callable_mp(this, &AudioStreamPlayerInternal::_update_stream_parameters)); } - stop(); + stop_callable.call(); stream = p_stream; _update_stream_parameters(); if (stream.is_valid()) { @@ -253,12 +254,12 @@ void AudioStreamPlayerInternal::set_stream(Ref<AudioStream> p_stream) { void AudioStreamPlayerInternal::seek(float p_seconds) { if (is_playing()) { - stop(); + stop_callable.call(); play_callable.call(p_seconds); } } -void AudioStreamPlayerInternal::stop() { +void AudioStreamPlayerInternal::stop_basic() { for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { AudioServer::get_singleton()->stop_playback_stream(playback); } @@ -289,7 +290,7 @@ void AudioStreamPlayerInternal::set_playing(bool p_enable) { if (p_enable) { play_callable.call(0.0); } else { - stop(); + stop_callable.call(); } } @@ -339,9 +340,10 @@ StringName AudioStreamPlayerInternal::get_bus() const { return SceneStringName(Master); } -AudioStreamPlayerInternal::AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical) { +AudioStreamPlayerInternal::AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, const Callable &p_stop_callable, bool p_physical) { node = p_node; play_callable = p_play_callable; + stop_callable = p_stop_callable; physical = p_physical; bus = SceneStringName(Master); diff --git a/scene/audio/audio_stream_player_internal.h b/scene/audio/audio_stream_player_internal.h index ec4489067e..7d8faeba06 100644 --- a/scene/audio/audio_stream_player_internal.h +++ b/scene/audio/audio_stream_player_internal.h @@ -53,6 +53,7 @@ private: Node *node = nullptr; Callable play_callable; + Callable stop_callable; bool physical = false; AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT; @@ -94,7 +95,7 @@ public: Ref<AudioStreamPlayback> play_basic(); void seek(float p_seconds); - void stop(); + void stop_basic(); bool is_playing() const; float get_playback_position(); @@ -110,7 +111,7 @@ public: void set_playback_type(AudioServer::PlaybackType p_playback_type); AudioServer::PlaybackType get_playback_type() const; - AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical); + AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, const Callable &p_stop_callable, bool p_physical); }; #endif // AUDIO_STREAM_PLAYER_INTERNAL_H diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 01e3cce78b..72299f788d 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -140,11 +140,13 @@ void BaseButton::_pressed() { void BaseButton::_toggled(bool p_pressed) { GDVIRTUAL_CALL(_toggled, p_pressed); toggled(p_pressed); - emit_signal(SNAME("toggled"), p_pressed); + emit_signal(SceneStringName(toggled), p_pressed); } void BaseButton::on_action_event(Ref<InputEvent> p_event) { - if (p_event->is_pressed()) { + Ref<InputEventMouseButton> mouse_button = p_event; + + if (p_event->is_pressed() && (mouse_button.is_null() || status.hovering)) { status.press_attempt = true; status.pressing_inside = true; emit_signal(SNAME("button_down")); @@ -174,12 +176,6 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } if (!p_event->is_pressed()) { - Ref<InputEventMouseButton> mouse_button = p_event; - if (mouse_button.is_valid()) { - if (!has_point(mouse_button->get_position())) { - status.hovering = false; - } - } status.press_attempt = false; status.pressing_inside = false; emit_signal(SNAME("button_up")); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 00b9a3478a..c3287035ff 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -40,8 +40,18 @@ void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { set_gutter_width(main_gutter, get_line_height()); - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + _update_line_number_gutter_width(); set_gutter_width(fold_gutter, get_line_height() / 1.2); + _clear_line_number_text_cache(); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_VISIBILITY_CHANGED: { + // Avoid having many hidden text editors with unused cache filling up memory. + _clear_line_number_text_cache(); } break; case NOTIFICATION_DRAW: { @@ -211,7 +221,13 @@ void CodeEdit::_notification(int p_what) { tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); } else { if (code_completion_options[l].default_value.get_type() == Variant::COLOR) { - draw_rect(Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value); + const Color color = code_completion_options[l].default_value; + const Rect2 rect = Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size); + if (color.a < 1.0) { + draw_texture_rect(theme_cache.completion_color_bg, rect, true); + } + + draw_rect(rect, color); } tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); } @@ -765,7 +781,7 @@ void CodeEdit::_backspace_internal(int p_caret) { continue; } - if (to_line > 0 && _is_line_hidden(to_line - 1)) { + if (to_line > 0 && to_column == 0 && _is_line_hidden(to_line - 1)) { unfold_line(to_line - 1); } @@ -1281,9 +1297,9 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const { } void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { + bool hovering = get_hovered_gutter() == Vector2i(main_gutter, p_line); if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) { bool breakpointed = is_line_breakpointed(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) { @@ -1302,7 +1318,6 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 if (draw_bookmarks && theme_cache.bookmark_icon.is_valid()) { bool bookmarked = is_line_bookmarked(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) { @@ -1436,7 +1451,13 @@ bool CodeEdit::is_draw_line_numbers_enabled() const { } void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) { - p_zero_padded ? line_number_padding = "0" : line_number_padding = " "; + String new_line_number_padding = p_zero_padded ? "0" : " "; + if (line_number_padding == new_line_number_padding) { + return; + } + + line_number_padding = new_line_number_padding; + _clear_line_number_text_cache(); queue_redraw(); } @@ -1445,19 +1466,55 @@ bool CodeEdit::is_line_numbers_zero_padded() const { } void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { - String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); - if (is_localizing_numeral_system()) { - fc = TS->format_number(fc); - } - Ref<TextLine> tl; - tl.instantiate(); - tl->add_string(fc, theme_cache.font, theme_cache.font_size); - int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2; + if (!Rect2(Vector2(0, 0), get_size()).intersects(p_region)) { + return; + } + + bool rtl = is_layout_rtl(); + HashMap<int, RID>::Iterator E = line_number_text_cache.find(p_line); + RID text_rid; + if (E) { + text_rid = E->value; + } else { + String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); + if (is_localizing_numeral_system()) { + fc = TS->format_number(fc); + } + + text_rid = TS->create_shaped_text(); + if (theme_cache.font.is_valid()) { + TS->shaped_text_add_string(text_rid, fc, theme_cache.font->get_rids(), theme_cache.font_size, theme_cache.font->get_opentype_features()); + } + line_number_text_cache.insert(p_line, text_rid); + } + + Size2 text_size = TS->shaped_text_get_size(text_rid); + Point2 ofs = p_region.get_center() - text_size / 2; + ofs.y += TS->shaped_text_get_ascent(text_rid); + + if (rtl) { + ofs.x = p_region.position.x; + } else { + ofs.x = p_region.get_end().x - text_size.width; + } + Color number_color = get_line_gutter_item_color(p_line, line_number_gutter); if (number_color == Color(1, 1, 1)) { number_color = theme_cache.line_number_color; } - tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color); + + TS->shaped_text_draw(text_rid, get_canvas_item(), ofs, -1, -1, number_color); +} + +void CodeEdit::_clear_line_number_text_cache() { + for (const KeyValue<int, RID> &KV : line_number_text_cache) { + TS->free_rid(KV.value); + } + line_number_text_cache.clear(); +} + +void CodeEdit::_update_line_number_gutter_width() { + set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); } /* Fold Gutter */ @@ -1977,12 +2034,18 @@ Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const { /* Code hint */ void CodeEdit::set_code_hint(const String &p_hint) { + if (code_hint == p_hint) { + return; + } code_hint = p_hint; code_hint_xpos = -0xFFFF; queue_redraw(); } void CodeEdit::set_code_hint_draw_below(bool p_below) { + if (code_hint_draw_below == p_below) { + return; + } code_hint_draw_below = p_below; queue_redraw(); } @@ -2771,6 +2834,7 @@ void CodeEdit::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, can_fold_code_region_icon, "can_fold_code_region"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, folded_code_region_icon, "folded_code_region"); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CodeEdit, folded_eol_icon); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CodeEdit, completion_color_bg); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, breakpoint_color); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, breakpoint_icon, "breakpoint"); @@ -3602,16 +3666,13 @@ void CodeEdit::_text_changed() { } int lc = get_line_count(); - line_number_digits = 1; - while (lc /= 10) { - line_number_digits++; - } - - if (theme_cache.font.is_valid()) { - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + int new_line_number_digits = log10l(lc) + 1; + if (line_number_digits != new_line_number_digits) { + _clear_line_number_text_cache(); } + line_number_digits = new_line_number_digits; + _update_line_number_gutter_width(); - lc = get_line_count(); List<int> breakpoints; for (const KeyValue<int, bool> &E : breakpointed_lines) { breakpoints.push_back(E.key); @@ -3698,6 +3759,7 @@ CodeEdit::CodeEdit() { } CodeEdit::~CodeEdit() { + _clear_line_number_text_cache(); } // Return true if l should come before r diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 580435f65e..ab443e95e1 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -113,6 +113,9 @@ private: int line_number_gutter = -1; int line_number_digits = 1; String line_number_padding = " "; + HashMap<int, RID> line_number_text_cache; + void _clear_line_number_text_cache(); + void _update_line_number_gutter_width(); void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region); /* Fold Gutter */ @@ -245,6 +248,7 @@ private: Ref<Texture2D> can_fold_code_region_icon; Ref<Texture2D> folded_code_region_icon; Ref<Texture2D> folded_eol_icon; + Ref<Texture2D> completion_color_bg; Color breakpoint_color = Color(1, 1, 1); Ref<Texture2D> breakpoint_icon = Ref<Texture2D>(); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index a2aee353f9..c92dcbc153 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -54,6 +54,18 @@ void ColorPicker::_notification(int p_what) { _update_color(); } break; + case NOTIFICATION_READY: { + // FIXME: The embedding check is needed to fix a bug in single-window mode (GH-93718). + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE) && !get_tree()->get_root()->is_embedding_subwindows()) { + btn_pick->set_tooltip_text(ETR("Pick a color from the screen.")); + 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(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); + } + } break; + case NOTIFICATION_TRANSLATION_CHANGED: { List<BaseButton *> buttons; preset_group->get_buttons(&buttons); @@ -771,7 +783,7 @@ void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { recent_preset_hbc->add_child(btn_preset_new); recent_preset_hbc->move_child(btn_preset_new, 0); btn_preset_new->set_pressed(true); - btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); + btn_preset_new->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); } void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) { @@ -1834,14 +1846,6 @@ ColorPicker::ColorPicker() { btn_pick = memnew(Button); 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(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(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); - } sample = memnew(TextureRect); sample_hbc->add_child(sample); @@ -1999,7 +2003,7 @@ ColorPicker::ColorPicker() { btn_preset->set_toggle_mode(true); btn_preset->set_focus_mode(FOCUS_NONE); btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); + btn_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); real_vbox->add_child(btn_preset); real_vbox->add_child(preset_container); @@ -2016,7 +2020,7 @@ ColorPicker::ColorPicker() { btn_recent_preset->set_toggle_mode(true); btn_recent_preset->set_focus_mode(FOCUS_NONE); btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); + btn_recent_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); real_vbox->add_child(btn_recent_preset); real_vbox->add_child(recent_preset_hbc); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index d169e82e5d..cecddebe88 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "control.h" -#include "control.compat.inc" #include "container.h" #include "core/config/project_settings.h" @@ -37,7 +36,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "scene/gui/label.h" #include "scene/gui/panel.h" #include "scene/main/canvas_layer.h" @@ -248,7 +247,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List PackedStringArray Control::get_configuration_warnings() const { ERR_READ_THREAD_GUARD_V(PackedStringArray()); - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = CanvasItem::get_configuration_warnings(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { 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\".")); @@ -682,12 +681,10 @@ Size2 Control::get_parent_area_size() const { // Positioning and sizing. Transform2D Control::_get_internal_transform() const { - Transform2D rot_scale; - rot_scale.set_rotation_and_scale(data.rotation, data.scale); - Transform2D offset; - offset.set_origin(-data.pivot_offset); - - return offset.affine_inverse() * (rot_scale * offset); + // T(pivot_offset) * R(rotation) * S(scale) * T(-pivot_offset) + Transform2D xform(data.rotation, data.scale, 0.0f, data.pivot_offset); + xform.translate_local(-data.pivot_offset); + return xform; } void Control::_update_canvas_item_transform() { @@ -1748,10 +1745,10 @@ void Control::_size_changed() { // so an up to date global transform could be obtained when handling these. _notify_transform(); + item_rect_changed(size_changed); if (size_changed) { notification(NOTIFICATION_RESIZED); } - item_rect_changed(size_changed); } if (pos_changed && !size_changed) { @@ -2358,6 +2355,24 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons points[2] = xform.xform(c->get_size()); points[3] = xform.xform(Point2(0, c->get_size().y)); + // Tie-breaking aims to address situations where a potential focus neighbor's bounding rect + // is right next to the currently focused control (e.g. in BoxContainer with + // separation overridden to 0). This needs specific handling so that the correct + // focus neighbor is selected. + + // Calculate centers of the potential neighbor, currently focused, and closest controls. + Point2 center = xform.xform(0.5 * c->get_size()); + // We only have the points, not an actual reference. + Point2 p_center = 0.25 * (p_points[0] + p_points[1] + p_points[2] + p_points[3]); + Point2 closest_center; + bool should_tiebreak = false; + if (*r_closest != nullptr) { + should_tiebreak = true; + Control *closest = *r_closest; + Transform2D closest_xform = closest->get_global_transform(); + closest_center = closest_xform.xform(0.5 * closest->get_size()); + } + real_t min = 1e7; for (int i = 0; i < 4; i++) { @@ -2378,10 +2393,15 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons Vector2 pa, pb; real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb); - //real_t d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0)); if (d < r_closest_dist) { r_closest_dist = d; *r_closest = c; + } else if (should_tiebreak && d == r_closest_dist) { + // Tie-break in favor of the control most aligned with p_dir. + if (p_dir.dot((center - p_center).normalized()) > p_dir.dot((closest_center - p_center).normalized())) { + r_closest_dist = d; + *r_closest = c; + } } } } @@ -2577,8 +2597,8 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam return data.theme_icon_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<Texture2D> icon = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); data.theme_icon_cache[p_theme_type][p_name] = icon; return icon; @@ -2601,8 +2621,8 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String return data.theme_style_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<StyleBox> style = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); data.theme_style_cache[p_theme_type][p_name] = style; return style; @@ -2625,8 +2645,8 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_ return data.theme_font_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<Font> font = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); data.theme_font_cache[p_theme_type][p_name] = font; return font; @@ -2649,8 +2669,8 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t return data.theme_font_size_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); int font_size = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); data.theme_font_size_cache[p_theme_type][p_name] = font_size; return font_size; @@ -2673,8 +2693,8 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the return data.theme_color_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Color color = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); data.theme_color_cache[p_theme_type][p_name] = color; return color; @@ -2697,8 +2717,8 @@ int Control::get_theme_constant(const StringName &p_name, const StringName &p_th return data.theme_constant_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); int constant = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); data.theme_constant_cache[p_theme_type][p_name] = constant; return constant; @@ -2743,8 +2763,8 @@ bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); } @@ -2760,8 +2780,8 @@ bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_t } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } @@ -2777,8 +2797,8 @@ bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); } @@ -2794,8 +2814,8 @@ bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_ } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } @@ -2811,8 +2831,8 @@ bool Control::has_theme_color(const StringName &p_name, const StringName &p_them } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); } @@ -2828,8 +2848,8 @@ bool Control::has_theme_constant(const StringName &p_name, const StringName &p_t } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } @@ -3608,7 +3628,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_GROUP("Mouse", "mouse_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); diff --git a/scene/gui/control.h b/scene/gui/control.h index c784d4330d..2655b14562 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -348,10 +348,6 @@ protected: void _notification(int p_notification); static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - static void _bind_compatibility_methods(); -#endif - // Exposed virtual methods. GDVIRTUAL1RC(bool, _has_point, Vector2) diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 8047369ab1..1fc8586448 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -76,6 +76,7 @@ void FileDialog::popup(const Rect2i &p_rect) { #ifdef TOOLS_ENABLED if (is_part_of_edited_scene()) { ConfirmationDialog::popup(p_rect); + return; } #endif @@ -124,6 +125,8 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int file_name = ProjectSettings::get_singleton()->localize_path(file_name); } } + selected_options = p_selected_options; + String f = files[0]; if (mode == FILE_MODE_OPEN_FILES) { emit_signal(SNAME("files_selected"), files); @@ -155,7 +158,6 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int } file->set_text(f); dir->set_text(f.get_base_dir()); - selected_options = p_selected_options; filter->select(p_filter); } @@ -1143,7 +1145,7 @@ void FileDialog::_update_option_controls() { CheckBox *cb = memnew(CheckBox); cb->set_pressed(opt.default_idx); grid_options->add_child(cb); - cb->connect("toggled", callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name)); + cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name)); selected_options[opt.name] = (bool)opt.default_idx; } else { OptionButton *ob = memnew(OptionButton); @@ -1379,7 +1381,7 @@ void FileDialog::set_use_native_dialog(bool p_native) { #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())) { + if (is_inside_tree() && 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(); } @@ -1441,7 +1443,7 @@ FileDialog::FileDialog() { show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files.")); - show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); + show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); shortcuts_container = memnew(HBoxContainer); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 55a2c607e3..11a6411e65 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -695,6 +695,10 @@ void GraphEdit::remove_child_notify(Node *p_child) { 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)); + if (connections_layer != nullptr && connections_layer->is_inside_tree()) { + graph_element->disconnect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + } + // In case of the whole GraphEdit being destroyed these references can already be freed. if (minimap != nullptr && minimap->is_inside_tree()) { graph_element->disconnect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); @@ -908,7 +912,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { // 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)) { + if (graph_node_rect.has_point(p_point)) { break; } } @@ -1040,12 +1044,6 @@ 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; - } } } @@ -1117,6 +1115,12 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { } connecting_target_valid = false; } + + // 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(mm->get_position())) { + break; + } } } } @@ -1642,24 +1646,34 @@ void GraphEdit::_draw_grid() { Color transparent_grid_minor = theme_cache.grid_minor; transparent_grid_minor.a *= CLAMP(1.0 * (zoom - 0.4), 0, 1); - for (int i = from_pos.x; i < from_pos.x + len.x; i++) { - for (int j = from_pos.y; j < from_pos.y + len.y; j++) { - Color color = transparent_grid_minor; + // Minor dots. + if (transparent_grid_minor.a != 0) { + for (int i = from_pos.x; i < from_pos.x + len.x; i++) { + for (int j = from_pos.y; j < from_pos.y + len.y; j++) { + if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0 && ABS(j) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0) { + continue; + } - if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0 && ABS(j) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0) { - color = theme_cache.grid_major; - } + float base_offset_x = i * snapping_distance * zoom - offset.x * zoom; + float base_offset_y = j * snapping_distance * zoom - offset.y * zoom; - if (color.a == 0) { - continue; + draw_rect(Rect2(base_offset_x - 1, base_offset_y - 1, 3, 3), transparent_grid_minor); } + } + } - float base_offset_x = i * snapping_distance * zoom - offset.x * zoom; - float base_offset_y = j * snapping_distance * zoom - offset.y * zoom; + // Major dots. + if (theme_cache.grid_major.a != 0) { + for (int i = from_pos.x - from_pos.x % GRID_MINOR_STEPS_PER_MAJOR_DOT; i < from_pos.x + len.x; i += GRID_MINOR_STEPS_PER_MAJOR_DOT) { + for (int j = from_pos.y - from_pos.y % GRID_MINOR_STEPS_PER_MAJOR_DOT; j < from_pos.y + len.y; j += GRID_MINOR_STEPS_PER_MAJOR_DOT) { + float base_offset_x = i * snapping_distance * zoom - offset.x * zoom; + float base_offset_y = j * snapping_distance * zoom - offset.y * zoom; - draw_rect(Rect2(base_offset_x - 1, base_offset_y - 1, 3, 3), color); + draw_rect(Rect2(base_offset_x - 1, base_offset_y - 1, 3, 3), theme_cache.grid_major); + } } } + } break; } } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 0006204ae3..1a066b0728 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -45,6 +45,111 @@ #include "editor/editor_settings.h" #endif +void LineEdit::_edit() { + if (!is_inside_tree()) { + return; + } + + if (!has_focus()) { + grab_focus(); + } + + if (!editable || editing) { + return; + } + + editing = true; + _validate_caret_can_draw(); + + show_virtual_keyboard(); + queue_redraw(); + emit_signal(SNAME("editing_toggled"), true); +} + +void LineEdit::_unedit() { + if (!editing) { + return; + } + + editing = false; + _validate_caret_can_draw(); + + apply_ime(); + set_caret_column(caret_column); // Update scroll_offset. + + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { + DisplayServer::get_singleton()->virtual_keyboard_hide(); + } + + if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { + deselect(); + } + + emit_signal(SNAME("editing_toggled"), false); +} + +bool LineEdit::is_editing() const { + return editing; +} + +void LineEdit::_close_ime_window() { + DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; + if (wid == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { + return; + } + DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid); + DisplayServer::get_singleton()->window_set_ime_active(false, wid); +} + +void LineEdit::_update_ime_window_position() { + DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; + if (wid == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { + return; + } + DisplayServer::get_singleton()->window_set_ime_active(true, wid); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); + if (get_window()->get_embedder()) { + pos += get_viewport()->get_popup_base_transform().get_origin(); + } + // The window will move to the updated position the next time the IME is updated, not immediately. + DisplayServer::get_singleton()->window_set_ime_position(pos, wid); +} + +bool LineEdit::has_ime_text() const { + return !ime_text.is_empty(); +} + +void LineEdit::cancel_ime() { + if (!has_ime_text()) { + return; + } + ime_text = String(); + ime_selection = Vector2i(); + alt_start = false; + alt_start_no_hold = false; + _close_ime_window(); + _shape(); +} + +void LineEdit::apply_ime() { + if (!has_ime_text()) { + return; + } + + // Force apply the current IME text. + if (alt_start || alt_start_no_hold) { + cancel_ime(); + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; + insert_text_at_caret(ucodestr); + } + } else { + String insert_ime_text = ime_text; + cancel_ime(); + insert_text_at_caret(insert_ime_text); + } +} + void LineEdit::_swap_current_input_direction() { if (input_direction == TEXT_DIRECTION_LTR) { input_direction = TEXT_DIRECTION_RTL; @@ -52,7 +157,6 @@ void LineEdit::_swap_current_input_direction() { input_direction = TEXT_DIRECTION_LTR; } set_caret_column(get_caret_column()); - queue_redraw(); } void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) { @@ -68,10 +172,15 @@ void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + if (words.is_empty() || cc <= words[0]) { + // Move to the start when there are no more words. + cc = 0; + } else { + for (int i = words.size() - 2; i >= 0; i = i - 2) { + if (words[i] < cc) { + cc = words[i]; + break; + } } } @@ -101,10 +210,15 @@ void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + if (words.is_empty() || cc >= words[words.size() - 1]) { + // Move to the end when there are no more words. + cc = text.length(); + } else { + for (int i = 1; i < words.size(); i = i + 2) { + if (words[i] > cc) { + cc = words[i]; + break; + } } } @@ -159,10 +273,15 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + if (words.is_empty() || cc <= words[0]) { + // Delete to the start when there are no more words. + cc = 0; + } else { + for (int i = words.size() - 2; i >= 0; i = i - 2) { + if (words[i] < cc) { + cc = words[i]; + break; + } } } @@ -198,10 +317,15 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { if (p_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + if (words.is_empty() || cc >= words[words.size() - 1]) { + // Delete to the end when there are no more words. + cc = text.length(); + } else { + for (int i = 1; i < words.size(); i = i + 2) { + if (words[i] > cc) { + cc = words[i]; + break; + } } } @@ -220,6 +344,11 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { } void LineEdit::unhandled_key_input(const Ref<InputEvent> &p_event) { + // Return to prevent editing if just focused. + if (!editing) { + return; + } + Ref<InputEventKey> k = p_event; if (k.is_valid()) { @@ -246,25 +375,40 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { - if (ime_text.length() != 0) { - // Ignore mouse clicks in IME input mode. - return; - } - if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { - _update_context_menu(); - menu->set_position(get_screen_position() + get_local_mouse_position()); - menu->reset_size(); - menu->popup(); - grab_focus(); + if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) { + apply_ime(); + + if (editable && !selection.enabled) { + set_caret_at_pixel_pos(b->get_position().x); + } + + if (context_menu_enabled) { + _update_context_menu(); + menu->set_position(get_screen_position() + get_local_mouse_position()); + menu->reset_size(); + menu->popup(); + } + + if (editable && !editing) { + _edit(); + } + accept_event(); return; } - if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (editable && is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + apply_ime(); + String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes(); deselect(); set_caret_at_pixel_pos(b->get_position().x); + + if (!editing) { + _edit(); + } + if (!paste_buffer.is_empty()) { insert_text_at_caret(paste_buffer); @@ -275,7 +419,6 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { text_changed_dirty = true; } } - grab_focus(); accept_event(); return; } @@ -284,10 +427,15 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - _reset_caret_blink_timer(); + if (editing) { + _reset_caret_blink_timer(); + } + if (b->is_pressed()) { + apply_ime(); + accept_event(); // Don't pass event further when clicked on text field. - if (!text.is_empty() && is_editable() && _is_over_clear_button(b->get_position())) { + if (editable && !text.is_empty() && _is_over_clear_button(b->get_position())) { clear_button_status.press_attempt = true; clear_button_status.pressing_inside = true; queue_redraw(); @@ -310,7 +458,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { const int triple_click_tolerance = 5; const bool is_triple_click = !b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && b->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance; - if (is_triple_click && text.length()) { + if (is_triple_click && !text.is_empty()) { // Triple-click select all. selection.enabled = true; selection.begin = 0; @@ -357,13 +505,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } } + if (editable && !editing) { + _edit(); + return; + } queue_redraw(); } else { if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - if (!text.is_empty() && is_editable() && clear_button_enabled) { + if (editable && !text.is_empty() && clear_button_enabled) { bool press_attempt = clear_button_status.press_attempt; clear_button_status.press_attempt = false; if (press_attempt && clear_button_status.pressing_inside && _is_over_clear_button(b->get_position())) { @@ -396,7 +548,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> m = p_event; if (m.is_valid()) { - if (!text.is_empty() && is_editable() && clear_button_enabled) { + if (editable && !text.is_empty() && clear_button_enabled) { bool last_press_inside = clear_button_status.pressing_inside; clear_button_status.pressing_inside = clear_button_status.press_attempt && _is_over_clear_button(m->get_position()); if (last_press_inside != clear_button_status.pressing_inside) { @@ -442,221 +594,305 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - if (k.is_valid()) { - if (!k->is_pressed()) { - if (alt_start && k->get_keycode() == Key::ALT) { - alt_start = false; - if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { - char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; - insert_text_at_caret(ucodestr); - } - accept_event(); - return; - } - return; - } + if (k.is_null()) { + return; + } - // Alt + Unicode input: - if (k->is_alt_pressed()) { - if (!alt_start) { - if (k->get_keycode() == Key::KP_ADD) { - alt_start = true; - alt_code = 0; - accept_event(); - return; - } - } else { - if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); - } - if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); - } - if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; - } - accept_event(); - return; - } - } + if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) { + _edit(); + return; + } - if (context_menu_enabled) { - if (k->is_action("ui_menu", true)) { - _update_context_menu(); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); - menu->set_position(get_screen_position() + pos); - menu->reset_size(); - menu->popup(); - menu->grab_focus(); + if (!editing) { + return; + } - accept_event(); - return; - } + // Start Unicode input (hold). + if (k->is_alt_pressed() && k->get_keycode() == Key::KP_ADD && !alt_start && !alt_start_no_hold) { + if (selection.enabled) { + selection_delete(); } + alt_start = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _shape(); + queue_redraw(); + accept_event(); + return; + } - // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE. - if (k->is_action("ui_text_submit", false)) { - emit_signal(SNAME("text_submitted"), text); - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - DisplayServer::get_singleton()->virtual_keyboard_hide(); - } - accept_event(); - return; + // Start Unicode input (press). + if (k->is_action("ui_unicode_start", true) && !alt_start && !alt_start_no_hold) { + if (selection.enabled) { + selection_delete(); } + alt_start_no_hold = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _shape(); + queue_redraw(); + accept_event(); + return; + } - if (k->is_action("ui_cancel")) { - callable_mp((Control *)this, &Control::release_focus).call_deferred(); - accept_event(); - return; + // Update Unicode input. + if (k->is_pressed() && ((k->is_alt_pressed() && alt_start) || alt_start_no_hold)) { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } else if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } else if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } else if ((Key)k->get_unicode() >= Key::KEY_0 && (Key)k->get_unicode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::KEY_0); + } else if ((Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::A) + 10; + } else if (k->get_physical_keycode() >= Key::KEY_0 && k->get_physical_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_physical_keycode() - Key::KEY_0); } + if (k->get_keycode() == Key::BACKSPACE) { + alt_code = alt_code >> 4; + } + if (alt_code > 0x10ffff) { + alt_code = 0x10ffff; + } + if (alt_code > 0) { + ime_text = vformat("u%s", String::num_int64(alt_code, 16, true)); + } else { + ime_text = "u"; + } + ime_selection = Vector2i(0, -1); + _shape(); + queue_redraw(); + accept_event(); + return; + } - if (is_shortcut_keys_enabled()) { - if (k->is_action("ui_copy", true)) { - copy_text(); - accept_event(); - return; - } - - if (k->is_action("ui_text_select_all", true)) { - select(); - accept_event(); - return; - } - - // Cut / Paste - if (k->is_action("ui_cut", true)) { - cut_text(); - accept_event(); - return; - } + // Submit Unicode input. + if ((!k->is_pressed() && alt_start && k->get_keycode() == Key::ALT) || (alt_start_no_hold && (k->is_action("ui_text_submit", true) || k->is_action("ui_accept", true)))) { + alt_start = false; + alt_start_no_hold = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + ime_text = String(); + ime_selection = Vector2i(); + char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; + insert_text_at_caret(ucodestr); + } else { + ime_text = String(); + ime_selection = Vector2i(); + _shape(); + } + queue_redraw(); + accept_event(); + return; + } - if (k->is_action("ui_paste", true)) { - paste_text(); - accept_event(); - return; - } + // Cancel Unicode input. + if (alt_start_no_hold && k->is_action("ui_cancel", true)) { + alt_start = false; + alt_start_no_hold = false; + ime_text = String(); + ime_selection = Vector2i(); + _shape(); + queue_redraw(); + accept_event(); + return; + } - // Undo / Redo - if (k->is_action("ui_undo", true)) { - undo(); - accept_event(); - return; - } + if (!k->is_pressed()) { + return; + } - if (k->is_action("ui_redo", true)) { - redo(); - accept_event(); - return; - } - } + // Open context menu. + if (context_menu_enabled) { + if (k->is_action("ui_menu", true)) { + _update_context_menu(); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); + menu->set_position(get_screen_position() + pos); + menu->reset_size(); + menu->popup(); + menu->grab_focus(); - // BACKSPACE - if (k->is_action("ui_text_backspace_all_to_left", true)) { - _backspace(false, true); - accept_event(); - return; - } - if (k->is_action("ui_text_backspace_word", true)) { - _backspace(true); - accept_event(); - return; - } - if (k->is_action("ui_text_backspace", true)) { - _backspace(); accept_event(); return; } + } - // DELETE - if (k->is_action("ui_text_delete_all_to_right", true)) { - _delete(false, true); - accept_event(); - return; - } - if (k->is_action("ui_text_delete_word", true)) { - _delete(true); - accept_event(); - return; - } - if (k->is_action("ui_text_delete", true)) { - _delete(); - accept_event(); - return; + // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE. + if (k->is_action_pressed("ui_text_submit")) { + emit_signal(SNAME("text_submitted"), text); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { + DisplayServer::get_singleton()->virtual_keyboard_hide(); } - // Cursor Movement + if (editing) { + _unedit(); + } - k = k->duplicate(); - bool shift_pressed = k->is_shift_pressed(); - // Remove shift or else actions will not match. Use above variable for selection. - k->set_shift_pressed(false); + accept_event(); + return; + } - if (k->is_action("ui_text_caret_word_left", true)) { - _move_caret_left(shift_pressed, true); - accept_event(); - return; + if (k->is_action("ui_cancel")) { + if (editing) { + _unedit(); } - if (k->is_action("ui_text_caret_left", true)) { - _move_caret_left(shift_pressed); + + accept_event(); + return; + } + + if (is_shortcut_keys_enabled()) { + if (k->is_action("ui_copy", true)) { + copy_text(); accept_event(); return; } - if (k->is_action("ui_text_caret_word_right", true)) { - _move_caret_right(shift_pressed, true); + + if (k->is_action("ui_text_select_all", true)) { + select(); accept_event(); return; } - if (k->is_action("ui_text_caret_right", true)) { - _move_caret_right(shift_pressed, false); + + // Cut / Paste + if (k->is_action("ui_cut", true)) { + cut_text(); accept_event(); return; } - // Up = Home, Down = End - if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) { - _move_caret_start(shift_pressed); + if (k->is_action("ui_paste", true)) { + paste_text(); accept_event(); return; } - if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) { - _move_caret_end(shift_pressed); + + // Undo / Redo + if (k->is_action("ui_undo", true)) { + undo(); accept_event(); return; } - // Misc - if (k->is_action("ui_swap_input_direction", true)) { - _swap_current_input_direction(); + if (k->is_action("ui_redo", true)) { + redo(); accept_event(); return; } + } + + // BACKSPACE + if (k->is_action("ui_text_backspace_all_to_left", true)) { + _backspace(false, true); + accept_event(); + return; + } + if (k->is_action("ui_text_backspace_word", true)) { + _backspace(true); + accept_event(); + return; + } + if (k->is_action("ui_text_backspace", true)) { + _backspace(); + accept_event(); + return; + } - _reset_caret_blink_timer(); + // DELETE + if (k->is_action("ui_text_delete_all_to_right", true)) { + _delete(false, true); + accept_event(); + return; + } + if (k->is_action("ui_text_delete_word", true)) { + _delete(true); + accept_event(); + return; + } + if (k->is_action("ui_text_delete", true)) { + _delete(); + accept_event(); + return; + } - // Allow unicode handling if: - // * No Modifiers are pressed (except shift) - bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + // Cursor Movement - if (allow_unicode_handling && editable && k->get_unicode() >= 32) { - // Handle Unicode if no modifiers are active. - selection_delete(); - char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 }; - int prev_len = text.length(); - insert_text_at_caret(ucodestr); - if (text.length() != prev_len) { - if (!text_changed_dirty) { - if (is_inside_tree()) { - callable_mp(this, &LineEdit::_text_changed).call_deferred(); - } - text_changed_dirty = true; + k = k->duplicate(); + bool shift_pressed = k->is_shift_pressed(); + // Remove shift or else actions will not match. Use above variable for selection. + k->set_shift_pressed(false); + + if (k->is_action("ui_text_caret_word_left", true)) { + _move_caret_left(shift_pressed, true); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_left", true)) { + _move_caret_left(shift_pressed); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_word_right", true)) { + _move_caret_right(shift_pressed, true); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_right", true)) { + _move_caret_right(shift_pressed, false); + accept_event(); + return; + } + + // Up = Home, Down = End + if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) { + _move_caret_start(shift_pressed); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) { + _move_caret_end(shift_pressed); + accept_event(); + return; + } + + // Misc + if (k->is_action("ui_swap_input_direction", true)) { + _swap_current_input_direction(); + accept_event(); + return; + } + + _reset_caret_blink_timer(); + + // Allow unicode handling if: + // * No Modifiers are pressed (except shift) + bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + + if (allow_unicode_handling && editable && k->get_unicode() >= 32) { + // Handle Unicode if no modifiers are active. + selection_delete(); + char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 }; + int prev_len = text.length(); + insert_text_at_caret(ucodestr); + if (text.length() != prev_len) { + if (!text_changed_dirty) { + if (is_inside_tree()) { + callable_mp(this, &LineEdit::_text_changed).call_deferred(); } + text_changed_dirty = true; } - accept_event(); - return; } + accept_event(); + return; } } @@ -698,13 +934,15 @@ bool LineEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const return drop_override; } - return is_editable() && p_data.get_type() == Variant::STRING; + return is_editable() && p_data.is_string(); } void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); - if (p_data.get_type() == Variant::STRING && is_editable()) { + if (p_data.is_string() && is_editable()) { + apply_ime(); + set_caret_at_pixel_pos(p_point.x); int caret_column_tmp = caret_column; bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end; @@ -1087,22 +1325,12 @@ void LineEdit::_notification(int p_what) { } } - if (has_focus()) { - DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; - if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_active(true, wid); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); - if (get_window()->get_embedder()) { - pos += get_viewport()->get_popup_base_transform().get_origin(); - } - DisplayServer::get_singleton()->window_set_ime_position(pos, wid); - } + if (editing) { + _update_ime_window_position(); } } break; case NOTIFICATION_FOCUS_ENTER: { - _validate_caret_can_draw(); - if (select_all_on_focus) { if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { // Select all when the mouse button is up. @@ -1112,43 +1340,20 @@ void LineEdit::_notification(int p_what) { } } - DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; - if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_active(true, wid); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); - if (get_window()->get_embedder()) { - pos += get_viewport()->get_popup_base_transform().get_origin(); - } - DisplayServer::get_singleton()->window_set_ime_position(pos, wid); + // Only allow editing if the LineEdit is not focused with arrow keys. + if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) { + _edit(); } - - show_virtual_keyboard(); } break; case NOTIFICATION_FOCUS_EXIT: { - _validate_caret_can_draw(); - - DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; - if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid); - DisplayServer::get_singleton()->window_set_ime_active(false, wid); - } - ime_text = ""; - ime_selection = Point2(); - _shape(); - set_caret_column(caret_column); // Update scroll_offset. - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - DisplayServer::get_singleton()->virtual_keyboard_hide(); - } - - if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { - deselect(); + if (editing) { + _unedit(); } } break; case MainLoop::NOTIFICATION_OS_IME_UPDATE: { - if (has_focus()) { + if (editing) { ime_text = DisplayServer::get_singleton()->ime_get_text(); ime_selection = DisplayServer::get_singleton()->ime_get_selection(); @@ -1158,8 +1363,6 @@ void LineEdit::_notification(int p_what) { _shape(); set_caret_column(caret_column); // Update scroll_offset. - - queue_redraw(); } } break; @@ -1399,7 +1602,7 @@ Vector2 LineEdit::get_caret_pixel_pos() { Vector2 ret; CaretInfo caret; // Get position of the start of caret. - if (ime_text.length() != 0 && ime_selection.x != 0) { + if (!ime_text.is_empty() && ime_selection.x != 0) { caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x); } else { caret = TS->shaped_text_get_carets(text_rid, caret_column); @@ -1412,7 +1615,7 @@ Vector2 LineEdit::get_caret_pixel_pos() { } // Get position of the end of caret. - if (ime_text.length() != 0) { + if (!ime_text.is_empty()) { if (ime_selection.y != 0) { caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y); } else { @@ -1505,11 +1708,11 @@ void LineEdit::_validate_caret_can_draw() { draw_caret = true; caret_blink_timer = 0.0; } - caret_can_draw = editable && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed); + caret_can_draw = editing && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed); } void LineEdit::delete_char() { - if ((text.length() <= 0) || (caret_column == 0)) { + if (text.is_empty() || caret_column == 0) { return; } @@ -1641,12 +1844,14 @@ void LineEdit::clear() { _text_changed(); // This should reset virtual keyboard state if needed. - if (has_focus()) { + if (editing) { show_virtual_keyboard(); } } void LineEdit::show_virtual_keyboard() { + _update_ime_window_position(); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { if (selection.enabled) { DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), DisplayServer::VirtualKeyboardType(virtual_keyboard_type), max_length, selection.begin, selection.end); @@ -1814,7 +2019,8 @@ Size2 LineEdit::get_minimum_size() const { Size2 min_size; // Minimum size of text. - float em_space_size = font->get_char_size('M', font_size).x; + // W is wider than M in most fonts, Using M may result in hiding the last digit when using float values in SpinBox, ie. ColorPicker RAW values. + float em_space_size = font->get_char_size('W', font_size).x; min_size.width = theme_cache.minimum_character_width * em_space_size; if (expand_to_text_length) { @@ -1912,7 +2118,8 @@ void LineEdit::select_all() { return; } - if (!text.length()) { + if (text.is_empty()) { + set_caret_column(0); return; } @@ -1928,6 +2135,10 @@ void LineEdit::set_editable(bool p_editable) { } editable = p_editable; + + if (!editable && editing) { + _unedit(); + } _validate_caret_can_draw(); update_minimum_size(); @@ -2308,6 +2519,7 @@ void LineEdit::_emit_text_change() { emit_signal(SceneStringName(text_changed), text); text_changed_dirty = false; } + PackedStringArray LineEdit::get_configuration_warnings() const { PackedStringArray warnings = Control::get_configuration_warnings(); if (secret_character.length() > 1) { @@ -2327,13 +2539,13 @@ void LineEdit::_shape() { TS->shaped_text_clear(text_rid); String t; - if (text.length() == 0 && ime_text.length() == 0) { + if (text.is_empty() && ime_text.is_empty()) { t = placeholder_translated; } else if (pass) { - String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•"; + String s = secret_character.is_empty() ? U"•" : secret_character.left(1); t = s.repeat(text.length() + ime_text.length()); } else { - if (ime_text.length() > 0) { + if (!ime_text.is_empty()) { t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length()); } else { t = text; @@ -2539,9 +2751,14 @@ void LineEdit::_validate_property(PropertyInfo &p_property) const { } void LineEdit::_bind_methods() { + ClassDB::bind_method(D_METHOD("has_ime_text"), &LineEdit::has_ime_text); + ClassDB::bind_method(D_METHOD("cancel_ime"), &LineEdit::cancel_ime); + ClassDB::bind_method(D_METHOD("apply_ime"), &LineEdit::apply_ime); + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing); ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear); ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all); @@ -2622,6 +2839,7 @@ void LineEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text"))); ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring"))); ADD_SIGNAL(MethodInfo("text_submitted", PropertyInfo(Variant::STRING, "new_text"))); + ADD_SIGNAL(MethodInfo("editing_toggled", PropertyInfo(Variant::BOOL, "toggled_on"))); BIND_ENUM_CONSTANT(MENU_CUT); BIND_ENUM_CONSTANT(MENU_COPY); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 993bc727e4..ac7436646b 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -86,11 +86,13 @@ public: private: HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; + bool editing = false; bool editable = false; bool pass = false; bool text_changed_dirty = false; bool alt_start = false; + bool alt_start_no_hold = false; uint32_t alt_code = 0; String undo_text; @@ -205,6 +207,12 @@ private: float base_scale = 1.0; } theme_cache; + void _edit(); + void _unedit(); + + void _close_ime_window(); + void _update_ime_window_position(); + void _clear_undo_stack(); void _clear_redo(); void _create_undo_state(); @@ -257,6 +265,12 @@ protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; public: + bool is_editing() const; + + bool has_ime_text() const; + void cancel_ime(); + void apply_ime(); + void set_horizontal_alignment(HorizontalAlignment p_alignment); HorizontalAlignment get_horizontal_alignment() const; diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 4f8d818a6c..46c9c7cccc 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -121,8 +121,9 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) { } Rect2 item_rect = _get_menu_item_rect(p_index); - Point2 screen_pos = get_screen_position() + item_rect.position * get_viewport()->get_canvas_transform().get_scale(); - Size2 screen_size = item_rect.size * get_viewport()->get_canvas_transform().get_scale(); + Size2 canvas_scale = get_canvas_transform().get_scale(); + Point2 screen_pos = get_screen_position() + item_rect.position * canvas_scale; + Size2 screen_size = item_rect.size * canvas_scale; active_menu = p_index; @@ -217,15 +218,18 @@ void MenuBar::bind_global_menu() { int global_start_idx = -1; int count = nmenu->get_item_count(main_menu); String prev_tag; - for (int i = 0; i < count; i++) { - String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1); - if (!tag.is_empty() && tag != prev_tag) { - if (i >= start_index) { - global_start_idx = i; - break; + if (start_index >= 0) { + for (int i = 0; i < count; i++) { + String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1); + if (!tag.is_empty() && tag != prev_tag) { + MenuBar *mb = Object::cast_to<MenuBar>(ObjectDB::get_instance(ObjectID(static_cast<uint64_t>(tag.to_int())))); + if (mb && mb->get_start_index() >= start_index) { + global_start_idx = i; + break; + } } + prev_tag = tag; } - prev_tag = tag; } if (global_start_idx == -1) { global_start_idx = count; @@ -679,10 +683,7 @@ void MenuBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); - // TODO: Properly handle popups when advanced GUI is disabled. -#ifndef ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); -#endif // ADVANCED_GUI_DISABLED ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::INT, "start_index"), "set_start_index", "get_start_index"); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index c60f728f34..1069a752c4 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -173,10 +173,7 @@ bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { } void MenuButton::_bind_methods() { - // TODO: Properly handle popups when advanced GUI is disabled. -#ifndef ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); -#endif // ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover); ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover); @@ -198,7 +195,7 @@ void MenuButton::_bind_methods() { 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::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL), defaults.id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator); PropertyListHelper::register_base_helper(&base_property_helper); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index a1425fb847..5432058f7b 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -574,7 +574,7 @@ void OptionButton::_bind_methods() { 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::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL), 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); PropertyListHelper::register_base_helper(&base_property_helper); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index f62421061b..3c04094526 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -589,6 +589,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { // This allows for opening the popup and triggering an action in a single mouse click. if (button_idx == MouseButton::LEFT || initial_button_mask.has_flag(mouse_button_to_mask(button_idx))) { if (b->is_pressed()) { + during_grabbed_click = false; is_scrolling = is_layout_rtl() ? b->get_position().x < item_clickable_area.position.x : b->get_position().x > item_clickable_area.size.width; if (!item_clickable_area.has_point(b->get_position())) { @@ -608,7 +609,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { return; } // Disable clicks under a time threshold to avoid selection right when opening the popup. - if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 150) { + if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 400) { return; } @@ -1064,6 +1065,7 @@ void PopupMenu::_notification(int p_what) { } break; case NOTIFICATION_POST_POPUP: { + popup_time_msec = OS::get_singleton()->get_ticks_msec(); initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); during_grabbed_click = (bool)initial_button_mask; } break; @@ -2590,14 +2592,6 @@ void PopupMenu::clear_autohide_areas() { autohide_areas.clear(); } -void PopupMenu::take_mouse_focus() { - ERR_FAIL_COND(!is_inside_tree()); - - if (get_parent()) { - get_parent()->get_viewport()->pass_mouse_focus_to(this, control); - } -} - bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { if (property_helper.property_set_value(p_name, p_value)) { return true; @@ -2821,7 +2815,7 @@ void PopupMenu::_bind_methods() { 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); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked, &PopupMenu::set_item_checked, &PopupMenu::is_item_checked); - base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &PopupMenu::set_item_id, &PopupMenu::get_item_id); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL), defaults.id, &PopupMenu::set_item_id, &PopupMenu::get_item_id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &PopupMenu::set_item_disabled, &PopupMenu::is_item_disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &PopupMenu::set_item_as_separator, &PopupMenu::is_item_separator); PropertyListHelper::register_base_helper(&base_property_helper); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 5313dae404..b8aa51c1ad 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -369,8 +369,6 @@ public: virtual void popup(const Rect2i &p_bounds = Rect2i()) override; virtual void set_visible(bool p_visible) override; - void take_mouse_focus(); - PopupMenu(); ~PopupMenu(); }; diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 83359653f1..d7b1a4933d 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -31,7 +31,7 @@ #include "range.h" PackedStringArray Range::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Control::get_configuration_warnings(); if (shared->exp_ratio && shared->min <= 0) { warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.")); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 1da3668ebe..0c3c90d070 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -333,6 +333,8 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< font_size = font_size_it->font_size; } TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features()); + } else { + TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features()); } } @@ -547,7 +549,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (font_size_it && font_size_it->font_size > 0) { font_size = font_size_it->font_size; } - l.text_buf->add_string("\n", font, font_size); + l.text_buf->add_string(String::chr(0x200B), font, font_size); txt += "\n"; l.char_count++; remaining_characters--; @@ -817,6 +819,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } int line_count = 0; + // Bottom margin for text clipping. + float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM); Size2 ctrl_size = get_size(); // Draw text. for (int line = 0; line < l.text_buf->get_line_count(); line++) { @@ -824,7 +828,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o off.y += theme_cache.line_separation; } - if (p_ofs.y + off.y >= ctrl_size.height) { + if (p_ofs.y + off.y >= ctrl_size.height - v_limit) { break; } @@ -910,84 +914,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o double uth = TS->shaped_text_get_underline_thickness(rid); off.y += l_ascent; - // Draw inlined objects. - Array objects = TS->shaped_text_get_objects(rid); - for (int i = 0; i < objects.size(); i++) { - Item *it = items.get_or_null(objects[i]); - if (it != nullptr) { - Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); - if (trim_chars && l.char_offset + obj_range.y > visible_characters) { - continue; - } - if (trim_glyphs_ltr || trim_glyphs_rtl) { - int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); - if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { - continue; - } - } - Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); - switch (it->type) { - case ITEM_IMAGE: { - ItemImage *img = static_cast<ItemImage *>(it); - if (img->pad) { - 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 { - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); - } - } break; - case ITEM_TABLE: { - ItemTable *table = static_cast<ItemTable *>(it); - Color odd_row_bg = theme_cache.table_odd_row_bg; - Color even_row_bg = theme_cache.table_even_row_bg; - Color border = theme_cache.table_border; - float h_separation = theme_cache.table_h_separation; - float v_separation = theme_cache.table_v_separation; - - int col_count = table->columns.size(); - int row_count = table->rows.size(); - - int idx = 0; - for (Item *E : table->subitems) { - ItemFrame *frame = static_cast<ItemFrame *>(E); - - int col = idx % col_count; - int row = idx / col_count; - - if (frame->lines.size() != 0 && row < row_count) { - Vector2 coff = frame->lines[0].offset; - if (rtl) { - coff.x = rect.size.width - table->columns[col].width - coff.x; - } - if (row % 2 == 0) { - Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } else { - Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } - Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; - if (bc.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); - } - } - - for (int j = 0; j < (int)frame->lines.size(); j++) { - _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); - } - idx++; - } - } break; - default: - break; - } - } - } const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); int gl_size = TS->shaped_text_get_glyph_count(rid); @@ -1003,6 +929,86 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o int processed_glyphs_step = 0; for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) { + if (step == DRAW_STEP_TEXT) { + // Draw inlined objects. + Array objects = TS->shaped_text_get_objects(rid); + for (int i = 0; i < objects.size(); i++) { + Item *it = items.get_or_null(objects[i]); + if (it != nullptr) { + Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); + if (trim_chars && l.char_offset + obj_range.y > visible_characters) { + continue; + } + if (trim_glyphs_ltr || trim_glyphs_rtl) { + int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); + if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { + continue; + } + } + Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); + switch (it->type) { + case ITEM_IMAGE: { + ItemImage *img = static_cast<ItemImage *>(it); + if (img->pad) { + 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 { + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); + } + } break; + case ITEM_TABLE: { + ItemTable *table = static_cast<ItemTable *>(it); + Color odd_row_bg = theme_cache.table_odd_row_bg; + Color even_row_bg = theme_cache.table_even_row_bg; + Color border = theme_cache.table_border; + float h_separation = theme_cache.table_h_separation; + float v_separation = theme_cache.table_v_separation; + + int col_count = table->columns.size(); + int row_count = table->rows.size(); + + int idx = 0; + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); + + int col = idx % col_count; + int row = idx / col_count; + + if (frame->lines.size() != 0 && row < row_count) { + Vector2 coff = frame->lines[0].offset; + if (rtl) { + coff.x = rect.size.width - table->columns[col].width - coff.x; + } + if (row % 2 == 0) { + Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } else { + Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } + Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; + if (bc.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); + } + } + + for (int j = 0; j < (int)frame->lines.size(); j++) { + _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); + } + idx++; + } + } break; + default: + break; + } + } + } + } Vector2 off_step = off; processed_glyphs_step = r_processed_glyphs; @@ -1448,6 +1454,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V bool line_clicked = false; float text_rect_begin = 0.0; int char_pos = -1; + bool char_clicked = false; Line &l = p_frame->lines[p_line]; MutexLock lock(l.text_buf->get_mutex()); @@ -1578,6 +1585,9 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { + if (!p_meta) { + char_pos = rtl ? TS->shaped_text_get_range(rid).y : TS->shaped_text_get_range(rid).x; + } if ((!rtl && p_click.x >= rect.position.x) || (rtl && p_click.x <= rect.position.x + rect.size.x)) { if (p_meta) { int64_t glyph_idx = TS->shaped_text_hit_test_grapheme(rid, p_click.x - rect.position.x); @@ -1592,6 +1602,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V 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; + char_clicked = true; } break; } @@ -1602,18 +1613,21 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V 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; + char_clicked = true; } } 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; + char_clicked = true; } } } } else { char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); char_pos = TS->shaped_text_closest_character_pos(rid, char_pos); + char_clicked = true; } } line_clicked = true; @@ -1621,7 +1635,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } // If table hit was detected, and line hit is in the table bounds use table hit. - if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || char_pos == -1)) { + if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || !char_clicked)) { if (r_click_frame != nullptr) { *r_click_frame = table_click_frame; } @@ -1792,13 +1806,13 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_RESIZED: { _stop_thread(); - main->first_resized_line.store(0); //invalidate ALL + main->first_resized_line.store(0); // Invalidate all lines. queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { _stop_thread(); - main->first_invalid_font_line.store(0); //invalidate ALL + main->first_invalid_font_line.store(0); // Invalidate all lines. queue_redraw(); } break; @@ -1808,7 +1822,7 @@ void RichTextLabel::_notification(int p_what) { set_text(text); } - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. queue_redraw(); } break; @@ -1882,10 +1896,12 @@ void RichTextLabel::_notification(int p_what) { visible_paragraph_count = 0; visible_line_count = 0; + // Bottom margin for text clipping. + float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM); // New cache draw. Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); int processed_glyphs = 0; - while (ofs.y < size.height && from_line < to_line) { + while (ofs.y < size.height - v_limit && from_line < to_line) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); visible_paragraph_count++; @@ -1897,7 +1913,7 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree()) { - if (!is_ready()) { + if (!is_finished()) { return; } double dt = get_process_delta_time(); @@ -2520,7 +2536,7 @@ PackedFloat32Array RichTextLabel::_find_tab_stops(Item *p_item) { item = item->parent; } - return PackedFloat32Array(); + return default_tab_stops; } HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) { @@ -2788,7 +2804,7 @@ int RichTextLabel::get_pending_paragraphs() const { return lines - to_line; } -bool RichTextLabel::is_ready() const { +bool RichTextLabel::is_finished() const { const_cast<RichTextLabel *>(this)->_validate_line_caches(); if (updating.load()) { @@ -3031,7 +3047,7 @@ void RichTextLabel::add_text(const String &p_text) { int pos = 0; while (pos < p_text.length()) { - int end = p_text.find("\n", pos); + int end = p_text.find_char('\n', pos); String line; bool eol = false; if (end == -1) { @@ -3398,6 +3414,21 @@ bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) { selection.click_item = nullptr; selection.active = false; + if (is_processing_internal()) { + bool process_enabled = false; + Item *it = main; + while (it) { + Vector<ItemFX *> fx_stack; + _fetch_item_fx_stack(it, fx_stack); + if (fx_stack.size()) { + process_enabled = true; + break; + } + it = _get_next_item(it, true); + } + set_process_internal(process_enabled); + } + 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; @@ -3971,6 +4002,7 @@ void RichTextLabel::pop_all() { void RichTextLabel::clear() { _stop_thread(); + set_process_internal(false); MutexLock data_lock(data_mutex); main->_clear_children(); @@ -4080,6 +4112,74 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) { append_text(p_bbcode); } +String RichTextLabel::_get_tag_value(const String &p_tag) { + return p_tag.substr(p_tag.find_char('=') + 1); +} + +int RichTextLabel::_find_unquoted(const String &p_src, char32_t p_chr, int p_from) { + if (p_from < 0) { + return -1; + } + + const int len = p_src.length(); + if (len == 0) { + return -1; + } + + const char32_t *src = p_src.get_data(); + bool in_single_quote = false; + bool in_double_quote = false; + for (int i = p_from; i < len; i++) { + if (in_double_quote) { + if (src[i] == '"') { + in_double_quote = false; + } + } else if (in_single_quote) { + if (src[i] == '\'') { + in_single_quote = false; + } + } else { + if (src[i] == '"') { + in_double_quote = true; + } else if (src[i] == '\'') { + in_single_quote = true; + } else if (src[i] == p_chr) { + return i; + } + } + } + + return -1; +} + +Vector<String> RichTextLabel::_split_unquoted(const String &p_src, char32_t p_splitter) { + Vector<String> ret; + + if (p_src.is_empty()) { + return ret; + } + + int from = 0; + int len = p_src.length(); + + while (true) { + int end = _find_unquoted(p_src, p_splitter, from); + if (end < 0) { + end = len; + } + if (end > from) { + ret.push_back(p_src.substr(from, end - from)); + } + if (end == len) { + break; + } + + from = end + 1; + } + + return ret; +} + void RichTextLabel::append_text(const String &p_bbcode) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -4095,10 +4195,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { bool after_list_open_tag = false; bool after_list_close_tag = false; - set_process_internal(false); - while (pos <= p_bbcode.length()) { - int brk_pos = p_bbcode.find("[", pos); + int brk_pos = p_bbcode.find_char('[', pos); if (brk_pos < 0) { brk_pos = p_bbcode.length(); @@ -4123,7 +4221,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { break; //nothing else to add } - int brk_end = p_bbcode.find("]", brk_pos + 1); + int brk_end = _find_unquoted(p_bbcode, ']', brk_pos + 1); if (brk_end == -1) { //no close, add the rest @@ -4133,7 +4231,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } String tag = p_bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); - Vector<String> split_tag_block = tag.split(" ", false); + Vector<String> split_tag_block = _split_unquoted(tag, ' '); // Find optional parameters. String bbcode_name; @@ -4143,7 +4241,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { bbcode_name = split_tag_block[0]; for (int i = 1; i < split_tag_block.size(); i++) { const String &expr = split_tag_block[i]; - int value_pos = expr.find("="); + int value_pos = expr.find_char('='); if (value_pos > -1) { bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote(); } @@ -4154,7 +4252,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { // Find main parameter. String bbcode_value; - int main_value_pos = bbcode_name.find("="); + int main_value_pos = bbcode_name.find_char('='); if (main_value_pos > -1) { bbcode_value = bbcode_name.substr(main_value_pos + 1); bbcode_name = bbcode_name.substr(0, main_value_pos); @@ -4253,10 +4351,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("table=")) { - Vector<String> subtag = tag.substr(6, tag.length()).split(","); + Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U','); _normalize_subtags(subtag); - int columns = subtag[0].to_int(); + int columns = (subtag.is_empty()) ? 1 : subtag[0].to_int(); if (columns < 1) { columns = 1; } @@ -4303,7 +4401,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("cell=")) { - int ratio = tag.substr(5, tag.length()).to_int(); + int ratio = _get_tag_value(tag).to_int(); if (ratio < 1) { ratio = 1; } @@ -4314,54 +4412,45 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("cell"); } else if (tag.begins_with("cell ")) { - Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - _normalize_subtags(subtag); - - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "expand") { - int ratio = subtag_a[1].to_int(); - if (ratio < 1) { - ratio = 1; - } - set_table_column_expand(get_current_table_column(), true, ratio); - } + OptionMap::Iterator expand_option = bbcode_options.find("expand"); + if (expand_option) { + int ratio = expand_option->value.to_int(); + if (ratio < 1) { + ratio = 1; } + set_table_column_expand(get_current_table_column(), true, ratio); } + push_cell(); const Color fallback_color = Color(0, 0, 0, 0); - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "border") { - Color color = Color::from_string(subtag_a[1], fallback_color); - set_cell_border_color(color); - } else if (subtag_a[0] == "bg") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); - if (subtag_b.size() == 2) { - Color color1 = Color::from_string(subtag_b[0], fallback_color); - Color color2 = Color::from_string(subtag_b[1], fallback_color); - set_cell_row_background_color(color1, color2); - } - if (subtag_b.size() == 1) { - Color color1 = Color::from_string(subtag_a[1], fallback_color); - set_cell_row_background_color(color1, color1); - } - } else if (subtag_a[0] == "padding") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); + OptionMap::Iterator border_option = bbcode_options.find("border"); + if (border_option) { + Color color = Color::from_string(border_option->value, fallback_color); + set_cell_border_color(color); + } + OptionMap::Iterator bg_option = bbcode_options.find("bg"); + if (bg_option) { + Vector<String> subtag_b = _split_unquoted(bg_option->value, U','); + _normalize_subtags(subtag_b); - if (subtag_b.size() == 4) { - set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float())); - } - } + if (subtag_b.size() == 2) { + Color color1 = Color::from_string(subtag_b[0], fallback_color); + Color color2 = Color::from_string(subtag_b[1], fallback_color); + set_cell_row_background_color(color1, color2); + } + if (subtag_b.size() == 1) { + Color color1 = Color::from_string(bg_option->value, fallback_color); + set_cell_row_background_color(color1, color1); + } + } + OptionMap::Iterator padding_option = bbcode_options.find("padding"); + if (padding_option) { + Vector<String> subtag_b = _split_unquoted(padding_option->value, U','); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 4) { + set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float())); } } @@ -4378,7 +4467,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { 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(); + int32_t char_code = _get_tag_value(tag).hex_to_int(); add_text(String::chr(char_code)); pos = brk_end + 1; } else if (tag == "lb") { @@ -4436,19 +4525,19 @@ void RichTextLabel::append_text(const String &p_bbcode) { add_text(String::chr(0x00AD)); pos = brk_end + 1; } else if (tag == "center") { - push_paragraph(HORIZONTAL_ALIGNMENT_CENTER); + push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "fill") { - push_paragraph(HORIZONTAL_ALIGNMENT_FILL); + push_paragraph(HORIZONTAL_ALIGNMENT_FILL, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "left") { - push_paragraph(HORIZONTAL_ALIGNMENT_LEFT); + push_paragraph(HORIZONTAL_ALIGNMENT_LEFT, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "right") { - push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT); + push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "ul") { @@ -4457,7 +4546,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("ul bullet=")) { - String bullet = tag.substr(10, 1); + String bullet = _get_tag_value(tag); indent_level++; push_list(indent_level, LIST_DOTS, false, bullet); pos = brk_end + 1; @@ -4493,7 +4582,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("lang=")) { - String lang = tag.substr(5, tag.length()).unquote(); + String lang = _get_tag_value(tag).unquote(); push_language(lang); pos = brk_end + 1; tag_stack.push_front("lang"); @@ -4502,89 +4591,104 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag.begins_with("p ")) { - Vector<String> subtag = tag.substr(2, tag.length()).split(" "); - _normalize_subtags(subtag); - HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; - String lang; - PackedFloat32Array tab_stops; + String lang = language; + PackedFloat32Array tab_stops = default_tab_stops; TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; BitField<TextServer::JustificationFlag> jst_flags = default_jst_flags; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "justification_flags" || subtag_a[0] == "jst") { - Vector<String> subtag_b = subtag_a[1].split(","); - jst_flags = 0; // Clear flags. - for (const String &E : subtag_b) { - if (E == "kashida" || E == "k") { - jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA); - } else if (E == "word" || E == "w") { - jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND); - } else if (E == "trim" || E == "tr") { - jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); - } else if (E == "after_last_tab" || E == "lt") { - jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); - } else if (E == "skip_last" || E == "sl") { - jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE); - } else if (E == "skip_last_with_chars" || E == "sv") { - jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); - } else if (E == "do_not_skip_single" || E == "ns") { - jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); - } - } - } else if (subtag_a[0] == "tab_stops") { - Vector<String> splitters; - splitters.push_back(","); - splitters.push_back(";"); - tab_stops = subtag_a[1].split_floats_mk(splitters); - } else if (subtag_a[0] == "align") { - if (subtag_a[1] == "l" || subtag_a[1] == "left") { - alignment = HORIZONTAL_ALIGNMENT_LEFT; - } else if (subtag_a[1] == "c" || subtag_a[1] == "center") { - alignment = HORIZONTAL_ALIGNMENT_CENTER; - } else if (subtag_a[1] == "r" || subtag_a[1] == "right") { - alignment = HORIZONTAL_ALIGNMENT_RIGHT; - } else if (subtag_a[1] == "f" || subtag_a[1] == "fill") { - alignment = HORIZONTAL_ALIGNMENT_FILL; - } - } else if (subtag_a[0] == "dir" || subtag_a[0] == "direction") { - if (subtag_a[1] == "a" || subtag_a[1] == "auto") { - dir = Control::TEXT_DIRECTION_AUTO; - } else if (subtag_a[1] == "l" || subtag_a[1] == "ltr") { - dir = Control::TEXT_DIRECTION_LTR; - } else if (subtag_a[1] == "r" || subtag_a[1] == "rtl") { - dir = Control::TEXT_DIRECTION_RTL; - } - } else if (subtag_a[0] == "lang" || subtag_a[0] == "language") { - lang = subtag_a[1]; - } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") { - if (subtag_a[1] == "d" || subtag_a[1] == "default") { - st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; - } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") { - st_parser_type = TextServer::STRUCTURED_TEXT_URI; - } else if (subtag_a[1] == "f" || subtag_a[1] == "file") { - st_parser_type = TextServer::STRUCTURED_TEXT_FILE; - } else if (subtag_a[1] == "e" || subtag_a[1] == "email") { - st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; - } else if (subtag_a[1] == "l" || subtag_a[1] == "list") { - st_parser_type = TextServer::STRUCTURED_TEXT_LIST; - } else if (subtag_a[1] == "n" || subtag_a[1] == "gdscript") { - st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT; - } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") { - st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; - } + + OptionMap::Iterator justification_flags_option = bbcode_options.find("justification_flags"); + if (!justification_flags_option) { + justification_flags_option = bbcode_options.find("jst"); + } + if (justification_flags_option) { + Vector<String> subtag_b = _split_unquoted(justification_flags_option->value, U','); + jst_flags = 0; // Clear flags. + for (const String &E : subtag_b) { + if (E == "kashida" || E == "k") { + jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA); + } else if (E == "word" || E == "w") { + jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND); + } else if (E == "trim" || E == "tr") { + jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + } else if (E == "after_last_tab" || E == "lt") { + jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); + } else if (E == "skip_last" || E == "sl") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE); + } else if (E == "skip_last_with_chars" || E == "sv") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); + } else if (E == "do_not_skip_single" || E == "ns") { + jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); } } } + OptionMap::Iterator tab_stops_option = bbcode_options.find("tab_stops"); + if (tab_stops_option) { + Vector<String> splitters; + splitters.push_back(","); + splitters.push_back(";"); + tab_stops = tab_stops_option->value.split_floats_mk(splitters); + } + OptionMap::Iterator align_option = bbcode_options.find("align"); + if (align_option) { + if (align_option->value == "l" || align_option->value == "left") { + alignment = HORIZONTAL_ALIGNMENT_LEFT; + } else if (align_option->value == "c" || align_option->value == "center") { + alignment = HORIZONTAL_ALIGNMENT_CENTER; + } else if (align_option->value == "r" || align_option->value == "right") { + alignment = HORIZONTAL_ALIGNMENT_RIGHT; + } else if (align_option->value == "f" || align_option->value == "fill") { + alignment = HORIZONTAL_ALIGNMENT_FILL; + } + } + OptionMap::Iterator direction_option = bbcode_options.find("direction"); + if (!direction_option) { + direction_option = bbcode_options.find("dir"); + } + if (direction_option) { + if (direction_option->value == "a" || direction_option->value == "auto") { + dir = Control::TEXT_DIRECTION_AUTO; + } else if (direction_option->value == "l" || direction_option->value == "ltr") { + dir = Control::TEXT_DIRECTION_LTR; + } else if (direction_option->value == "r" || direction_option->value == "rtl") { + dir = Control::TEXT_DIRECTION_RTL; + } + } + OptionMap::Iterator language_option = bbcode_options.find("language"); + if (!language_option) { + language_option = bbcode_options.find("lang"); + } + if (language_option) { + lang = language_option->value; + } + OptionMap::Iterator bidi_override_option = bbcode_options.find("bidi_override"); + if (!bidi_override_option) { + bidi_override_option = bbcode_options.find("st"); + } + if (bidi_override_option) { + if (bidi_override_option->value == "d" || bidi_override_option->value == "default") { + st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; + } else if (bidi_override_option->value == "u" || bidi_override_option->value == "uri") { + st_parser_type = TextServer::STRUCTURED_TEXT_URI; + } else if (bidi_override_option->value == "f" || bidi_override_option->value == "file") { + st_parser_type = TextServer::STRUCTURED_TEXT_FILE; + } else if (bidi_override_option->value == "e" || bidi_override_option->value == "email") { + st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; + } else if (bidi_override_option->value == "l" || bidi_override_option->value == "list") { + st_parser_type = TextServer::STRUCTURED_TEXT_LIST; + } else if (bidi_override_option->value == "n" || bidi_override_option->value == "gdscript") { + st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT; + } else if (bidi_override_option->value == "c" || bidi_override_option->value == "custom") { + st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; + } + } + push_paragraph(alignment, dir, lang, st_parser_type, jst_flags, tab_stops); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { - int end = p_bbcode.find("[", brk_end); + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4595,19 +4699,16 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front(tag); } else if (tag.begins_with("url=")) { - String url = tag.substr(4, tag.length()).unquote(); + String url = _get_tag_value(tag).unquote(); push_meta(url, META_UNDERLINE_ALWAYS); pos = brk_end + 1; tag_stack.push_front("url"); } else if (tag.begins_with("hint=")) { - String description = tag.substr(5, tag.length()).unquote(); + String description = _get_tag_value(tag).unquote(); push_hint(description); pos = brk_end + 1; tag_stack.push_front("hint"); } else if (tag.begins_with("dropcap")) { - Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - _normalize_subtags(subtag); - int fs = theme_cache.normal_font_size * 3; Ref<Font> f = theme_cache.normal_font; Color color = theme_cache.default_color; @@ -4615,39 +4716,47 @@ void RichTextLabel::append_text(const String &p_bbcode) { int outline_size = theme_cache.outline_size; Rect2 dropcap_margins; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "font" || subtag_a[0] == "f") { - const String &fnt = subtag_a[1]; - Ref<Font> font = ResourceLoader::load(fnt, "Font"); - if (font.is_valid()) { - f = font; - } - } else if (subtag_a[0] == "font_size") { - fs = subtag_a[1].to_int(); - } else if (subtag_a[0] == "margins") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); + OptionMap::Iterator font_option = bbcode_options.find("font"); + if (!font_option) { + font_option = bbcode_options.find("f"); + } + if (font_option) { + const String &fnt = font_option->value; + Ref<Font> font = ResourceLoader::load(fnt, "Font"); + if (font.is_valid()) { + f = font; + } + } + OptionMap::Iterator font_size_option = bbcode_options.find("font_size"); + if (font_size_option) { + fs = font_size_option->value.to_int(); + } + OptionMap::Iterator margins_option = bbcode_options.find("margins"); + if (margins_option) { + Vector<String> subtag_b = _split_unquoted(margins_option->value, U','); + _normalize_subtags(subtag_b); - if (subtag_b.size() == 4) { - dropcap_margins.position.x = subtag_b[0].to_float(); - dropcap_margins.position.y = subtag_b[1].to_float(); - dropcap_margins.size.x = subtag_b[2].to_float(); - dropcap_margins.size.y = subtag_b[3].to_float(); - } - } else if (subtag_a[0] == "outline_size") { - outline_size = subtag_a[1].to_int(); - } else if (subtag_a[0] == "color") { - color = Color::from_string(subtag_a[1], color); - } else if (subtag_a[0] == "outline_color") { - outline_color = Color::from_string(subtag_a[1], outline_color); - } + if (subtag_b.size() == 4) { + dropcap_margins.position.x = subtag_b[0].to_float(); + dropcap_margins.position.y = subtag_b[1].to_float(); + dropcap_margins.size.x = subtag_b[2].to_float(); + dropcap_margins.size.y = subtag_b[3].to_float(); } } - int end = p_bbcode.find("[", brk_end); + OptionMap::Iterator outline_size_option = bbcode_options.find("outline_size"); + if (outline_size_option) { + outline_size = outline_size_option->value.to_int(); + } + OptionMap::Iterator color_option = bbcode_options.find("color"); + if (color_option) { + color = Color::from_string(color_option->value, color); + } + OptionMap::Iterator outline_color_option = bbcode_options.find("outline_color"); + if (outline_color_option) { + outline_color = Color::from_string(outline_color_option->value, outline_color); + } + + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4661,7 +4770,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("img")) { int alignment = INLINE_ALIGNMENT_CENTER; if (tag.begins_with("img=")) { - Vector<String> subtag = tag.substr(4, tag.length()).split(","); + Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U','); _normalize_subtags(subtag); if (subtag.size() > 1) { @@ -4692,7 +4801,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } - int end = p_bbcode.find("[", brk_end); + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4704,7 +4813,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { Rect2 region; OptionMap::Iterator region_option = bbcode_options.find("region"); if (region_option) { - Vector<String> region_values = region_option->value.split(",", false); + Vector<String> region_values = _split_unquoted(region_option->value, U','); if (region_values.size() == 4) { region.position.x = region_values[0].to_float(); region.position.y = region_values[1].to_float(); @@ -4766,27 +4875,27 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = end; tag_stack.push_front(bbcode_name); } else if (tag.begins_with("color=")) { - String color_str = tag.substr(6, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_color(color); pos = brk_end + 1; tag_stack.push_front("color"); } else if (tag.begins_with("outline_color=")) { - String color_str = tag.substr(14, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_outline_color(color); pos = brk_end + 1; tag_stack.push_front("outline_color"); } else if (tag.begins_with("font_size=")) { - int fnt_size = tag.substr(10, tag.length()).to_int(); + int fnt_size = _get_tag_value(tag).to_int(); push_font_size(fnt_size); pos = brk_end + 1; tag_stack.push_front("font_size"); } else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) { - int value_pos = tag.find("="); + int value_pos = tag.find_char('='); String fnt_ftr = tag.substr(value_pos + 1); Vector<String> subtag = fnt_ftr.split(","); _normalize_subtags(subtag); @@ -4830,7 +4939,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front(tag.substr(0, value_pos)); } else if (tag.begins_with("font=")) { - String fnt = tag.substr(5, tag.length()).unquote(); + String fnt = _get_tag_value(tag).unquote(); Ref<Font> fc = ResourceLoader::load(fnt, "Font"); if (fc.is_valid()) { @@ -4841,11 +4950,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("font"); } else if (tag.begins_with("font ")) { - Vector<String> subtag = tag.substr(2, tag.length()).split(" "); - _normalize_subtags(subtag); - Ref<Font> font = theme_cache.normal_font; DefaultFont def_font = NORMAL_FONT; + int fnt_size = -1; ItemFont *font_it = _find_font(current); if (font_it) { @@ -4858,75 +4965,122 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<FontVariation> fc; fc.instantiate(); - int fnt_size = -1; - for (int i = 1; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("=", true, 1); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "name" || subtag_a[0] == "n") { - const String &fnt = subtag_a[1]; - Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); - if (font_data.is_valid()) { - font = font_data; - def_font = CUSTOM_FONT; - } - } else if (subtag_a[0] == "size" || subtag_a[0] == "s") { - fnt_size = subtag_a[1].to_int(); - } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_GLYPH, spacing); - } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_SPACE, spacing); - } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_TOP, spacing); - } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); - } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") { - float emb = subtag_a[1].to_float(); - fc->set_variation_embolden(emb); - } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") { - int fi = subtag_a[1].to_int(); - fc->set_variation_face_index(fi); - } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") { - float slant = subtag_a[1].to_float(); - fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); - } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") { - Dictionary variations; - if (!subtag_a[1].is_empty()) { - Vector<String> variation_tags = subtag_a[1].split(","); - for (int j = 0; j < variation_tags.size(); j++) { - Vector<String> subtag_b = variation_tags[j].split("="); - _normalize_subtags(subtag_b); - - if (subtag_b.size() == 2) { - variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); - } - } - fc->set_variation_opentype(variations); + OptionMap::Iterator name_option = bbcode_options.find("name"); + if (!name_option) { + name_option = bbcode_options.find("n"); + } + if (name_option) { + const String &fnt = name_option->value; + Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); + if (font_data.is_valid()) { + font = font_data; + def_font = CUSTOM_FONT; + } + } + OptionMap::Iterator size_option = bbcode_options.find("size"); + if (!size_option) { + size_option = bbcode_options.find("s"); + } + if (size_option) { + fnt_size = size_option->value.to_int(); + } + OptionMap::Iterator glyph_spacing_option = bbcode_options.find("glyph_spacing"); + if (!glyph_spacing_option) { + glyph_spacing_option = bbcode_options.find("gl"); + } + if (glyph_spacing_option) { + int spacing = glyph_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_GLYPH, spacing); + } + OptionMap::Iterator space_spacing_option = bbcode_options.find("space_spacing"); + if (!space_spacing_option) { + space_spacing_option = bbcode_options.find("sp"); + } + if (space_spacing_option) { + int spacing = space_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_SPACE, spacing); + } + OptionMap::Iterator top_spacing_option = bbcode_options.find("top_spacing"); + if (!top_spacing_option) { + top_spacing_option = bbcode_options.find("top"); + } + if (top_spacing_option) { + int spacing = top_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_TOP, spacing); + } + OptionMap::Iterator bottom_spacing_option = bbcode_options.find("bottom_spacing"); + if (!bottom_spacing_option) { + bottom_spacing_option = bbcode_options.find("bt"); + } + if (bottom_spacing_option) { + int spacing = bottom_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); + } + OptionMap::Iterator embolden_option = bbcode_options.find("embolden"); + if (!embolden_option) { + embolden_option = bbcode_options.find("emb"); + } + if (embolden_option) { + float emb = embolden_option->value.to_float(); + fc->set_variation_embolden(emb); + } + OptionMap::Iterator face_index_option = bbcode_options.find("face_index"); + if (!face_index_option) { + face_index_option = bbcode_options.find("fi"); + } + if (face_index_option) { + int fi = face_index_option->value.to_int(); + fc->set_variation_face_index(fi); + } + OptionMap::Iterator slant_option = bbcode_options.find("slant"); + if (!slant_option) { + slant_option = bbcode_options.find("sln"); + } + if (slant_option) { + float slant = slant_option->value.to_float(); + fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); + } + OptionMap::Iterator opentype_variation_option = bbcode_options.find("opentype_variation"); + if (!opentype_variation_option) { + opentype_variation_option = bbcode_options.find("otv"); + } + if (opentype_variation_option) { + Dictionary variations; + if (!opentype_variation_option->value.is_empty()) { + Vector<String> variation_tags = opentype_variation_option->value.split(","); + for (int j = 0; j < variation_tags.size(); j++) { + Vector<String> subtag_b = variation_tags[j].split("="); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 2) { + variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); } - } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") { - Dictionary features; - if (!subtag_a[1].is_empty()) { - Vector<String> feature_tags = subtag_a[1].split(","); - for (int j = 0; j < feature_tags.size(); j++) { - Vector<String> subtag_b = feature_tags[j].split("="); - _normalize_subtags(subtag_b); - - if (subtag_b.size() == 2) { - features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); - } else if (subtag_b.size() == 1) { - features[TS->name_to_tag(subtag_b[0])] = 1; - } - } - fc->set_opentype_features(features); + } + fc->set_variation_opentype(variations); + } + } + OptionMap::Iterator opentype_features_option = bbcode_options.find("opentype_features"); + if (!opentype_features_option) { + opentype_features_option = bbcode_options.find("otf"); + } + if (opentype_features_option) { + Dictionary features; + if (!opentype_features_option->value.is_empty()) { + Vector<String> feature_tags = opentype_features_option->value.split(","); + for (int j = 0; j < feature_tags.size(); j++) { + Vector<String> subtag_b = feature_tags[j].split("="); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 2) { + features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); + } else if (subtag_b.size() == 1) { + features[TS->name_to_tag(subtag_b[0])] = 1; } } + fc->set_opentype_features(features); } } + fc->set_base_font(font); if (def_font != CUSTOM_FONT) { @@ -4939,7 +5093,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("font"); } else if (tag.begins_with("outline_size=")) { - int fnt_size = tag.substr(13, tag.length()).to_int(); + int fnt_size = _get_tag_value(tag).to_int(); if (fnt_size > 0) { push_outline_size(fnt_size); } @@ -5078,7 +5232,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("pulse"); set_process_internal(true); } else if (tag.begins_with("bgcolor=")) { - String color_str = tag.substr(8, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_bgcolor(color); @@ -5086,7 +5240,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("bgcolor"); } else if (tag.begins_with("fgcolor=")) { - String color_str = tag.substr(8, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_fgcolor(color); @@ -5115,17 +5269,6 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - - Vector<ItemFX *> fx_items; - for (Item *E : main->subitems) { - Item *subitem = static_cast<Item *>(E); - _fetch_item_fx_stack(subitem, fx_items); - - if (fx_items.size()) { - set_process_internal(true); - break; - } - } } void RichTextLabel::scroll_to_selection() { @@ -5726,19 +5869,89 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) if (text_direction != p_text_direction) { text_direction = p_text_direction; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +Control::TextDirection RichTextLabel::get_text_direction() const { + return text_direction; +} + +void RichTextLabel::set_horizontal_alignment(HorizontalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + _stop_thread(); + + if (default_alignment != p_alignment) { + default_alignment = p_alignment; + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +HorizontalAlignment RichTextLabel::get_horizontal_alignment() const { + return default_alignment; +} + +void RichTextLabel::set_justification_flags(BitField<TextServer::JustificationFlag> p_flags) { + _stop_thread(); + + if (default_jst_flags != p_flags) { + default_jst_flags = p_flags; + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } +BitField<TextServer::JustificationFlag> RichTextLabel::get_justification_flags() const { + return default_jst_flags; +} + +void RichTextLabel::set_tab_stops(const PackedFloat32Array &p_tab_stops) { + _stop_thread(); + + if (default_tab_stops != p_tab_stops) { + default_tab_stops = p_tab_stops; + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +PackedFloat32Array RichTextLabel::get_tab_stops() const { + return default_tab_stops; +} + void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { _stop_thread(); st_parser = p_parser; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } @@ -5752,7 +5965,7 @@ void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) { _stop_thread(); st_args = p_args; - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -5762,17 +5975,17 @@ Array RichTextLabel::get_structured_text_bidi_override_options() const { return st_args; } -Control::TextDirection RichTextLabel::get_text_direction() const { - return text_direction; -} - void RichTextLabel::set_language(const String &p_language) { if (language != p_language) { _stop_thread(); language = p_language; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } @@ -5786,7 +5999,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) { _stop_thread(); autowrap_mode = p_mode; - main->first_invalid_line = 0; //invalidate ALL + main->first_invalid_line = 0; // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -5812,7 +6025,7 @@ void RichTextLabel::set_visible_ratio(float p_ratio) { } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); // Invalidate ALL. + main->first_invalid_line.store(0); // Invalidate all lines.. _validate_line_caches(); } queue_redraw(); @@ -5940,6 +6153,13 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language); ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language); + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &RichTextLabel::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &RichTextLabel::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &RichTextLabel::set_justification_flags); + ClassDB::bind_method(D_METHOD("get_justification_flags"), &RichTextLabel::get_justification_flags); + ClassDB::bind_method(D_METHOD("set_tab_stops", "tab_stops"), &RichTextLabel::set_tab_stops); + ClassDB::bind_method(D_METHOD("get_tab_stops"), &RichTextLabel::get_tab_stops); + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode); ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode); @@ -5994,7 +6214,10 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); - ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_ready); +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_finished); +#endif // DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("is_finished"), &RichTextLabel::is_finished); ClassDB::bind_method(D_METHOD("set_threaded", "threaded"), &RichTextLabel::set_threaded); ClassDB::bind_method(D_METHOD("is_threaded"), &RichTextLabel::is_threaded); @@ -6057,6 +6280,10 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops"); + ADD_GROUP("Markup", ""); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); @@ -6159,7 +6386,7 @@ void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharacter _stop_thread(); visible_chars_behavior = p_behavior; - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -6179,7 +6406,7 @@ void RichTextLabel::set_visible_characters(int p_visible) { } } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); } queue_redraw(); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 83285bd7cd..a01da02b27 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -482,6 +482,7 @@ private: HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; BitField<TextServer::JustificationFlag> default_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; + PackedFloat32Array default_tab_stops; ItemMeta *meta_hovering = nullptr; Variant current_meta; @@ -613,6 +614,10 @@ private: String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items); + static int _find_unquoted(const String &p_src, char32_t p_chr, int p_from); + static Vector<String> _split_unquoted(const String &p_src, char32_t p_splitter); + static String _get_tag_value(const String &p_tag); + #ifndef DISABLE_DEPRECATED // Kept for compatibility from 3.x to 4.0. bool _set(const StringName &p_name, const Variant &p_value); @@ -785,7 +790,7 @@ public: void deselect(); int get_pending_paragraphs() const; - bool is_ready() const; + bool is_finished() const; bool is_updating() const; void set_threaded(bool p_threaded); @@ -808,6 +813,15 @@ public: void set_text(const String &p_bbcode); String get_text() const; + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; + + void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags); + BitField<TextServer::JustificationFlag> get_justification_flags() const; + + void set_tab_stops(const PackedFloat32Array &p_tab_stops); + PackedFloat32Array get_tab_stops() const; + void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index d96809b67a..f1902bade4 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -35,8 +35,6 @@ #include "scene/theme/theme_db.h" Size2 ScrollContainer::get_minimum_size() const { - Size2 min_size; - // Calculated in this function, as it needs to traverse all child controls once to calculate; // and needs to be calculated before being used by update_scrollbars(). largest_child_min_size = Size2(); @@ -55,21 +53,23 @@ Size2 ScrollContainer::get_minimum_size() const { largest_child_min_size = largest_child_min_size.max(child_min_size); } + Size2 min_size; + const Size2 size = get_size(); + if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { - min_size.x = MAX(min_size.x, largest_child_min_size.x); - } - if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { - min_size.y = MAX(min_size.y, largest_child_min_size.y); + min_size.x = largest_child_min_size.x; + bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > size.y); + if (v_scroll_show && v_scroll->get_parent() == this) { + min_size.x += v_scroll->get_minimum_size().x; + } } - bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > min_size.x); - bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > min_size.y); - - if (h_scroll_show && h_scroll->get_parent() == this) { - min_size.y += h_scroll->get_minimum_size().y; - } - if (v_scroll_show && v_scroll->get_parent() == this) { - min_size.x += v_scroll->get_minimum_size().x; + if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { + min_size.y = largest_child_min_size.y; + bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > size.x); + if (h_scroll_show && h_scroll->get_parent() == this) { + min_size.y += h_scroll->get_minimum_size().y; + } } min_size += theme_cache.panel_style->get_minimum_size(); diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index f984d781d3..6098548d32 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -275,7 +275,7 @@ void Slider::_notification(int p_what) { double areasize = size.height - (theme_cache.center_grabber ? 0 : grabber->get_height()); int grabber_shift = theme_cache.center_grabber ? grabber->get_height() / 2 : 0; style->draw(ci, Rect2i(Point2i(size.width / 2 - widget_width / 2, 0), Size2i(widget_width, size.height))); - grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_height() / 2 + grabber_shift), Size2i(widget_width, areasize * ratio + grabber->get_height() / 2 - grabber_shift))); + grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, Math::round(size.height - areasize * ratio - grabber->get_height() / 2 + grabber_shift)), Size2i(widget_width, Math::round(areasize * ratio + grabber->get_height() / 2 - grabber_shift)))); if (ticks > 1) { int grabber_offset = (grabber->get_height() / 2 - tick->get_height() / 2); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 2c08d36e7e..01c2b9bffe 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -36,7 +36,7 @@ Size2 SpinBox::get_minimum_size() const { Size2 ms = line_edit->get_combined_minimum_size(); - ms.width += last_w; + ms.width += sizing_cache.buttons_block_width; return ms; } @@ -46,7 +46,7 @@ void SpinBox::_update_text(bool p_keep_line_edit) { value = TS->format_number(value); } - if (!line_edit->has_focus()) { + if (!line_edit->is_editing()) { if (!prefix.is_empty()) { value = prefix + " " + value; } @@ -128,7 +128,7 @@ void SpinBox::_range_click_timeout() { } } -void SpinBox::_release_mouse() { +void SpinBox::_release_mouse_from_drag_mode() { if (drag.enabled) { drag.enabled = false; Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_HIDDEN); @@ -137,6 +137,14 @@ void SpinBox::_release_mouse() { } } +void SpinBox::_mouse_exited() { + if (state_cache.up_button_hovered || state_cache.down_button_hovered) { + state_cache.up_button_hovered = false; + state_cache.down_button_hovered = false; + queue_redraw(); + } +} + void SpinBox::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -144,18 +152,36 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { return; } + Ref<InputEventMouse> me = p_event; Ref<InputEventMouseButton> mb = p_event; + Ref<InputEventMouseMotion> mm = p_event; double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step(); - if (mb.is_valid() && mb->is_pressed()) { - bool up = mb->get_position().y < (get_size().height / 2); + Vector2 mpos; + bool mouse_on_up_button = false; + bool mouse_on_down_button = false; + if (mb.is_valid() || mm.is_valid()) { + Rect2 up_button_rc = Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height); + Rect2 down_button_rc = Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height); + + mpos = me->get_position(); + + mouse_on_up_button = up_button_rc.has_point(mpos); + mouse_on_down_button = down_button_rc.has_point(mpos); + } + if (mb.is_valid() && mb->is_pressed()) { switch (mb->get_button_index()) { case MouseButton::LEFT: { line_edit->grab_focus(); - set_value(get_value() + (up ? step : -step)); + if (mouse_on_up_button || mouse_on_down_button) { + set_value(get_value() + (mouse_on_up_button ? step : -step)); + } + state_cache.up_button_pressed = mouse_on_up_button; + state_cache.down_button_pressed = mouse_on_down_button; + queue_redraw(); range_click_timer->set_wait_time(0.6); range_click_timer->set_one_shot(true); @@ -166,16 +192,18 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } break; case MouseButton::RIGHT: { line_edit->grab_focus(); - set_value((up ? get_max() : get_min())); + if (mouse_on_up_button || mouse_on_down_button) { + set_value(mouse_on_up_button ? get_max() : get_min()); + } } break; case MouseButton::WHEEL_UP: { - if (line_edit->has_focus()) { + if (line_edit->is_editing()) { set_value(get_value() + step * mb->get_factor()); accept_event(); } } break; case MouseButton::WHEEL_DOWN: { - if (line_edit->has_focus()) { + if (line_edit->is_editing()) { set_value(get_value() - step * mb->get_factor()); accept_event(); } @@ -186,14 +214,30 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + if (state_cache.up_button_pressed || state_cache.down_button_pressed) { + state_cache.up_button_pressed = false; + state_cache.down_button_pressed = false; + queue_redraw(); + } + //set_default_cursor_shape(CURSOR_ARROW); range_click_timer->stop(); - _release_mouse(); + _release_mouse_from_drag_mode(); drag.allowed = false; line_edit->clear_pending_select_all_on_focus(); } - Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + bool old_up_hovered = state_cache.up_button_hovered; + bool old_down_hovered = state_cache.down_button_hovered; + + state_cache.up_button_hovered = mouse_on_up_button; + state_cache.down_button_hovered = mouse_on_down_button; + + if (old_up_hovered != state_cache.up_button_hovered || old_down_hovered != state_cache.down_button_hovered) { + queue_redraw(); + } + } if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { if (drag.enabled) { @@ -209,71 +253,158 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } } -void SpinBox::_line_edit_focus_enter() { - int col = line_edit->get_caret_column(); - _update_text(); - line_edit->set_caret_column(col); +void SpinBox::_line_edit_editing_toggled(bool p_toggled_on) { + if (p_toggled_on) { + int col = line_edit->get_caret_column(); + _update_text(); + line_edit->set_caret_column(col); - // LineEdit text might change and it clears any selection. Have to re-select here. - if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - line_edit->select_all(); + // LineEdit text might change and it clears any selection. Have to re-select here. + if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + line_edit->select_all(); + } + } else { + // Discontinue because the focus_exit was caused by canceling. + if (Input::get_singleton()->is_action_pressed("ui_cancel")) { + _update_text(); + return; + } + + line_edit->deselect(); + _text_submitted(line_edit->get_text()); } } -void SpinBox::_line_edit_focus_exit() { - // Discontinue because the focus_exit was caused by left-clicking the arrows. - const Viewport *viewport = get_viewport(); - if (!viewport || viewport->gui_get_focus_owner() == get_line_edit()) { - return; - } - // Discontinue because the focus_exit was caused by right-click context menu. - if (line_edit->is_menu_visible()) { - return; - } - // Discontinue because the focus_exit was caused by canceling. - if (Input::get_singleton()->is_action_pressed("ui_cancel")) { - _update_text(); - return; - } +inline void SpinBox::_compute_sizes() { + int buttons_block_wanted_width = theme_cache.buttons_width + theme_cache.field_and_buttons_separation; + int buttons_block_icon_enforced_width = _get_widest_button_icon_width() + theme_cache.field_and_buttons_separation; - _text_submitted(line_edit->get_text()); -} +#ifndef DISABLE_DEPRECATED + const bool min_width_from_icons = theme_cache.set_min_buttons_width_from_icons || (theme_cache.buttons_width < 0); +#else + const bool min_width_from_icons = theme_cache.buttons_width < 0; +#endif + int w = min_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width; -inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) { - int w = icon->get_width(); - if ((w != last_w)) { + if (w != sizing_cache.buttons_block_width) { line_edit->set_offset(SIDE_LEFT, 0); line_edit->set_offset(SIDE_RIGHT, -w); - last_w = w; + sizing_cache.buttons_block_width = w; } + + Size2i size = get_size(); + + sizing_cache.buttons_width = w - theme_cache.field_and_buttons_separation; + sizing_cache.buttons_vertical_separation = CLAMP(theme_cache.buttons_vertical_separation, 0, size.height); + sizing_cache.buttons_left = is_layout_rtl() ? 0 : size.width - sizing_cache.buttons_width; + sizing_cache.button_up_height = (size.height - sizing_cache.buttons_vertical_separation) / 2; + sizing_cache.button_down_height = size.height - sizing_cache.button_up_height - sizing_cache.buttons_vertical_separation; + sizing_cache.second_button_top = size.height - sizing_cache.button_down_height; + + sizing_cache.buttons_separator_top = sizing_cache.button_up_height; + sizing_cache.field_and_buttons_separator_left = is_layout_rtl() ? sizing_cache.buttons_width : size.width - sizing_cache.buttons_block_width; + sizing_cache.field_and_buttons_separator_width = theme_cache.field_and_buttons_separation; +} + +inline int SpinBox::_get_widest_button_icon_width() { + int max = 0; + max = MAX(max, theme_cache.updown_icon->get_width()); + max = MAX(max, theme_cache.up_icon->get_width()); + max = MAX(max, theme_cache.up_hover_icon->get_width()); + max = MAX(max, theme_cache.up_pressed_icon->get_width()); + max = MAX(max, theme_cache.up_disabled_icon->get_width()); + max = MAX(max, theme_cache.down_icon->get_width()); + max = MAX(max, theme_cache.down_hover_icon->get_width()); + max = MAX(max, theme_cache.down_pressed_icon->get_width()); + max = MAX(max, theme_cache.down_disabled_icon->get_width()); + return max; } void SpinBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { _update_text(true); - _adjust_width_for_icon(theme_cache.updown_icon); + _compute_sizes(); RID ci = get_canvas_item(); Size2i size = get_size(); - if (is_layout_rtl()) { - theme_cache.updown_icon->draw(ci, Point2i(0, (size.height - theme_cache.updown_icon->get_height()) / 2)); - } else { - theme_cache.updown_icon->draw(ci, Point2i(size.width - theme_cache.updown_icon->get_width(), (size.height - theme_cache.updown_icon->get_height()) / 2)); + Ref<StyleBox> up_stylebox = theme_cache.up_base_stylebox; + Ref<StyleBox> down_stylebox = theme_cache.down_base_stylebox; + Ref<Texture2D> up_icon = theme_cache.up_icon; + Ref<Texture2D> down_icon = theme_cache.down_icon; + Color up_icon_modulate = theme_cache.up_icon_modulate; + Color down_icon_modulate = theme_cache.down_icon_modulate; + + bool is_fully_disabled = !is_editable(); + + if (state_cache.up_button_disabled || is_fully_disabled) { + up_stylebox = theme_cache.up_disabled_stylebox; + up_icon = theme_cache.up_disabled_icon; + up_icon_modulate = theme_cache.up_disabled_icon_modulate; + } else if (state_cache.up_button_pressed && !drag.enabled) { + up_stylebox = theme_cache.up_pressed_stylebox; + up_icon = theme_cache.up_pressed_icon; + up_icon_modulate = theme_cache.up_pressed_icon_modulate; + } else if (state_cache.up_button_hovered && !drag.enabled) { + up_stylebox = theme_cache.up_hover_stylebox; + up_icon = theme_cache.up_hover_icon; + up_icon_modulate = theme_cache.up_hover_icon_modulate; } + + if (state_cache.down_button_disabled || is_fully_disabled) { + down_stylebox = theme_cache.down_disabled_stylebox; + down_icon = theme_cache.down_disabled_icon; + down_icon_modulate = theme_cache.down_disabled_icon_modulate; + } else if (state_cache.down_button_pressed && !drag.enabled) { + down_stylebox = theme_cache.down_pressed_stylebox; + down_icon = theme_cache.down_pressed_icon; + down_icon_modulate = theme_cache.down_pressed_icon_modulate; + } else if (state_cache.down_button_hovered && !drag.enabled) { + down_stylebox = theme_cache.down_hover_stylebox; + down_icon = theme_cache.down_hover_icon; + down_icon_modulate = theme_cache.down_hover_icon_modulate; + } + + int updown_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - theme_cache.updown_icon->get_width()) / 2; + int updown_icon_top = (size.height - theme_cache.updown_icon->get_height()) / 2; + + // Compute center icon positions once we know which one is used. + int up_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - up_icon->get_width()) / 2; + int up_icon_top = (sizing_cache.button_up_height - up_icon->get_height()) / 2; + int down_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - down_icon->get_width()) / 2; + int down_icon_top = sizing_cache.second_button_top + (sizing_cache.button_down_height - down_icon->get_height()) / 2; + + // Draw separators. + draw_style_box(theme_cache.up_down_buttons_separator, Rect2(sizing_cache.buttons_left, sizing_cache.buttons_separator_top, sizing_cache.buttons_width, sizing_cache.buttons_vertical_separation)); + draw_style_box(theme_cache.field_and_buttons_separator, Rect2(sizing_cache.field_and_buttons_separator_left, 0, sizing_cache.field_and_buttons_separator_width, size.height)); + + // Draw buttons. + draw_style_box(up_stylebox, Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height)); + draw_style_box(down_stylebox, Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height)); + + // Draw arrows. + theme_cache.updown_icon->draw(ci, Point2i(updown_icon_left, updown_icon_top)); + draw_texture(up_icon, Point2i(up_icon_left, up_icon_top), up_icon_modulate); + draw_texture(down_icon, Point2i(down_icon_left, down_icon_top), down_icon_modulate); + + } break; + + case NOTIFICATION_MOUSE_EXIT: { + _mouse_exited(); } break; case NOTIFICATION_ENTER_TREE: { - _adjust_width_for_icon(theme_cache.updown_icon); + _compute_sizes(); _update_text(); + _update_buttons_state_for_current_value(); } break; case NOTIFICATION_VISIBILITY_CHANGED: drag.allowed = false; [[fallthrough]]; case NOTIFICATION_EXIT_TREE: { - _release_mouse(); + _release_mouse_from_drag_mode(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { @@ -353,6 +484,7 @@ bool SpinBox::is_select_all_on_focus() const { void SpinBox::set_editable(bool p_enabled) { line_edit->set_editable(p_enabled); + queue_redraw(); } bool SpinBox::is_editable() const { @@ -371,6 +503,22 @@ double SpinBox::get_custom_arrow_step() const { return custom_arrow_step; } +void SpinBox::_value_changed(double p_value) { + _update_buttons_state_for_current_value(); +} + +void SpinBox::_update_buttons_state_for_current_value() { + double value = get_value(); + bool should_disable_up = value == get_max() && !is_greater_allowed(); + bool should_disable_down = value == get_min() && !is_lesser_allowed(); + + if (state_cache.up_button_disabled != should_disable_up || state_cache.down_button_disabled != should_disable_down) { + state_cache.up_button_disabled = should_disable_up; + state_cache.down_button_disabled = should_disable_down; + queue_redraw(); + } +} + void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment); @@ -397,20 +545,56 @@ void SpinBox::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_arrow_step", PROPERTY_HINT_RANGE, "0,10000,0.0001,or_greater"), "set_custom_arrow_step", "get_custom_arrow_step"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus"); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_vertical_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, field_and_buttons_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_width); +#ifndef DISABLE_DEPRECATED + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, set_min_buttons_width_from_icons); +#endif + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_icon, "up"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_hover_icon, "up_hover"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_pressed_icon, "up_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_disabled_icon, "up_disabled"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_icon, "down"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_hover_icon, "down_hover"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_pressed_icon, "down_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_disabled_icon, "down_disabled"); + + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_base_stylebox, "up_background"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_hover_stylebox, "up_background_hovered"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_pressed_stylebox, "up_background_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_disabled_stylebox, "up_background_disabled"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_base_stylebox, "down_background"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_hover_stylebox, "down_background_hovered"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_pressed_stylebox, "down_background_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_disabled_stylebox, "down_background_disabled"); + + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_icon_modulate, "up_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_hover_icon_modulate, "up_hover_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_pressed_icon_modulate, "up_pressed_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_disabled_icon_modulate, "up_disabled_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_icon_modulate, "down_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_hover_icon_modulate, "down_hover_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_pressed_icon_modulate, "down_pressed_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_disabled_icon_modulate, "down_disabled_icon_modulate"); + + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, field_and_buttons_separator, "field_and_buttons_separator"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_down_buttons_separator, "up_down_buttons_separator"); } SpinBox::SpinBox() { line_edit = memnew(LineEdit); add_child(line_edit, false, INTERNAL_MODE_FRONT); + line_edit->set_theme_type_variation("SpinBoxInnerLineEdit"); line_edit->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); line_edit->set_mouse_filter(MOUSE_FILTER_PASS); line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED); - 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("editing_toggled", callable_mp(this, &SpinBox::_line_edit_editing_toggled), CONNECT_DEFERRED); line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input)); range_click_timer = memnew(Timer); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 4d49626d71..294dc3e5d5 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -39,12 +39,24 @@ class SpinBox : public Range { GDCLASS(SpinBox, Range); LineEdit *line_edit = nullptr; - int last_w = 0; bool update_on_text_changed = false; + struct SizingCache { + int buttons_block_width = 0; + int buttons_width = 0; + int buttons_vertical_separation = 0; + int buttons_left = 0; + int button_up_height = 0; + int button_down_height = 0; + int second_button_top = 0; + int buttons_separator_top = 0; + int field_and_buttons_separator_left = 0; + int field_and_buttons_separator_width = 0; + } sizing_cache; + Timer *range_click_timer = nullptr; void _range_click_timeout(); - void _release_mouse(); + void _release_mouse_from_drag_mode(); void _update_text(bool p_keep_line_edit = false); void _text_submitted(const String &p_string); @@ -65,17 +77,66 @@ class SpinBox : public Range { double diff_y = 0.0; } drag; - void _line_edit_focus_enter(); - void _line_edit_focus_exit(); + struct StateCache { + bool up_button_hovered = false; + bool up_button_pressed = false; + bool up_button_disabled = false; + bool down_button_hovered = false; + bool down_button_pressed = false; + bool down_button_disabled = false; + } state_cache; + + void _line_edit_editing_toggled(bool p_toggled_on); - inline void _adjust_width_for_icon(const Ref<Texture2D> &icon); + inline void _compute_sizes(); + inline int _get_widest_button_icon_width(); struct ThemeCache { Ref<Texture2D> updown_icon; + Ref<Texture2D> up_icon; + Ref<Texture2D> up_hover_icon; + Ref<Texture2D> up_pressed_icon; + Ref<Texture2D> up_disabled_icon; + Ref<Texture2D> down_icon; + Ref<Texture2D> down_hover_icon; + Ref<Texture2D> down_pressed_icon; + Ref<Texture2D> down_disabled_icon; + + Ref<StyleBox> up_base_stylebox; + Ref<StyleBox> up_hover_stylebox; + Ref<StyleBox> up_pressed_stylebox; + Ref<StyleBox> up_disabled_stylebox; + Ref<StyleBox> down_base_stylebox; + Ref<StyleBox> down_hover_stylebox; + Ref<StyleBox> down_pressed_stylebox; + Ref<StyleBox> down_disabled_stylebox; + + Color up_icon_modulate; + Color up_hover_icon_modulate; + Color up_pressed_icon_modulate; + Color up_disabled_icon_modulate; + Color down_icon_modulate; + Color down_hover_icon_modulate; + Color down_pressed_icon_modulate; + Color down_disabled_icon_modulate; + + Ref<StyleBox> field_and_buttons_separator; + Ref<StyleBox> up_down_buttons_separator; + + int buttons_vertical_separation = 0; + int field_and_buttons_separation = 0; + int buttons_width = 0; +#ifndef DISABLE_DEPRECATED + bool set_min_buttons_width_from_icons = false; +#endif } theme_cache; + void _mouse_exited(); + void _update_buttons_state_for_current_value(); + protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + void _value_changed(double p_value) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 8ab9b4c1cd..be03db526d 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -32,6 +32,7 @@ #include "scene/gui/label.h" #include "scene/gui/margin_container.h" +#include "scene/main/window.h" #include "scene/theme/theme_db.h" void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { @@ -39,7 +40,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) { + if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { return; } @@ -48,8 +49,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - sc->_compute_middle_sep(true); + sc->_compute_split_offset(true); dragging = true; + sc->emit_signal(SNAME("drag_started")); drag_ofs = sc->split_offset; if (sc->vertical) { drag_from = get_transform().xform(mb->get_position()).y; @@ -59,6 +61,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { } else { dragging = false; queue_redraw(); + sc->emit_signal(SNAME("drag_ended")); } } } @@ -76,7 +79,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { } else { sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); } - sc->_compute_middle_sep(true); + sc->_compute_split_offset(true); sc->queue_sort(); sc->emit_signal(SNAME("dragged"), sc->get_split_offset()); } @@ -84,11 +87,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - - if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) { + if (!sc->collapsed && sc->dragging_enabled) { return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); } - return Control::get_cursor_shape(p_pos); } @@ -101,7 +102,6 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; - case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); @@ -109,22 +109,25 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; - case NOTIFICATION_DRAW: { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - if (!dragging && !mouse_inside && sc->theme_cache.autohide) { - return; + draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect); + if (sc->dragger_visibility == sc->DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide)) { + Ref<Texture2D> tex = sc->_get_grabber_icon(); + float available_size = sc->vertical ? (sc->get_size().x - tex->get_size().x) : (sc->get_size().y - tex->get_size().y); + if (available_size - sc->drag_area_margin_begin - sc->drag_area_margin_end > 0) { // Draw the grabber only if it fits. + draw_texture(tex, (split_bar_rect.get_position() + (split_bar_rect.get_size() - tex->get_size()) * 0.5)); + } + } + if (sc->show_drag_area && Engine::get_singleton()->is_editor_hint()) { + draw_rect(Rect2(Vector2(0, 0), get_size()), sc->dragging_enabled ? Color(1, 1, 0, 0.3) : Color(1, 0, 0, 0.3)); } - - Ref<Texture2D> tex = sc->_get_grabber_icon(); - draw_texture(tex, (get_size() - tex->get_size()) / 2); } break; } } Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode) const { int idx = 0; - for (int i = 0; i < get_child_count(false); i++) { Control *c = as_sortable_control(get_child(i, false), p_visibility_mode); if (!c) { @@ -137,7 +140,6 @@ Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_ idx++; } - return nullptr; } @@ -153,45 +155,48 @@ Ref<Texture2D> SplitContainer::_get_grabber_icon() const { } } -void SplitContainer::_compute_middle_sep(bool p_clamp) { +int SplitContainer::_get_separation() const { + if (dragger_visibility == DRAGGER_HIDDEN_COLLAPSED) { + return 0; + } + // DRAGGER_VISIBLE or DRAGGER_HIDDEN. + Ref<Texture2D> g = _get_grabber_icon(); + return MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()); +} + +void SplitContainer::_compute_split_offset(bool p_clamp) { Control *first = _get_sortable_child(0); Control *second = _get_sortable_child(1); + int axis_index = vertical ? 1 : 0; + int size = get_size()[axis_index]; + int sep = _get_separation(); - // Determine expanded children. - bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; - bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; - - // Compute the minimum size. - int axis = vertical ? 1 : 0; - int size = get_size()[axis]; - int ms_first = first->get_combined_minimum_size()[axis]; - int ms_second = second->get_combined_minimum_size()[axis]; - - // Determine the separation between items. - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; - - // Compute the wished separation_point. - int wished_middle_sep = 0; + // Compute the wished size. + int wished_size = 0; int split_offset_with_collapse = 0; if (!collapsed) { split_offset_with_collapse = split_offset; } - if (first_expanded && second_expanded) { + bool first_is_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; + bool second_is_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; + + if (first_is_expanded && second_is_expanded) { float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio()); - wished_middle_sep = size * ratio - sep / 2 + split_offset_with_collapse; - } else if (first_expanded) { - wished_middle_sep = size - sep + split_offset_with_collapse; + wished_size = size * ratio - sep * 0.5 + split_offset_with_collapse; + } else if (first_is_expanded) { + wished_size = size - sep + split_offset_with_collapse; } else { - wished_middle_sep = split_offset_with_collapse; + wished_size = split_offset_with_collapse; } - // Clamp the middle sep to acceptatble values. - middle_sep = CLAMP(wished_middle_sep, ms_first, size - sep - ms_second); + // Clamp the split offset to acceptable values. + int first_min_size = first->get_combined_minimum_size()[axis_index]; + int second_min_size = second->get_combined_minimum_size()[axis_index]; + computed_split_offset = CLAMP(wished_size, first_min_size, size - sep - second_min_size); // Clamp the split_offset if requested. if (p_clamp) { - split_offset -= wished_middle_sep - middle_sep; + split_offset -= wished_size - computed_split_offset; } } @@ -199,8 +204,7 @@ void SplitContainer::_resort() { Control *first = _get_sortable_child(0); Control *second = _get_sortable_child(1); - // If we have only one element. - if (!first || !second) { + if (!first || !second) { // Only one child. if (first) { fit_child_in_rect(first, Rect2(Point2(), get_size())); } else if (second) { @@ -209,53 +213,50 @@ void SplitContainer::_resort() { dragging_area_control->hide(); return; } + dragging_area_control->set_visible(!collapsed); - // If we have more that one. - _compute_middle_sep(false); + _compute_split_offset(false); // This recalculates and sets computed_split_offset. - // Determine the separation between items. - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; + int sep = _get_separation(); + bool is_rtl = is_layout_rtl(); - // Move the children, including the dragger. + // Move the children. if (vertical) { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep))); - int sofs = middle_sep + sep; + fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, computed_split_offset))); + int sofs = computed_split_offset + sep; fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs))); } else { - if (is_layout_rtl()) { - middle_sep = get_size().width - middle_sep - sep; - fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height))); - int sofs = middle_sep + sep; + if (is_rtl) { + computed_split_offset = get_size().width - computed_split_offset - sep; + fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); + int sofs = computed_split_offset + sep; fit_child_in_rect(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); } else { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height))); - int sofs = middle_sep + sep; + fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); + int sofs = computed_split_offset + sep; fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); } } - // Handle the dragger visibility and position. - if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) { - dragging_area_control->show(); - - int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); - if (vertical) { - dragging_area_control->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, dragger_ctrl_size))); - } else { - dragging_area_control->set_rect(Rect2(Point2(middle_sep - (dragger_ctrl_size - sep) / 2, 0), Size2(dragger_ctrl_size, get_size().height))); - } - - dragging_area_control->queue_redraw(); + dragging_area_control->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE); + const int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); + float split_bar_offset = (dragger_ctrl_size - sep) * 0.5; + if (vertical) { + Rect2 split_bar_rect = Rect2(is_rtl ? drag_area_margin_end : drag_area_margin_begin, computed_split_offset, get_size().width - drag_area_margin_begin - drag_area_margin_end, sep); + dragging_area_control->set_rect(Rect2(split_bar_rect.position.x, split_bar_rect.position.y - split_bar_offset + drag_area_offset, split_bar_rect.size.x, dragger_ctrl_size)); + dragging_area_control->split_bar_rect = Rect2(Vector2(0.0, int(split_bar_offset) - drag_area_offset), split_bar_rect.size); } else { - dragging_area_control->hide(); + Rect2 split_bar_rect = Rect2(computed_split_offset, drag_area_margin_begin, sep, get_size().height - drag_area_margin_begin - drag_area_margin_end); + dragging_area_control->set_rect(Rect2(split_bar_rect.position.x - split_bar_offset + drag_area_offset * (is_rtl ? -1 : 1), split_bar_rect.position.y, dragger_ctrl_size, split_bar_rect.size.y)); + dragging_area_control->split_bar_rect = Rect2(Vector2(int(split_bar_offset) - drag_area_offset * (is_rtl ? -1 : 1), 0.0), split_bar_rect.size); } + queue_redraw(); + dragging_area_control->queue_redraw(); } Size2 SplitContainer::get_minimum_size() const { Size2i minimum; - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; + int sep = _get_separation(); for (int i = 0; i < 2; i++) { Control *child = _get_sortable_child(i, SortableVisbilityMode::VISIBLE); @@ -297,11 +298,9 @@ void SplitContainer::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { queue_sort(); } break; - case NOTIFICATION_SORT_CHILDREN: { _resort(); } break; - case NOTIFICATION_THEME_CHANGED: { update_minimum_size(); } break; @@ -312,9 +311,7 @@ void SplitContainer::set_split_offset(int p_offset) { if (split_offset == p_offset) { return; } - split_offset = p_offset; - queue_sort(); } @@ -326,8 +323,7 @@ void SplitContainer::clamp_split_offset() { if (!_get_sortable_child(0) || !_get_sortable_child(1)) { return; } - - _compute_middle_sep(true); + _compute_split_offset(true); queue_sort(); } @@ -335,7 +331,6 @@ void SplitContainer::set_collapsed(bool p_collapsed) { if (collapsed == p_collapsed) { return; } - collapsed = p_collapsed; queue_sort(); } @@ -344,7 +339,6 @@ void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) { if (dragger_visibility == p_visibility) { return; } - dragger_visibility = p_visibility; queue_sort(); } @@ -368,6 +362,26 @@ bool SplitContainer::is_vertical() const { return vertical; } +void SplitContainer::set_dragging_enabled(bool p_enabled) { + if (dragging_enabled == p_enabled) { + return; + } + dragging_enabled = p_enabled; + if (!dragging_enabled && dragging_area_control->dragging) { + dragging_area_control->dragging = false; + // queue_redraw() is called by _resort(). + emit_signal(SNAME("drag_ended")); + } + if (get_viewport()) { + get_viewport()->update_mouse_cursor_state(); + } + _resort(); +} + +bool SplitContainer::is_dragging_enabled() const { + return dragging_enabled; +} + Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const { Vector<int> flags; flags.append(SIZE_FILL); @@ -392,6 +406,51 @@ Vector<int> SplitContainer::get_allowed_size_flags_vertical() const { return flags; } +void SplitContainer::set_drag_area_margin_begin(int p_margin) { + if (drag_area_margin_begin == p_margin) { + return; + } + drag_area_margin_begin = p_margin; + queue_sort(); +} + +int SplitContainer::get_drag_area_margin_begin() const { + return drag_area_margin_begin; +} + +void SplitContainer::set_drag_area_margin_end(int p_margin) { + if (drag_area_margin_end == p_margin) { + return; + } + drag_area_margin_end = p_margin; + queue_sort(); +} + +int SplitContainer::get_drag_area_margin_end() const { + return drag_area_margin_end; +} + +void SplitContainer::set_drag_area_offset(int p_offset) { + if (drag_area_offset == p_offset) { + return; + } + drag_area_offset = p_offset; + queue_sort(); +} + +int SplitContainer::get_drag_area_offset() const { + return drag_area_offset; +} + +void SplitContainer::set_show_drag_area_enabled(bool p_enabled) { + show_drag_area = p_enabled; + dragging_area_control->queue_redraw(); +} + +bool SplitContainer::is_show_drag_area_enabled() const { + return show_drag_area; +} + void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); @@ -406,13 +465,39 @@ void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical); + ClassDB::bind_method(D_METHOD("set_dragging_enabled", "dragging_enabled"), &SplitContainer::set_dragging_enabled); + ClassDB::bind_method(D_METHOD("is_dragging_enabled"), &SplitContainer::is_dragging_enabled); + + ClassDB::bind_method(D_METHOD("set_drag_area_margin_begin", "margin"), &SplitContainer::set_drag_area_margin_begin); + ClassDB::bind_method(D_METHOD("get_drag_area_margin_begin"), &SplitContainer::get_drag_area_margin_begin); + + ClassDB::bind_method(D_METHOD("set_drag_area_margin_end", "margin"), &SplitContainer::set_drag_area_margin_end); + ClassDB::bind_method(D_METHOD("get_drag_area_margin_end"), &SplitContainer::get_drag_area_margin_end); + + ClassDB::bind_method(D_METHOD("set_drag_area_offset", "offset"), &SplitContainer::set_drag_area_offset); + ClassDB::bind_method(D_METHOD("get_drag_area_offset"), &SplitContainer::get_drag_area_offset); + + ClassDB::bind_method(D_METHOD("set_drag_area_highlight_in_editor", "drag_area_highlight_in_editor"), &SplitContainer::set_show_drag_area_enabled); + ClassDB::bind_method(D_METHOD("is_drag_area_highlight_in_editor_enabled"), &SplitContainer::is_show_drag_area_enabled); + + ClassDB::bind_method(D_METHOD("get_drag_area_control"), &SplitContainer::get_drag_area_control); + ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset"))); + ADD_SIGNAL(MethodInfo("drag_started")); + ADD_SIGNAL(MethodInfo("drag_ended")); ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dragging_enabled"), "set_dragging_enabled", "is_dragging_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); + ADD_GROUP("Drag Area", "drag_area_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_begin", "get_drag_area_margin_begin"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_end", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_end", "get_drag_area_margin_end"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_offset", "get_drag_area_offset"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_area_highlight_in_editor"), "set_drag_area_highlight_in_editor", "is_drag_area_highlight_in_editor_enabled"); + BIND_ENUM_CONSTANT(DRAGGER_VISIBLE); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED); @@ -423,6 +508,7 @@ void SplitContainer::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon, "grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SplitContainer, split_bar_background, "split_bar_background"); } SplitContainer::SplitContainer(bool p_vertical) { diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index db870554c2..2bba96b4b8 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -35,6 +35,8 @@ class SplitContainerDragger : public Control { GDCLASS(SplitContainerDragger, Control); + friend class SplitContainer; + Rect2 split_bar_rect; protected: void _notification(int p_what); @@ -62,11 +64,16 @@ public: }; private: + int show_drag_area = false; + int drag_area_margin_begin = 0; + int drag_area_margin_end = 0; + int drag_area_offset = 0; int split_offset = 0; - int middle_sep = 0; + int computed_split_offset = 0; bool vertical = false; bool collapsed = false; DraggerVisibility dragger_visibility = DRAGGER_VISIBLE; + bool dragging_enabled = true; SplitContainerDragger *dragging_area_control = nullptr; @@ -77,10 +84,13 @@ private: Ref<Texture2D> grabber_icon; Ref<Texture2D> grabber_icon_h; Ref<Texture2D> grabber_icon_v; + float base_scale = 1.0; + Ref<StyleBox> split_bar_background; } theme_cache; Ref<Texture2D> _get_grabber_icon() const; - void _compute_middle_sep(bool p_clamp); + void _compute_split_offset(bool p_clamp); + int _get_separation() const; void _resort(); Control *_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const; @@ -105,11 +115,28 @@ public: void set_vertical(bool p_vertical); bool is_vertical() const; + void set_dragging_enabled(bool p_enabled); + bool is_dragging_enabled() const; + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; + void set_drag_area_margin_begin(int p_margin); + int get_drag_area_margin_begin() const; + + void set_drag_area_margin_end(int p_margin); + int get_drag_area_margin_end() const; + + void set_drag_area_offset(int p_offset); + int get_drag_area_offset() const; + + void set_show_drag_area_enabled(bool p_enabled); + bool is_show_drag_area_enabled() const; + + Control *get_drag_area_control() { return dragging_area_control; } + SplitContainer(bool p_vertical = false); }; diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index c715aceb0b..a443ae9abf 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -259,7 +259,7 @@ void SubViewportContainer::remove_child_notify(Node *p_child) { } PackedStringArray SubViewportContainer::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Container::get_configuration_warnings(); bool has_viewport = false; for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 1ae18f5728..3e0d6adf42 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -620,6 +620,8 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in } cb->draw(ci, Point2i(cb_rect.position.x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); + } else { + tabs.write[p_index].cb_rect = Rect2(); } } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 3f1b9fc981..d7799588ea 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -38,7 +38,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/string_builder.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "scene/gui/label.h" #include "scene/main/window.h" #include "scene/theme/theme_db.h" @@ -112,8 +112,34 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } +int TextEdit::Text::get_max_width() const { + if (max_line_width_dirty) { + int new_max_line_width = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_width = MAX(new_max_line_width, l.width); + } + max_line_width = new_max_line_width; + } + + return max_line_width; +} + int TextEdit::Text::get_line_height() const { - return line_height; + if (max_line_height_dirty) { + int new_max_line_height = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_height = MAX(new_max_line_height, l.height); + } + max_line_height = new_max_line_height; + } + + return max_line_height; } void TextEdit::Text::set_width(float p_width) { @@ -135,15 +161,17 @@ BitField<TextServer::LineBreakFlag> TextEdit::Text::get_brk_flags() const { int TextEdit::Text::get_line_wrap_amount(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); - return text[p_line].data_buf->get_line_count() - 1; + return text[p_line].line_count - 1; } Vector<Vector2i> TextEdit::Text::get_line_wrap_ranges(int p_line) const { Vector<Vector2i> ret; ERR_FAIL_INDEX_V(p_line, text.size(), ret); - for (int i = 0; i < text[p_line].data_buf->get_line_count(); i++) { - ret.push_back(text[p_line].data_buf->get_line_range(i)); + Ref<TextParagraph> data_buf = text[p_line].data_buf; + int line_count = data_buf->get_line_count(); + for (int i = 0; i < line_count; i++) { + ret.push_back(data_buf->get_line_range(i)); } return ret; } @@ -153,40 +181,11 @@ const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const { return text[p_line].data_buf; } -_FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { +_FORCE_INLINE_ String TextEdit::Text::operator[](int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), ""); return text[p_line].data; } -void TextEdit::Text::_calculate_line_height() { - int height = 0; - for (const Line &l : text) { - // Found another line with the same height...nothing to update. - if (l.height == line_height) { - height = line_height; - break; - } - height = MAX(height, l.height); - } - line_height = height; -} - -void TextEdit::Text::_calculate_max_line_width() { - int line_width = 0; - for (const Line &l : text) { - if (l.hidden) { - continue; - } - - // Found another line with the same width...nothing to update. - if (l.width == max_width) { - line_width = max_width; - break; - } - line_width = MAX(line_width, l.width); - } - max_width = line_width; -} - void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -194,8 +193,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan return; // Not in tree? } + Line &text_line = text.write[p_line]; if (p_text_changed) { - text.write[p_line].data_buf->clear(); + text_line.data_buf->clear(); } BitField<TextServer::LineBreakFlag> flags = brk_flags; @@ -203,30 +203,30 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan flags.set_flag(TextServer::BREAK_TRIM_INDENT); } - text.write[p_line].data_buf->set_width(width); - text.write[p_line].data_buf->set_direction((TextServer::Direction)direction); - text.write[p_line].data_buf->set_break_flags(flags); - text.write[p_line].data_buf->set_preserve_control(draw_control_chars); - text.write[p_line].data_buf->set_custom_punctuation(get_enabled_word_separators()); + text_line.data_buf->set_width(width); + text_line.data_buf->set_direction((TextServer::Direction)direction); + text_line.data_buf->set_break_flags(flags); + text_line.data_buf->set_preserve_control(draw_control_chars); + text_line.data_buf->set_custom_punctuation(get_enabled_word_separators()); if (p_ime_text.length() > 0) { if (p_text_changed) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language); + text_line.data_buf->add_string(p_ime_text, font, font_size, language); } if (!p_bidi_override.is_empty()) { - TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), p_bidi_override); } } else { if (p_text_changed) { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language); + text_line.data_buf->add_string(text_line.data, font, font_size, language); } - if (!text[p_line].bidi_override.is_empty()) { - TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override); + if (!text_line.bidi_override.is_empty()) { + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), text_line.bidi_override); } } if (!p_text_changed) { - RID r = text.write[p_line].data_buf->get_rid(); + RID r = text_line.data_buf->get_rid(); int spans = TS->shaped_get_span_count(r); for (int i = 0; i < spans; i++) { TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features()); @@ -237,61 +237,58 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[p_line].data_buf->tab_align(tabs); + text_line.data_buf->tab_align(tabs); + } + + // Update wrap amount. + const int old_line_count = text_line.line_count; + text_line.line_count = text_line.data_buf->get_line_count(); + if (!text_line.hidden && text_line.line_count != old_line_count) { + total_visible_line_count += text_line.line_count - old_line_count; } // Update height. - const int old_height = text.write[p_line].height; - const int wrap_amount = get_line_wrap_amount(p_line); - int height = font_height; - for (int i = 0; i <= wrap_amount; i++) { - height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + const int old_height = text_line.height; + text_line.height = font_height; + for (int i = 0; i < text_line.line_count; i++) { + text_line.height = MAX(text_line.height, text_line.data_buf->get_line_size(i).y); } - text.write[p_line].height = height; - // If this line has shrunk, this may no longer the tallest line. - if (old_height == line_height && height < line_height) { - _calculate_line_height(); - } else { - line_height = MAX(height, line_height); + // If this line has shrunk, this may no longer be the tallest line. + if (!text_line.hidden) { + if (old_height == max_line_height && text_line.height < old_height) { + max_line_height_dirty = true; + } else { + max_line_height = MAX(text_line.height, max_line_height); + } } // Update width. - const int old_width = text.write[p_line].width; - int line_width = get_line_width(p_line); - text.write[p_line].width = line_width; + const int old_width = text_line.width; + text_line.width = get_line_width(p_line); - // If this line has shrunk, this may no longer the longest line. - if (old_width == max_width && line_width < max_width) { - _calculate_max_line_width(); - } else if (!is_hidden(p_line)) { - max_width = MAX(line_width, max_width); + if (!text_line.hidden) { + // If this line has shrunk, this may no longer be the longest line. + if (old_width == max_line_width && text_line.width < old_width) { + max_line_width_dirty = true; + } else { + max_line_width = MAX(text_line.width, max_line_width); + } } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { - BitField<TextServer::LineBreakFlag> flags = brk_flags; - if (indent_wrapped_lines) { - flags.set_flag(TextServer::BREAK_TRIM_INDENT); - } - text.write[i].data_buf->set_width(width); - text.write[i].data_buf->set_break_flags(flags); - text.write[i].data_buf->set_custom_punctuation(get_enabled_word_separators()); - if (tab_size_dirty) { if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + text[i].data_buf->tab_align(tabs); } } - text.write[i].width = get_line_width(i); + invalidate_cache(i, -1, false); } tab_size_dirty = false; - - max_width = -1; - _calculate_max_line_width(); } void TextEdit::Text::invalidate_font() { @@ -299,8 +296,8 @@ void TextEdit::Text::invalidate_font() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -317,8 +314,8 @@ void TextEdit::Text::invalidate_all() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -333,8 +330,8 @@ void TextEdit::Text::invalidate_all() { void TextEdit::Text::clear() { text.clear(); - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; Line line; line.gutters.resize(gutter_count); @@ -343,8 +340,8 @@ void TextEdit::Text::clear() { invalidate_cache(0, -1, true); } -int TextEdit::Text::get_max_width() const { - return max_width; +int TextEdit::Text::get_total_visible_line_count() const { + return total_visible_line_count; } void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_override) { @@ -355,7 +352,37 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o invalidate_cache(p_line, -1, true); } +void TextEdit::Text::set_hidden(int p_line, bool p_hidden) { + ERR_FAIL_INDEX(p_line, text.size()); + + Line &text_line = text.write[p_line]; + if (text_line.hidden == p_hidden) { + return; + } + text_line.hidden = p_hidden; + if (p_hidden) { + total_visible_line_count -= text_line.line_count; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; + } + if (text_line.height == max_line_height) { + max_line_height_dirty = true; + } + } else { + total_visible_line_count += text_line.line_count; + max_line_width = MAX(text_line.width, max_line_width); + max_line_height = MAX(text_line.height, max_line_height); + } +} + +bool TextEdit::Text::is_hidden(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), true); + return text[p_line].hidden; +} + void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) { + ERR_FAIL_INDEX(p_at, text.size() + 1); + int new_line_count = p_text.size() - 1; if (new_line_count > 0) { text.resize(text.size() + new_line_count); @@ -382,24 +409,25 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector } void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { + p_from_line = MAX(p_from_line, 0); + ERR_FAIL_INDEX(p_from_line, text.size()); + + p_to_line = MIN(p_to_line, text.size()); + ERR_FAIL_COND(p_to_line < p_from_line); + if (p_from_line == p_to_line) { return; } - bool dirty_height = false; - bool dirty_width = false; for (int i = p_from_line; i < p_to_line; i++) { - if (!dirty_height && text[i].height == line_height) { - dirty_height = true; - } - - if (!dirty_width && text[i].width == max_width) { - dirty_width = true; + const Line &text_line = text[i]; + if (text_line.height == max_line_height) { + max_line_height_dirty = true; } - - if (dirty_height && dirty_width) { - break; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; } + total_visible_line_count -= text_line.line_count; } int diff = (p_to_line - p_from_line); @@ -407,16 +435,6 @@ void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { text.write[(i - diff) + 1] = text[i + 1]; } text.resize(text.size() - diff); - - if (dirty_height) { - line_height = -1; - _calculate_line_height(); - } - - if (dirty_width) { - max_width = -1; - _calculate_max_line_width(); - } } void TextEdit::Text::add_gutter(int p_at) { @@ -431,6 +449,8 @@ void TextEdit::Text::add_gutter(int p_at) { } void TextEdit::Text::remove_gutter(int p_gutter) { + ERR_FAIL_INDEX(p_gutter, text.size()); + for (int i = 0; i < text.size(); i++) { text.write[i].gutters.remove_at(p_gutter); } @@ -438,6 +458,9 @@ void TextEdit::Text::remove_gutter(int p_gutter) { } void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_to_line, text.size()); + text.write[p_to_line].gutters = text[p_from_line].gutters; text.write[p_from_line].gutters.clear(); text.write[p_from_line].gutters.resize(gutter_count); @@ -625,6 +648,8 @@ void TextEdit::_notification(int p_what) { brace_matching.resize(get_caret_count()); for (int caret = 0; caret < get_caret_count(); caret++) { + BraceMatchingData &brace_match = brace_matching.write[caret]; + if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) { continue; } @@ -678,20 +703,20 @@ void TextEdit::_notification(int p_what) { } if (stack == 0) { - brace_matching.write[caret].open_match_line = i; - brace_matching.write[caret].open_match_column = j; - brace_matching.write[caret].open_matching = true; + brace_match.open_match_line = i; + brace_match.open_match_column = j; + brace_match.open_matching = true; break; } } - if (brace_matching.write[caret].open_match_line != -1) { + if (brace_match.open_match_line != -1) { break; } } - if (!brace_matching.write[caret].open_matching) { - brace_matching.write[caret].open_mismatch = true; + if (!brace_match.open_matching) { + brace_match.open_mismatch = true; } } } @@ -744,27 +769,27 @@ void TextEdit::_notification(int p_what) { } if (stack == 0) { - brace_matching.write[caret].close_match_line = i; - brace_matching.write[caret].close_match_column = j; - brace_matching.write[caret].close_matching = true; + brace_match.close_match_line = i; + brace_match.close_match_column = j; + brace_match.close_matching = true; break; } } - if (brace_matching.write[caret].close_match_line != -1) { + if (brace_match.close_match_line != -1) { break; } } - if (!brace_matching.write[caret].close_matching) { - brace_matching.write[caret].close_mismatch = true; + if (!brace_match.close_matching) { + brace_match.close_mismatch = true; } } } } } - bool draw_placeholder = text.size() == 1 && text[0].is_empty() && ime_text.is_empty(); + bool draw_placeholder = _using_placeholder(); // Get the highlighted words. String highlighted_text = get_selected_text(0); @@ -772,13 +797,14 @@ void TextEdit::_notification(int p_what) { // Check if highlighted words contain only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); - HashMap<int, HashSet<int>> caret_line_wrap_index_map; + Vector<Pair<int, int>> highlighted_lines; + highlighted_lines.resize(carets.size()); Vector<int> carets_wrap_index; carets_wrap_index.resize(carets.size()); for (int i = 0; i < carets.size(); i++) { carets.write[i].visible = false; int wrap_index = get_caret_wrap_index(i); - caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index); + highlighted_lines.write[i] = Pair<int, int>(get_caret_line(i), wrap_index); carets_wrap_index.write[i] = wrap_index; } @@ -842,7 +868,7 @@ void TextEdit::_notification(int p_what) { break; } - Dictionary color_map = _get_line_syntax_highlighting(minimap_line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(minimap_line); Color line_background_color = text.get_line_background_color(minimap_line); @@ -853,12 +879,9 @@ void TextEdit::_notification(int p_what) { line_background_color.a *= 0.6; } - Color current_color = theme_cache.font_color; - if (!editable) { - current_color = theme_cache.font_readonly_color; - } + Color current_color = editable ? theme_cache.font_color : theme_cache.font_readonly_color; - Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); + const Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); int line_wrap_amount = get_line_wrap_count(minimap_line); int last_wrap_column = 0; @@ -881,13 +904,13 @@ void TextEdit::_notification(int p_what) { last_wrap_column += wrap_rows[line_wrap_index - 1].length(); } - if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) { + if (highlight_current_line && highlighted_lines.has(Pair<int, int>(minimap_line, line_wrap_index))) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), theme_cache.current_line_color); } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), theme_cache.current_line_color); } - } else if (line_background_color != Color(0, 0, 0, 0)) { + } else if (line_background_color.a > 0) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), line_background_color); } else { @@ -905,13 +928,17 @@ void TextEdit::_notification(int p_what) { // 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; + + for (const Pair<int64_t, Color> &color_data : color_map) { + if (last_wrap_column + next_char_index >= color_data.first) { + next_color = color_data.second; + if (!editable) { + next_color.a = theme_cache.font_readonly_color.a; + } + next_color.a *= 0.6; + } else { + break; } - next_color.a *= 0.6; } if (characters == 0) { current_color = next_color; @@ -1002,7 +1029,7 @@ void TextEdit::_notification(int p_what) { LineDrawingCache cache_entry; - Dictionary color_map = _get_line_syntax_highlighting(line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(line); // Ensure we at least use the font color. Color current_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color; @@ -1012,7 +1039,7 @@ void TextEdit::_notification(int p_what) { const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line); - Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); + const Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(line); for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) { @@ -1053,20 +1080,20 @@ void TextEdit::_notification(int p_what) { break; } - if (text.get_line_background_color(line) != Color(0, 0, 0, 0)) { + if (text.get_line_background_color(line).a > 0.0) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } } // 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 (highlight_current_line && highlighted_lines.has(Pair<int, int>(line, 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); + RS::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); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } } @@ -1077,7 +1104,7 @@ void TextEdit::_notification(int p_what) { int gutter_offset = theme_cache.style_normal->get_margin(SIDE_LEFT); for (int g = 0; g < gutters.size(); g++) { - const GutterInfo gutter = gutters[g]; + const GutterInfo &gutter = gutters[g]; if (!gutter.draw || gutter.width <= 0) { continue; @@ -1184,7 +1211,7 @@ void TextEdit::_notification(int p_what) { if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.selection_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.selection_color); } } } @@ -1194,7 +1221,7 @@ void TextEdit::_notification(int p_what) { int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); int search_text_len = search_text.length(); while (search_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1206,7 +1233,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.search_result_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.search_result_color); draw_rect(rect, theme_cache.search_result_border_color, false); } @@ -1218,7 +1245,7 @@ void TextEdit::_notification(int p_what) { int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int highlighted_text_len = highlighted_text.length(); while (highlighted_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1230,7 +1257,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.word_highlighted_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.word_highlighted_color); } highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len); @@ -1243,7 +1270,7 @@ void TextEdit::_notification(int p_what) { 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) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (theme_cache.line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1257,7 +1284,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, highlight_underline_color); + RS::get_singleton()->canvas_item_add_rect(ci, 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); @@ -1293,21 +1320,16 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - int64_t color_start = -1; - for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key)) { - if (int64_t(*key) <= glyphs[j].start) { - color_start = *key; + for (const Pair<int64_t, Color> &color_data : color_map) { + if (color_data.first <= glyphs[j].start) { + current_color = color_data.second; + if (!editable && current_color.a > theme_cache.font_readonly_color.a) { + current_color.a = theme_cache.font_readonly_color.a; + } } else { break; } } - const Variant *color_data = (color_start >= 0) ? color_map.getptr(color_start) : nullptr; - 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) { - current_color.a = theme_cache.font_readonly_color.a; - } - } Color gl_color = current_color; for (int c = 0; c < get_caret_count(); c++) { @@ -1325,22 +1347,23 @@ void TextEdit::_notification(int p_what) { if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { 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) { + const BraceMatchingData &brace_match = brace_matching[c]; + if ((brace_match.open_match_line == line && brace_match.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_match.open_matching || brace_match.open_mismatch))) { + if (brace_match.open_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color); } - if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) || - (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) { - if (brace_matching[c].close_mismatch) { + if ((brace_match.close_match_line == line && brace_match.close_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_match.close_matching || brace_match.close_mismatch))) { + if (brace_match.close_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color); } } } @@ -1451,11 +1474,11 @@ void TextEdit::_notification(int p_what) { // Draw split caret (leading part). ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } } else { // End of the line. if (gl_size > 0) { @@ -1488,28 +1511,28 @@ void TextEdit::_notification(int p_what) { // Draw extra marker on top of mid caret. Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.t_caret.size.x = caret_width; - draw_rect(ts_caret.t_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.t_caret, theme_cache.caret_color); } } } @@ -1517,7 +1540,7 @@ void TextEdit::_notification(int p_what) { if (!ime_text.is_empty()) { { // IME Intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1534,9 +1557,9 @@ void TextEdit::_notification(int p_what) { carets.write[c].draw_pos.x = rect.position.x; } } - { + if (ime_selection.y > 0) { // IME caret. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1575,26 +1598,7 @@ void TextEdit::_notification(int p_what) { draw_caret = true; } - _update_ime_window_position(); - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - int caret_start = -1; - int caret_end = -1; - - if (!has_selection(0)) { - String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); - - caret_start = full_text.length(); - } else { - String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); - String post_text = get_selected_text(0); - - caret_start = pre_text.length(); - caret_end = caret_start + post_text.length(); - } - - DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); - } + _show_virtual_keyboard(); } break; case NOTIFICATION_FOCUS_EXIT: { @@ -1684,39 +1688,93 @@ void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) { bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { - if (!k->is_pressed()) { - if (alt_start && k->get_keycode() == Key::ALT) { - alt_start = false; - if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { - handle_unicode_input(alt_code); - } - return true; + // Start Unicode input (hold). + if (k->is_alt_pressed() && k->get_keycode() == Key::KP_ADD && !alt_start && !alt_start_no_hold) { + if (has_selection()) { + delete_selection(); } - return false; + alt_start = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _update_ime_text(); + return true; } - if (k->is_alt_pressed()) { - if (!alt_start) { - if (k->get_keycode() == Key::KP_ADD) { - alt_start = true; - alt_code = 0; - return true; - } + // Start Unicode input (press). + if (k->is_action("ui_unicode_start", true) && !alt_start && !alt_start_no_hold) { + if (has_selection()) { + delete_selection(); + } + alt_start_no_hold = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _update_ime_text(); + return true; + } + + // Update Unicode input. + if (k->is_pressed() && ((k->is_alt_pressed() && alt_start) || alt_start_no_hold)) { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } else if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } else if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } else if ((Key)k->get_unicode() >= Key::KEY_0 && (Key)k->get_unicode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::KEY_0); + } else if ((Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::A) + 10; + } else if (k->get_physical_keycode() >= Key::KEY_0 && k->get_physical_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_physical_keycode() - Key::KEY_0); + } + if (k->get_keycode() == Key::BACKSPACE) { + alt_code = alt_code >> 4; + } + if (alt_code > 0x10ffff) { + alt_code = 0x10ffff; + } + if (alt_code > 0) { + ime_text = vformat("u%s", String::num_int64(alt_code, 16, true)); } else { - if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); - } - if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); - } - if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; - } - return true; + ime_text = "u"; } + ime_selection = Vector2i(0, -1); + _update_ime_text(); + return true; + } + + // Submit Unicode input. + if ((!k->is_pressed() && alt_start && k->get_keycode() == Key::ALT) || (alt_start_no_hold && (k->is_action("ui_text_submit", true) || k->is_action("ui_accept", true)))) { + alt_start = false; + alt_start_no_hold = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + ime_text = String(); + ime_selection = Vector2i(); + handle_unicode_input(alt_code); + } else { + ime_text = String(); + ime_selection = Vector2i(); + } + _update_ime_text(); + return true; + } + + // Cancel Unicode input. + if (alt_start_no_hold && k->is_action("ui_cancel", true)) { + alt_start = false; + alt_start_no_hold = false; + ime_text = String(); + ime_selection = Vector2i(); + _update_ime_text(); + return true; } } return false; @@ -1943,8 +2001,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - // Notify to show soft keyboard. - notification(NOTIFICATION_FOCUS_ENTER); + _show_virtual_keyboard(); } } @@ -2393,7 +2450,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); if (words.is_empty() || cc <= words[0]) { - // This solves the scenario where there are no words but glyfs that can be ignored. + // Move to the start when there are no more words. cc = 0; } else { for (int j = words.size() - 2; j >= 0; j = j - 2) { @@ -2450,7 +2507,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); if (words.is_empty() || cc >= words[words.size() - 1]) { - // This solves the scenario where there are no words but glyfs that can be ignored. + // Move to the end when there are no more words. cc = text[get_caret_line(i)].length(); } else { for (int j = 1; j < words.size(); j = j + 2) { @@ -2666,7 +2723,7 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { // 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_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. + // Delete to the start when there are no more words. column = 0; } else { // Otherwise search for the first word break that is smaller than the index from we're currently deleting. @@ -2731,10 +2788,15 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { 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) { - if (words[j] > column) { - column = words[j]; - break; + if (words.is_empty() || column >= words[words.size() - 1]) { + // Delete to the end when there are no more words. + column = text[get_caret_line(i)].length(); + } else { + for (int j = 1; j < words.size(); j = j + 2) { + if (words[j] > column) { + column = words[j]; + break; + } } } @@ -2849,6 +2911,10 @@ void TextEdit::_update_placeholder() { } } +bool TextEdit::_using_placeholder() const { + return text.size() == 1 && text[0].is_empty() && ime_text.is_empty(); +} + void TextEdit::_update_theme_item_cache() { Control::_update_theme_item_cache(); @@ -2920,11 +2986,37 @@ void TextEdit::_update_ime_text() { queue_redraw(); } +void TextEdit::_show_virtual_keyboard() { + _update_ime_window_position(); + + if (virtual_keyboard_enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) { + int caret_start = -1; + int caret_end = -1; + + if (!has_selection(0)) { + String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); + + caret_start = full_text.length(); + } else { + String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); + String post_text = get_selected_text(0); + + caret_start = pre_text.length(); + caret_end = caret_start + post_text.length(); + } + + DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); + } +} + /* General overrides. */ Size2 TextEdit::get_minimum_size() const { Size2 size = theme_cache.style_normal->get_minimum_size(); if (fit_content_height) { - size.y += content_height_cache; + size.y += content_size_cache.y; + } + if (fit_content_width) { + size.x += content_size_cache.x; } return size; } @@ -2956,13 +3048,13 @@ bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const return drop_override; } - return is_editable() && p_data.get_type() == Variant::STRING; + return is_editable() && p_data.is_string(); } void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); - if (p_data.get_type() == Variant::STRING && is_editable()) { + if (p_data.is_string() && is_editable()) { Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); int drop_at_line = pos.y; int drop_at_column = pos.x; @@ -3079,7 +3171,9 @@ void TextEdit::cancel_ime() { return; } ime_text = String(); - ime_selection = Point2(); + ime_selection = Vector2i(); + alt_start = false; + alt_start_no_hold = false; _close_ime_window(); _update_ime_text(); } @@ -3088,13 +3182,21 @@ void TextEdit::apply_ime() { if (!has_ime_text()) { return; } + // Force apply the current IME text. - String insert_ime_text = ime_text; - cancel_ime(); - insert_text_at_caret(insert_ime_text); + if (alt_start || alt_start_no_hold) { + cancel_ime(); + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + handle_unicode_input(alt_code); + } + } else { + String insert_ime_text = ime_text; + cancel_ime(); + insert_text_at_caret(insert_ime_text); + } } -void TextEdit::set_editable(const bool p_editable) { +void TextEdit::set_editable(bool p_editable) { if (editable == p_editable) { return; } @@ -3219,7 +3321,7 @@ bool TextEdit::is_indent_wrapped_lines() const { } // User controls -void TextEdit::set_overtype_mode_enabled(const bool p_enabled) { +void TextEdit::set_overtype_mode_enabled(bool p_enabled) { if (overtype_mode == p_enabled) { return; } @@ -4324,25 +4426,12 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ return Point2i(text[row].length(), row); } - 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. - Vector<String> rows2 = get_line_wrapped_text(row); - int row_end_col = 0; - for (int i = 0; i < wrap_index + 1; i++) { - row_end_col += rows2[i].length(); - } - if (col >= row_end_col) { - col -= 1; - } - } RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); float wrap_indent = (text.is_indent_wrapped_lines() && wrap_index > 0) ? get_indent_level(row) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width : 0.0; @@ -4351,7 +4440,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ } else { colx -= wrap_indent; } - col = TS->shaped_text_hit_test_position(text_rid, colx); + int col = TS->shaped_text_hit_test_position(text_rid, colx); if (!caret_mid_grapheme_enabled) { col = TS->shaped_text_closest_character_pos(text_rid, col); } @@ -4482,7 +4571,7 @@ TextEdit::CaretType TextEdit::get_caret_type() const { return caret_type; } -void TextEdit::set_caret_blink_enabled(const bool p_enabled) { +void TextEdit::set_caret_blink_enabled(bool p_enabled) { if (caret_blink_enabled == p_enabled) { return; } @@ -4524,7 +4613,7 @@ bool TextEdit::is_drawing_caret_when_editable_disabled() const { return draw_caret_when_editable_disabled; } -void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enabled) { +void TextEdit::set_move_caret_on_right_click_enabled(bool p_enabled) { move_caret_on_right_click = p_enabled; } @@ -4532,7 +4621,7 @@ bool TextEdit::is_move_caret_on_right_click_enabled() const { return move_caret_on_right_click; } -void TextEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) { +void TextEdit::set_caret_mid_grapheme_enabled(bool p_enabled) { caret_mid_grapheme_enabled = p_enabled; } @@ -4642,7 +4731,7 @@ void TextEdit::add_caret_at_carets(bool p_below) { 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; + 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); @@ -5107,7 +5196,7 @@ String TextEdit::get_word_under_caret(int p_caret) const { } /* Selection. */ -void TextEdit::set_selecting_enabled(const bool p_enabled) { +void TextEdit::set_selecting_enabled(bool p_enabled) { if (selecting_enabled == p_enabled) { return; } @@ -5123,7 +5212,7 @@ bool TextEdit::is_selecting_enabled() const { return selecting_enabled; } -void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) { +void TextEdit::set_deselect_on_focus_loss_enabled(bool p_enabled) { if (deselect_on_focus_loss_enabled == p_enabled) { return; } @@ -5138,7 +5227,7 @@ bool TextEdit::is_deselect_on_focus_loss_enabled() const { return deselect_on_focus_loss_enabled; } -void TextEdit::set_drag_and_drop_selection_enabled(const bool p_enabled) { +void TextEdit::set_drag_and_drop_selection_enabled(bool p_enabled) { drag_and_drop_selection_enabled = p_enabled; } @@ -5698,7 +5787,7 @@ Vector<String> TextEdit::get_line_wrapped_text(int p_line) const { /* Viewport */ // Scrolling. -void TextEdit::set_smooth_scroll_enabled(const bool p_enabled) { +void TextEdit::set_smooth_scroll_enabled(bool p_enabled) { v_scroll->set_smooth_scroll_enabled(p_enabled); smooth_scroll_enabled = p_enabled; } @@ -5707,7 +5796,7 @@ bool TextEdit::is_smooth_scroll_enabled() const { return smooth_scroll_enabled; } -void TextEdit::set_scroll_past_end_of_file_enabled(const bool p_enabled) { +void TextEdit::set_scroll_past_end_of_file_enabled(bool p_enabled) { if (scroll_past_end_of_file_enabled == p_enabled) { return; } @@ -5761,7 +5850,7 @@ float TextEdit::get_v_scroll_speed() const { return v_scroll_speed; } -void TextEdit::set_fit_content_height_enabled(const bool p_enabled) { +void TextEdit::set_fit_content_height_enabled(bool p_enabled) { if (fit_content_height == p_enabled) { return; } @@ -5773,6 +5862,18 @@ bool TextEdit::is_fit_content_height_enabled() const { return fit_content_height; } +void TextEdit::set_fit_content_width_enabled(bool p_enabled) { + if (fit_content_width == p_enabled) { + return; + } + fit_content_width = p_enabled; + update_minimum_size(); +} + +bool TextEdit::is_fit_content_width_enabled() const { + return fit_content_width; +} + double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); ERR_FAIL_COND_V(p_wrap_index < 0, 0); @@ -5877,7 +5978,7 @@ int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) co } int TextEdit::get_total_visible_line_count() const { - return get_visible_line_count_in_range(0, text.size() - 1); + return text.get_total_visible_line_count(); } // Auto adjust. @@ -5929,7 +6030,7 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) { // Get position of the end of caret. if (has_ime_text()) { - if (ime_selection.y != 0) { + if (ime_selection.y > 0) { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); @@ -5981,7 +6082,7 @@ void TextEdit::center_viewport_to_caret(int p_caret) { // Get position of the end of caret. if (has_ime_text()) { - if (ime_selection.y != 0) { + if (ime_selection.y > 0) { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); @@ -6326,7 +6427,7 @@ bool TextEdit::is_highlight_current_line_enabled() const { return highlight_current_line; } -void TextEdit::set_highlight_all_occurrences(const bool p_enabled) { +void TextEdit::set_highlight_all_occurrences(bool p_enabled) { if (highlight_all_occurrences == p_enabled) { return; } @@ -6744,6 +6845,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled", "enabled"), &TextEdit::set_fit_content_height_enabled); ClassDB::bind_method(D_METHOD("is_fit_content_height_enabled"), &TextEdit::is_fit_content_height_enabled); + ClassDB::bind_method(D_METHOD("set_fit_content_width_enabled", "enabled"), &TextEdit::set_fit_content_width_enabled); + ClassDB::bind_method(D_METHOD("is_fit_content_width_enabled"), &TextEdit::is_fit_content_width_enabled); + ClassDB::bind_method(D_METHOD("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0)); // Visible lines. @@ -6863,11 +6967,12 @@ void TextEdit::_bind_methods() { ADD_GROUP("Scroll", "scroll_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_v_scroll_speed", "get_v_scroll_speed"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:lines/s"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_past_end_of_file"), "set_scroll_past_end_of_file_enabled", "is_scroll_past_end_of_file_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:lines"), "set_v_scroll", "get_v_scroll"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_height"), "set_fit_content_height_enabled", "is_fit_content_height_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_width"), "set_fit_content_width_enabled", "is_fit_content_width_enabled"); ADD_GROUP("Minimap", "minimap_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "set_draw_minimap", "is_drawing_minimap"); @@ -7527,7 +7632,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column int row = 0; Vector<Vector2i> rows2 = text.get_line_wrap_ranges(p_line); for (int i = 0; i < rows2.size(); i++) { - if ((p_char >= rows2[i].x) && (p_char <= rows2[i].y)) { + if ((p_char >= rows2[i].x) && (p_char < rows2[i].y || (i == rows2.size() - 1 && p_char == rows2[i].y))) { row = i; break; } @@ -7840,10 +7945,10 @@ void TextEdit::_update_scrollbars() { h_scroll->set_begin(Point2(0, size.height - hmin.height)); h_scroll->set_end(Point2(size.width - vmin.width, size.height)); - bool draw_placeholder = text.size() == 1 && text[0].length() == 0; + bool draw_placeholder = _using_placeholder(); int visible_rows = get_visible_line_count(); - int total_rows = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_total_visible_line_count(); + int total_rows = draw_placeholder ? placeholder_wraped_rows.size() : get_total_visible_line_count(); if (scroll_past_end_of_file_enabled && !fit_content_height) { total_rows += visible_rows - 1; } @@ -7855,8 +7960,8 @@ void TextEdit::_update_scrollbars() { total_width += minimap_width; } - content_height_cache = MAX(total_rows, 1) * get_line_height(); - if (fit_content_height) { + content_size_cache = Vector2i(total_width + 10, MAX(total_rows, 1) * get_line_height()); + if (fit_content_height || fit_content_width) { update_minimum_size(); } @@ -7921,7 +8026,7 @@ void TextEdit::_scroll_moved(double p_to_val) { } if (v_scroll->is_visible_in_tree()) { // Set line ofs and wrap ofs. - bool draw_placeholder = text.size() == 1 && text[0].length() == 0; + bool draw_placeholder = _using_placeholder(); int v_scroll_i = floor(get_v_scroll()); int sc = 0; @@ -8063,7 +8168,7 @@ void TextEdit::_update_minimap_hover() { const Point2 mp = get_local_mouse_pos(); const int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT); - const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; + bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; if (!hovering_sidebar) { if (hovering_minimap) { // Only redraw if the hovering status changed. @@ -8077,7 +8182,7 @@ void TextEdit::_update_minimap_hover() { const int row = get_minimap_line_at_pos(mp); - const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); + bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); if (new_hovering_minimap != hovering_minimap) { // Only redraw if the hovering status changed. hovering_minimap = new_hovering_minimap; @@ -8147,8 +8252,36 @@ void TextEdit::_update_gutter_width() { } /* Syntax highlighting. */ -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); +Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighter.is_null() || setting_text) { + return Vector<Pair<int64_t, Color>>(); + } + + HashMap<int, Vector<Pair<int64_t, Color>>>::Iterator E = syntax_highlighting_cache.find(p_line); + if (E) { + return E->value; + } + + Dictionary color_map = syntax_highlighter->get_line_syntax_highlighting(p_line); + Vector<Pair<int64_t, Color>> result; + result.resize(color_map.size()); + int i = 0; + for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key), i++) { + int64_t key_data = *key; + const Variant *color_data = color_map.getptr(*key); + Color color_value = editable ? theme_cache.font_color : theme_cache.font_readonly_color; + if (color_data != nullptr) { + color_value = (color_data->operator Dictionary()).get("color", color_value); + } + result.write[i] = Pair<int64_t, Color>(key_data, color_value); + } + syntax_highlighting_cache.insert(p_line, result); + + return result; +} + +void TextEdit::_clear_syntax_highlighting_cache() { + syntax_highlighting_cache.clear(); } /* Deprecated. */ @@ -8174,6 +8307,7 @@ int TextEdit::get_selection_column(int p_caret) const { /*** Super internal Core API. Everything builds on it. ***/ void TextEdit::_text_changed() { + _clear_syntax_highlighting_cache(); _cancel_drag_and_drop_text(); queue_redraw(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 4d9d169c1c..c5f838020b 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -153,6 +153,7 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int line_count = 0; int height = 0; int width = 0; @@ -178,16 +179,19 @@ private: bool use_default_word_separators = true; bool use_custom_word_separators = false; - int line_height = -1; - int max_width = -1; + mutable bool max_line_width_dirty = true; + mutable bool max_line_height_dirty = true; + mutable int max_line_width = 0; + mutable int max_line_height = 0; + mutable int total_visible_line_count = 0; int width = -1; int tab_size = 4; int gutter_count = 0; bool indent_wrapped_lines = false; - void _calculate_line_height(); - void _calculate_max_line_width(); + void _calculate_line_height() const; + void _calculate_max_line_width() const; public: void set_tab_size(int p_tab_size); @@ -203,6 +207,7 @@ private: int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; int get_max_width() const; + int get_total_visible_line_count() const; void set_use_default_word_separators(bool p_enabled); bool is_default_word_separators_enabled() const; @@ -226,18 +231,8 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Array &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { - if (text[p_line].hidden == p_hidden) { - return; - } - text.write[p_line].hidden = p_hidden; - if (!p_hidden && text[p_line].width > max_width) { - max_width = text[p_line].width; - } else if (p_hidden && text[p_line].width == max_width) { - _calculate_max_line_width(); - } - } - bool is_hidden(int p_line) const { return text[p_line].hidden; } + void set_hidden(int p_line, bool p_hidden); + bool is_hidden(int p_line) const; void insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override); void remove_range(int p_from_line, int p_to_line); int size() const { return text.size(); } @@ -248,7 +243,7 @@ private: void invalidate_all(); void invalidate_all_lines(); - _FORCE_INLINE_ const String &operator[](int p_line) const; + _FORCE_INLINE_ String operator[](int p_line) const; /* Gutters. */ void add_gutter(int p_at); @@ -281,6 +276,7 @@ private: bool setting_text = false; bool alt_start = false; + bool alt_start_no_hold = false; uint32_t alt_code = 0; // Text properties. @@ -297,6 +293,7 @@ private: Vector<String> placeholder_wraped_rows; void _update_placeholder(); + bool _using_placeholder() const; /* Initialize to opposite first, so we get past the early-out in set_editable. */ bool editable = false; @@ -452,6 +449,7 @@ private: void _caret_changed(int p_caret = -1); void _emit_caret_changed(); + void _show_virtual_keyboard(); void _reset_caret_blink_timer(); void _toggle_draw_caret(); @@ -504,8 +502,9 @@ private: HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; - float content_height_cache = 0.0; + Vector2i content_size_cache; bool fit_content_height = false; + bool fit_content_width = false; bool scroll_past_end_of_file_enabled = false; // Smooth scrolling. @@ -566,8 +565,10 @@ private: /* Syntax highlighting. */ Ref<SyntaxHighlighter> syntax_highlighter; + HashMap<int, Vector<Pair<int64_t, Color>>> syntax_highlighting_cache; - Dictionary _get_line_syntax_highlighting(int p_line); + Vector<Pair<int64_t, Color>> _get_line_syntax_highlighting(int p_line); + void _clear_syntax_highlighting_cache(); /* Visual. */ struct ThemeCache { @@ -733,7 +734,7 @@ public: void cancel_ime(); void apply_ime(); - void set_editable(const bool p_editable); + void set_editable(bool p_editable); bool is_editable() const; void set_text_direction(TextDirection p_text_direction); @@ -754,7 +755,7 @@ public: bool is_indent_wrapped_lines() const; // User controls - void set_overtype_mode_enabled(const bool p_enabled); + void set_overtype_mode_enabled(bool p_enabled); bool is_overtype_mode_enabled() const; void set_context_menu_enabled(bool p_enabled); @@ -861,7 +862,7 @@ public: void set_caret_type(CaretType p_type); CaretType get_caret_type() const; - void set_caret_blink_enabled(const bool p_enabled); + void set_caret_blink_enabled(bool p_enabled); bool is_caret_blink_enabled() const; void set_caret_blink_interval(const float p_interval); @@ -870,10 +871,10 @@ public: void set_draw_caret_when_editable_disabled(bool p_enable); bool is_drawing_caret_when_editable_disabled() const; - void set_move_caret_on_right_click_enabled(const bool p_enabled); + void set_move_caret_on_right_click_enabled(bool p_enabled); bool is_move_caret_on_right_click_enabled() const; - void set_caret_mid_grapheme_enabled(const bool p_enabled); + void set_caret_mid_grapheme_enabled(bool p_enabled); bool is_caret_mid_grapheme_enabled() const; void set_multiple_carets_enabled(bool p_enabled); @@ -909,13 +910,13 @@ public: String get_word_under_caret(int p_caret = -1) const; /* Selection. */ - void set_selecting_enabled(const bool p_enabled); + void set_selecting_enabled(bool p_enabled); bool is_selecting_enabled() const; - void set_deselect_on_focus_loss_enabled(const bool p_enabled); + void set_deselect_on_focus_loss_enabled(bool p_enabled); bool is_deselect_on_focus_loss_enabled() const; - void set_drag_and_drop_selection_enabled(const bool p_enabled); + void set_drag_and_drop_selection_enabled(bool p_enabled); bool is_drag_and_drop_selection_enabled() const; void set_selection_mode(SelectionMode p_mode); @@ -964,10 +965,10 @@ public: /* Viewport. */ // Scrolling. - void set_smooth_scroll_enabled(const bool p_enabled); + void set_smooth_scroll_enabled(bool p_enabled); bool is_smooth_scroll_enabled() const; - void set_scroll_past_end_of_file_enabled(const bool p_enabled); + void set_scroll_past_end_of_file_enabled(bool p_enabled); bool is_scroll_past_end_of_file_enabled() const; VScrollBar *get_v_scroll_bar() const; @@ -982,9 +983,12 @@ public: void set_v_scroll_speed(float p_speed); float get_v_scroll_speed() const; - void set_fit_content_height_enabled(const bool p_enabled); + void set_fit_content_height_enabled(bool p_enabled); bool is_fit_content_height_enabled() const; + void set_fit_content_width_enabled(bool p_enabled); + bool is_fit_content_width_enabled() const; + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; // Visible lines. @@ -1018,6 +1022,7 @@ public: void add_gutter(int p_at = -1); void remove_gutter(int p_gutter); int get_gutter_count() const; + Vector2i get_hovered_gutter() const { return hovered_gutter; } void set_gutter_name(int p_gutter, const String &p_name); String get_gutter_name(int p_gutter) const; @@ -1070,7 +1075,7 @@ public: void set_highlight_current_line(bool p_enabled); bool is_highlight_current_line_enabled() const; - void set_highlight_all_occurrences(const bool p_enabled); + void set_highlight_all_occurrences(bool p_enabled); bool is_highlight_all_occurrences_enabled() const; void set_draw_control_chars(bool p_enabled); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 46fcdcf7f6..e4f52ee8ee 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -62,8 +62,15 @@ void TreeItem::Cell::draw_icon(const RID &p_where, const Point2 &p_pos, const Si if (icon_region == Rect2i()) { icon->draw_rect_region(p_where, Rect2(p_pos, dsize), Rect2(Point2(), icon->get_size()), p_color); + if (icon_overlay.is_valid()) { + Vector2 offset = icon->get_size() - icon_overlay->get_size(); + icon_overlay->draw_rect_region(p_where, Rect2(p_pos + offset, dsize), Rect2(Point2(), icon_overlay->get_size()), p_color); + } } else { icon->draw_rect_region(p_where, Rect2(p_pos, dsize), icon_region, p_color); + if (icon_overlay.is_valid()) { + icon_overlay->draw_rect_region(p_where, Rect2(p_pos, dsize), icon_region, p_color); + } } } @@ -477,6 +484,24 @@ Ref<Texture2D> TreeItem::get_icon(int p_column) const { return cells[p_column].icon; } +void TreeItem::set_icon_overlay(int p_column, const Ref<Texture2D> &p_icon_overlay) { + ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].icon_overlay == p_icon_overlay) { + return; + } + + cells.write[p_column].icon_overlay = p_icon_overlay; + cells.write[p_column].cached_minimum_size_dirty = true; + + _changed_notify(p_column); +} + +Ref<Texture2D> TreeItem::get_icon_overlay(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), Ref<Texture2D>()); + return cells[p_column].icon_overlay; +} + void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -773,17 +798,21 @@ TreeItem *TreeItem::create_child(int p_index) { TreeItem *item_prev = nullptr; TreeItem *item_next = first_child; - int idx = 0; - while (item_next) { - if (idx == p_index) { - item_next->prev = ti; - ti->next = item_next; - break; - } + if (p_index < 0 && last_child) { + item_prev = last_child; + } else { + int idx = 0; + while (item_next) { + if (idx == p_index) { + item_next->prev = ti; + ti->next = item_next; + break; + } - item_prev = item_next; - item_next = item_next->next; - idx++; + item_prev = item_next; + item_next = item_next->next; + idx++; + } } if (item_prev) { @@ -804,6 +833,10 @@ TreeItem *TreeItem::create_child(int p_index) { } } + if (item_prev == last_child) { + last_child = ti; + } + ti->parent = this; ti->parent_visible_in_tree = is_visible_in_tree(); @@ -820,17 +853,13 @@ void TreeItem::add_child(TreeItem *p_item) { p_item->parent_visible_in_tree = is_visible_in_tree(); p_item->_handle_visibility_changed(p_item->parent_visible_in_tree); - TreeItem *item_prev = first_child; - while (item_prev && item_prev->next) { - item_prev = item_prev->next; - } - - if (item_prev) { - item_prev->next = p_item; - p_item->prev = item_prev; + if (last_child) { + last_child->next = p_item; + p_item->prev = last_child; } else { first_child = p_item; } + last_child = p_item; if (!children_cache.is_empty()) { children_cache.append(p_item); @@ -910,13 +939,8 @@ TreeItem *TreeItem::_get_prev_in_tree(bool p_wrap, bool p_include_invisible) { } } else { current = prev_item; - while ((!current->collapsed || p_include_invisible) && current->first_child) { - //go to the very end - - current = current->first_child; - while (current->next) { - current = current->next; - } + while ((!current->collapsed || p_include_invisible) && current->last_child) { + current = current->last_child; } } @@ -1037,6 +1061,8 @@ void TreeItem::clear_children() { } first_child = nullptr; + last_child = nullptr; + children_cache.clear(); }; int TreeItem::get_index() { @@ -1141,6 +1167,7 @@ void TreeItem::move_after(TreeItem *p_item) { if (next) { parent->children_cache.clear(); } else { + parent->last_child = this; // If the cache is empty, it has not been built but there // are items in the tree (note p_item != nullptr,) so we cannot update it. if (!parent->children_cache.is_empty()) { @@ -1562,7 +1589,7 @@ void TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Call return; } - if (p_args[0]->get_type() != Variant::STRING && p_args[0]->get_type() != Variant::STRING_NAME) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -1631,6 +1658,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_icon", "column", "texture"), &TreeItem::set_icon); ClassDB::bind_method(D_METHOD("get_icon", "column"), &TreeItem::get_icon); + ClassDB::bind_method(D_METHOD("set_icon_overlay", "column", "texture"), &TreeItem::set_icon_overlay); + ClassDB::bind_method(D_METHOD("get_icon_overlay", "column"), &TreeItem::get_icon_overlay); + ClassDB::bind_method(D_METHOD("set_icon_region", "column", "region"), &TreeItem::set_icon_region); ClassDB::bind_method(D_METHOD("get_icon_region", "column"), &TreeItem::get_icon_region); @@ -3272,12 +3302,10 @@ void Tree::value_editor_changed(double p_value) { return; } - TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col]; - c.val = p_value; + const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col]; - line_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step))); + line_editor->set_text(String::num(p_value, Math::range_step_decimals(c.step))); - item_edited(popup_edited_item_col, popup_edited_item); queue_redraw(); } @@ -3466,29 +3494,37 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) { + if (!selected_item || selected_col > (columns.size() - 1)) { return; } + if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(false); - } else { + } else if (select_mode != SELECT_ROW) { _go_right(); + } else if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) { + selected_item->set_collapsed(false); + } else { + _go_down(); } } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col < 0) { + if (!selected_item || selected_col < 0) { return; } if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(true); - } else { + } else if (select_mode != SELECT_ROW) { _go_left(); + } else if (selected_item->get_first_child() != nullptr && !selected_item->is_collapsed()) { + selected_item->set_collapsed(true); + } else { + _go_up(); } - } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); @@ -4468,15 +4504,8 @@ TreeItem *Tree::get_root() const { TreeItem *Tree::get_last_item() const { TreeItem *last = root; - - while (last) { - if (last->next) { - last = last->next; - } else if (last->first_child && !last->collapsed) { - last = last->first_child; - } else { - break; - } + while (last && last->last_child && !last->collapsed) { + last = last->last_child; } return last; @@ -4495,9 +4524,16 @@ void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mous } void Tree::item_changed(int p_column, TreeItem *p_item) { - if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) { - p_item->cells.write[p_column].dirty = true; - columns.write[p_column].cached_minimum_width_dirty = true; + if (p_item != nullptr) { + if (p_column >= 0 && p_column < p_item->cells.size()) { + p_item->cells.write[p_column].dirty = true; + columns.write[p_column].cached_minimum_width_dirty = true; + } else if (p_column == -1) { + for (int i = 0; i < p_item->cells.size(); i++) { + p_item->cells.write[i].dirty = true; + columns.write[i].cached_minimum_width_dirty = true; + } + } } queue_redraw(); } diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 3200459b5a..17ea31a733 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -60,6 +60,7 @@ private: TreeCellMode mode = TreeItem::CELL_MODE_STRING; Ref<Texture2D> icon; + Ref<Texture2D> icon_overlay; Rect2i icon_region; String text; String xl_text; @@ -136,6 +137,7 @@ private: TreeItem *prev = nullptr; // previous in list TreeItem *next = nullptr; // next in list TreeItem *first_child = nullptr; + TreeItem *last_child = nullptr; Vector<TreeItem *> children_cache; bool is_root = false; // for tree root @@ -177,6 +179,9 @@ private: if (parent->first_child == this) { parent->first_child = next; } + if (parent->last_child == this) { + parent->last_child = prev; + } } } @@ -253,6 +258,9 @@ public: void set_icon(int p_column, const Ref<Texture2D> &p_icon); Ref<Texture2D> get_icon(int p_column) const; + void set_icon_overlay(int p_column, const Ref<Texture2D> &p_icon_overlay); + Ref<Texture2D> get_icon_overlay(int p_column) const; + void set_icon_region(int p_column, const Rect2 &p_icon_region); Rect2 get_icon_region(int p_column) const; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 0396f3ab4a..858fc2246b 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -138,6 +138,12 @@ void Node::_notification(int p_notification) { get_tree()->nodes_in_tree_count++; orphan_node_count--; + + // Allow physics interpolated nodes to automatically reset when added to the tree + // (this is to save the user from doing this manually each time). + if (get_tree()->is_physics_interpolation_enabled()) { + _set_physics_interpolation_reset_requested(true); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -437,6 +443,18 @@ void Node::_propagate_physics_interpolated(bool p_interpolated) { data.blocked--; } +void Node::_propagate_physics_interpolation_reset_requested(bool p_requested) { + if (is_physics_interpolated()) { + data.physics_interpolation_reset_requested = p_requested; + } + + data.blocked++; + for (KeyValue<StringName, Node *> &K : data.children) { + K.value->_propagate_physics_interpolation_reset_requested(p_requested); + } + 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); @@ -739,7 +757,7 @@ void Node::rpc_config(const StringName &p_method, const Variant &p_config) { } } -const Variant Node::get_node_rpc_config() const { +Variant Node::get_rpc_config() const { return data.rpc_config; } @@ -752,8 +770,7 @@ Error Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallErro return ERR_INVALID_PARAMETER; } - Variant::Type type = p_args[0]->get_type(); - if (type != Variant::STRING_NAME && type != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -781,8 +798,7 @@ Error Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallE return ERR_INVALID_PARAMETER; } - Variant::Type type = p_args[1]->get_type(); - if (type != Variant::STRING_NAME && type != Variant::STRING) { + if (!p_args[1]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; r_error.expected = Variant::STRING_NAME; @@ -890,15 +906,23 @@ void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) { } // 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(); + if (is_physics_interpolated() && !interpolate && is_inside_tree()) { + propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); } _propagate_physics_interpolated(interpolate); } void Node::reset_physics_interpolation() { - propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + if (is_inside_tree()) { + propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + + // If `reset_physics_interpolation()` is called explicitly by the user + // (e.g. from scripts) then we prevent deferred auto-resets taking place. + // The user is trusted to call reset in the right order, and auto-reset + // will interfere with their control of prev / curr, so should be turned off. + _propagate_physics_interpolation_reset_requested(false); + } } bool Node::_is_enabled() const { @@ -2783,9 +2807,11 @@ Node *Node::duplicate(int p_flags) const { ERR_THREAD_GUARD_V(nullptr); Node *dupe = _duplicate(p_flags); + ERR_FAIL_NULL_V_MSG(dupe, nullptr, "Failed to duplicate node."); + _duplicate_properties(this, this, dupe, p_flags); - if (dupe && (p_flags & DUPLICATE_SIGNALS)) { + if (p_flags & DUPLICATE_SIGNALS) { _duplicate_signals(this, dupe); } @@ -2801,6 +2827,8 @@ Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, con int flags = DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANTIATION | DUPLICATE_FROM_EDITOR; Node *dupe = _duplicate(flags, &r_duplimap); + ERR_FAIL_NULL_V_MSG(dupe, nullptr, "Failed to duplicate node."); + _duplicate_properties(this, this, dupe, flags); // This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated. @@ -3406,7 +3434,7 @@ Variant Node::_call_deferred_thread_group_bind(const Variant **p_args, int p_arg return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -3429,7 +3457,7 @@ Variant Node::_call_thread_safe_bind(const Variant **p_args, int p_argcount, Cal return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -3610,6 +3638,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer); ClassDB::bind_method(D_METHOD("rpc_config", "method", "config"), &Node::rpc_config); + ClassDB::bind_method(D_METHOD("get_rpc_config"), &Node::get_rpc_config); ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description); ClassDB::bind_method(D_METHOD("get_editor_description"), &Node::get_editor_description); @@ -3825,6 +3854,9 @@ Node::Node() { data.unhandled_key_input = false; data.physics_interpolated = true; + data.physics_interpolation_reset_requested = false; + data.physics_interpolated_client_side = false; + data.use_identity_transform = false; data.parent_owned = false; data.in_constructor = true; diff --git a/scene/main/node.h b/scene/main/node.h index ee195ddef9..dc65513fca 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -199,7 +199,7 @@ private: void *process_group = nullptr; // to avoid cyclic dependency int multiplayer_authority = 1; // Server by default. - Variant rpc_config; + Variant rpc_config = Dictionary(); // Variables used to properly sort the node when processing, ignored otherwise. int process_priority = 0; @@ -225,6 +225,21 @@ private: // is switched on. bool physics_interpolated : 1; + // We can auto-reset physics interpolation when e.g. adding a node for the first time. + bool physics_interpolation_reset_requested : 1; + + // Most nodes need not be interpolated in the scene tree, physics interpolation + // is normally only needed in the RenderingServer. However if we need to read the + // interpolated transform of a node in the SceneTree, it is necessary to duplicate + // the interpolation logic client side, in order to prevent stalling the RenderingServer + // by reading back. + bool physics_interpolated_client_side : 1; + + // For certain nodes (e.g. CPU particles in global mode) + // it can be useful to not send the instance transform to the + // RenderingServer, and specify the mesh in world space. + bool use_identity_transform : 1; + bool parent_owned : 1; bool in_constructor : 1; bool use_placeholder : 1; @@ -263,6 +278,7 @@ private: void _propagate_exit_tree(); void _propagate_after_exit_tree(); void _propagate_physics_interpolated(bool p_interpolated); + void _propagate_physics_interpolation_reset_requested(bool p_requested); 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); @@ -334,6 +350,15 @@ protected: void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); + void _set_physics_interpolated_client_side(bool p_enable) { data.physics_interpolated_client_side = p_enable; } + bool _is_physics_interpolated_client_side() const { return data.physics_interpolated_client_side; } + + void _set_physics_interpolation_reset_requested(bool p_enable) { data.physics_interpolation_reset_requested = p_enable; } + bool _is_physics_interpolation_reset_requested() const { return data.physics_interpolation_reset_requested; } + + void _set_use_identity_transform(bool p_enable) { data.use_identity_transform = p_enable; } + bool _is_using_identity_transform() const { return data.use_identity_transform; } + //call from SceneTree void _call_input(const Ref<InputEvent> &p_event); void _call_shortcut_input(const Ref<InputEvent> &p_event); @@ -632,7 +657,7 @@ public: return binds; } - void replace_by(Node *p_node, bool p_keep_data = false); + void replace_by(Node *p_node, bool p_keep_groups = false); void set_process_mode(ProcessMode p_mode); ProcessMode get_process_mode() const; @@ -692,7 +717,7 @@ public: bool is_multiplayer_authority() const; void rpc_config(const StringName &p_method, const Variant &p_config); // config a local method for RPC - const Variant get_node_rpc_config() const; + Variant get_rpc_config() const; template <typename... VarArgs> Error rpc(const StringName &p_method, VarArgs... p_args); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index ced6d9aaa6..106130872d 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -59,6 +59,7 @@ #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" #ifndef _3D_DISABLED +#include "scene/3d/node_3d.h" #include "scene/resources/3d/world_3d.h" #include "servers/physics_server_3d.h" #endif // _3D_DISABLED @@ -118,6 +119,29 @@ void SceneTreeTimer::release_connections() { SceneTreeTimer::SceneTreeTimer() {} +#ifndef _3D_DISABLED +// This should be called once per physics tick, to make sure the transform previous and current +// is kept up to date on the few Node3Ds that are using client side physics interpolation. +void SceneTree::ClientPhysicsInterpolation::physics_process() { + for (SelfList<Node3D> *E = _node_3d_list.first(); E;) { + Node3D *node_3d = E->self(); + + SelfList<Node3D> *current = E; + + // Get the next element here BEFORE we potentially delete one. + E = E->next(); + + // This will return false if the Node3D has timed out .. + // i.e. if get_global_transform_interpolated() has not been called + // for a few seconds, we can delete from the list to keep processing + // to a minimum. + if (!node_3d->update_client_physics_interpolation_data()) { + _node_3d_list.remove(current); + } + } +} +#endif + void SceneTree::tree_changed() { emit_signal(tree_changed_name); } @@ -466,9 +490,31 @@ bool SceneTree::is_physics_interpolation_enabled() const { return _physics_interpolation_enabled; } +#ifndef _3D_DISABLED +void SceneTree::client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem) { + // This ensures that _update_physics_interpolation_data() will be called at least once every + // physics tick, to ensure the previous and current transforms are kept up to date. + _client_physics_interpolation._node_3d_list.add(p_elem); +} + +void SceneTree::client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem) { + _client_physics_interpolation._node_3d_list.remove(p_elem); +} +#endif + void SceneTree::iteration_prepare() { if (_physics_interpolation_enabled) { + // Make sure any pending transforms from the last tick / frame + // are flushed before pumping the interpolation prev and currents. + flush_transform_notifications(); RenderingServer::get_singleton()->tick(); + +#ifndef _3D_DISABLED + // Any objects performing client physics interpolation + // should be given an opportunity to keep their previous transforms + // up to date before each new physics tick. + _client_physics_interpolation.physics_process(); +#endif } } @@ -503,6 +549,14 @@ bool SceneTree::physics_process(double p_time) { return _quit; } +void SceneTree::iteration_end() { + // When physics interpolation is active, we want all pending transforms + // to be flushed to the RenderingServer before finishing a physics tick. + if (_physics_interpolation_enabled) { + flush_transform_notifications(); + } +} + bool SceneTree::process(double p_time) { if (MainLoop::process(p_time)) { _quit = true; @@ -570,6 +624,10 @@ bool SceneTree::process(double p_time) { #endif // _3D_DISABLED #endif // TOOLS_ENABLED + if (_physics_interpolation_enabled) { + RenderingServer::get_singleton()->pre_draw(true); + } + return _quit; } @@ -1257,8 +1315,8 @@ void SceneTree::_call_group_flags(const Variant **p_args, int p_argcount, Callab ERR_FAIL_COND(p_argcount < 3); ERR_FAIL_COND(!p_args[0]->is_num()); - ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING); - ERR_FAIL_COND(p_args[2]->get_type() != Variant::STRING_NAME && p_args[2]->get_type() != Variant::STRING); + ERR_FAIL_COND(!p_args[1]->is_string()); + ERR_FAIL_COND(!p_args[2]->is_string()); int flags = *p_args[0]; StringName group = *p_args[1]; @@ -1271,8 +1329,8 @@ void SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable::Ca r_error.error = Callable::CallError::CALL_OK; ERR_FAIL_COND(p_argcount < 2); - ERR_FAIL_COND(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING); - ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING); + ERR_FAIL_COND(!p_args[0]->is_string()); + ERR_FAIL_COND(!p_args[1]->is_string()); StringName group = *p_args[0]; StringName method = *p_args[1]; @@ -1761,6 +1819,13 @@ SceneTree::SceneTree() { set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false)); + // Always disable jitter fix if physics interpolation is enabled - + // Jitter fix will interfere with interpolation, and is not necessary + // when interpolation is active. + if (is_physics_interpolation_enabled()) { + Engine::get_singleton()->set_physics_jitter_fix(0); + } + // Initialize network state. set_multiplayer(MultiplayerAPI::create_default_interface()); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 6f0a61ec51..7e44541105 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -41,6 +41,9 @@ class PackedScene; class Node; +#ifndef _3D_DISABLED +class Node3D; +#endif class Window; class Material; class Mesh; @@ -120,6 +123,13 @@ private: bool changed = false; }; +#ifndef _3D_DISABLED + struct ClientPhysicsInterpolation { + SelfList<Node3D>::List _node_3d_list; + void physics_process(); + } _client_physics_interpolation; +#endif + Window *root = nullptr; double physics_process_time = 0.0; @@ -315,6 +325,7 @@ public: virtual void iteration_prepare() override; virtual bool physics_process(double p_time) override; + virtual void iteration_end() override; virtual bool process(double p_time) override; virtual void finalize() override; @@ -423,6 +434,11 @@ public: void set_physics_interpolation_enabled(bool p_enabled); bool is_physics_interpolation_enabled() const; +#ifndef _3D_DISABLED + void client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem); + void client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem); +#endif + SceneTree(); ~SceneTree(); }; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1302e3c53e..a4e27a3d16 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -655,9 +655,7 @@ void Viewport::_notification(int p_what) { case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { _gui_cancel_tooltip(); _drop_physics_mouseover(); - if (gui.mouse_focus && !gui.forced_mouse_focus) { - _drop_mouse_focus(); - } + _drop_mouse_focus(); // When the window focus changes, we want to end mouse_focus, but // not the mouse_over. Note: The OS will trigger a separate mouse // exit event if the change in focus results in the mouse exiting @@ -884,6 +882,10 @@ void Viewport::_process_picking() { } #ifndef _3D_DISABLED + if (physics_object_picking_first_only && is_input_handled()) { + continue; + } + CollisionObject3D *capture_object = nullptr; if (physics_object_capture.is_valid()) { capture_object = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_capture)); @@ -1233,14 +1235,16 @@ Ref<World2D> Viewport::find_world_2d() const { } } -void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) { +void Viewport::_propagate_drag_notification(Node *p_node, int p_what) { + // Send notification to p_node and all children and descendant nodes of p_node, except to SubViewports which are not children of a SubViewportContainer. p_node->notification(p_what); + bool is_svc = Object::cast_to<SubViewportContainer>(p_node); for (int i = 0; i < p_node->get_child_count(); i++) { Node *c = p_node->get_child(i); - if (Object::cast_to<Viewport>(c)) { + if (!is_svc && Object::cast_to<SubViewport>(c)) { continue; } - _propagate_viewport_notification(c, p_what); + Viewport::_propagate_drag_notification(c, p_what); } } @@ -1343,7 +1347,7 @@ Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) { Vector2 Viewport::get_mouse_position() const { ERR_READ_THREAD_GUARD_V(Vector2()); - if (!is_directly_attached_to_screen()) { + if (get_section_root_viewport() != SceneTree::get_singleton()->get_root()) { // Rely on the most recent mouse coordinate from an InputEventMouse in push_input. // In this case get_screen_transform is not applicable, because it is ambiguous. return gui.last_mouse_pos; @@ -1511,6 +1515,7 @@ void Viewport::_gui_show_tooltip() { r.size *= win_scale; vr = window->get_usable_parent_rect(); } + r.size = r.size.ceil(); r.size = r.size.min(panel->get_max_size()); if (r.size.x + r.position.x > vr.size.x + vr.position.x) { @@ -1545,8 +1550,7 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_popup->child_controls_changed(); } -bool Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) { - bool stopped = false; +void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) { Ref<InputEvent> ev = p_input; // Returns true if an event should be impacted by a control's mouse filter. @@ -1570,19 +1574,15 @@ bool Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu if (!control->is_inside_tree() || control->is_set_as_top_level()) { break; } - if (gui.key_event_accepted) { - stopped = true; - break; - } if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP && is_pointer_event && !(is_scroll_event && control->data.force_pass_scroll_events)) { // Mouse, ScreenDrag and ScreenTouch events are stopped by default with MOUSE_FILTER_STOP, unless we have a scroll event and force_pass_scroll_events set to true - stopped = true; + set_input_as_handled(); break; } } if (is_input_handled()) { - // Break after Physics Picking in SubViewport. + // Break when the event is set to handled in a child Control node or after physics picking in SubViewport. break; } @@ -1593,7 +1593,6 @@ bool Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu ev = ev->xformed_by(ci->get_transform()); // Transform event upwards. ci = ci->get_parent_item(); } - return stopped; } void Viewport::_gui_call_notification(Control *p_control, int p_what) { @@ -1698,14 +1697,15 @@ Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_ } bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) { - // Attempt grab, try parent controls too. + // Attempt drop, try parent controls too. CanvasItem *ci = p_at_control; + Viewport *section_root = get_section_root_viewport(); while (ci) { Control *control = Object::cast_to<Control>(ci); if (control) { - if (control->can_drop_data(p_at_pos, gui.drag_data)) { + if (control->can_drop_data(p_at_pos, section_root->gui.drag_data)) { if (!p_just_check) { - control->drop_data(p_at_pos, gui.drag_data); + control->drop_data(p_at_pos, section_root->gui.drag_data); } return true; @@ -1733,8 +1733,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - gui.key_event_accepted = false; - Point2 mpos = mb->get_position(); if (mb->is_pressed()) { MouseButtonMask button_mask = mouse_button_to_mask(mb->get_button_index()); @@ -1777,7 +1775,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *control = Object::cast_to<Control>(ci); if (control) { if (control->get_focus_mode() != Control::FOCUS_NONE) { - if (control != gui.key_focus) { + // Grabbing unhovered focus can cause issues when mouse is dragged + // with another button held down. + if (control != gui.key_focus && gui.mouse_over_hierarchy.has(control)) { control->grab_focus(); } break; @@ -1796,20 +1796,19 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - bool stopped = gui.mouse_focus && gui.mouse_focus->can_process() && _gui_call_input(gui.mouse_focus, mb); - if (stopped) { - set_input_as_handled(); + if (gui.mouse_focus && gui.mouse_focus->can_process()) { + _gui_call_input(gui.mouse_focus, mb); } if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) { // Alternate drop use (when using force_drag(), as proposed by #5342). - _perform_drop(gui.mouse_focus, pos); + _perform_drop(gui.mouse_focus); } _gui_cancel_tooltip(); } else { if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) { - _perform_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos); + _perform_drop(gui.drag_mouse_over); } gui.mouse_focus_mask.clear_flag(mouse_button_to_mask(mb->get_button_index())); // Remove from mask. @@ -1830,23 +1829,21 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { // as the release will never be received otherwise. if (gui.mouse_focus_mask.is_empty()) { gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; } - bool stopped = mouse_focus && mouse_focus->can_process() && _gui_call_input(mouse_focus, mb); - if (stopped) { - set_input_as_handled(); + if (mouse_focus && mouse_focus->can_process()) { + _gui_call_input(mouse_focus, mb); } } } Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - gui.key_event_accepted = false; Point2 mpos = mm->get_position(); // Drag & drop. - if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { + Viewport *section_root = get_section_root_viewport(); + if (!gui.drag_attempted && gui.mouse_focus && section_root && !section_root->gui.global_dragging && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { gui.drag_accum += mm->get_relative(); float len = gui.drag_accum.length(); if (len > 10) { @@ -1855,12 +1852,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { while (ci) { Control *control = Object::cast_to<Control>(ci); if (control) { - gui.dragging = true; - gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum)); - if (gui.drag_data.get_type() != Variant::NIL) { + section_root->gui.global_dragging = true; + section_root->gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum)); + if (section_root->gui.drag_data.get_type() != Variant::NIL) { gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); + gui.dragging = true; break; } else { Control *drag_preview = _gui_get_drag_preview(); @@ -1869,7 +1866,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { memdelete(drag_preview); gui.drag_preview_id = ObjectID(); } - gui.dragging = false; + section_root->gui.global_dragging = false; } if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) { @@ -1887,7 +1884,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_attempted = true; if (gui.dragging) { - _propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN); + Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN); } } } @@ -1932,7 +1929,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - if (!is_tooltip_shown && over->can_process()) { + // If the tooltip timer isn't running, start it. + // Otherwise, only reset the timer if the mouse has moved more than 5 pixels. + if (!is_tooltip_shown && over->can_process() && + (gui.tooltip_timer.is_null() || + Math::is_zero_approx(gui.tooltip_timer->get_time_left()) || + mm->get_relative().length() > 5.0)) { if (gui.tooltip_timer.is_valid()) { gui.tooltip_timer->release_connections(); gui.tooltip_timer = Ref<SceneTreeTimer>(); @@ -1973,112 +1975,35 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { ds_cursor_shape = (DisplayServer::CursorShape)cursor_shape; - bool stopped = over->can_process() && _gui_call_input(over, mm); - if (stopped) { - set_input_as_handled(); + if (over->can_process()) { + _gui_call_input(over, mm); } } if (gui.dragging) { - // Handle drag & drop. + // Handle drag & drop. This happens in the viewport where dragging started. Control *drag_preview = _gui_get_drag_preview(); if (drag_preview) { drag_preview->set_position(mpos); } - gui.drag_mouse_over = over; - gui.drag_mouse_over_pos = Vector2(); - - // Find the window this is above of. - // See if there is an embedder. - Viewport *embedder = nullptr; - Vector2 viewport_pos; - - if (is_embedding_subwindows()) { - embedder = this; - viewport_pos = mpos; - } else { - // Not an embedder, but may be a subwindow of an embedder. - Window *w = Object::cast_to<Window>(this); - if (w) { - if (w->is_embedded()) { - embedder = w->get_embedder(); - - viewport_pos = get_final_transform().xform(mpos) + w->get_position(); // To parent coords. - } - } - } - - Viewport *viewport_under = nullptr; - - if (embedder) { - // Use embedder logic. - - for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) { - Window *sw = embedder->gui.sub_windows[i].window; - Rect2 swrect = Rect2i(sw->get_position(), sw->get_size()); - if (!sw->get_flag(Window::FLAG_BORDERLESS)) { - int title_height = sw->theme_cache.title_height; - swrect.position.y -= title_height; - swrect.size.y += title_height; - } - - if (swrect.has_point(viewport_pos)) { - viewport_under = sw; - viewport_pos -= sw->get_position(); - } - } - - if (!viewport_under) { - // Not in a subwindow, likely in embedder. - viewport_under = embedder; - } - } else { - // Use DisplayServer logic. - Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); - - DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos); - - if (window_id != DisplayServer::INVALID_WINDOW_ID) { - ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id); - - if (object_under != ObjectID()) { // Fetch window. - Window *w = Object::cast_to<Window>(ObjectDB::get_instance(object_under)); - if (w) { - viewport_under = w; - viewport_pos = w->get_final_transform().affine_inverse().xform(screen_mouse_pos - w->get_position()); - } - } - } - } - - if (viewport_under) { - if (viewport_under != this) { - Transform2D ai = viewport_under->get_final_transform().affine_inverse(); - viewport_pos = ai.xform(viewport_pos); + gui.drag_mouse_over = section_root->gui.target_control; + if (gui.drag_mouse_over) { + if (!_gui_drop(gui.drag_mouse_over, gui.drag_mouse_over->get_local_mouse_position(), true)) { + gui.drag_mouse_over = nullptr; } - // Find control under at position. - gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos); if (gui.drag_mouse_over) { - Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse(); - gui.drag_mouse_over_pos = localizer.xform(viewport_pos); - - bool can_drop = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, true); - - if (!can_drop) { - ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN; - } else { - ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP; - } + ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP; + } else { + ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN; } - - } else { - gui.drag_mouse_over = nullptr; } } - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && !Object::cast_to<SubViewportContainer>(over)) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE) && (gui.dragging || (!section_root->gui.global_dragging && !Object::cast_to<SubViewportContainer>(over)))) { + // If dragging is active, then set the cursor shape only from the Viewport where dragging started. + // If dragging is inactive, then set the cursor shape only when not over a SubViewportContainer. DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); } } @@ -2091,20 +2016,15 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Control *over = gui_find_control(pos); if (over) { gui.touch_focus[touch_index] = over->get_instance_id(); - bool stopped = false; if (over->can_process()) { touch_event = touch_event->xformed_by(Transform2D()); // Make a copy. pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos); touch_event->set_position(pos); - stopped = _gui_call_input(over, touch_event); - } - if (stopped) { - set_input_as_handled(); + _gui_call_input(over, touch_event); } return; } } else { - bool stopped = false; ObjectID control_id = gui.touch_focus[touch_index]; Control *over = control_id.is_valid() ? Object::cast_to<Control>(ObjectDB::get_instance(control_id)) : nullptr; if (over && over->can_process()) { @@ -2112,10 +2032,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos); touch_event->set_position(pos); - stopped = _gui_call_input(over, touch_event); - } - if (stopped) { - set_input_as_handled(); + _gui_call_input(over, touch_event); } gui.touch_focus.erase(touch_index); return; @@ -2124,23 +2041,17 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Ref<InputEventGesture> gesture_event = p_event; if (gesture_event.is_valid()) { - gui.key_event_accepted = false; - _gui_cancel_tooltip(); Size2 pos = gesture_event->get_position(); Control *over = gui_find_control(pos); if (over) { - bool stopped = false; if (over->can_process()) { gesture_event = gesture_event->xformed_by(Transform2D()); // Make a copy. pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos); gesture_event->set_position(pos); - stopped = _gui_call_input(over, gesture_event); - } - if (stopped) { - set_input_as_handled(); + _gui_call_input(over, gesture_event); } return; } @@ -2155,7 +2066,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { over = gui_find_control(drag_event->get_position()); } if (over) { - bool stopped = false; if (over->can_process()) { Transform2D localizer = over->get_global_transform_with_canvas().affine_inverse(); Size2 pos = localizer.xform(drag_event->get_position()); @@ -2168,24 +2078,21 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { drag_event->set_relative(rel); drag_event->set_position(pos); - stopped = _gui_call_input(over, drag_event); + _gui_call_input(over, drag_event); } - if (stopped) { - set_input_as_handled(); - } return; } } if (mm.is_null() && mb.is_null() && p_event->is_action_type()) { - if (gui.dragging && p_event->is_action_pressed("ui_cancel") && Input::get_singleton()->is_action_just_pressed("ui_cancel")) { + if (gui.dragging && p_event->is_action_pressed(SNAME("ui_cancel")) && Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"))) { _perform_drop(); set_input_as_handled(); return; } - if (p_event->is_action_pressed("ui_cancel")) { + if (p_event->is_action_pressed(SNAME("ui_cancel"))) { // Cancel tooltip timer or hide tooltip when pressing Escape (this is standard behavior in most applications). _gui_cancel_tooltip(); if (gui.tooltip_popup) { @@ -2202,13 +2109,11 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (gui.key_focus) { - gui.key_event_accepted = false; if (gui.key_focus->can_process()) { gui.key_focus->_call_gui_input(p_event); } - if (gui.key_event_accepted) { - set_input_as_handled(); + if (is_input_handled()) { return; } } @@ -2222,51 +2127,51 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (joypadmotion_event.is_valid()) { Input *input = Input::get_singleton(); - if (p_event->is_action_pressed("ui_focus_next") && input->is_action_just_pressed("ui_focus_next")) { + if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed(SNAME("ui_focus_next"))) { next = from->find_next_valid_focus(); } - if (p_event->is_action_pressed("ui_focus_prev") && input->is_action_just_pressed("ui_focus_prev")) { + if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed(SNAME("ui_focus_prev"))) { next = from->find_prev_valid_focus(); } - if (p_event->is_action_pressed("ui_up") && input->is_action_just_pressed("ui_up")) { + if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed(SNAME("ui_up"))) { next = from->_get_focus_neighbor(SIDE_TOP); } - if (p_event->is_action_pressed("ui_left") && input->is_action_just_pressed("ui_left")) { + if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed(SNAME("ui_left"))) { next = from->_get_focus_neighbor(SIDE_LEFT); } - if (p_event->is_action_pressed("ui_right") && input->is_action_just_pressed("ui_right")) { + if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed(SNAME("ui_right"))) { next = from->_get_focus_neighbor(SIDE_RIGHT); } - if (p_event->is_action_pressed("ui_down") && input->is_action_just_pressed("ui_down")) { + if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed(SNAME("ui_down"))) { next = from->_get_focus_neighbor(SIDE_BOTTOM); } } else { - if (p_event->is_action_pressed("ui_focus_next", true, true)) { + if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true)) { next = from->find_next_valid_focus(); } - if (p_event->is_action_pressed("ui_focus_prev", true, true)) { + if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true)) { next = from->find_prev_valid_focus(); } - if (p_event->is_action_pressed("ui_up", true, true)) { + if (p_event->is_action_pressed(SNAME("ui_up"), true, true)) { next = from->_get_focus_neighbor(SIDE_TOP); } - if (p_event->is_action_pressed("ui_left", true, true)) { + if (p_event->is_action_pressed(SNAME("ui_left"), true, true)) { next = from->_get_focus_neighbor(SIDE_LEFT); } - if (p_event->is_action_pressed("ui_right", true, true)) { + if (p_event->is_action_pressed(SNAME("ui_right"), true, true)) { next = from->_get_focus_neighbor(SIDE_RIGHT); } - if (p_event->is_action_pressed("ui_down", true, true)) { + if (p_event->is_action_pressed(SNAME("ui_down"), true, true)) { next = from->_get_focus_neighbor(SIDE_BOTTOM); } } @@ -2278,10 +2183,10 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } -void Viewport::_perform_drop(Control *p_control, Point2 p_pos) { +void Viewport::_perform_drop(Control *p_control) { // Without any arguments, simply cancel Drag and Drop. if (p_control) { - gui.drag_successful = _gui_drop(p_control, p_pos, false); + gui.drag_successful = _gui_drop(p_control, p_control->get_local_mouse_position(), false); } else { gui.drag_successful = false; } @@ -2292,10 +2197,12 @@ void Viewport::_perform_drop(Control *p_control, Point2 p_pos) { gui.drag_preview_id = ObjectID(); } - gui.drag_data = Variant(); + Viewport *section_root = get_section_root_viewport(); + section_root->gui.drag_data = Variant(); gui.dragging = false; + section_root->gui.global_dragging = false; gui.drag_mouse_over = nullptr; - _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); + Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_END); // Display the new cursor shape instantly. update_mouse_cursor_state(); } @@ -2325,14 +2232,16 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control * ERR_FAIL_COND_MSG(p_data.get_type() == Variant::NIL, "Drag data must be a value."); gui.dragging = true; - gui.drag_data = p_data; + Viewport *section_root = get_section_root_viewport(); + section_root->gui.global_dragging = true; + section_root->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); } - _propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN); + Viewport::_propagate_drag_notification(section_root, NOTIFICATION_DRAG_BEGIN); } void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) { @@ -2397,7 +2306,6 @@ void Viewport::_gui_hide_control(Control *p_control) { void Viewport::_gui_remove_control(Control *p_control) { if (gui.mouse_focus == p_control) { gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); } if (gui.key_focus == p_control) { @@ -2553,7 +2461,6 @@ void Viewport::_gui_control_grab_focus(Control *p_control) { } void Viewport::_gui_accept_event() { - gui.key_event_accepted = true; if (is_inside_tree()) { set_input_as_handled(); } @@ -2563,9 +2470,12 @@ void Viewport::_drop_mouse_focus() { Control *c = gui.mouse_focus; BitField<MouseButtonMask> mask = gui.mouse_focus_mask; gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); + if (!c) { + return; + } + for (int i = 0; i < 3; i++) { if ((int)mask & (1 << i)) { Ref<InputEventMouseButton> mb; @@ -3014,6 +2924,7 @@ void Viewport::_update_mouse_over() { } void Viewport::_update_mouse_over(Vector2 p_pos) { + gui.last_mouse_pos = p_pos; // Necessary, because mouse cursor can be over Viewports that are not reached by the InputEvent. // Look for embedded windows at mouse position. if (is_embedding_subwindows()) { for (int i = gui.sub_windows.size() - 1; i >= 0; i--) { @@ -3065,6 +2976,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { // Look for Controls at mouse position. Control *over = gui_find_control(p_pos); + get_section_root_viewport()->gui.target_control = over; bool notify_embedded_viewports = false; if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) { // Find the common ancestor of `gui.mouse_over` and `over`. @@ -3187,6 +3099,10 @@ void Viewport::_drop_mouse_over(Control *p_until_control) { if (gui.mouse_over && gui.mouse_over->is_inside_tree()) { gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF); } + Viewport *section_root = get_section_root_viewport(); + if (section_root && section_root->gui.target_control == gui.mouse_over) { + section_root->gui.target_control = nullptr; + } gui.mouse_over = nullptr; // Send Mouse Exit notifications to children first. Don't send to p_until_control or above. @@ -3395,7 +3311,7 @@ bool Viewport::is_input_disabled() const { Variant Viewport::gui_get_drag_data() const { ERR_READ_THREAD_GUARD_V(Variant()); - return gui.drag_data; + return get_section_root_viewport()->gui.drag_data; } PackedStringArray Viewport::get_configuration_warnings() const { @@ -3589,7 +3505,7 @@ bool Viewport::is_snap_2d_vertices_to_pixel_enabled() const { bool Viewport::gui_is_dragging() const { ERR_READ_THREAD_GUARD_V(false); - return gui.dragging; + return get_section_root_viewport()->gui.global_dragging; } bool Viewport::gui_is_drag_successful() const { @@ -3892,23 +3808,6 @@ Rect2i Viewport::subwindow_get_popup_safe_rect(Window *p_window) const { return gui.sub_windows[index].parent_safe_rect; } -void Viewport::pass_mouse_focus_to(Viewport *p_viewport, Control *p_control) { - ERR_MAIN_THREAD_GUARD; - ERR_FAIL_NULL(p_viewport); - ERR_FAIL_NULL(p_control); - - if (gui.mouse_focus) { - p_viewport->gui.mouse_focus = p_control; - p_viewport->gui.mouse_focus_mask = gui.mouse_focus_mask; - p_viewport->gui.key_focus = p_control; - p_viewport->gui.forced_mouse_focus = true; - - gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; - gui.mouse_focus_mask.clear(); - } -} - void Viewport::set_sdf_oversize(SDFOversize p_sdf_oversize) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_INDEX(p_sdf_oversize, SDF_OVERSIZE_MAX); @@ -4704,6 +4603,7 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Viewport::warp_mouse); ClassDB::bind_method(D_METHOD("update_mouse_cursor_state"), &Viewport::update_mouse_cursor_state); + ClassDB::bind_method(D_METHOD("gui_cancel_drag"), &Viewport::gui_cancel_drag); ClassDB::bind_method(D_METHOD("gui_get_drag_data"), &Viewport::gui_get_drag_data); ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging); ClassDB::bind_method(D_METHOD("gui_is_drag_successful"), &Viewport::gui_is_drag_successful); @@ -5024,6 +4924,13 @@ Viewport::Viewport() { #endif // _3D_DISABLED set_sdf_oversize(sdf_oversize); // Set to server. + + // Physics interpolation mode for viewports is a special case. + // Typically viewports will be housed within Controls, + // and Controls default to PHYSICS_INTERPOLATION_MODE_OFF. + // Viewports can thus inherit physics interpolation OFF, which is unexpected. + // Setting to ON allows each viewport to have a fresh interpolation state. + set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON); } Viewport::~Viewport() { @@ -5031,6 +4938,9 @@ Viewport::~Viewport() { for (ViewportTexture *E : viewport_textures) { E->vp = nullptr; } + if (world_2d.is_valid()) { + world_2d->remove_viewport(this); + } ERR_FAIL_NULL(RenderingServer::get_singleton()); RenderingServer::get_singleton()->free(viewport); } @@ -5062,6 +4972,7 @@ void SubViewport::_internal_set_size(const Size2i &p_size, bool p_force) { if (c) { c->update_minimum_size(); + c->queue_redraw(); } } @@ -5153,9 +5064,12 @@ Transform2D SubViewport::get_popup_base_transform() const { return c->get_screen_transform() * container_transform * get_final_transform(); } -bool SubViewport::is_directly_attached_to_screen() const { - // SubViewports, that are used as Textures are not considered to be directly attached to screen. - return Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport() && get_parent()->get_viewport()->is_directly_attached_to_screen(); +Viewport *SubViewport::get_section_root_viewport() const { + if (Object::cast_to<SubViewportContainer>(get_parent()) && get_parent()->get_viewport()) { + return get_parent()->get_viewport()->get_section_root_viewport(); + } + SubViewport *vp = const_cast<SubViewport *>(this); + return vp; } bool SubViewport::is_attached_in_viewport() const { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 0d31c07e57..edacee2d88 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -281,7 +281,7 @@ private: bool disable_3d = false; - void _propagate_viewport_notification(Node *p_node, int p_what); + static void _propagate_drag_notification(Node *p_node, int p_what); void _update_global_transform(); @@ -349,9 +349,7 @@ private: Ref<Texture2D> vrs_texture; struct GUI { - bool forced_mouse_focus = false; //used for menu buttons bool mouse_in_viewport = false; - bool key_event_accepted = false; HashMap<int, ObjectID> touch_focus; Control *mouse_focus = nullptr; Control *mouse_click_grabber = nullptr; @@ -363,7 +361,6 @@ private: Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr. Window *windowmanager_window_over = nullptr; // Only used in root Viewport. Control *drag_mouse_over = nullptr; - Vector2 drag_mouse_over_pos; Control *tooltip_control = nullptr; Window *tooltip_popup = nullptr; Label *tooltip_label = nullptr; @@ -372,7 +369,7 @@ private: Point2 last_mouse_pos; Point2 drag_accum; bool drag_attempted = false; - Variant drag_data; + Variant drag_data; // Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer. ObjectID drag_preview_id; Ref<SceneTreeTimer> tooltip_timer; double tooltip_delay = 0.0; @@ -380,8 +377,10 @@ private: List<Control *> roots; HashSet<ObjectID> canvas_parents_with_dirty_order; int canvas_sort_index = 0; //for sorting items with canvas as root - bool dragging = false; + bool dragging = false; // Is true in the viewport in which dragging started while dragging is active. + bool global_dragging = false; // Is true while dragging is active. Only used in root-Viewport and SubViewports that are not children of a SubViewportContainer. bool drag_successful = false; + Control *target_control = nullptr; // Control that the mouse is over in the innermost nested Viewport. Only used in root-Viewport and SubViewports, that are not children of a SubViewportContainer. bool embed_subwindows_hint = false; Window *subwindow_focused = nullptr; @@ -402,14 +401,14 @@ private: bool disable_input = false; - bool _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input); + void _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input); void _gui_call_notification(Control *p_control, int p_what); void _gui_sort_roots(); Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform); void _gui_input_event(Ref<InputEvent> p_event); - void _perform_drop(Control *p_control = nullptr, Point2 p_pos = Point2()); + void _perform_drop(Control *p_control = nullptr); void _gui_cleanup_internal_state(Ref<InputEvent> p_event); void _push_unhandled_input_internal(const Ref<InputEvent> &p_event); @@ -662,8 +661,6 @@ public: Viewport *get_parent_viewport() const; Window *get_base_window() const; - void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control); - void set_canvas_cull_mask(uint32_t p_layers); uint32_t get_canvas_cull_mask() const; @@ -675,9 +672,9 @@ public: Transform2D get_screen_transform() const; virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const; virtual Transform2D get_popup_base_transform() const { return Transform2D(); } - virtual bool is_directly_attached_to_screen() const { return false; }; - virtual bool is_attached_in_viewport() const { return false; }; - virtual bool is_sub_viewport() const { return false; }; + virtual Viewport *get_section_root_viewport() const { return nullptr; } + virtual bool is_attached_in_viewport() const { return false; } + virtual bool is_sub_viewport() const { return false; } private: // 2D audio, camera, and physics. (don't put World2D here because World2D is needed for Control nodes). @@ -839,9 +836,9 @@ public: virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override; virtual Transform2D get_popup_base_transform() const override; - virtual bool is_directly_attached_to_screen() const override; + virtual Viewport *get_section_root_viewport() const override; virtual bool is_attached_in_viewport() const override; - virtual bool is_sub_viewport() const override { return true; }; + virtual bool is_sub_viewport() const override { return true; } void _validate_property(PropertyInfo &p_property) const; SubViewport(); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index e5873f0e9a..803ce89bc9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -29,12 +29,11 @@ /**************************************************************************/ #include "window.h" -#include "window.compat.inc" #include "core/config/project_settings.h" #include "core/debugger/engine_debugger.h" #include "core/input/shortcut.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "core/variant/variant_parser.h" #include "scene/gui/control.h" #include "scene/theme/theme_db.h" @@ -810,7 +809,7 @@ void Window::update_mouse_cursor_state() { mm->set_position(pos); mm->set_global_position(xform.xform(pos)); mm->set_device(InputEvent::DEVICE_ID_INTERNAL); - push_input(mm); + push_input(mm, true); } void Window::show() { @@ -1224,6 +1223,7 @@ void Window::_update_viewport_size() { TS->font_set_global_oversampling(font_oversampling); if (!ci_updated) { update_canvas_items(); + emit_signal(SNAME("size_changed")); } } } @@ -1602,7 +1602,7 @@ Size2 Window::_get_contents_minimum_size() const { } } - return max; + return max * content_scale_factor; } void Window::child_controls_changed() { @@ -2149,8 +2149,8 @@ Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName return theme_icon_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<Texture2D> icon = theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); theme_icon_cache[p_theme_type][p_name] = icon; return icon; @@ -2173,8 +2173,8 @@ Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringN return theme_style_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<StyleBox> style = theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); theme_style_cache[p_theme_type][p_name] = style; return style; @@ -2197,8 +2197,8 @@ Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_t return theme_font_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<Font> font = theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); theme_font_cache[p_theme_type][p_name] = font; return font; @@ -2221,8 +2221,8 @@ int Window::get_theme_font_size(const StringName &p_name, const StringName &p_th return theme_font_size_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); int font_size = theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); theme_font_size_cache[p_theme_type][p_name] = font_size; return font_size; @@ -2245,8 +2245,8 @@ Color Window::get_theme_color(const StringName &p_name, const StringName &p_them return theme_color_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Color color = theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); theme_color_cache[p_theme_type][p_name] = color; return color; @@ -2269,8 +2269,8 @@ int Window::get_theme_constant(const StringName &p_name, const StringName &p_the return theme_constant_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); int constant = theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); theme_constant_cache[p_theme_type][p_name] = constant; return constant; @@ -2315,8 +2315,8 @@ bool Window::has_theme_icon(const StringName &p_name, const StringName &p_theme_ } } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); } @@ -2332,8 +2332,8 @@ bool Window::has_theme_stylebox(const StringName &p_name, const StringName &p_th } } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } @@ -2349,8 +2349,8 @@ bool Window::has_theme_font(const StringName &p_name, const StringName &p_theme_ } } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); } @@ -2366,8 +2366,8 @@ bool Window::has_theme_font_size(const StringName &p_name, const StringName &p_t } } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } @@ -2383,8 +2383,8 @@ bool Window::has_theme_color(const StringName &p_name, const StringName &p_theme } } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); } @@ -2400,8 +2400,8 @@ bool Window::has_theme_constant(const StringName &p_name, const StringName &p_th } } - List<StringName> theme_types; - theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } @@ -2755,12 +2755,16 @@ Transform2D Window::get_popup_base_transform() const { return popup_base_transform; } -bool Window::is_directly_attached_to_screen() const { +Viewport *Window::get_section_root_viewport() const { if (get_embedder()) { - return get_embedder()->is_directly_attached_to_screen(); + return get_embedder()->get_section_root_viewport(); } - // Distinguish between the case that this is a native Window and not inside the tree. - return is_inside_tree(); + if (is_inside_tree()) { + // Native window. + return SceneTree::get_singleton()->get_root(); + } + Window *vp = const_cast<Window *>(this); + return vp; } bool Window::is_attached_in_viewport() const { diff --git a/scene/main/window.h b/scene/main/window.h index 33d593711f..47aaf73728 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -247,10 +247,6 @@ protected: void _notification(int p_what); static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - static void _bind_compatibility_methods(); -#endif - 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; @@ -473,7 +469,7 @@ public: virtual Transform2D get_final_transform() const override; virtual Transform2D get_screen_transform_internal(bool p_absolute_position = false) const override; virtual Transform2D get_popup_base_transform() const override; - virtual bool is_directly_attached_to_screen() const override; + virtual Viewport *get_section_root_viewport() const override; virtual bool is_attached_in_viewport() const override; Rect2i get_parent_rect() const; diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp index f840aaa759..c6c21e0dba 100644 --- a/scene/property_list_helper.cpp +++ b/scene/property_list_helper.cpp @@ -142,7 +142,7 @@ void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list) const { const Property &property = E.value; PropertyInfo info = property.info; - if (_call_getter(&property, i) == property.default_value) { + if (!(info.usage & PROPERTY_USAGE_STORE_IF_NULL) && _call_getter(&property, i) == property.default_value) { info.usage &= (~PROPERTY_USAGE_STORAGE); } diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index aa8ff75c6a..76678e609a 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -402,8 +402,6 @@ void register_scene_types() { GDREGISTER_CLASS(VSlider); GDREGISTER_CLASS(Popup); GDREGISTER_CLASS(PopupPanel); - GDREGISTER_CLASS(MenuBar); - GDREGISTER_CLASS(MenuButton); GDREGISTER_CLASS(CheckBox); GDREGISTER_CLASS(CheckButton); GDREGISTER_CLASS(LinkButton); @@ -458,6 +456,8 @@ void register_scene_types() { GDREGISTER_CLASS(CodeHighlighter); GDREGISTER_ABSTRACT_CLASS(TreeItem); + GDREGISTER_CLASS(MenuBar); + GDREGISTER_CLASS(MenuButton); GDREGISTER_CLASS(OptionButton); GDREGISTER_CLASS(SpinBox); GDREGISTER_CLASS(ColorPicker); diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp index 686560829b..e3f14539a8 100644 --- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp +++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp @@ -37,6 +37,7 @@ void NavigationMeshSourceGeometryData2D::clear() { traversable_outlines.clear(); obstruction_outlines.clear(); _projected_obstructions.clear(); + bounds_dirty = true; } bool NavigationMeshSourceGeometryData2D::has_data() { @@ -47,16 +48,19 @@ bool NavigationMeshSourceGeometryData2D::has_data() { void NavigationMeshSourceGeometryData2D::clear_projected_obstructions() { RWLockWrite write_lock(geometry_rwlock); _projected_obstructions.clear(); + bounds_dirty = true; } void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) { RWLockWrite write_lock(geometry_rwlock); traversable_outlines = p_traversable_outlines; + bounds_dirty = true; } void NavigationMeshSourceGeometryData2D::_set_obstruction_outlines(const Vector<Vector<Vector2>> &p_obstruction_outlines) { RWLockWrite write_lock(geometry_rwlock); obstruction_outlines = p_obstruction_outlines; + bounds_dirty = true; } const Vector<Vector<Vector2>> &NavigationMeshSourceGeometryData2D::_get_traversable_outlines() const { @@ -73,6 +77,7 @@ void NavigationMeshSourceGeometryData2D::_add_traversable_outline(const Vector<V if (p_shape_outline.size() > 1) { RWLockWrite write_lock(geometry_rwlock); traversable_outlines.push_back(p_shape_outline); + bounds_dirty = true; } } @@ -80,6 +85,7 @@ void NavigationMeshSourceGeometryData2D::_add_obstruction_outline(const Vector<V if (p_shape_outline.size() > 1) { RWLockWrite write_lock(geometry_rwlock); obstruction_outlines.push_back(p_shape_outline); + bounds_dirty = true; } } @@ -89,6 +95,7 @@ void NavigationMeshSourceGeometryData2D::set_traversable_outlines(const TypedArr for (int i = 0; i < p_traversable_outlines.size(); i++) { traversable_outlines.write[i] = p_traversable_outlines[i]; } + bounds_dirty = true; } TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_traversable_outlines() const { @@ -108,6 +115,7 @@ void NavigationMeshSourceGeometryData2D::set_obstruction_outlines(const TypedArr for (int i = 0; i < p_obstruction_outlines.size(); i++) { obstruction_outlines.write[i] = p_obstruction_outlines[i]; } + bounds_dirty = true; } TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_outlines() const { @@ -128,6 +136,7 @@ void NavigationMeshSourceGeometryData2D::append_traversable_outlines(const Typed for (int i = traversable_outlines_size; i < p_traversable_outlines.size(); i++) { traversable_outlines.write[i] = p_traversable_outlines[i]; } + bounds_dirty = true; } void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) { @@ -137,6 +146,7 @@ void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const Typed for (int i = obstruction_outlines_size; i < p_obstruction_outlines.size(); i++) { obstruction_outlines.write[i] = p_obstruction_outlines[i]; } + bounds_dirty = true; } void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) { @@ -148,6 +158,7 @@ void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVec traversable_outline.write[i] = p_shape_outline[i]; } traversable_outlines.push_back(traversable_outline); + bounds_dirty = true; } } @@ -160,11 +171,12 @@ void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVec obstruction_outline.write[i] = p_shape_outline[i]; } obstruction_outlines.push_back(obstruction_outline); + bounds_dirty = true; } } void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry) { - ERR_FAIL_NULL(p_other_geometry); + ERR_FAIL_COND(p_other_geometry.is_null()); Vector<Vector<Vector2>> other_traversable_outlines; Vector<Vector<Vector2>> other_obstruction_outlines; @@ -176,6 +188,7 @@ void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeo traversable_outlines.append_array(other_traversable_outlines); obstruction_outlines.append_array(other_obstruction_outlines); _projected_obstructions.append_array(other_projected_obstructions); + bounds_dirty = true; } void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve) { @@ -195,6 +208,7 @@ void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector< RWLockWrite write_lock(geometry_rwlock); _projected_obstructions.push_back(projected_obstruction); + bounds_dirty = true; } void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array &p_array) { @@ -217,6 +231,7 @@ void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array RWLockWrite write_lock(geometry_rwlock); _projected_obstructions.push_back(projected_obstruction); + bounds_dirty = true; } } @@ -266,6 +281,7 @@ void NavigationMeshSourceGeometryData2D::set_data(const Vector<Vector<Vector2>> traversable_outlines = p_traversable_outlines; obstruction_outlines = p_obstruction_outlines; _projected_obstructions = p_projected_obstructions; + bounds_dirty = true; } void NavigationMeshSourceGeometryData2D::get_data(Vector<Vector<Vector2>> &r_traversable_outlines, Vector<Vector<Vector2>> &r_obstruction_outlines, Vector<ProjectedObstruction> &r_projected_obstructions) { @@ -275,6 +291,58 @@ void NavigationMeshSourceGeometryData2D::get_data(Vector<Vector<Vector2>> &r_tra r_projected_obstructions = _projected_obstructions; } +Rect2 NavigationMeshSourceGeometryData2D::get_bounds() { + geometry_rwlock.read_lock(); + + if (bounds_dirty) { + geometry_rwlock.read_unlock(); + RWLockWrite write_lock(geometry_rwlock); + + bounds_dirty = false; + bounds = Rect2(); + bool first_vertex = true; + + for (const Vector<Vector2> &traversable_outline : traversable_outlines) { + for (const Vector2 &traversable_point : traversable_outline) { + if (first_vertex) { + first_vertex = false; + bounds.position = traversable_point; + } else { + bounds.expand_to(traversable_point); + } + } + } + + for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) { + for (const Vector2 &obstruction_point : obstruction_outline) { + if (first_vertex) { + first_vertex = false; + bounds.position = obstruction_point; + } else { + bounds.expand_to(obstruction_point); + } + } + } + + for (const ProjectedObstruction &projected_obstruction : _projected_obstructions) { + for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { + const Vector2 vertex = Vector2(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + if (first_vertex) { + first_vertex = false; + bounds.position = vertex; + } else { + bounds.expand_to(vertex); + } + } + } + } else { + geometry_rwlock.read_unlock(); + } + + RWLockRead read_lock(geometry_rwlock); + return bounds; +} + void NavigationMeshSourceGeometryData2D::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData2D::clear); ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData2D::has_data); @@ -298,6 +366,8 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() { 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); + ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationMeshSourceGeometryData2D::get_bounds); + 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/2d/navigation_mesh_source_geometry_data_2d.h b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h index 01e97eee48..b29c106fb5 100644 --- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h +++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h @@ -42,6 +42,9 @@ class NavigationMeshSourceGeometryData2D : public Resource { Vector<Vector<Vector2>> traversable_outlines; Vector<Vector<Vector2>> obstruction_outlines; + Rect2 bounds; + bool bounds_dirty = true; + public: struct ProjectedObstruction; @@ -103,6 +106,8 @@ public: void set_data(const Vector<Vector<Vector2>> &p_traversable_outlines, const Vector<Vector<Vector2>> &p_obstruction_outlines, Vector<ProjectedObstruction> &p_projected_obstructions); void get_data(Vector<Vector<Vector2>> &r_traversable_outlines, Vector<Vector<Vector2>> &r_obstruction_outlines, Vector<ProjectedObstruction> &r_projected_obstructions); + Rect2 get_bounds(); + NavigationMeshSourceGeometryData2D() {} ~NavigationMeshSourceGeometryData2D() { clear(); } }; diff --git a/scene/resources/2d/navigation_polygon.cpp b/scene/resources/2d/navigation_polygon.cpp index 4a290db86b..3dfa906e3b 100644 --- a/scene/resources/2d/navigation_polygon.cpp +++ b/scene/resources/2d/navigation_polygon.cpp @@ -104,7 +104,7 @@ void NavigationPolygon::_set_polygons(const TypedArray<Vector<int32_t>> &p_array } polygons.resize(p_array.size()); for (int i = 0; i < p_array.size(); i++) { - polygons.write[i].indices = p_array[i]; + polygons.write[i] = p_array[i]; } } @@ -113,7 +113,7 @@ TypedArray<Vector<int32_t>> NavigationPolygon::_get_polygons() const { TypedArray<Vector<int32_t>> ret; ret.resize(polygons.size()); for (int i = 0; i < ret.size(); i++) { - ret[i] = polygons[i].indices; + ret[i] = polygons[i]; } return ret; @@ -141,9 +141,7 @@ TypedArray<Vector<Vector2>> NavigationPolygon::_get_outlines() const { void NavigationPolygon::add_polygon(const Vector<int> &p_polygon) { RWLockWrite write_lock(rwlock); - Polygon polygon; - polygon.indices = p_polygon; - polygons.push_back(polygon); + polygons.push_back(p_polygon); { MutexLock lock(navigation_mesh_generation); navigation_mesh.unref(); @@ -164,7 +162,7 @@ int NavigationPolygon::get_polygon_count() const { Vector<int> NavigationPolygon::get_polygon(int p_idx) { RWLockRead read_lock(rwlock); ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector<int>()); - return polygons[p_idx].indices; + return polygons[p_idx]; } void NavigationPolygon::clear_polygons() { @@ -189,10 +187,19 @@ void NavigationPolygon::clear() { void NavigationPolygon::set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons) { RWLockWrite write_lock(rwlock); vertices = p_vertices; - polygons.resize(p_polygons.size()); - for (int i = 0; i < p_polygons.size(); i++) { - polygons.write[i].indices = p_polygons[i]; + polygons = p_polygons; + { + MutexLock lock(navigation_mesh_generation); + navigation_mesh.unref(); } +} + +void NavigationPolygon::set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons, const Vector<Vector<Vector2>> &p_outlines) { + RWLockWrite write_lock(rwlock); + vertices = p_vertices; + polygons = p_polygons; + outlines = p_outlines; + rect_cache_dirty = true; { MutexLock lock(navigation_mesh_generation); navigation_mesh.unref(); @@ -202,10 +209,14 @@ void NavigationPolygon::set_data(const Vector<Vector2> &p_vertices, const Vector void NavigationPolygon::get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons) { RWLockRead read_lock(rwlock); r_vertices = vertices; - r_polygons.resize(polygons.size()); - for (int i = 0; i < polygons.size(); i++) { - r_polygons.write[i] = polygons[i].indices; - } + r_polygons = polygons; +} + +void NavigationPolygon::get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons, Vector<Vector<Vector2>> &r_outlines) { + RWLockRead read_lock(rwlock); + r_vertices = vertices; + r_polygons = polygons; + r_outlines = outlines; } Ref<NavigationMesh> NavigationPolygon::get_navigation_mesh() { @@ -237,6 +248,31 @@ Ref<NavigationMesh> NavigationPolygon::get_navigation_mesh() { return navigation_mesh; } +void NavigationPolygon::set_outlines(const Vector<Vector<Vector2>> &p_outlines) { + RWLockWrite write_lock(rwlock); + outlines = p_outlines; + rect_cache_dirty = true; +} + +Vector<Vector<Vector2>> NavigationPolygon::get_outlines() const { + RWLockRead read_lock(rwlock); + return outlines; +} + +void NavigationPolygon::set_polygons(const Vector<Vector<int>> &p_polygons) { + RWLockWrite write_lock(rwlock); + polygons = p_polygons; + { + MutexLock lock(navigation_mesh_generation); + navigation_mesh.unref(); + } +} + +Vector<Vector<int>> NavigationPolygon::get_polygons() const { + RWLockRead read_lock(rwlock); + return polygons; +} + void NavigationPolygon::add_outline(const Vector<Vector2> &p_outline) { RWLockWrite write_lock(rwlock); outlines.push_back(p_outline); @@ -364,7 +400,7 @@ void NavigationPolygon::make_polygons_from_outlines() { for (List<TPPLPoly>::Element *I = out_poly.front(); I; I = I->next()) { TPPLPoly &tp = I->get(); - struct Polygon p; + Vector<int> p; for (int64_t i = 0; i < tp.GetNumPoints(); i++) { HashMap<Vector2, int>::Iterator E = points.find(tp[i]); @@ -372,7 +408,7 @@ void NavigationPolygon::make_polygons_from_outlines() { E = points.insert(tp[i], vertices.size()); vertices.push_back(tp[i]); } - p.indices.push_back(E->value); + p.push_back(E->value); } polygons.push_back(p); @@ -400,6 +436,15 @@ real_t NavigationPolygon::get_border_size() const { return border_size; } +void NavigationPolygon::set_sample_partition_type(SamplePartitionType p_value) { + ERR_FAIL_INDEX(p_value, SAMPLE_PARTITION_MAX); + partition_type = p_value; +} + +NavigationPolygon::SamplePartitionType NavigationPolygon::get_sample_partition_type() const { + return partition_type; +} + void NavigationPolygon::set_parsed_geometry_type(ParsedGeometryType p_geometry_type) { ERR_FAIL_INDEX(p_geometry_type, PARSED_GEOMETRY_MAX); parsed_geometry_type = p_geometry_type; @@ -514,6 +559,9 @@ void NavigationPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_border_size", "border_size"), &NavigationPolygon::set_border_size); ClassDB::bind_method(D_METHOD("get_border_size"), &NavigationPolygon::get_border_size); + ClassDB::bind_method(D_METHOD("set_sample_partition_type", "sample_partition_type"), &NavigationPolygon::set_sample_partition_type); + ClassDB::bind_method(D_METHOD("get_sample_partition_type"), &NavigationPolygon::get_sample_partition_type); + ClassDB::bind_method(D_METHOD("set_parsed_geometry_type", "geometry_type"), &NavigationPolygon::set_parsed_geometry_type); ClassDB::bind_method(D_METHOD("get_parsed_geometry_type"), &NavigationPolygon::get_parsed_geometry_type); @@ -543,6 +591,8 @@ void NavigationPolygon::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines"); + ADD_GROUP("Sampling", "sample_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type", PROPERTY_HINT_ENUM, "Convex Partition,Triangulate"), "set_sample_partition_type", "get_sample_partition_type"); ADD_GROUP("Geometry", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Meshes and Static Colliders"), "set_parsed_geometry_type", "get_parsed_geometry_type"); ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_parsed_collision_mask", "get_parsed_collision_mask"); @@ -559,6 +609,10 @@ void NavigationPolygon::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::RECT2, "baking_rect"), "set_baking_rect", "get_baking_rect"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "baking_rect_offset"), "set_baking_rect_offset", "get_baking_rect_offset"); + BIND_ENUM_CONSTANT(SAMPLE_PARTITION_CONVEX_PARTITION); + BIND_ENUM_CONSTANT(SAMPLE_PARTITION_TRIANGULATE); + BIND_ENUM_CONSTANT(SAMPLE_PARTITION_MAX); + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES); BIND_ENUM_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS); BIND_ENUM_CONSTANT(PARSED_GEOMETRY_BOTH); diff --git a/scene/resources/2d/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h index 4e99660b0e..ed2c606c55 100644 --- a/scene/resources/2d/navigation_polygon.h +++ b/scene/resources/2d/navigation_polygon.h @@ -33,16 +33,14 @@ #include "scene/2d/node_2d.h" #include "scene/resources/navigation_mesh.h" +#include "servers/navigation/navigation_globals.h" class NavigationPolygon : public Resource { GDCLASS(NavigationPolygon, Resource); RWLock rwlock; Vector<Vector2> vertices; - struct Polygon { - Vector<int> indices; - }; - Vector<Polygon> polygons; + Vector<Vector<int>> polygons; Vector<Vector<Vector2>> outlines; Vector<Vector<Vector2>> baked_outlines; @@ -53,7 +51,7 @@ class NavigationPolygon : public Resource { // Navigation mesh Ref<NavigationMesh> navigation_mesh; - real_t cell_size = 1.0f; // Must match ProjectSettings default 2D cell_size. + real_t cell_size = NavigationDefaults2D::navmesh_cell_size; real_t border_size = 0.0f; Rect2 baking_rect; @@ -74,6 +72,11 @@ public: Rect2 _edit_get_rect() const; bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; #endif + enum SamplePartitionType { + SAMPLE_PARTITION_CONVEX_PARTITION = 0, + SAMPLE_PARTITION_TRIANGULATE, + SAMPLE_PARTITION_MAX + }; enum ParsedGeometryType { PARSED_GEOMETRY_MESH_INSTANCES = 0, @@ -91,6 +94,7 @@ public: real_t agent_radius = 10.0f; + SamplePartitionType partition_type = SAMPLE_PARTITION_CONVEX_PARTITION; ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_BOTH; uint32_t parsed_collision_mask = 0xFFFFFFFF; @@ -109,6 +113,8 @@ public: Vector<Vector2> get_outline(int p_idx) const; void remove_outline(int p_idx); int get_outline_count() const; + void set_outlines(const Vector<Vector<Vector2>> &p_outlines); + Vector<Vector<Vector2>> get_outlines() const; void clear_outlines(); #ifndef DISABLE_DEPRECATED @@ -116,10 +122,13 @@ public: #endif // DISABLE_DEPRECATED void set_polygons(const Vector<Vector<int>> &p_polygons); - const Vector<Vector<int>> &get_polygons() const; + Vector<Vector<int>> get_polygons() const; Vector<int> get_polygon(int p_idx); void clear_polygons(); + void set_sample_partition_type(SamplePartitionType p_value); + SamplePartitionType get_sample_partition_type() const; + void set_parsed_geometry_type(ParsedGeometryType p_geometry_type); ParsedGeometryType get_parsed_geometry_type() const; @@ -155,12 +164,15 @@ public: void clear(); void set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons); + void set_data(const Vector<Vector2> &p_vertices, const Vector<Vector<int>> &p_polygons, const Vector<Vector<Vector2>> &p_outlines); void get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons); + void get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons, Vector<Vector<Vector2>> &r_outlines); NavigationPolygon() {} ~NavigationPolygon() {} }; +VARIANT_ENUM_CAST(NavigationPolygon::SamplePartitionType); VARIANT_ENUM_CAST(NavigationPolygon::ParsedGeometryType); VARIANT_ENUM_CAST(NavigationPolygon::SourceGeometryMode); diff --git a/scene/resources/2d/tile_set.compat.inc b/scene/resources/2d/tile_set.compat.inc index 873ae3aa93..58e33f7b35 100644 --- a/scene/resources/2d/tile_set.compat.inc +++ b/scene/resources/2d/tile_set.compat.inc @@ -37,7 +37,7 @@ Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p } Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const { - return get_occluder(p_layer_id, false, false, false); + return get_occluder_polygon(p_layer_id, 0, false, false, false); } void TileData::_bind_compatibility_methods() { diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp index d124577d25..229e18be23 100644 --- a/scene/resources/2d/tile_set.cpp +++ b/scene/resources/2d/tile_set.cpp @@ -3444,7 +3444,8 @@ void TileSet::_compatibility_conversion() { polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); } occluder->set_polygon(polygon); - tile_data->set_occluder(0, occluder); + tile_data->add_occluder_polygon(0); + tile_data->set_occluder_polygon(0, 0, occluder); } if (ctd->navigation.is_valid()) { if (get_navigation_layers_count() < 1) { @@ -3558,7 +3559,8 @@ void TileSet::_compatibility_conversion() { polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); } occluder->set_polygon(polygon); - tile_data->set_occluder(0, occluder); + tile_data->add_occluder_polygon(0); + tile_data->set_occluder_polygon(0, 0, occluder); } if (ctd->autotile_navpoly_map.has(coords)) { if (get_navigation_layers_count() < 1) { @@ -3920,7 +3922,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); ERR_FAIL_COND_V(terrain_index < 0, false); if (components[2] == "name") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + ERR_FAIL_COND_V(!p_value.is_string(), false); while (terrain_set_index >= terrain_sets.size()) { add_terrain_set(); } @@ -3958,7 +3960,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { int index = components[0].trim_prefix("custom_data_layer_").to_int(); ERR_FAIL_COND_V(index < 0, false); if (components[1] == "name") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + ERR_FAIL_COND_V(!p_value.is_string(), false); while (index >= custom_data_layers.size()) { add_custom_data_layer(); } @@ -5275,11 +5277,26 @@ Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int bool TileSetAtlasSource::is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const { Size2 size = get_tile_texture_region(p_atlas_coords).size; - Rect2 rect = Rect2(-size / 2 - get_tile_data(p_atlas_coords, p_alternative_tile)->get_texture_origin(), size); + TileData *tile_data = get_tile_data(p_atlas_coords, p_alternative_tile); + if (tile_data->get_transpose()) { + size = Size2(size.y, size.x); + } + Rect2 rect = Rect2(-size / 2 - tile_data->get_texture_origin(), size); return rect.has_point(p_position); } +bool TileSetAtlasSource::is_rect_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Rect2 p_rect) const { + Size2 size = get_tile_texture_region(p_atlas_coords).size; + TileData *tile_data = get_tile_data(p_atlas_coords, p_alternative_tile); + if (tile_data->get_transpose()) { + size = Size2(size.y, size.x); + } + Rect2 rect = Rect2(-size / 2 - tile_data->get_texture_origin(), size); + + return p_rect.intersection(rect) == p_rect; +} + int TileSetAtlasSource::alternative_no_transform(int p_alternative_id) { return p_alternative_id & ~(TRANSFORM_FLIP_H | TRANSFORM_FLIP_V | TRANSFORM_TRANSPOSE); } @@ -6205,33 +6222,86 @@ int TileData::get_y_sort_origin() const { return y_sort_origin; } +#ifndef DISABLE_DEPRECATED void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) { 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(); + if (get_occluder_polygons_count(p_layer_id) == 0) { + add_occluder_polygon(p_layer_id); + } + set_occluder_polygon(p_layer_id, 0, p_occluder_polygon); 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 { ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>()); + if (get_occluder_polygons_count(p_layer_id) == 0) { + return Ref<OccluderPolygon2D>(); + } + return get_occluder_polygon(p_layer_id, 0, p_flip_h, p_flip_v, p_transpose); +} +#endif // DISABLE_DEPRECATED + +void TileData::set_occluder_polygons_count(int p_layer_id, int p_polygons_count) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + ERR_FAIL_COND(p_polygons_count < 0); + if (p_polygons_count == occluders.write[p_layer_id].polygons.size()) { + return; + } + occluders.write[p_layer_id].polygons.resize(p_polygons_count); + notify_property_list_changed(); + emit_signal(CoreStringName(changed)); +} + +int TileData::get_occluder_polygons_count(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), 0); + return occluders[p_layer_id].polygons.size(); +} + +void TileData::add_occluder_polygon(int p_layer_id) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + occluders.write[p_layer_id].polygons.push_back(OcclusionLayerTileData::PolygonOccluderTileData()); + emit_signal(CoreStringName(changed)); +} + +void TileData::remove_occluder_polygon(int p_layer_id, int p_polygon_index) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size()); + occluders.write[p_layer_id].polygons.remove_at(p_polygon_index); + emit_signal(CoreStringName(changed)); +} + +void TileData::set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size()); + + OcclusionLayerTileData::PolygonOccluderTileData &polygon_occluder_tile_data = occluders.write[p_layer_id].polygons.write[p_polygon_index]; + polygon_occluder_tile_data.occluder_polygon = p_occluder_polygon; + polygon_occluder_tile_data.transformed_polygon_occluders.clear(); + emit_signal(CoreStringName(changed)); +} + +Ref<OccluderPolygon2D> TileData::get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const { + ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>()); + ERR_FAIL_INDEX_V(p_polygon_index, occluders[p_layer_id].polygons.size(), Ref<OccluderPolygon2D>()); const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id]; + const Ref<OccluderPolygon2D> &occluder_polygon = layer_tile_data.polygons[p_polygon_index].occluder_polygon; int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; if (key == 0) { - return layer_tile_data.occluder; + return occluder_polygon; } - if (layer_tile_data.occluder.is_null()) { + if (occluder_polygon.is_null()) { return Ref<OccluderPolygon2D>(); } - HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.transformed_occluders.find(key); + HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders.find(key); if (!I) { Ref<OccluderPolygon2D> transformed_polygon; transformed_polygon.instantiate(); - transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose)); - layer_tile_data.transformed_occluders[key] = transformed_polygon; + transformed_polygon->set_polygon(get_transformed_vertices(occluder_polygon->get_polygon(), p_flip_h, p_flip_v, p_transpose)); + layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders[key] = transformed_polygon; return transformed_polygon; } else { return I->value; @@ -6487,18 +6557,19 @@ Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id, bool p_f transformed_polygon.instantiate(); PackedVector2Array new_points = get_transformed_vertices(layer_tile_data.navigation_polygon->get_vertices(), p_flip_h, p_flip_v, p_transpose); - transformed_polygon->set_vertices(new_points); - int num_polygons = layer_tile_data.navigation_polygon->get_polygon_count(); - for (int i = 0; i < num_polygons; ++i) { - transformed_polygon->add_polygon(layer_tile_data.navigation_polygon->get_polygon(i)); - } + const Vector<Vector<Vector2>> outlines = layer_tile_data.navigation_polygon->get_outlines(); + int outline_count = outlines.size(); - for (int i = 0; i < layer_tile_data.navigation_polygon->get_outline_count(); i++) { - PackedVector2Array new_outline = get_transformed_vertices(layer_tile_data.navigation_polygon->get_outline(i), p_flip_h, p_flip_v, p_transpose); - transformed_polygon->add_outline(new_outline); + Vector<Vector<Vector2>> new_outlines; + new_outlines.resize(outline_count); + + for (int i = 0; i < outline_count; i++) { + new_outlines.write[i] = get_transformed_vertices(outlines[i], p_flip_h, p_flip_v, p_transpose); } + transformed_polygon->set_data(new_points, layer_tile_data.navigation_polygon->get_polygons(), new_outlines); + layer_tile_data.transformed_navigation_polygon[key] = transformed_polygon; return transformed_polygon; } else { @@ -6578,13 +6649,37 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { #endif Vector<String> components = String(p_name).split("/", true, 2); - - if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + if (components.size() >= 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { // Occlusion layers. int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); ERR_FAIL_COND_V(layer_index < 0, false); - if (components[1] == "polygon") { - Ref<OccluderPolygon2D> polygon = p_value; + if (components.size() == 2) { + if (components[1] == "polygon") { + // Kept for compatibility. + Ref<OccluderPolygon2D> polygon = p_value; + if (layer_index >= occluders.size()) { + if (tile_set) { + return false; + } else { + occluders.resize(layer_index + 1); + } + } + if (get_occluder_polygons_count(layer_index) == 0) { + add_occluder_polygon(layer_index); + } + set_occluder_polygon(layer_index, 0, polygon); + return true; + } else if (components[1] == "polygons_count") { + if (p_value.get_type() != Variant::INT) { + return false; + } + set_occluder_polygons_count(layer_index, p_value); + return true; + } + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + // Polygons. + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); if (layer_index >= occluders.size()) { if (tile_set) { @@ -6593,8 +6688,16 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { occluders.resize(layer_index + 1); } } - set_occluder(layer_index, polygon); - return true; + + if (polygon_index >= occluders[layer_index].polygons.size()) { + occluders.write[layer_index].polygons.resize(polygon_index + 1); + } + + if (components[2] == "polygon") { + Ref<OccluderPolygon2D> polygon = p_value; + set_occluder_polygon(layer_index, polygon_index, polygon); + return true; + } } } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { // Physics layers. @@ -6622,6 +6725,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { return true; } } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + // Polygons. int polygon_index = components[1].trim_prefix("polygon_").to_int(); ERR_FAIL_COND_V(polygon_index < 0, false); @@ -6708,16 +6812,36 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (tile_set) { - if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + if (components.size() >= 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { // Occlusion layers. int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); ERR_FAIL_COND_V(layer_index < 0, false); if (layer_index >= occluders.size()) { return false; } - if (components[1] == "polygon") { - r_ret = get_occluder(layer_index); - return true; + if (components.size() == 2) { + if (components[1] == "polygon") { + // Kept for compatibility. + if (occluders[layer_index].polygons.is_empty()) { + return false; + } + r_ret = get_occluder_polygon(layer_index, 0); + return true; + } else if (components[1] == "polygons_count") { + r_ret = get_occluder_polygons_count(layer_index); + return true; + } + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + // Polygons. + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + if (polygon_index >= occluders[layer_index].polygons.size()) { + return false; + } + if (components[2] == "polygon") { + r_ret = get_occluder_polygon(layer_index, polygon_index); + return true; + } } } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { // Physics layers. @@ -6797,12 +6921,15 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const { // Occlusion layers. p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int i = 0; i < occluders.size(); i++) { - // occlusion_layer_%d/polygon - property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT); - if (occluders[i].occluder.is_null()) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; + p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + for (int j = 0; j < occluders[i].polygons.size(); j++) { + // occlusion_layer_%d/polygon_%d/polygon + property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/polygon_%d/%s", i, j, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT); + if (occluders[i].polygons[j].occluder_polygon.is_null()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); } - p_list->push_back(property_info); } // Physics layers. @@ -6907,8 +7034,17 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin); ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin); + ClassDB::bind_method(D_METHOD("set_occluder_polygons_count", "layer_id", "polygons_count"), &TileData::set_occluder_polygons_count); + ClassDB::bind_method(D_METHOD("get_occluder_polygons_count", "layer_id"), &TileData::get_occluder_polygons_count); + ClassDB::bind_method(D_METHOD("add_occluder_polygon", "layer_id"), &TileData::add_occluder_polygon); + ClassDB::bind_method(D_METHOD("remove_occluder_polygon", "layer_id", "polygon_index"), &TileData::remove_occluder_polygon); + ClassDB::bind_method(D_METHOD("set_occluder_polygon", "layer_id", "polygon_index", "polygon"), &TileData::set_occluder_polygon); + ClassDB::bind_method(D_METHOD("get_occluder_polygon", "layer_id", "polygon_index", "flip_h", "flip_v", "transpose"), &TileData::get_occluder_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder); ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false)); +#endif // DISABLE_DEPRECATED // Physics. ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity); diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h index e083fa45b9..931495d020 100644 --- a/scene/resources/2d/tile_set.h +++ b/scene/resources/2d/tile_set.h @@ -763,6 +763,7 @@ public: Vector2i get_atlas_grid_size() const; Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; bool is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const; + bool is_rect_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Rect2 p_rect) const; static int alternative_no_transform(int p_alternative_id); @@ -840,8 +841,11 @@ private: int z_index = 0; int y_sort_origin = 0; struct OcclusionLayerTileData { - Ref<OccluderPolygon2D> occluder; - mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_occluders; + struct PolygonOccluderTileData { + Ref<OccluderPolygon2D> occluder_polygon; + mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_polygon_occluders; + }; + Vector<PolygonOccluderTileData> polygons; }; Vector<OcclusionLayerTileData> occluders; @@ -940,8 +944,17 @@ public: void set_y_sort_origin(int p_y_sort_origin); int get_y_sort_origin() const; +#ifndef DISABLE_DEPRECATED void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon); Ref<OccluderPolygon2D> get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; +#endif // DISABLE_DEPRECATED + + void set_occluder_polygons_count(int p_layer_id, int p_polygons_count); + int get_occluder_polygons_count(int p_layer_id) const; + void add_occluder_polygon(int p_layer_id); + void remove_occluder_polygon(int p_layer_id, int p_polygon_index); + void set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon); + Ref<OccluderPolygon2D> get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; // Physics void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity); diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp index 5e4f1970ee..92246b50db 100644 --- a/scene/resources/3d/fog_material.cpp +++ b/scene/resources/3d/fog_material.cpp @@ -138,7 +138,7 @@ void FogMaterial::cleanup_shader() { } void FogMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader.is_null()) { shader = RS::get_singleton()->shader_create(); @@ -165,7 +165,6 @@ void fog() { } )"); } - shader_mutex.unlock(); } FogMaterial::FogMaterial() { diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp index f912d2650d..47cd64f19a 100644 --- a/scene/resources/3d/importer_mesh.cpp +++ b/scene/resources/3d/importer_mesh.cpp @@ -34,6 +34,7 @@ #include "core/math/convex_hull.h" #include "core/math/random_pcg.h" #include "core/math/static_raycaster.h" +#include "scene/resources/animation_library.h" #include "scene/resources/surface_tool.h" #include <cstdint> @@ -134,9 +135,18 @@ void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector<in } } +String ImporterMesh::validate_blend_shape_name(const String &p_name) { + String name = p_name; + const char *characters = ":"; + for (const char *p = characters; *p; p++) { + name = name.replace(String::chr(*p), "_"); + } + return name; +} + void ImporterMesh::add_blend_shape(const String &p_name) { ERR_FAIL_COND(surfaces.size() > 0); - blend_shapes.push_back(p_name); + blend_shapes.push_back(validate_blend_shape_name(p_name)); } int ImporterMesh::get_blend_shape_count() const { @@ -256,6 +266,33 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma mesh.unref(); } +void ImporterMesh::optimize_indices_for_cache() { + if (!SurfaceTool::optimize_vertex_cache_func) { + return; + } + + for (int i = 0; i < surfaces.size(); i++) { + if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + Vector<Vector3> vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; + PackedInt32Array indices = surfaces[i].arrays[RS::ARRAY_INDEX]; + + unsigned int index_count = indices.size(); + unsigned int vertex_count = vertices.size(); + + if (index_count == 0) { + continue; + } + + int *indices_ptr = indices.ptrw(); + SurfaceTool::optimize_vertex_cache_func((unsigned int *)indices_ptr, (const unsigned int *)indices_ptr, index_count, vertex_count); + + surfaces.write[i].arrays[RS::ARRAY_INDEX] = indices; + } +} + #define VERTEX_SKIN_FUNC(bone_count, vert_idx, read_array, write_array, transform_array, bone_array, weight_array) \ Vector3 transformed_vert; \ for (unsigned int weight_idx = 0; weight_idx < bone_count; weight_idx++) { \ @@ -269,7 +306,7 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma } \ write_array[vert_idx] = transformed_vert; -void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array) { +void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array, bool p_raycast_normals) { if (!SurfaceTool::simplify_scale_func) { return; } @@ -432,6 +469,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli unsigned int index_target = 12; // Start with the smallest target, 4 triangles unsigned int last_index_count = 0; + // Only used for normal raycasting int split_vertex_count = vertex_count; LocalVector<Vector3> split_vertex_normals; LocalVector<int> split_vertex_indices; @@ -441,7 +479,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli RandomPCG pcg; pcg.seed(123456789); // Keep seed constant across imports - Ref<StaticRaycaster> raycaster = StaticRaycaster::create(); + Ref<StaticRaycaster> raycaster = p_raycast_normals ? StaticRaycaster::create() : Ref<StaticRaycaster>(); if (raycaster.is_valid()) { raycaster->add_mesh(vertices, indices, 0); raycaster->commit(); @@ -488,19 +526,22 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } new_indices.resize(new_index_count); - - LocalVector<LocalVector<int>> vertex_corners; - vertex_corners.resize(vertex_count); { int *ptrw = new_indices.ptrw(); for (unsigned int j = 0; j < new_index_count; j++) { - const int &remapped = vertex_inverse_remap[ptrw[j]]; - vertex_corners[remapped].push_back(j); - ptrw[j] = remapped; + ptrw[j] = vertex_inverse_remap[ptrw[j]]; } } if (raycaster.is_valid()) { + LocalVector<LocalVector<int>> vertex_corners; + vertex_corners.resize(vertex_count); + + int *ptrw = new_indices.ptrw(); + for (unsigned int j = 0; j < new_index_count; j++) { + vertex_corners[ptrw[j]].push_back(j); + } + float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15)); const float ray_bias = 0.05; float ray_length = ray_bias + mesh_error * scale * 3.0f; @@ -671,7 +712,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } } - surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); + if (raycaster.is_valid()) { + surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); + } + surfaces.write[i].lods.sort_custom<Surface::LODComparator>(); for (int j = 0; j < surfaces.write[i].lods.size(); j++) { @@ -682,6 +726,10 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } } +void ImporterMesh::_generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array) { + generate_lods(p_normal_merge_angle, p_normal_split_angle, p_skin_pose_transform_array); +} + bool ImporterMesh::has_mesh() const { return mesh.is_valid(); } @@ -811,6 +859,10 @@ void ImporterMesh::create_shadow_mesh() { index_wptr[j] = vertex_remap[index]; } + if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) { + SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size()); + } + new_surface[RS::ARRAY_INDEX] = new_indices; // Make sure the same LODs as the full version are used. @@ -829,6 +881,10 @@ void ImporterMesh::create_shadow_mesh() { index_wptr[k] = vertex_remap[index]; } + if (SurfaceTool::optimize_vertex_cache_func && surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) { + SurfaceTool::optimize_vertex_cache_func((unsigned int *)index_wptr, (const unsigned int *)index_wptr, index_count, new_vertices.size()); + } + lods[surfaces[i].lods[j].distance] = new_indices; } } @@ -1062,9 +1118,12 @@ Ref<NavigationMesh> ImporterMesh::create_navigation_mesh() { } HashMap<Vector3, int> unique_vertices; - LocalVector<int> face_indices; + Vector<Vector<int>> face_polygons; + face_polygons.resize(faces.size()); for (int i = 0; i < faces.size(); i++) { + Vector<int> face_indices; + face_indices.resize(3); for (int j = 0; j < 3; j++) { Vector3 v = faces[i].vertex[j]; int idx; @@ -1074,8 +1133,9 @@ Ref<NavigationMesh> ImporterMesh::create_navigation_mesh() { idx = unique_vertices.size(); unique_vertices[v] = idx; } - face_indices.push_back(idx); + face_indices.write[j] = idx; } + face_polygons.write[i] = face_indices; } Vector<Vector3> vertices; @@ -1086,16 +1146,7 @@ Ref<NavigationMesh> ImporterMesh::create_navigation_mesh() { Ref<NavigationMesh> nm; nm.instantiate(); - nm->set_vertices(vertices); - - Vector<int> v3; - v3.resize(3); - for (uint32_t i = 0; i < face_indices.size(); i += 3) { - v3.write[0] = face_indices[i + 0]; - v3.write[1] = face_indices[i + 1]; - v3.write[2] = face_indices[i + 2]; - nm->add_polygon(v3); - } + nm->set_data(vertices, face_polygons); return nm; } @@ -1367,7 +1418,7 @@ void ImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name); ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material); - ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::generate_lods); + ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::_generate_lods_bind); ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>())); ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear); @@ -1377,5 +1428,5 @@ void ImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &ImporterMesh::set_lightmap_size_hint); ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &ImporterMesh::get_lightmap_size_hint); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); } diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h index ff8683449b..c7e3a059d6 100644 --- a/scene/resources/3d/importer_mesh.h +++ b/scene/resources/3d/importer_mesh.h @@ -86,6 +86,8 @@ protected: void _set_data(const Dictionary &p_data); Dictionary _get_data() const; + void _generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array); + static void _bind_methods(); public: @@ -93,6 +95,8 @@ public: int get_blend_shape_count() const; String get_blend_shape_name(int p_blend_shape) const; + static String validate_blend_shape_name(const String &p_name); + void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray<Array> &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref<Material> &p_material = Ref<Material>(), const String &p_name = String(), const uint64_t p_flags = 0); int get_surface_count() const; @@ -112,7 +116,9 @@ public: void set_surface_material(int p_surface, const Ref<Material> &p_material); - void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array); + void optimize_indices_for_cache(); + + void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array, bool p_raycast_normals = false); void create_shadow_mesh(); Ref<ImporterMesh> get_shadow_mesh() const; diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp index 4c9f381aba..59366592ce 100644 --- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp +++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp @@ -33,6 +33,7 @@ void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_vertices) { RWLockWrite write_lock(geometry_rwlock); vertices = p_vertices; + bounds_dirty = true; } const Vector<float> &NavigationMeshSourceGeometryData3D::get_vertices() const { @@ -44,6 +45,7 @@ void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indice ERR_FAIL_COND(vertices.size() < p_indices.size()); RWLockWrite write_lock(geometry_rwlock); indices = p_indices; + bounds_dirty = true; } const Vector<int> &NavigationMeshSourceGeometryData3D::get_indices() const { @@ -63,6 +65,7 @@ void NavigationMeshSourceGeometryData3D::append_arrays(const Vector<float> &p_ve for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) { indices.set(i, indices[i] + number_of_vertices_before_merge / 3); } + bounds_dirty = true; } bool NavigationMeshSourceGeometryData3D::has_data() { @@ -75,11 +78,13 @@ void NavigationMeshSourceGeometryData3D::clear() { vertices.clear(); indices.clear(); _projected_obstructions.clear(); + bounds_dirty = true; } void NavigationMeshSourceGeometryData3D::clear_projected_obstructions() { RWLockWrite write_lock(geometry_rwlock); _projected_obstructions.clear(); + bounds_dirty = true; } void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) { @@ -207,16 +212,18 @@ void NavigationMeshSourceGeometryData3D::add_mesh_array(const Array &p_mesh_arra ERR_FAIL_COND(p_mesh_array.size() != Mesh::ARRAY_MAX); RWLockWrite write_lock(geometry_rwlock); _add_mesh_array(p_mesh_array, root_node_transform * p_xform); + bounds_dirty = true; } void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform) { ERR_FAIL_COND(p_faces.size() % 3 != 0); RWLockWrite write_lock(geometry_rwlock); _add_faces(p_faces, root_node_transform * p_xform); + bounds_dirty = true; } void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) { - ERR_FAIL_NULL(p_other_geometry); + ERR_FAIL_COND(p_other_geometry.is_null()); Vector<float> other_vertices; Vector<int> other_indices; @@ -236,6 +243,7 @@ void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeo } _projected_obstructions.append_array(other_projected_obstructions); + bounds_dirty = true; } void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve) { @@ -259,6 +267,7 @@ void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector< RWLockWrite write_lock(geometry_rwlock); _projected_obstructions.push_back(projected_obstruction); + bounds_dirty = true; } void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array &p_array) { @@ -285,6 +294,7 @@ void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array RWLockWrite write_lock(geometry_rwlock); _projected_obstructions.push_back(projected_obstruction); + bounds_dirty = true; } } @@ -336,6 +346,7 @@ void NavigationMeshSourceGeometryData3D::set_data(const Vector<float> &p_vertice vertices = p_vertices; indices = p_indices; _projected_obstructions = p_projected_obstructions; + bounds_dirty = true; } void NavigationMeshSourceGeometryData3D::get_data(Vector<float> &r_vertices, Vector<int> &r_indices, Vector<ProjectedObstruction> &r_projected_obstructions) { @@ -345,6 +356,45 @@ void NavigationMeshSourceGeometryData3D::get_data(Vector<float> &r_vertices, Vec r_projected_obstructions = _projected_obstructions; } +AABB NavigationMeshSourceGeometryData3D::get_bounds() { + geometry_rwlock.read_lock(); + + if (bounds_dirty) { + geometry_rwlock.read_unlock(); + RWLockWrite write_lock(geometry_rwlock); + + bounds_dirty = false; + bounds = AABB(); + bool first_vertex = true; + + for (int i = 0; i < vertices.size() / 3; i++) { + const Vector3 vertex = Vector3(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]); + if (first_vertex) { + first_vertex = false; + bounds.position = vertex; + } else { + bounds.expand_to(vertex); + } + } + for (const ProjectedObstruction &projected_obstruction : _projected_obstructions) { + for (int i = 0; i < projected_obstruction.vertices.size() / 3; i++) { + const Vector3 vertex = Vector3(projected_obstruction.vertices[i * 3], projected_obstruction.vertices[i * 3 + 1], projected_obstruction.vertices[i * 3 + 2]); + if (first_vertex) { + first_vertex = false; + bounds.position = vertex; + } else { + bounds.expand_to(vertex); + } + } + } + } else { + geometry_rwlock.read_unlock(); + } + + RWLockRead read_lock(geometry_rwlock); + return bounds; +} + 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); @@ -367,6 +417,8 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() { 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); + ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationMeshSourceGeometryData3D::get_bounds); + 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/3d/navigation_mesh_source_geometry_data_3d.h b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h index a8e613a51d..d7e3c3071c 100644 --- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h +++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h @@ -41,6 +41,9 @@ class NavigationMeshSourceGeometryData3D : public Resource { Vector<float> vertices; Vector<int> indices; + AABB bounds; + bool bounds_dirty = true; + public: struct ProjectedObstruction; @@ -101,6 +104,8 @@ public: void set_data(const Vector<float> &p_vertices, const Vector<int> &p_indices, Vector<ProjectedObstruction> &p_projected_obstructions); void get_data(Vector<float> &r_vertices, Vector<int> &r_indices, Vector<ProjectedObstruction> &r_projected_obstructions); + AABB get_bounds(); + NavigationMeshSourceGeometryData3D() {} ~NavigationMeshSourceGeometryData3D() { clear(); } }; diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp index ee772f960a..ceeb73d0ef 100644 --- a/scene/resources/3d/primitive_meshes.cpp +++ b/scene/resources/3d/primitive_meshes.cpp @@ -324,22 +324,43 @@ Vector2 PrimitiveMesh::get_uv2_scale(Vector2 p_margin_scale) const { } float PrimitiveMesh::get_lightmap_texel_size() const { - float texel_size = GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"); + return texel_size; +} - if (texel_size <= 0.0) { - texel_size = 0.2; +void PrimitiveMesh::_on_settings_changed() { + float new_texel_size = float(GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size")); + if (new_texel_size <= 0.0) { + new_texel_size = 0.2; + } + if (texel_size == new_texel_size) { + return; } - return texel_size; + texel_size = new_texel_size; + _update_lightmap_size(); + request_update(); } PrimitiveMesh::PrimitiveMesh() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); mesh = RenderingServer::get_singleton()->mesh_create(); + + ERR_FAIL_NULL(ProjectSettings::get_singleton()); + texel_size = float(GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size")); + if (texel_size <= 0.0) { + texel_size = 0.2; + } + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + project_settings->connect("settings_changed", callable_mp(this, &PrimitiveMesh::_on_settings_changed)); } PrimitiveMesh::~PrimitiveMesh() { ERR_FAIL_NULL(RenderingServer::get_singleton()); RenderingServer::get_singleton()->free(mesh); + + ERR_FAIL_NULL(ProjectSettings::get_singleton()); + ProjectSettings *project_settings = ProjectSettings::get_singleton(); + project_settings->disconnect("settings_changed", callable_mp(this, &PrimitiveMesh::_on_settings_changed)); } /** @@ -350,7 +371,6 @@ void CapsuleMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend @@ -365,7 +385,6 @@ void CapsuleMesh::_update_lightmap_size() { void CapsuleMesh::_create_mesh_array(Array &p_arr) const { bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; create_mesh_array(p_arr, radius, height, radial_segments, rings, _add_uv2, _uv2_padding); @@ -613,7 +632,6 @@ void BoxMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float width = (size.x + size.z) / texel_size; @@ -632,7 +650,6 @@ void BoxMesh::_create_mesh_array(Array &p_arr) const { // With 3 faces along the width and 2 along the height of the texture we need to adjust our scale // accordingly. bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d, _add_uv2, _uv2_padding); @@ -937,7 +954,6 @@ void CylinderMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float top_circumference = top_radius * Math_PI * 2.0; @@ -957,7 +973,6 @@ void CylinderMesh::_update_lightmap_size() { void CylinderMesh::_create_mesh_array(Array &p_arr) const { bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom, _add_uv2, _uv2_padding); @@ -1244,7 +1259,6 @@ void PlaneMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); _lightmap_size_hint.x = MAX(1.0, (size.x / texel_size) + padding); @@ -1416,7 +1430,6 @@ void PrismMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); // left_to_right does not effect the surface area of the prism so we ignore that. @@ -1440,7 +1453,6 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { // Only used if we calculate UV2 bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; float horizontal_total = size.x + size.z + 2.0 * _uv2_padding; @@ -1762,7 +1774,6 @@ void SphereMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float _width = radius * Math_TAU; @@ -1776,7 +1787,6 @@ void SphereMesh::_update_lightmap_size() { void SphereMesh::_create_mesh_array(Array &p_arr) const { bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding); @@ -1950,7 +1960,6 @@ void TorusMesh::_update_lightmap_size() { if (get_add_uv2()) { // size must have changed, update lightmap size hint Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); float padding = get_uv2_padding(); float min_radius = inner_radius; @@ -2000,7 +2009,6 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { // Only used if we calculate UV2 bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); float _uv2_padding = get_uv2_padding() * texel_size; float horizontal_total = max_radius * Math_TAU + _uv2_padding; @@ -3468,13 +3476,13 @@ Ref<Font> TextMesh::_get_font_or_default() const { } StringName theme_name = "font"; - List<StringName> theme_types; - ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types); + Vector<StringName> theme_types; + ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), theme_types); ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context(); - List<Ref<Theme>> themes = global_context->get_themes(); + Vector<Ref<Theme>> themes = global_context->get_themes(); if (Engine::get_singleton()->is_editor_hint()) { - themes.push_front(ThemeDB::get_singleton()->get_project_theme()); + themes.insert(0, ThemeDB::get_singleton()->get_project_theme()); } for (const Ref<Theme> &theme : themes) { diff --git a/scene/resources/3d/primitive_meshes.h b/scene/resources/3d/primitive_meshes.h index 4d2d0760b3..fc2489923a 100644 --- a/scene/resources/3d/primitive_meshes.h +++ b/scene/resources/3d/primitive_meshes.h @@ -67,6 +67,9 @@ protected: // assume primitive triangles as the type, correct for all but one and it will change this :) Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES; + // Copy of our texel_size project setting. + float texel_size = 0.2; + static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const {} @@ -76,6 +79,8 @@ protected: float get_lightmap_texel_size() const; virtual void _update_lightmap_size(){}; + void _on_settings_changed(); + public: virtual int get_surface_count() const override; virtual int surface_get_array_len(int p_idx) const override; diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp index 640261d615..c470db5d7f 100644 --- a/scene/resources/3d/sky_material.cpp +++ b/scene/resources/3d/sky_material.cpp @@ -269,7 +269,7 @@ void ProceduralSkyMaterial::cleanup_shader() { } void ProceduralSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -354,7 +354,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - shader_mutex.unlock(); } ProceduralSkyMaterial::ProceduralSkyMaterial() { @@ -463,7 +462,7 @@ void PanoramaSkyMaterial::cleanup_shader() { } void PanoramaSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -484,8 +483,6 @@ void sky() { i ? "filter_linear" : "filter_nearest")); } } - - shader_mutex.unlock(); } PanoramaSkyMaterial::PanoramaSkyMaterial() { @@ -692,7 +689,7 @@ void PhysicalSkyMaterial::cleanup_shader() { } void PhysicalSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -785,8 +782,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - - shader_mutex.unlock(); } PhysicalSkyMaterial::PhysicalSkyMaterial() { diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index c0ab636adc..a2ed6af23c 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -969,10 +969,6 @@ void Animation::remove_track(int p_track) { _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; } @@ -1721,7 +1717,7 @@ int Animation::track_insert_key(int p_track, double p_time, const Variant &p_key ERR_FAIL_COND_V(p_key.get_type() != Variant::DICTIONARY, -1); Dictionary d = p_key; - ERR_FAIL_COND_V(!d.has("method") || (d["method"].get_type() != Variant::STRING_NAME && d["method"].get_type() != Variant::STRING), -1); + ERR_FAIL_COND_V(!d.has("method") || !d["method"].is_string(), -1); ERR_FAIL_COND_V(!d.has("args") || !d["args"].is_array(), -1); MethodKey k; @@ -3908,13 +3904,12 @@ 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"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_included", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "", "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 d5daaac58a..0c29790ea4 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -45,7 +45,7 @@ public: static inline String PARAMETERS_BASE_PATH = "parameters/"; - enum TrackType { + enum TrackType : uint8_t { TYPE_VALUE, // Set a value in a property, can be interpolated. TYPE_POSITION_3D, // Position 3D track, can be compressed. TYPE_ROTATION_3D, // Rotation 3D track, can be compressed. @@ -57,7 +57,7 @@ public: TYPE_ANIMATION, }; - enum InterpolationType { + enum InterpolationType : uint8_t { INTERPOLATION_NEAREST, INTERPOLATION_LINEAR, INTERPOLATION_CUBIC, @@ -65,26 +65,26 @@ public: INTERPOLATION_CUBIC_ANGLE, }; - enum UpdateMode { + enum UpdateMode : uint8_t { UPDATE_CONTINUOUS, UPDATE_DISCRETE, UPDATE_CAPTURE, }; - enum LoopMode { + enum LoopMode : uint8_t { LOOP_NONE, LOOP_LINEAR, LOOP_PINGPONG, }; // LoopedFlag is used in Animataion to "process the keys at both ends correct". - enum LoopedFlag { + enum LoopedFlag : uint8_t { LOOPED_FLAG_NONE, LOOPED_FLAG_END, LOOPED_FLAG_START, }; - enum FindMode { + enum FindMode : uint8_t { FIND_MODE_NEAREST, FIND_MODE_APPROX, FIND_MODE_EXACT, @@ -104,7 +104,6 @@ public: }; #endif // TOOLS_ENABLED -private: struct Track { TrackType type = TrackType::TYPE_ANIMATION; InterpolationType interpolation = INTERPOLATION_LINEAR; @@ -117,6 +116,7 @@ private: virtual ~Track() {} }; +private: struct Key { real_t transition = 1.0; double time = 0.0; // Time in secs. @@ -396,7 +396,10 @@ 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); + _FORCE_INLINE_ const Vector<Track *> get_tracks() { + return tracks; + } + bool is_capture_included() const; int get_track_count() const; diff --git a/scene/resources/audio_stream_polyphonic.cpp b/scene/resources/audio_stream_polyphonic.cpp index e617096f3b..c7b8b1c723 100644 --- a/scene/resources/audio_stream_polyphonic.cpp +++ b/scene/resources/audio_stream_polyphonic.cpp @@ -143,6 +143,10 @@ int AudioStreamPlaybackPolyphonic::mix(AudioFrame *p_buffer, float p_rate_scale, } if (s.stream_playback->get_is_sample()) { + if (s.finish_request.is_set()) { + s.active.clear(); + AudioServer::get_singleton()->stop_sample_playback(s.stream_playback->get_sample_playback()); + } continue; } @@ -243,6 +247,11 @@ AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(con sp->volume_vector.write[2] = AudioFrame(linear_volume, linear_volume); sp->volume_vector.write[3] = AudioFrame(linear_volume, linear_volume); sp->bus = p_bus; + + if (streams[i].stream_playback->get_sample_playback().is_valid()) { + AudioServer::get_singleton()->stop_playback_stream(sp); + } + streams[i].stream_playback->set_sample_playback(sp); AudioServer::get_singleton()->start_sample_playback(sp); } @@ -311,6 +320,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackPolyphonic::get_sample_playback() co void AudioStreamPlaybackPolyphonic::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } } void AudioStreamPlaybackPolyphonic::_bind_methods() { diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index e2ac0e6d26..f9787dde2e 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -123,10 +123,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int16_t nibble, diff, step; p_ima_adpcm[i].last_nibble++; - const uint8_t *src_ptr = (const uint8_t *)base->data; - src_ptr += AudioStreamWAV::DATA_PAD; - uint8_t nbb = src_ptr[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i]; + uint8_t nbb = p_src[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i]; nibble = (p_ima_adpcm[i].last_nibble & 1) ? (nbb >> 4) : (nbb & 0xF); step = _ima_adpcm_step_table[p_ima_adpcm[i].step_index]; @@ -179,17 +177,16 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, 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 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); + const uint8_t *ofs_src = (uint8_t *)p_src + p_qoa->data_ofs; + qoa_decode_frame(ofs_src, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec.ptr(), &p_qoa->dec_len); } - uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels; + 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]; @@ -267,7 +264,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, } int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { - if (!base->data || !active) { + if (base->data.is_empty() || !active) { for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } @@ -286,7 +283,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ len *= 2; break; case AudioStreamWAV::FORMAT_QOA: - len = qoa.desc->samples * qoa.desc->channels; + len = qoa.desc.samples * qoa.desc.channels; break; } @@ -300,7 +297,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ int64_t loop_end_fp = ((int64_t)base->loop_end << MIX_FRAC_BITS); int64_t length_fp = ((int64_t)len << MIX_FRAC_BITS); int64_t begin_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_begin_fp : 0; - int64_t end_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_end_fp : length_fp; + int64_t end_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_end_fp : length_fp - MIX_FRAC_LEN; bool is_stereo = base->stereo; int32_t todo = p_frames; @@ -324,8 +321,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ /* audio data */ - uint8_t *dataptr = (uint8_t *)base->data; - const void *data = dataptr + AudioStreamWAV::DATA_PAD; + const uint8_t *data = base->data.ptr() + AudioStreamWAV::DATA_PAD; AudioFrame *dst_buff = p_buffer; if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) { @@ -479,19 +475,14 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackWAV::get_sample_playback() const { void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { sample_playback = p_playback; + if (sample_playback.is_valid()) { + sample_playback->stream_playback = Ref<AudioStreamPlayback>(this); + } } AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {} -AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() { - if (qoa.desc) { - memfree(qoa.desc); - } - - if (qoa.dec) { - memfree(qoa.dec); - } -} +AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {} ///////////////////// @@ -557,9 +548,10 @@ double AudioStreamWAV::get_length() const { 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); + qoa_desc desc = {}; + qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &desc); len = desc.samples * desc.channels; + break; } if (stereo) { @@ -575,22 +567,16 @@ bool AudioStreamWAV::is_monophonic() const { void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) { AudioServer::get_singleton()->lock(); - if (data) { - memfree(data); - data = nullptr; - data_bytes = 0; - } - int datalen = p_data.size(); - if (datalen) { - const uint8_t *r = p_data.ptr(); - int alloc_len = datalen + DATA_PAD * 2; - data = memalloc(alloc_len); //alloc with some padding for interpolation - memset(data, 0, alloc_len); - uint8_t *dataptr = (uint8_t *)data; - memcpy(dataptr + DATA_PAD, r, datalen); - data_bytes = datalen; - } + int src_data_len = p_data.size(); + + data.clear(); + + int alloc_len = src_data_len + DATA_PAD * 2; + data.resize(alloc_len); + memset(data.ptr(), 0, alloc_len); + memcpy(data.ptr() + DATA_PAD, p_data.ptr(), src_data_len); + data_bytes = src_data_len; AudioServer::get_singleton()->unlock(); } @@ -598,13 +584,9 @@ void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) { Vector<uint8_t> AudioStreamWAV::get_data() const { Vector<uint8_t> pv; - if (data) { + if (!data.is_empty()) { pv.resize(data_bytes); - { - uint8_t *w = pv.ptrw(); - uint8_t *dataptr = (uint8_t *)data; - memcpy(w, dataptr + DATA_PAD, data_bytes); - } + memcpy(pv.ptrw(), data.ptr() + DATA_PAD, data_bytes); } return pv; @@ -696,13 +678,12 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { 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); + uint32_t ffp = qoa_decode_header(data.ptr() + 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); + 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 dec_len = sample->qoa.desc.channels * samples_len; + sample->qoa.dec.resize(dec_len); } return sample; @@ -764,7 +745,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,QOA"), "set_format", "get_format"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "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"); @@ -784,10 +765,4 @@ void AudioStreamWAV::_bind_methods() { AudioStreamWAV::AudioStreamWAV() {} -AudioStreamWAV::~AudioStreamWAV() { - if (data) { - memfree(data); - data = nullptr; - data_bytes = 0; - } -} +AudioStreamWAV::~AudioStreamWAV() {} diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 806db675b6..bc62e8883a 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -59,10 +59,10 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { } ima_adpcm[2]; struct QOA_State { - qoa_desc *desc = nullptr; + qoa_desc desc = {}; uint32_t data_ofs = 0; uint32_t frame_len = 0; - int16_t *dec = nullptr; + LocalVector<int16_t> dec; uint32_t dec_len = 0; int64_t cache_pos = -1; int16_t cache[2] = { 0, 0 }; @@ -137,7 +137,7 @@ private: int loop_begin = 0; int loop_end = 0; int mix_rate = 44100; - void *data = nullptr; + LocalVector<uint8_t> data; uint32_t data_bytes = 0; protected: diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index 653a4f4949..53f97fefc9 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -559,6 +559,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2i &p_rect) { bool bit_value = p_pixels > 0; p_pixels = Math::abs(p_pixels); + const int pixels2 = p_pixels * p_pixels; Rect2i r = Rect2i(0, 0, width, height).intersection(p_rect); @@ -588,8 +589,8 @@ void BitMap::grow_mask(int p_pixels, const Rect2i &p_rect) { } } - float d = Point2(j, i).distance_to(Point2(x, y)) - CMP_EPSILON; - if (d > p_pixels) { + float d = Point2(j, i).distance_squared_to(Point2(x, y)) - CMP_EPSILON2; + if (d > pixels2) { continue; } diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 37d9d57722..5e4136f449 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -149,6 +149,25 @@ bool Font::_is_cyclic(const Ref<Font> &p_f, int p_depth) const { return false; } +bool Font::_is_base_cyclic(const Ref<Font> &p_f, int p_depth) const { + ERR_FAIL_COND_V(p_depth > MAX_FALLBACK_DEPTH, true); + if (p_f.is_null()) { + return false; + } + if (p_f == this) { + return true; + } + Ref<FontVariation> fv = p_f; + if (fv.is_valid()) { + return _is_base_cyclic(fv->get_base_font(), p_depth + 1); + } + Ref<SystemFont> fs = p_f; + if (fs.is_valid()) { + return _is_base_cyclic(fs->get_base_font(), p_depth + 1); + } + return false; +} + void Font::reset_state() { _invalidate_rids(); } @@ -1463,8 +1482,8 @@ Error FontFile::_load_bitmap_font(const String &p_path, List<String> *r_image_fi switch (block_type) { case 1: /* info */ { ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, "Invalid BMFont info block size."); - base_size = f->get_16(); - if (base_size <= 0) { + base_size = ABS(static_cast<int16_t>(f->get_16())); + if (base_size == 0) { base_size = 16; } uint8_t flags = f->get_8(); @@ -1757,7 +1776,10 @@ Error FontFile::_load_bitmap_font(const String &p_path, List<String> *r_image_fi if (type == "info") { if (keys.has("size")) { - base_size = keys["size"].to_int(); + base_size = ABS(keys["size"].to_int()); + if (base_size == 0) { + base_size = 16; + } } if (keys.has("outline")) { outline = keys["outline"].to_int(); @@ -2890,13 +2912,13 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { } StringName theme_name = "font"; - List<StringName> theme_types; - ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types); + Vector<StringName> theme_types; + ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), theme_types); ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context(); - List<Ref<Theme>> themes = global_context->get_themes(); + Vector<Ref<Theme>> themes = global_context->get_themes(); if (Engine::get_singleton()->is_editor_hint()) { - themes.push_front(ThemeDB::get_singleton()->get_project_theme()); + themes.insert(0, ThemeDB::get_singleton()->get_project_theme()); } for (const Ref<Theme> &theme : themes) { @@ -2910,7 +2932,7 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { } Ref<Font> f = theme->get_font(theme_name, E); - if (f == this) { + if (_is_base_cyclic(f, 0)) { continue; } if (f.is_valid()) { @@ -2922,7 +2944,7 @@ Ref<Font> FontVariation::_get_base_font_or_default() const { } Ref<Font> f = global_context->get_fallback_theme()->get_font(theme_name, StringName()); - if (f != this) { + if (!_is_base_cyclic(f, 0)) { if (f.is_valid()) { theme_font = f; theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<FontVariation *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); @@ -3258,8 +3280,8 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { } StringName theme_name = "font"; - List<StringName> theme_types; - ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types); + Vector<StringName> theme_types; + ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), theme_types); ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context(); for (const Ref<Theme> &theme : global_context->get_themes()) { @@ -3273,7 +3295,7 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { } Ref<Font> f = theme->get_font(theme_name, E); - if (f == this) { + if (_is_base_cyclic(f, 0)) { continue; } if (f.is_valid()) { @@ -3285,7 +3307,7 @@ Ref<Font> SystemFont::_get_base_font_or_default() const { } Ref<Font> f = global_context->get_fallback_theme()->get_font(theme_name, StringName()); - if (f != this) { + if (!_is_base_cyclic(f, 0)) { if (f.is_valid()) { theme_font = f; theme_font->connect_changed(callable_mp(reinterpret_cast<Font *>(const_cast<SystemFont *>(this)), &Font::_invalidate_rids), CONNECT_REFERENCE_COUNTED); diff --git a/scene/resources/font.h b/scene/resources/font.h index 1878539a3f..68c391c35e 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -99,8 +99,6 @@ protected: virtual void _update_rids_fb(const Ref<Font> &p_f, int p_depth) const; virtual void _update_rids() const; - virtual bool _is_cyclic(const Ref<Font> &p_f, int p_depth) const; - virtual void reset_state() override; #ifndef DISABLE_DEPRECATED @@ -110,6 +108,8 @@ protected: #endif public: + virtual bool _is_cyclic(const Ref<Font> &p_f, int p_depth) const; + virtual bool _is_base_cyclic(const Ref<Font> &p_f, int p_depth) const; virtual void _invalidate_rids(); static constexpr int DEFAULT_FONT_SIZE = 16; @@ -494,6 +494,7 @@ protected: virtual void reset_state() override; public: + virtual Ref<Font> get_base_font() const { return base_font; } virtual Ref<Font> _get_base_font_or_default() const; virtual void set_antialiasing(TextServer::FontAntialiasing p_antialiasing); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 7d121c9d87..927e76e4b2 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -37,7 +37,7 @@ #include "scene/main/scene_tree.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()) { + for (Ref<Material> pass_child = p_pass; pass_child.is_valid(); pass_child = pass_child->get_next_pass()) { ERR_FAIL_COND_MSG(pass_child == this, "Can't set as next_pass one of its parents to prevent crashes due to recursive loop."); } @@ -379,6 +379,8 @@ bool ShaderMaterial::_property_can_revert(const StringName &p_name) const { Variant default_value = RenderingServer::get_singleton()->shader_get_parameter_default(shader->get_rid(), *pr); Variant current_value = get_shader_parameter(*pr); return default_value.get_type() != Variant::NIL && default_value != current_value; + } else if (p_name == "render_priority" || p_name == "next_pass") { + return true; } } return false; @@ -390,6 +392,12 @@ bool ShaderMaterial::_property_get_revert(const StringName &p_name, Variant &r_p if (pr) { r_property = RenderingServer::get_singleton()->shader_get_parameter_default(shader->get_rid(), *pr); return true; + } else if (p_name == "render_priority") { + r_property = 0; + return true; + } else if (p_name == "next_pass") { + r_property = Variant(); + return true; } } return false; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 8b5e438aea..22e2e9138f 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -2251,6 +2251,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo } void ArrayMesh::set_shadow_mesh(const Ref<ArrayMesh> &p_mesh) { + ERR_FAIL_COND_MSG(p_mesh == this, "Cannot set a mesh as its own shadow mesh."); shadow_mesh = p_mesh; if (shadow_mesh.is_valid()) { RS::get_singleton()->mesh_set_shadow_mesh(mesh, shadow_mesh->get_rid()); diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index 8cddfb5840..bf3caa1edd 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -202,6 +202,10 @@ Vector<float> MultiMesh::get_buffer() const { return RS::get_singleton()->multimesh_get_buffer(multimesh); } +void MultiMesh::set_buffer_interpolated(const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev) { + RS::get_singleton()->multimesh_set_buffer_interpolated(multimesh, p_buffer_curr, p_buffer_prev); +} + void MultiMesh::set_mesh(const Ref<Mesh> &p_mesh) { mesh = p_mesh; if (!mesh.is_null()) { @@ -236,6 +240,11 @@ int MultiMesh::get_visible_instance_count() const { return visible_instance_count; } +void MultiMesh::set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality) { + _physics_interpolation_quality = p_quality; + RenderingServer::get_singleton()->multimesh_set_physics_interpolation_quality(multimesh, (RS::MultimeshPhysicsInterpolationQuality)p_quality); +} + void MultiMesh::set_instance_transform(int p_instance, const Transform3D &p_transform) { RenderingServer::get_singleton()->multimesh_instance_set_transform(multimesh, p_instance, p_transform); } @@ -269,6 +278,14 @@ Color MultiMesh::get_instance_custom_data(int p_instance) const { return RenderingServer::get_singleton()->multimesh_instance_get_custom_data(multimesh, p_instance); } +void MultiMesh::reset_instance_physics_interpolation(int p_instance) { + RenderingServer::get_singleton()->multimesh_instance_reset_physics_interpolation(multimesh, p_instance); +} + +void MultiMesh::set_physics_interpolated(bool p_interpolated) { + RenderingServer::get_singleton()->multimesh_set_physics_interpolated(multimesh, p_interpolated); +} + void MultiMesh::set_custom_aabb(const AABB &p_custom) { custom_aabb = p_custom; RS::get_singleton()->multimesh_set_custom_aabb(multimesh, custom_aabb); @@ -328,6 +345,8 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_count"), &MultiMesh::get_instance_count); ClassDB::bind_method(D_METHOD("set_visible_instance_count", "count"), &MultiMesh::set_visible_instance_count); ClassDB::bind_method(D_METHOD("get_visible_instance_count"), &MultiMesh::get_visible_instance_count); + ClassDB::bind_method(D_METHOD("set_physics_interpolation_quality", "quality"), &MultiMesh::set_physics_interpolation_quality); + ClassDB::bind_method(D_METHOD("get_physics_interpolation_quality"), &MultiMesh::get_physics_interpolation_quality); ClassDB::bind_method(D_METHOD("set_instance_transform", "instance", "transform"), &MultiMesh::set_instance_transform); ClassDB::bind_method(D_METHOD("set_instance_transform_2d", "instance", "transform"), &MultiMesh::set_instance_transform_2d); ClassDB::bind_method(D_METHOD("get_instance_transform", "instance"), &MultiMesh::get_instance_transform); @@ -336,6 +355,7 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_color", "instance"), &MultiMesh::get_instance_color); ClassDB::bind_method(D_METHOD("set_instance_custom_data", "instance", "custom_data"), &MultiMesh::set_instance_custom_data); ClassDB::bind_method(D_METHOD("get_instance_custom_data", "instance"), &MultiMesh::get_instance_custom_data); + ClassDB::bind_method(D_METHOD("reset_instance_physics_interpolation", "instance"), &MultiMesh::reset_instance_physics_interpolation); ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &MultiMesh::set_custom_aabb); ClassDB::bind_method(D_METHOD("get_custom_aabb"), &MultiMesh::get_custom_aabb); ClassDB::bind_method(D_METHOD("get_aabb"), &MultiMesh::get_aabb); @@ -343,6 +363,8 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_buffer"), &MultiMesh::get_buffer); ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &MultiMesh::set_buffer); + ClassDB::bind_method(D_METHOD("set_buffer_interpolated", "buffer_curr", "buffer_prev"), &MultiMesh::set_buffer_interpolated); + ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_format", PROPERTY_HINT_ENUM, "2D,3D"), "set_transform_format", "get_transform_format"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_colors"), "set_use_colors", "is_using_colors"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_data"), "set_use_custom_data", "is_using_custom_data"); @@ -369,8 +391,14 @@ void MultiMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "custom_data_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_custom_data_array", "_get_custom_data_array"); #endif + ADD_GROUP("Physics Interpolation", "physics_interpolation"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_quality", PROPERTY_HINT_ENUM, "Fast,High"), "set_physics_interpolation_quality", "get_physics_interpolation_quality"); + BIND_ENUM_CONSTANT(TRANSFORM_2D); BIND_ENUM_CONSTANT(TRANSFORM_3D); + + BIND_ENUM_CONSTANT(INTERP_QUALITY_FAST); + BIND_ENUM_CONSTANT(INTERP_QUALITY_HIGH); } MultiMesh::MultiMesh() { diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h index d7bcb13162..03505bb4d1 100644 --- a/scene/resources/multimesh.h +++ b/scene/resources/multimesh.h @@ -44,6 +44,11 @@ public: TRANSFORM_3D = RS::MULTIMESH_TRANSFORM_3D }; + enum PhysicsInterpolationQuality { + INTERP_QUALITY_FAST, + INTERP_QUALITY_HIGH, + }; + private: Ref<Mesh> mesh; RID multimesh; @@ -53,6 +58,7 @@ private: bool use_custom_data = false; int instance_count = 0; int visible_instance_count = -1; + PhysicsInterpolationQuality _physics_interpolation_quality = INTERP_QUALITY_FAST; protected: static void _bind_methods(); @@ -74,6 +80,8 @@ protected: void set_buffer(const Vector<float> &p_buffer); Vector<float> get_buffer() const; + void set_buffer_interpolated(const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev); + public: void set_mesh(const Ref<Mesh> &p_mesh); Ref<Mesh> get_mesh() const; @@ -93,6 +101,9 @@ public: void set_visible_instance_count(int p_count); int get_visible_instance_count() const; + void set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality); + PhysicsInterpolationQuality get_physics_interpolation_quality() const { return _physics_interpolation_quality; } + void set_instance_transform(int p_instance, const Transform3D &p_transform); void set_instance_transform_2d(int p_instance, const Transform2D &p_transform); Transform3D get_instance_transform(int p_instance) const; @@ -104,6 +115,10 @@ public: void set_instance_custom_data(int p_instance, const Color &p_custom_data); Color get_instance_custom_data(int p_instance) const; + void reset_instance_physics_interpolation(int p_instance); + + void set_physics_interpolated(bool p_interpolated); + void set_custom_aabb(const AABB &p_custom); AABB get_custom_aabb() const; @@ -116,5 +131,6 @@ public: }; VARIANT_ENUM_CAST(MultiMesh::TransformFormat); +VARIANT_ENUM_CAST(MultiMesh::PhysicsInterpolationQuality); #endif // MULTIMESH_H diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 2866ae7219..67ed65df0d 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -61,12 +61,12 @@ void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) { int rlen = iarr.size(); const int *r = iarr.ptr(); + Vector<int> polygon; for (int j = 0; j < rlen; j += 3) { - Polygon polygon; - polygon.indices.resize(3); - polygon.indices.write[0] = r[j + 0] + from; - polygon.indices.write[1] = r[j + 1] + from; - polygon.indices.write[2] = r[j + 2] + from; + polygon.resize(3); + polygon.write[0] = r[j + 0] + from; + polygon.write[1] = r[j + 1] + from; + polygon.write[2] = r[j + 2] + from; polygons.push_back(polygon); } } @@ -318,7 +318,7 @@ void NavigationMesh::_set_polygons(const Array &p_array) { RWLockWrite write_lock(rwlock); polygons.resize(p_array.size()); for (int i = 0; i < p_array.size(); i++) { - polygons.write[i].indices = p_array[i]; + polygons.write[i] = p_array[i]; } notify_property_list_changed(); } @@ -328,17 +328,26 @@ Array NavigationMesh::_get_polygons() const { Array ret; ret.resize(polygons.size()); for (int i = 0; i < ret.size(); i++) { - ret[i] = polygons[i].indices; + ret[i] = polygons[i]; } return ret; } +void NavigationMesh::set_polygons(const Vector<Vector<int>> &p_polygons) { + RWLockWrite write_lock(rwlock); + polygons = p_polygons; + notify_property_list_changed(); +} + +Vector<Vector<int>> NavigationMesh::get_polygons() const { + RWLockRead read_lock(rwlock); + return polygons; +} + void NavigationMesh::add_polygon(const Vector<int> &p_polygon) { RWLockWrite write_lock(rwlock); - Polygon polygon; - polygon.indices = p_polygon; - polygons.push_back(polygon); + polygons.push_back(p_polygon); notify_property_list_changed(); } @@ -350,7 +359,7 @@ int NavigationMesh::get_polygon_count() const { Vector<int> NavigationMesh::get_polygon(int p_idx) { RWLockRead read_lock(rwlock); ERR_FAIL_INDEX_V(p_idx, polygons.size(), Vector<int>()); - return polygons[p_idx].indices; + return polygons[p_idx]; } void NavigationMesh::clear_polygons() { @@ -367,19 +376,13 @@ void NavigationMesh::clear() { void NavigationMesh::set_data(const Vector<Vector3> &p_vertices, const Vector<Vector<int>> &p_polygons) { RWLockWrite write_lock(rwlock); vertices = p_vertices; - polygons.resize(p_polygons.size()); - for (int i = 0; i < p_polygons.size(); i++) { - polygons.write[i].indices = p_polygons[i]; - } + polygons = p_polygons; } void NavigationMesh::get_data(Vector<Vector3> &r_vertices, Vector<Vector<int>> &r_polygons) { RWLockRead read_lock(rwlock); r_vertices = vertices; - r_polygons.resize(polygons.size()); - for (int i = 0; i < polygons.size(); i++) { - r_polygons.write[i] = polygons[i].indices; - } + r_polygons = polygons; } #ifdef DEBUG_ENABLED diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index 0ec2cc1bb1..1b3db5bac2 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -33,16 +33,14 @@ #include "core/os/rw_lock.h" #include "scene/resources/mesh.h" +#include "servers/navigation/navigation_globals.h" class NavigationMesh : public Resource { GDCLASS(NavigationMesh, Resource); RWLock rwlock; Vector<Vector3> vertices; - struct Polygon { - Vector<int> indices; - }; - Vector<Polygon> polygons; + Vector<Vector<int>> polygons; Ref<ArrayMesh> debug_mesh; protected: @@ -80,8 +78,8 @@ public: }; protected: - float cell_size = 0.25f; // Must match ProjectSettings default 3D cell_size and NavigationServer NavMap cell_size. - float cell_height = 0.25f; // Must match ProjectSettings default 3D cell_height and NavigationServer NavMap cell_height. + float cell_size = NavigationDefaults3D::navmesh_cell_size; + float cell_height = NavigationDefaults3D::navmesh_cell_height; float border_size = 0.0f; float agent_height = 1.5f; float agent_radius = 0.5f; @@ -96,7 +94,7 @@ protected: float detail_sample_max_error = 1.0f; SamplePartitionType partition_type = SAMPLE_PARTITION_WATERSHED; - ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_MESH_INSTANCES; + ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_BOTH; uint32_t collision_mask = 0xFFFFFFFF; SourceGeometryMode source_geometry_mode = SOURCE_GEOMETRY_ROOT_NODE_CHILDREN; @@ -194,6 +192,8 @@ public: int get_polygon_count() const; Vector<int> get_polygon(int p_idx); void clear_polygons(); + void set_polygons(const Vector<Vector<int>> &p_polygons); + Vector<Vector<int>> get_polygons() const; void clear(); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 900629f5f8..d6fe4385c4 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -369,6 +369,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); } } + if (value.get_type() == Variant::ARRAY) { Array set_array = value; value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); @@ -383,25 +384,20 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary dictionary = value; - const Array keys = dictionary.keys(); - const Array values = dictionary.values(); - - if (has_local_resource(values) || has_local_resource(keys)) { - Array duplicated_keys = keys.duplicate(true); - Array duplicated_values = values.duplicate(true); - duplicated_keys = setup_resources_in_array(duplicated_keys, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); - duplicated_values = setup_resources_in_array(duplicated_values, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + value = setup_resources_in_dictionary(set_dict, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); - dictionary.clear(); + bool is_get_valid = false; + Variant get_value = node->get(snames[nprops[j].name], &is_get_valid); - for (int dictionary_index = 0; dictionary_index < keys.size(); dictionary_index++) { - dictionary[duplicated_keys[dictionary_index]] = duplicated_values[dictionary_index]; + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); } - - value = dictionary; } } @@ -531,7 +527,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { bool valid; Array array = dnp.base->get(dnp.property, &valid); - ERR_CONTINUE(!valid); + ERR_CONTINUE_EDMSG(!valid, vformat("Failed to get property '%s' from node '%s'.", dnp.property, dnp.base->get_name())); array = array.duplicate(); array.resize(paths.size()); @@ -539,6 +535,30 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { array.set(i, dnp.base->get_node_or_null(paths[i])); } dnp.base->set(dnp.property, array); + } else if (dnp.value.get_type() == Variant::DICTIONARY) { + Dictionary paths = dnp.value; + + bool valid; + Dictionary dict = dnp.base->get(dnp.property, &valid); + ERR_CONTINUE_EDMSG(!valid, vformat("Failed to get property '%s' from node '%s'.", dnp.property, dnp.base->get_name())); + dict = dict.duplicate(); + bool convert_key = dict.get_typed_key_builtin() == Variant::OBJECT && + ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node"); + bool convert_value = dict.get_typed_value_builtin() == Variant::OBJECT && + ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node"); + + for (int i = 0; i < paths.size(); i++) { + Variant key = paths.get_key_at_index(i); + if (convert_key) { + key = dnp.base->get_node_or_null(key); + } + Variant value = paths.get_value_at_index(i); + if (convert_value) { + value = dnp.base->get_node_or_null(value); + } + dict[key] = value; + } + dnp.base->set(dnp.property, dict); } else { dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value)); } @@ -641,6 +661,26 @@ Array SceneState::setup_resources_in_array(Array &p_array_to_scan, const SceneSt return p_array_to_scan; } +Dictionary SceneState::setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const { + const Array keys = p_dictionary_to_scan.keys(); + const Array values = p_dictionary_to_scan.values(); + + if (has_local_resource(values) || has_local_resource(keys)) { + Array duplicated_keys = keys.duplicate(true); + Array duplicated_values = values.duplicate(true); + + duplicated_keys = setup_resources_in_array(duplicated_keys, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state); + duplicated_values = setup_resources_in_array(duplicated_values, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state); + p_dictionary_to_scan.clear(); + + for (int i = 0; i < keys.size(); i++) { + p_dictionary_to_scan[duplicated_keys[i]] = duplicated_values[i]; + } + } + + return p_dictionary_to_scan; +} + bool SceneState::has_local_resource(const Array &p_array) const { for (int i = 0; i < p_array.size(); i++) { Ref<Resource> res = p_array[i]; @@ -810,6 +850,53 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has value = new_array; } } + } else if (E.type == Variant::DICTIONARY && E.hint == PROPERTY_HINT_TYPE_STRING) { + int key_value_separator = E.hint_string.find(";"); + if (key_value_separator >= 0) { + int key_subtype_separator = E.hint_string.find(":"); + String key_subtype_string = E.hint_string.substr(0, key_subtype_separator); + int key_slash_pos = key_subtype_string.find("/"); + PropertyHint key_subtype_hint = PropertyHint::PROPERTY_HINT_NONE; + if (key_slash_pos >= 0) { + key_subtype_hint = PropertyHint(key_subtype_string.get_slice("/", 1).to_int()); + key_subtype_string = key_subtype_string.substr(0, key_slash_pos); + } + Variant::Type key_subtype = Variant::Type(key_subtype_string.to_int()); + bool convert_key = key_subtype == Variant::OBJECT && key_subtype_hint == PROPERTY_HINT_NODE_TYPE; + + int value_subtype_separator = E.hint_string.find(":", key_value_separator) - (key_value_separator + 1); + String value_subtype_string = E.hint_string.substr(key_value_separator + 1, value_subtype_separator); + int value_slash_pos = value_subtype_string.find("/"); + PropertyHint value_subtype_hint = PropertyHint::PROPERTY_HINT_NONE; + if (value_slash_pos >= 0) { + value_subtype_hint = PropertyHint(value_subtype_string.get_slice("/", 1).to_int()); + value_subtype_string = value_subtype_string.substr(0, value_slash_pos); + } + Variant::Type value_subtype = Variant::Type(value_subtype_string.to_int()); + bool convert_value = value_subtype == Variant::OBJECT && value_subtype_hint == PROPERTY_HINT_NODE_TYPE; + + if (convert_key || convert_value) { + use_deferred_node_path_bit = true; + Dictionary dict = value; + Dictionary new_dict; + for (int i = 0; i < dict.size(); i++) { + Variant new_key = dict.get_key_at_index(i); + if (convert_key && new_key.get_type() == Variant::OBJECT) { + if (Node *n = Object::cast_to<Node>(new_key)) { + new_key = p_node->get_path_to(n); + } + } + Variant new_value = dict.get_value_at_index(i); + if (convert_value && new_value.get_type() == Variant::OBJECT) { + if (Node *n = Object::cast_to<Node>(new_value)) { + new_value = p_node->get_path_to(n); + } + } + new_dict[new_key] = new_value; + } + value = new_dict; + } + } } if (!pinned_props.has(name)) { @@ -1267,25 +1354,6 @@ Ref<SceneState> SceneState::get_base_scene_state() const { return Ref<SceneState>(); } -void SceneState::update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene) { - ERR_FAIL_COND(p_packed_scene.is_null()); - - for (const NodeData &nd : nodes) { - if (nd.instance >= 0) { - if (!(nd.instance & FLAG_INSTANCE_IS_PLACEHOLDER)) { - int instance_id = nd.instance & FLAG_MASK; - Ref<PackedScene> original_packed_scene = variants[instance_id]; - if (original_packed_scene.is_valid()) { - if (original_packed_scene->get_path() == p_path) { - variants.remove_at(instance_id); - variants.insert(instance_id, p_packed_scene); - } - } - } - } - } -} - int SceneState::find_node_by_path(const NodePath &p_node) const { ERR_FAIL_COND_V_MSG(node_path_cache.is_empty(), -1, "This operation requires the node cache to have been built."); @@ -1880,6 +1948,16 @@ Vector<NodePath> SceneState::get_editable_instances() const { return editable_instances; } +Ref<Resource> SceneState::get_sub_resource(const String &p_path) { + for (const Variant &v : variants) { + const Ref<Resource> &res = v; + if (res.is_valid() && res->get_path() == p_path) { + return res; + } + } + return Ref<Resource>(); +} + //add int SceneState::add_name(const StringName &p_name) { @@ -2199,7 +2277,7 @@ void PackedScene::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_bundled_scene"), &PackedScene::_get_bundled_scene); ClassDB::bind_method(D_METHOD("get_state"), &PackedScene::get_state); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_bundled"), "_set_bundled_scene", "_get_bundled_scene"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_bundled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "_set_bundled_scene", "_get_bundled_scene"); BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED); BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE); diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index e26b9f7b90..9f8088910f 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -158,6 +158,7 @@ public: Node *instantiate(GenEditState p_edit_state) const; Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const; + Dictionary setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const; Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const; bool has_local_resource(const Array &p_array) const; @@ -195,6 +196,7 @@ public: bool has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method, bool p_no_inheritance = false); Vector<NodePath> get_editable_instances() const; + Ref<Resource> get_sub_resource(const String &p_path); //build API diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index ee986f5820..8cfe4c92b7 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -110,6 +110,7 @@ void ParticleProcessMaterial::init_shaders() { shader_names->emission_ring_height = "emission_ring_height"; shader_names->emission_ring_radius = "emission_ring_radius"; shader_names->emission_ring_inner_radius = "emission_ring_inner_radius"; + shader_names->emission_ring_cone_angle = "emission_ring_cone_angle"; shader_names->emission_shape_offset = "emission_shape_offset"; shader_names->emission_shape_scale = "emission_shape_scale"; @@ -269,6 +270,7 @@ void ParticleProcessMaterial::_update_shader() { code += "uniform float " + shader_names->emission_ring_height + ";\n"; code += "uniform float " + shader_names->emission_ring_radius + ";\n"; code += "uniform float " + shader_names->emission_ring_inner_radius + ";\n"; + code += "uniform float " + shader_names->emission_ring_cone_angle + ";\n"; } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; @@ -643,8 +645,14 @@ void ParticleProcessMaterial::_update_shader() { code += " pos = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n"; } if (emission_shape == EMISSION_SHAPE_RING) { + code += " float radius_clamped = max(0.001, emission_ring_radius);\n"; + code += " float top_radius = max(radius_clamped - tan(radians(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);\n"; + code += " float y_pos = rand_from_seed(alt_seed);\n"; + code += " float skew = max(min(radius_clamped, top_radius) / max(radius_clamped, top_radius), 0.5);\n"; + code += " y_pos = radius_clamped < top_radius ? pow(y_pos, skew) : 1.0 - pow(y_pos, skew);\n"; code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\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 += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n"; + code += " ring_random_radius = mix(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);\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 (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n"; @@ -662,7 +670,7 @@ void ParticleProcessMaterial::_update_shader() { code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n"; code += " ) * ortho_axis;\n"; code += " ortho_axis = normalize(ortho_axis);\n"; - code += " pos = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n"; + code += " pos = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;\n"; } code += " }\n"; code += " return pos * emission_shape_scale + emission_shape_offset;\n"; @@ -1615,6 +1623,11 @@ void ParticleProcessMaterial::set_emission_ring_inner_radius(real_t p_radius) { RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius); } +void ParticleProcessMaterial::set_emission_ring_cone_angle(real_t p_angle) { + emission_ring_cone_angle = p_angle; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_cone_angle, p_angle); +} + void ParticleProcessMaterial::set_inherit_velocity_ratio(double p_ratio) { inherit_emitter_velocity_ratio = p_ratio; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->inherit_emitter_velocity_ratio, p_ratio); @@ -1664,6 +1677,10 @@ real_t ParticleProcessMaterial::get_emission_ring_inner_radius() const { return emission_ring_inner_radius; } +real_t ParticleProcessMaterial::get_emission_ring_cone_angle() const { + return emission_ring_cone_angle; +} + void ParticleProcessMaterial::set_emission_shape_offset(const Vector3 &p_emission_shape_offset) { emission_shape_offset = p_emission_shape_offset; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_offset, p_emission_shape_offset); @@ -2015,6 +2032,9 @@ void ParticleProcessMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticleProcessMaterial::set_emission_ring_inner_radius); ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticleProcessMaterial::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &ParticleProcessMaterial::set_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &ParticleProcessMaterial::get_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("set_emission_shape_offset", "emission_shape_offset"), &ParticleProcessMaterial::set_emission_shape_offset); ClassDB::bind_method(D_METHOD("get_emission_shape_offset"), &ParticleProcessMaterial::get_emission_shape_offset); @@ -2096,9 +2116,10 @@ void ParticleProcessMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_color_texture", "get_emission_color_texture"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle"); ADD_SUBGROUP("Angle", ""); ADD_MIN_MAX_PROPERTY("angle", "-720,720,0.1,or_less,or_greater,degrees", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE); @@ -2276,6 +2297,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() : set_emission_ring_height(1); set_emission_ring_radius(1); set_emission_ring_inner_radius(0); + set_emission_ring_cone_angle(90); set_emission_shape_offset(Vector3(0.0, 0.0, 0.0)); set_emission_shape_scale(Vector3(1.0, 1.0, 1.0)); diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index 25046b51cd..12e3fbb64e 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -259,6 +259,7 @@ private: StringName emission_ring_height; StringName emission_ring_radius; StringName emission_ring_inner_radius; + StringName emission_ring_cone_angle; StringName emission_shape_offset; StringName emission_shape_scale; @@ -325,6 +326,7 @@ private: real_t emission_ring_height = 0.0f; real_t emission_ring_radius = 0.0f; real_t emission_ring_inner_radius = 0.0f; + real_t emission_ring_cone_angle = 0.0f; int emission_point_count = 1; Vector3 emission_shape_offset; Vector3 emission_shape_scale; @@ -417,6 +419,7 @@ public: void set_emission_ring_height(real_t p_height); void set_emission_ring_radius(real_t p_radius); void set_emission_ring_inner_radius(real_t p_radius); + void set_emission_ring_cone_angle(real_t p_angle); void set_emission_point_count(int p_count); EmissionShape get_emission_shape() const; @@ -429,6 +432,7 @@ public: real_t get_emission_ring_height() const; real_t get_emission_ring_radius() const; real_t get_emission_ring_inner_radius() const; + real_t get_emission_ring_cone_angle() const; int get_emission_point_count() const; void set_turbulence_enabled(bool p_turbulence_enabled); diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp index 002db30379..06b5ec6d5a 100644 --- a/scene/resources/portable_compressed_texture.cpp +++ b/scene/resources/portable_compressed_texture.cpp @@ -345,7 +345,7 @@ void PortableCompressedTexture2D::_bind_methods() { ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("set_keep_all_compressed_buffers", "keep"), &PortableCompressedTexture2D::set_keep_all_compressed_buffers); ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override", PROPERTY_HINT_NONE, "suffix:px"), "set_size_override", "get_size_override"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer"); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 90102e44e4..d531eea311 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -35,17 +35,12 @@ #include "core/io/missing_resource.h" #include "core/object/script_language.h" -// 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 _printerr() ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data()); - /// +void ResourceLoaderText::_printerr() { + ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data()); +} + Ref<Resource> ResourceLoaderText::get_resource() { return resource; } @@ -617,14 +612,29 @@ Error ResourceLoaderText::load() { } } - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = res->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); + if (ClassDB::has_property(res->get_class_name(), assign)) { + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = res->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); + } + } + } + + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); + } } } } @@ -744,14 +754,29 @@ Error ResourceLoaderText::load() { } } - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = resource->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); + if (ClassDB::has_property(resource->get_class_name(), assign)) { + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = resource->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); + } + } + } + + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = resource->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); + } } } } @@ -1647,6 +1672,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, } break; case Variant::DICTIONARY: { Dictionary d = p_variant; + _find_resources(d.get_typed_key_script()); + _find_resources(d.get_typed_value_script()); List<Variant> keys; d.get_key_list(&keys); for (const Variant &E : keys) { @@ -1734,7 +1761,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (load_steps > 1) { title += "load_steps=" + itos(load_steps) + " "; } - title += "format=" + itos(use_compat ? FORMAT_VERSION_COMPAT : FORMAT_VERSION) + ""; + title += "format=" + itos(use_compat ? ResourceLoaderText::FORMAT_VERSION_COMPAT : ResourceLoaderText::FORMAT_VERSION) + ""; ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true); diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index b5542f77ba..8397bc985f 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -38,6 +38,17 @@ #include "scene/resources/packed_scene.h" class ResourceLoaderText { +public: + enum { + // 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. + FORMAT_VERSION = 4, + // For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray. + FORMAT_VERSION_COMPAT = 3, + }; + +private: bool translation_remapped = false; String local_path; String res_path; @@ -100,6 +111,7 @@ class ResourceLoaderText { static Error _parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str); static Error _parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str); + void _printerr(); VariantParser::ResourceParser rp; diff --git a/scene/gui/control.compat.inc b/scene/resources/shader.compat.inc index 96ee720d90..b68020605f 100644 --- a/scene/gui/control.compat.inc +++ b/scene/resources/shader.compat.inc @@ -1,5 +1,5 @@ /**************************************************************************/ -/* control.compat.inc */ +/* shader.compat.inc */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,19 +30,17 @@ #ifndef DISABLE_DEPRECATED -void Control::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Control::get_theme_icon, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Control::get_theme_stylebox, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Control::get_theme_font, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Control::get_theme_font_size, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Control::get_theme_color, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Control::get_theme_constant, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Control::has_theme_icon, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Control::has_theme_stylebox, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Control::has_theme_font, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Control::has_theme_font_size, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Control::has_theme_color, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Control::has_theme_constant, DEFVAL("")); +void Shader::_set_default_texture_parameter_bind_compat_95126(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index) { + set_default_texture_parameter(p_name, p_texture, p_index); } -#endif +Ref<Texture2D> Shader::_get_default_texture_parameter_bind_compat_95126(const StringName &p_name, int p_index) const { + return get_default_texture_parameter(p_name, p_index); +} + +void Shader::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("set_default_texture_parameter", "name", "texture", "index"), &Shader::_set_default_texture_parameter_bind_compat_95126, DEFVAL(0)); + ClassDB::bind_compatibility_method(D_METHOD("get_default_texture_parameter", "name", "index"), &Shader::_get_default_texture_parameter_bind_compat_95126, DEFVAL(0)); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index dfe5bd4a47..46d38146a6 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "shader.h" +#include "shader.compat.inc" #include "core/io/file_access.h" #include "servers/rendering/shader_language.h" @@ -157,7 +158,7 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr #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) { + if (pattern_ref.is_valid()) { RegExMatch *match = pattern_ref.ptr(); const RegEx pattern_tip("\\/\\*\\*([\\s\\S]*?)\\*/"); Ref<RegExMatch> pattern_tip_ref = pattern_tip.search(match->get_string(0)); @@ -185,10 +186,10 @@ RID Shader::get_rid() const { return shader; } -void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index) { +void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index) { if (p_texture.is_valid()) { if (!default_textures.has(p_name)) { - default_textures[p_name] = HashMap<int, Ref<Texture2D>>(); + default_textures[p_name] = HashMap<int, Ref<Texture>>(); } default_textures[p_name][p_index] = p_texture; RS::get_singleton()->shader_set_default_texture_parameter(shader, p_name, p_texture->get_rid(), p_index); @@ -206,7 +207,7 @@ void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<T emit_changed(); } -Ref<Texture2D> Shader::get_default_texture_parameter(const StringName &p_name, int p_index) const { +Ref<Texture> Shader::get_default_texture_parameter(const StringName &p_name, int p_index) const { if (default_textures.has(p_name) && default_textures[p_name].has(p_index)) { return default_textures[p_name][p_index]; } @@ -214,7 +215,7 @@ Ref<Texture2D> Shader::get_default_texture_parameter(const StringName &p_name, i } void Shader::get_default_texture_parameter_list(List<StringName> *r_textures) const { - for (const KeyValue<StringName, HashMap<int, Ref<Texture2D>>> &E : default_textures) { + for (const KeyValue<StringName, HashMap<int, Ref<Texture>>> &E : default_textures) { r_textures->push_back(E.key); } } diff --git a/scene/resources/shader.h b/scene/resources/shader.h index 921143c219..682fbd7ea6 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -58,7 +58,7 @@ private: String code; String include_path; - HashMap<StringName, HashMap<int, Ref<Texture2D>>> default_textures; + HashMap<StringName, HashMap<int, Ref<Texture>>> default_textures; void _dependency_changed(); void _recompile(); @@ -66,6 +66,12 @@ private: Array _get_shader_uniform_list(bool p_get_groups = false); protected: +#ifndef DISABLE_DEPRECATED + void _set_default_texture_parameter_bind_compat_95126(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index = 0); + Ref<Texture2D> _get_default_texture_parameter_bind_compat_95126(const StringName &p_name, int p_index = 0) const; + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + static void _bind_methods(); public: @@ -80,8 +86,8 @@ public: void get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups = false) const; - void set_default_texture_parameter(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index = 0); - Ref<Texture2D> get_default_texture_parameter(const StringName &p_name, int p_index = 0) const; + void set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index = 0); + Ref<Texture> get_default_texture_parameter(const StringName &p_name, int p_index = 0) const; void get_default_texture_parameter_list(List<StringName> *r_textures) const; virtual bool is_text_shader() const; diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp index 2c1d3d4a4c..c2d77ec7ff 100644 --- a/scene/resources/skeleton_profile.cpp +++ b/scene/resources/skeleton_profile.cpp @@ -132,7 +132,10 @@ void SkeletonProfile::_validate_property(PropertyInfo &p_property) const { if (p_property.name == ("root_bone") || p_property.name == ("scale_base_bone")) { String hint = ""; for (int i = 0; i < bones.size(); i++) { - hint += i == 0 ? String(bones[i].bone_name) : "," + String(bones[i].bone_name); + if (i > 0) { + hint += ","; + } + hint += String(bones[i].bone_name); } p_property.hint_string = hint; } diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index 6e43ea9b17..dac0ceaa78 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -106,6 +106,12 @@ bool SpriteFrames::has_animation(const StringName &p_anim) const { return animations.has(p_anim); } +void SpriteFrames::duplicate_animation(const StringName &p_from, const StringName &p_to) { + ERR_FAIL_COND_MSG(!animations.has(p_from), vformat("SpriteFrames doesn't have animation '%s'.", p_from)); + ERR_FAIL_COND_MSG(animations.has(p_to), vformat("Animation '%s' already exists.", p_to)); + animations[p_to] = animations[p_from]; +} + void SpriteFrames::remove_animation(const StringName &p_anim) { animations.erase(p_anim); } @@ -246,6 +252,7 @@ void SpriteFrames::get_argument_options(const StringName &p_function, int p_idx, void SpriteFrames::_bind_methods() { ClassDB::bind_method(D_METHOD("add_animation", "anim"), &SpriteFrames::add_animation); ClassDB::bind_method(D_METHOD("has_animation", "anim"), &SpriteFrames::has_animation); + ClassDB::bind_method(D_METHOD("duplicate_animation", "anim_from", "anim_to"), &SpriteFrames::duplicate_animation); ClassDB::bind_method(D_METHOD("remove_animation", "anim"), &SpriteFrames::remove_animation); ClassDB::bind_method(D_METHOD("rename_animation", "anim", "newname"), &SpriteFrames::rename_animation); diff --git a/scene/resources/sprite_frames.h b/scene/resources/sprite_frames.h index 0e0bb28d71..8d5b4232cf 100644 --- a/scene/resources/sprite_frames.h +++ b/scene/resources/sprite_frames.h @@ -60,6 +60,7 @@ protected: public: void add_animation(const StringName &p_anim); bool has_animation(const StringName &p_anim) const; + void duplicate_animation(const StringName &p_from, const StringName &p_to); void remove_animation(const StringName &p_anim); void rename_animation(const StringName &p_prev, const StringName &p_next); diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp index 52d02e92cb..60b91ef0cb 100644 --- a/scene/resources/style_box_flat.cpp +++ b/scene/resources/style_box_flat.cpp @@ -300,8 +300,8 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; - const float x_skew = -skew.x * (y - ring_rect.get_center().y); - const float y_skew = -skew.y * (x - ring_rect.get_center().x); + const float x_skew = -skew.x * (y - style_rect.get_center().y); + const float y_skew = -skew.y * (x - style_rect.get_center().x); verts.push_back(Vector2(x + x_skew, y + y_skew)); colors.push_back(color); } diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 3273bc3c00..6921885ee0 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -792,7 +792,7 @@ void SurfaceTool::deindex() { } void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) { - ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh"); Array arr = p_existing->surface_get_arrays(p_surface); ERR_FAIL_COND(arr.size() != RS::ARRAY_MAX); @@ -968,7 +968,7 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { } 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"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from() must be a valid object of type Mesh"); clear(); primitive = p_existing->surface_get_primitive_type(p_surface); @@ -983,7 +983,7 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { } void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) { - ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh"); clear(); primitive = p_existing->surface_get_primitive_type(p_surface); @@ -1023,7 +1023,7 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur } void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) { - ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::append_from() must be a valid object of type Mesh"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::append_from() must be a valid object of type Mesh"); if (vertex_array.size() == 0) { primitive = p_existing->surface_get_primitive_type(p_surface); diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 5da47966dd..29a8541cb0 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -173,6 +173,7 @@ void TextParagraph::_shape_lines() { v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; } + Size2i range = TS->shaped_text_get_range(rid); if (h_offset > 0) { // Dropcap, flow around. PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width - h_offset, 0, brk_flags); @@ -182,7 +183,7 @@ void TextParagraph::_shape_lines() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - start = line_breaks[i + 1]; + start = (i < line_breaks.size() - 2) ? line_breaks[i + 2] : range.y; lines_rid.push_back(line); if (v_offset < h) { break; @@ -192,13 +193,15 @@ void TextParagraph::_shape_lines() { } } // Use fixed for the rest of lines. - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags); - for (int i = 0; i < line_breaks.size(); i = i + 2) { - RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); - if (!tab_stops.is_empty()) { - TS->shaped_text_tab_align(line, tab_stops); + if (start == 0 || start < range.y) { + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags); + for (int i = 0; i < line_breaks.size(); i = i + 2) { + RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); + if (!tab_stops.is_empty()) { + TS->shaped_text_tab_align(line, tab_stops); + } + lines_rid.push_back(line); } - lines_rid.push_back(line); } BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM; @@ -550,18 +553,42 @@ Size2 TextParagraph::get_size() const { _THREAD_SAFE_METHOD_ const_cast<TextParagraph *>(this)->_shape_lines(); + + float h_offset = 0.f; + float v_offset = 0.f; + if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { + h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; + v_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y; + } else { + h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y; + v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; + } + Size2 size; int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size(); for (int i = 0; i < visible_lines; i++) { Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (h_offset > 0 && i <= dropcap_lines) { + lsize.x += h_offset; + } size.x = MAX(size.x, lsize.x); size.y += lsize.y; } else { + if (h_offset > 0 && i <= dropcap_lines) { + lsize.y += h_offset; + } size.x += lsize.x; size.y = MAX(size.y, lsize.y); } } + if (h_offset > 0) { + if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { + size.y = MAX(size.y, v_offset); + } else { + size.x = MAX(size.x, v_offset); + } + } return size; } @@ -624,7 +651,7 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } @@ -793,7 +820,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } @@ -895,7 +922,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index b6cd6f6dfe..1ac90b8980 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -1250,14 +1250,12 @@ void Theme::get_type_list(List<StringName> *p_list) const { } } -void Theme::get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variation, List<StringName> *p_list) { - ERR_FAIL_NULL(p_list); - +void Theme::get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variation, Vector<StringName> &r_result) { // Build the dependency chain for type variations. if (p_type_variation != StringName()) { StringName variation_name = p_type_variation; while (variation_name != StringName()) { - p_list->push_back(variation_name); + r_result.push_back(variation_name); variation_name = get_type_variation_base(variation_name); // If we have reached the base type dependency, it's safe to stop (assuming no funny business was done to the Theme). @@ -1268,7 +1266,7 @@ void Theme::get_type_dependencies(const StringName &p_base_type, const StringNam } // Continue building the chain using native class hierarchy. - ThemeDB::get_singleton()->get_native_type_dependencies(p_base_type, p_list); + ThemeDB::get_singleton()->get_native_type_dependencies(p_base_type, r_result); } // Internal methods for getting lists as a Vector of String (compatible with public API). diff --git a/scene/resources/theme.h b/scene/resources/theme.h index 73f1167c29..14ad4ae0fd 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -216,7 +216,7 @@ public: void add_type(const StringName &p_theme_type); void remove_type(const StringName &p_theme_type); void get_type_list(List<StringName> *p_list) const; - void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, List<StringName> *p_list); + void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, Vector<StringName> &r_result); void merge_with(const Ref<Theme> &p_other); void clear(); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 4bedcb1820..d0e55f4065 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -32,6 +32,7 @@ #include "core/templates/rb_map.h" #include "core/templates/vmap.h" +#include "core/variant/variant_utility.h" #include "servers/rendering/shader_types.h" #include "visual_shader_nodes.h" #include "visual_shader_particle_nodes.h" @@ -412,7 +413,6 @@ String VisualShaderNode::get_warning(Shader::Mode p_mode, VisualShader::Type p_t } VisualShaderNode::Category VisualShaderNode::get_category() const { - WARN_PRINT(get_caption() + " is missing a category."); return CATEGORY_NONE; } @@ -833,7 +833,7 @@ VisualShader::Type VisualShader::get_shader_type() const { } void VisualShader::add_varying(const String &p_name, VaryingMode p_mode, VaryingType p_type) { - ERR_FAIL_COND(!p_name.is_valid_identifier()); + ERR_FAIL_COND(!p_name.is_valid_ascii_identifier()); ERR_FAIL_INDEX((int)p_mode, (int)VARYING_MODE_MAX); ERR_FAIL_INDEX((int)p_type, (int)VARYING_TYPE_MAX); ERR_FAIL_COND(varyings.has(p_name)); @@ -898,6 +898,44 @@ VisualShader::VaryingType VisualShader::get_varying_type(const String &p_name) { return varyings[p_name].type; } +void VisualShader::_set_preview_shader_parameter(const String &p_name, const Variant &p_value) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + if (p_value.get_type() == Variant::NIL) { + if (!preview_params.erase(p_name)) { + return; + } + } else { + Variant *var = preview_params.getptr(p_name); + if (var != nullptr && *var == p_value) { + return; + } + preview_params.insert(p_name, p_value); + } + emit_changed(); + } +#endif // TOOLS_ENABLED +} + +Variant VisualShader::_get_preview_shader_parameter(const String &p_name) const { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + ERR_FAIL_COND_V(!preview_params.has(p_name), Variant()); + return preview_params.get(p_name); + } +#endif // TOOLS_ENABLED + return Variant(); +} + +bool VisualShader::_has_preview_shader_parameter(const String &p_name) const { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return preview_params.has(p_name); + } +#endif // TOOLS_ENABLED + return false; +} + void VisualShader::add_node(Type p_type, const Ref<VisualShaderNode> &p_node, const Vector2 &p_position, int p_id) { ERR_FAIL_COND(p_node.is_null()); ERR_FAIL_COND(p_id < 2); @@ -1696,7 +1734,16 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { } _queue_update(); return true; - } else if (prop_name.begins_with("nodes/")) { + } +#ifdef TOOLS_ENABLED + else if (prop_name.begins_with("preview_params/") && Engine::get_singleton()->is_editor_hint()) { + String param_name = prop_name.get_slicec('/', 1); + Variant value = VariantUtilityFunctions::str_to_var(p_value); + preview_params[param_name] = value; + return true; + } +#endif + else if (prop_name.begins_with("nodes/")) { String typestr = prop_name.get_slicec('/', 1); Type type = TYPE_VERTEX; for (int i = 0; i < TYPE_MAX; i++) { @@ -1768,7 +1815,19 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { r_ret = String(); } return true; - } else if (prop_name.begins_with("nodes/")) { + } +#ifdef TOOLS_ENABLED + else if (prop_name.begins_with("preview_params/") && Engine::get_singleton()->is_editor_hint()) { + String param_name = prop_name.get_slicec('/', 1); + if (preview_params.has(param_name)) { + r_ret = VariantUtilityFunctions::var_to_str(preview_params[param_name]); + } else { + r_ret = String(); + } + return true; + } +#endif // TOOLS_ENABLED + else if (prop_name.begins_with("nodes/")) { String typestr = prop_name.get_slicec('/', 1); Type type = TYPE_VERTEX; for (int i = 0; i < TYPE_MAX; i++) { @@ -1825,7 +1884,7 @@ void VisualShader::reset_state() { void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { //mode - p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky,Fog")); + p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Spatial,CanvasItem,Particles,Sky,Fog")); //render modes HashMap<String, String> blend_mode_enums; @@ -1865,6 +1924,14 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("varyings"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + for (const KeyValue<String, Variant> &E : preview_params) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("%s/%s", PNAME("preview_params"), E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + } + } +#endif // TOOLS_ENABLED + for (int i = 0; i < TYPE_MAX; i++) { for (const KeyValue<int, Node> &E : graph[i].nodes) { String prop_name = "nodes/"; @@ -2886,7 +2953,7 @@ void VisualShader::_update_shader() const { const_cast<VisualShader *>(this)->set_code(final_code); for (int i = 0; i < default_tex_params.size(); i++) { int j = 0; - for (List<Ref<Texture2D>>::ConstIterator itr = default_tex_params[i].params.begin(); itr != default_tex_params[i].params.end(); ++itr, ++j) { + for (List<Ref<Texture>>::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); } } @@ -2944,6 +3011,10 @@ void VisualShader::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_varying", "name"), &VisualShader::remove_varying); ClassDB::bind_method(D_METHOD("has_varying", "name"), &VisualShader::has_varying); + ClassDB::bind_method(D_METHOD("_set_preview_shader_parameter", "name", "value"), &VisualShader::_set_preview_shader_parameter); + ClassDB::bind_method(D_METHOD("_get_preview_shader_parameter", "name"), &VisualShader::_get_preview_shader_parameter); + ClassDB::bind_method(D_METHOD("_has_preview_shader_parameter", "name"), &VisualShader::_has_preview_shader_parameter); + ClassDB::bind_method(D_METHOD("_update_shader"), &VisualShader::_update_shader); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_graph_offset", "get_graph_offset"); @@ -3004,245 +3075,249 @@ VisualShader::VisualShader() { /////////////////////////////////////////////////////////// const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { - // Node3D + // Spatial // Node3D, Vertex - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "vertex_id", "VERTEX_ID" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "BINORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "clip_space_far", "CLIP_SPACE_FAR" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "instance_id", "INSTANCE_ID" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom0", "CUSTOM0" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom1", "CUSTOM1" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom2", "CUSTOM2" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom3", "CUSTOM3" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eye_offset", "EYE_OFFSET" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "instance_custom", "INSTANCE_CUSTOM" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "instance_id", "INSTANCE_ID" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection_matrix", "INV_PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_view_matrix", "INV_VIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "modelview_matrix", "MODELVIEW_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_view_matrix", "INV_VIEW_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "view_matrix", "VIEW_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_world", "NODE_POSITION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection_matrix", "PROJECTION_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection_matrix", "INV_PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "VIEWPORT_SIZE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "vertex_id", "VERTEX_ID" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_index", "VIEW_INDEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "view_matrix", "VIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_mono_left", "VIEW_MONO_LEFT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_right", "VIEW_RIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eye_offset", "EYE_OFFSET" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_world", "NODE_POSITION_WORLD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom0", "CUSTOM0" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom1", "CUSTOM1" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom2", "CUSTOM2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom3", "CUSTOM3" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "VIEWPORT_SIZE" }, // Node3D, Fragment - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "BINORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "view", "VIEW" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "clip_space_far", "CLIP_SPACE_FAR" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "view_matrix", "VIEW_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eye_offset", "EYE_OFFSET" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "front_facing", "FRONT_FACING" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection_matrix", "INV_PROJECTION_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_view_matrix", "INV_VIEW_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_world", "NODE_POSITION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection_matrix", "PROJECTION_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection_matrix", "INV_PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "TANGENT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "VIEWPORT_SIZE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "front_facing", "FRONT_FACING" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "vertex", "VERTEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "view", "VIEW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_index", "VIEW_INDEX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "view_matrix", "VIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_mono_left", "VIEW_MONO_LEFT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_INT, "view_right", "VIEW_RIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eye_offset", "EYE_OFFSET" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_world", "NODE_POSITION_WORLD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "VIEWPORT_SIZE" }, // Node3D, Light + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "attenuation", "ATTENUATION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "backlight", "BACKLIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "clip_space_far", "CLIP_SPACE_FAR" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "diffuse", "DIFFUSE_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "view", "VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection_matrix", "INV_PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_view_matrix", "INV_VIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light", "LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_color", "LIGHT_COLOR" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light_is_directional", "LIGHT_IS_DIRECTIONAL" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "attenuation", "ATTENUATION" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "backlight", "BACKLIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "diffuse", "DIFFUSE_LIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular", "SPECULAR_LIGHT" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "metallic", "METALLIC" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "view_matrix", "VIEW_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_view_matrix", "INV_VIEW_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection_matrix", "PROJECTION_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection_matrix", "INV_PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular", "SPECULAR_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV2" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "view", "VIEW" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "view_matrix", "VIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "VIEWPORT_SIZE" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, // Canvas Item // Canvas Item, Vertex - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "canvas_matrix", "CANVAS_MATRIX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "screen_matrix", "SCREEN_MATRIX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_light_pass", "AT_LIGHT_PASS" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "canvas_matrix", "CANVAS_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom0", "CUSTOM0" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom1", "CUSTOM1" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "instance_custom", "INSTANCE_CUSTOM" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "instance_id", "INSTANCE_ID" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_matrix", "MODEL_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "screen_matrix", "SCREEN_MATRIX" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "vertex_id", "VERTEX_ID" }, // Canvas Item, Fragment - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_pixel_size", "SCREEN_PIXEL_SIZE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_light_pass", "AT_LIGHT_PASS" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "normal_texture", "NORMAL_TEXTURE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_pixel_size", "SCREEN_PIXEL_SIZE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "specular_shininess", "SPECULAR_SHININESS" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, // Canvas Item, Light - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "light", "LIGHT" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "light_color", "LIGHT_COLOR" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_position", "LIGHT_POSITION" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_direction", "LIGHT_DIRECTION" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light_is_directional", "LIGHT_IS_DIRECTIONAL" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_energy", "LIGHT_ENERGY" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light_is_directional", "LIGHT_IS_DIRECTIONAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_position", "LIGHT_POSITION" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_vertex", "LIGHT_VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "shadow", "SHADOW_MODULATE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "NORMAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "shadow", "SHADOW_MODULATE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "specular_shininess", "SPECULAR_SHININESS" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "texture_pixel_size", "TEXTURE_PIXEL_SIZE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "point_coord", "POINT_COORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SAMPLER, "texture", "TEXTURE" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "specular_shininess", "SPECULAR_SHININESS" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, // Particles, Start + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "number", "NUMBER" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "random_seed", "RANDOM_SEED" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, // Particles, Start (Custom) + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "number", "NUMBER" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "random_seed", "RANDOM_SEED" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_START_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, // Particles, Process + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "number", "NUMBER" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "random_seed", "RANDOM_SEED" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, // Particles, Process (Custom) + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "number", "NUMBER" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "random_seed", "RANDOM_SEED" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_PROCESS_CUSTOM, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, // Particles, Collide + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "attractor_force", "ATTRACTOR_FORCE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "collision_depth", "COLLISION_DEPTH" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "collision_normal", "COLLISION_NORMAL" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "active", "ACTIVE" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom", "CUSTOM" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "delta", "DELTA" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "index", "INDEX" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "lifetime", "LIFETIME" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "number", "NUMBER" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "random_seed", "RANDOM_SEED" }, - { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "emission_transform", "EMISSION_TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_BOOLEAN, "restart", "RESTART" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_TRANSFORM, "transform", "TRANSFORM" }, + { Shader::MODE_PARTICLES, VisualShader::TYPE_COLLIDE, VisualShaderNode::PORT_TYPE_VECTOR_3D, "velocity", "VELOCITY" }, // Sky, Sky { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_cubemap_pass", "AT_CUBEMAP_PASS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_half_res_pass", "AT_HALF_RES_PASS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_BOOLEAN, "at_quarter_res_pass", "AT_QUARTER_RES_PASS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eyedir", "EYEDIR" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_4D, "half_res_color", "HALF_RES_COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light0_color", "LIGHT0_COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light0_direction", "LIGHT0_DIRECTION" }, @@ -3264,18 +3339,16 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_4D, "quarter_res_color", "QUARTER_RES_COLOR" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SAMPLER, "radiance", "RADIANCE" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_2D, "sky_coords", "SKY_COORDS" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Fog, Fog - - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "world_position", "WORLD_POSITION" }, { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "object_position", "OBJECT_POSITION" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "uvw", "UVW" }, - { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "size", "SIZE" }, { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "sdf", "SDF" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "size", "SIZE" }, { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "uvw", "UVW" }, + { Shader::MODE_FOG, VisualShader::TYPE_FOG, VisualShaderNode::PORT_TYPE_VECTOR_3D, "world_position", "WORLD_POSITION" }, { Shader::MODE_MAX, VisualShader::TYPE_MAX, VisualShaderNode::PORT_TYPE_TRANSFORM, nullptr, nullptr }, }; @@ -3283,60 +3356,60 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { // Spatial, Vertex + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "vec3(0.0, 1.0, 0.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Spatial, Fragment + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "tangent", "vec3(0.0, 1.0, 0.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "binormal", "vec3(1.0, 0.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Spatial, Light { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Canvas Item, Vertex - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_2D, "vertex", "VERTEX" }, // Canvas Item, Fragment - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, // Canvas Item, Light + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, // Particles @@ -3883,9 +3956,9 @@ VisualShaderNodeParameterRef::VisualShaderNodeParameterRef() { const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { //////////////////////////////////////////////////////////////////////// - // Node3D. + // Spatial. //////////////////////////////////////////////////////////////////////// - // Node3D, Vertex. + // Spatial, Vertex. //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Normal", "NORMAL" }, @@ -3900,7 +3973,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Model View Matrix", "MODELVIEW_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Projection Matrix", "PROJECTION_MATRIX" }, //////////////////////////////////////////////////////////////////////// - // Node3D, Fragment. + // Spatial, Fragment. //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Albedo", "ALBEDO" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Alpha", "ALPHA" }, @@ -3932,7 +4005,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "Depth", "DEPTH" }, //////////////////////////////////////////////////////////////////////// - // Node3D, Light. + // Spatial, Light. //////////////////////////////////////////////////////////////////////// { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Diffuse", "DIFFUSE_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Specular", "SPECULAR_LIGHT" }, @@ -4501,7 +4574,7 @@ String VisualShaderNodeGroupBase::get_outputs() const { } bool VisualShaderNodeGroupBase::is_valid_port_name(const String &p_name) const { - if (!p_name.is_valid_identifier()) { + if (!p_name.is_valid_ascii_identifier()) { return false; } for (int i = 0; i < get_input_port_count(); i++) { diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 9cd8f86d0f..8ec52fcaaa 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -42,8 +42,6 @@ class VisualShaderNode; class VisualShader : public Shader { GDCLASS(VisualShader, Shader); - friend class VisualShaderNodeVersionChecker; - public: enum Type { TYPE_VERTEX, @@ -68,7 +66,7 @@ public: struct DefaultTextureParam { StringName name; - List<Ref<Texture2D>> params; + List<Ref<Texture>> params; }; enum VaryingMode { @@ -142,6 +140,9 @@ private: HashSet<StringName> flags; HashMap<String, Varying> varyings; +#ifdef TOOLS_ENABLED + HashMap<String, Variant> preview_params; +#endif List<Varying> varyings_list; mutable SafeFlag dirty; @@ -199,6 +200,10 @@ public: // internal methods void set_varying_type(const String &p_name, VaryingType p_type); VaryingType get_varying_type(const String &p_name); + void _set_preview_shader_parameter(const String &p_name, const Variant &p_value); + Variant _get_preview_shader_parameter(const String &p_name) const; + bool _has_preview_shader_parameter(const String &p_name) const; + Vector2 get_node_position(Type p_type, int p_id) const; Ref<VisualShaderNode> get_node(Type p_type, int p_id) const; diff --git a/scene/resources/visual_shader_nodes.compat.inc b/scene/resources/visual_shader_nodes.compat.inc new file mode 100644 index 0000000000..31d96d9c0f --- /dev/null +++ b/scene/resources/visual_shader_nodes.compat.inc @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* visual_shader_nodes.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 + +// VisualShaderNodeCubemap + +void VisualShaderNodeCubemap::_set_cube_map_bind_compat_95126(Ref<Cubemap> p_cube_map) { + set_cube_map(p_cube_map); +} + +Ref<Cubemap> VisualShaderNodeCubemap::_get_cube_map_bind_compat_95126() const { + return cube_map; +} + +void VisualShaderNodeCubemap::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("set_cube_map", "value"), &VisualShaderNodeCubemap::_set_cube_map_bind_compat_95126); + ClassDB::bind_compatibility_method(D_METHOD("get_cube_map"), &VisualShaderNodeCubemap::_get_cube_map_bind_compat_95126); +} + +// VisualShaderNodeTexture2DArray + +void VisualShaderNodeTexture2DArray::_set_texture_array_bind_compat_95126(Ref<Texture2DArray> p_texture_array) { + set_texture_array(p_texture_array); +} + +Ref<Texture2DArray> VisualShaderNodeTexture2DArray::_get_texture_array_bind_compat_95126() const { + return texture_array; +} + +void VisualShaderNodeTexture2DArray::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("set_texture_array", "value"), &VisualShaderNodeTexture2DArray::_set_texture_array_bind_compat_95126); + ClassDB::bind_compatibility_method(D_METHOD("get_texture_array"), &VisualShaderNodeTexture2DArray::_get_texture_array_bind_compat_95126); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 5f70a24fcd..26666538af 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "visual_shader_nodes.h" +#include "visual_shader_nodes.compat.inc" #include "scene/resources/image_texture.h" @@ -1353,12 +1354,12 @@ String VisualShaderNodeTexture2DArray::generate_global(Shader::Mode p_mode, Visu return String(); } -void VisualShaderNodeTexture2DArray::set_texture_array(Ref<Texture2DArray> p_texture_array) { +void VisualShaderNodeTexture2DArray::set_texture_array(Ref<TextureLayered> p_texture_array) { texture_array = p_texture_array; emit_changed(); } -Ref<Texture2DArray> VisualShaderNodeTexture2DArray::get_texture_array() const { +Ref<TextureLayered> VisualShaderNodeTexture2DArray::get_texture_array() const { return texture_array; } @@ -1375,7 +1376,7 @@ void VisualShaderNodeTexture2DArray::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture_array", "value"), &VisualShaderNodeTexture2DArray::set_texture_array); ClassDB::bind_method(D_METHOD("get_texture_array"), &VisualShaderNodeTexture2DArray::get_texture_array); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_array", PROPERTY_HINT_RESOURCE_TYPE, "Texture2DArray"), "set_texture_array", "get_texture_array"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_array", PROPERTY_HINT_RESOURCE_TYPE, "Texture2DArray,CompressedTexture2DArray,PlaceholderTexture2DArray,Texture2DArrayRD"), "set_texture_array", "get_texture_array"); } VisualShaderNodeTexture2DArray::VisualShaderNodeTexture2DArray() { @@ -1568,12 +1569,12 @@ VisualShaderNodeCubemap::Source VisualShaderNodeCubemap::get_source() const { return source; } -void VisualShaderNodeCubemap::set_cube_map(Ref<Cubemap> p_cube_map) { +void VisualShaderNodeCubemap::set_cube_map(Ref<TextureLayered> p_cube_map) { cube_map = p_cube_map; emit_changed(); } -Ref<Cubemap> VisualShaderNodeCubemap::get_cube_map() const { +Ref<TextureLayered> VisualShaderNodeCubemap::get_cube_map() const { return cube_map; } @@ -1618,7 +1619,7 @@ void VisualShaderNodeCubemap::_bind_methods() { ClassDB::bind_method(D_METHOD("get_texture_type"), &VisualShaderNodeCubemap::get_texture_type); ADD_PROPERTY(PropertyInfo(Variant::INT, "source", PROPERTY_HINT_ENUM, "Texture,SamplerPort"), "set_source", "get_source"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "cube_map", PROPERTY_HINT_RESOURCE_TYPE, "Cubemap"), "set_cube_map", "get_cube_map"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "cube_map", PROPERTY_HINT_RESOURCE_TYPE, "Cubemap,CompressedCubemap,PlaceholderCubemap,TextureCubemapRD"), "set_cube_map", "get_cube_map"); ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normal Map"), "set_texture_type", "get_texture_type"); BIND_ENUM_CONSTANT(SOURCE_TEXTURE); @@ -5368,6 +5369,20 @@ String VisualShaderNodeIntParameter::generate_global(Shader::Mode p_mode, Visual code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ")"; } else if (hint == HINT_RANGE_STEP) { code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ", " + itos(hint_range_step) + ")"; + } else if (hint == HINT_ENUM) { + code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_enum("; + + bool first = true; + for (const String &_name : hint_enum_names) { + if (first) { + first = false; + } else { + code += ", "; + } + code += "\"" + _name.c_escape() + "\""; + } + + code += ")"; } else { code += _get_qual_str() + "uniform int " + get_parameter_name(); } @@ -5439,6 +5454,18 @@ int VisualShaderNodeIntParameter::get_step() const { return hint_range_step; } +void VisualShaderNodeIntParameter::set_enum_names(const PackedStringArray &p_names) { + if (hint_enum_names == p_names) { + return; + } + hint_enum_names = p_names; + emit_changed(); +} + +PackedStringArray VisualShaderNodeIntParameter::get_enum_names() const { + return hint_enum_names; +} + void VisualShaderNodeIntParameter::set_default_value_enabled(bool p_default_value_enabled) { if (default_value_enabled == p_default_value_enabled) { return; @@ -5476,22 +5503,27 @@ void VisualShaderNodeIntParameter::_bind_methods() { ClassDB::bind_method(D_METHOD("set_step", "value"), &VisualShaderNodeIntParameter::set_step); ClassDB::bind_method(D_METHOD("get_step"), &VisualShaderNodeIntParameter::get_step); + ClassDB::bind_method(D_METHOD("set_enum_names", "names"), &VisualShaderNodeIntParameter::set_enum_names); + ClassDB::bind_method(D_METHOD("get_enum_names"), &VisualShaderNodeIntParameter::get_enum_names); + ClassDB::bind_method(D_METHOD("set_default_value_enabled", "enabled"), &VisualShaderNodeIntParameter::set_default_value_enabled); ClassDB::bind_method(D_METHOD("is_default_value_enabled"), &VisualShaderNodeIntParameter::is_default_value_enabled); ClassDB::bind_method(D_METHOD("set_default_value", "value"), &VisualShaderNodeIntParameter::set_default_value); ClassDB::bind_method(D_METHOD("get_default_value"), &VisualShaderNodeIntParameter::get_default_value); - ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step"), "set_hint", "get_hint"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step,Enum"), "set_hint", "get_hint"); ADD_PROPERTY(PropertyInfo(Variant::INT, "min"), "set_min", "get_min"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max"), "set_max", "get_max"); ADD_PROPERTY(PropertyInfo(Variant::INT, "step"), "set_step", "get_step"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "enum_names"), "set_enum_names", "get_enum_names"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "default_value_enabled"), "set_default_value_enabled", "is_default_value_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "default_value"), "set_default_value", "get_default_value"); BIND_ENUM_CONSTANT(HINT_NONE); BIND_ENUM_CONSTANT(HINT_RANGE); BIND_ENUM_CONSTANT(HINT_RANGE_STEP); + BIND_ENUM_CONSTANT(HINT_ENUM); BIND_ENUM_CONSTANT(HINT_MAX); } @@ -5513,6 +5545,9 @@ Vector<StringName> VisualShaderNodeIntParameter::get_editable_properties() const if (hint == HINT_RANGE_STEP) { props.push_back("step"); } + if (hint == HINT_ENUM) { + props.push_back("enum_names"); + } props.push_back("default_value_enabled"); if (default_value_enabled) { props.push_back("default_value"); diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 7a37ffa0e0..ff02e55fb2 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -31,6 +31,7 @@ #ifndef VISUAL_SHADER_NODES_H #define VISUAL_SHADER_NODES_H +#include "scene/resources/compressed_texture.h" #include "scene/resources/curve_texture.h" #include "scene/resources/visual_shader.h" @@ -562,9 +563,15 @@ VARIANT_ENUM_CAST(VisualShaderNodeSample3D::Source) class VisualShaderNodeTexture2DArray : public VisualShaderNodeSample3D { GDCLASS(VisualShaderNodeTexture2DArray, VisualShaderNodeSample3D); - Ref<Texture2DArray> texture_array; + Ref<TextureLayered> texture_array; protected: +#ifndef DISABLE_DEPRECATED + void _set_texture_array_bind_compat_95126(Ref<Texture2DArray> p_texture_array); + Ref<Texture2DArray> _get_texture_array_bind_compat_95126() const; + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + static void _bind_methods(); public: @@ -575,8 +582,8 @@ public: virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override; - void set_texture_array(Ref<Texture2DArray> p_texture_array); - Ref<Texture2DArray> get_texture_array() const; + void set_texture_array(Ref<TextureLayered> p_texture_array); + Ref<TextureLayered> get_texture_array() const; virtual Vector<StringName> get_editable_properties() const override; @@ -608,7 +615,7 @@ public: class VisualShaderNodeCubemap : public VisualShaderNode { GDCLASS(VisualShaderNodeCubemap, VisualShaderNode); - Ref<Cubemap> cube_map; + Ref<TextureLayered> cube_map; public: enum Source { @@ -629,6 +636,12 @@ private: TextureType texture_type = TYPE_DATA; protected: +#ifndef DISABLE_DEPRECATED + void _set_cube_map_bind_compat_95126(Ref<Cubemap> p_cube_map); + Ref<Cubemap> _get_cube_map_bind_compat_95126() const; + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + static void _bind_methods(); public: @@ -650,8 +663,8 @@ public: void set_source(Source p_source); Source get_source() const; - void set_cube_map(Ref<Cubemap> p_cube_map); - Ref<Cubemap> get_cube_map() const; + void set_cube_map(Ref<TextureLayered> p_cube_map); + Ref<TextureLayered> get_cube_map() const; void set_texture_type(TextureType p_texture_type); TextureType get_texture_type() const; @@ -2115,6 +2128,7 @@ public: HINT_NONE, HINT_RANGE, HINT_RANGE_STEP, + HINT_ENUM, HINT_MAX, }; @@ -2123,6 +2137,7 @@ private: int hint_range_min = 0; int hint_range_max = 100; int hint_range_step = 1; + PackedStringArray hint_enum_names; bool default_value_enabled = false; int default_value = 0; @@ -2158,6 +2173,9 @@ public: void set_step(int p_value); int get_step() const; + void set_enum_names(const PackedStringArray &p_names); + PackedStringArray get_enum_names() const; + void set_default_value_enabled(bool p_enabled); bool is_default_value_enabled() const; diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 40f3ddf048..f8a0336b37 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -132,6 +132,7 @@ SceneStringNames::SceneStringNames() { pressed = StaticCString::create("pressed"); id_pressed = StaticCString::create("id_pressed"); + toggled = StaticCString::create("toggled"); panel = StaticCString::create("panel"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 7893d213e4..381a161ad5 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -145,6 +145,7 @@ public: StringName pressed; StringName id_pressed; + StringName toggled; StringName panel; diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index b2a3843b02..0c211be9f9 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -504,6 +504,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("can_fold_code_region", "CodeEdit", icons["region_unfolded"]); theme->set_icon("folded_code_region", "CodeEdit", icons["region_folded"]); theme->set_icon("folded_eol_icon", "CodeEdit", icons["text_edit_ellipsis"]); + theme->set_icon("completion_color_bg", "CodeEdit", icons["mini_checkerboard"]); theme->set_font(SceneStringName(font), "CodeEdit", Ref<Font>()); theme->set_font_size(SceneStringName(font_size), "CodeEdit", -1); @@ -613,7 +614,43 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // SpinBox - theme->set_icon("updown", "SpinBox", icons["updown"]); + theme->set_icon("updown", "SpinBox", empty_icon); + theme->set_icon("up", "SpinBox", icons["value_up"]); + theme->set_icon("up_hover", "SpinBox", icons["value_up"]); + theme->set_icon("up_pressed", "SpinBox", icons["value_up"]); + theme->set_icon("up_disabled", "SpinBox", icons["value_up"]); + theme->set_icon("down", "SpinBox", icons["value_down"]); + theme->set_icon("down_hover", "SpinBox", icons["value_down"]); + theme->set_icon("down_pressed", "SpinBox", icons["value_down"]); + theme->set_icon("down_disabled", "SpinBox", icons["value_down"]); + + theme->set_stylebox("up_background", "SpinBox", make_empty_stylebox()); + theme->set_stylebox("up_background_hovered", "SpinBox", button_hover); + theme->set_stylebox("up_background_pressed", "SpinBox", button_pressed); + theme->set_stylebox("up_background_disabled", "SpinBox", make_empty_stylebox()); + theme->set_stylebox("down_background", "SpinBox", make_empty_stylebox()); + theme->set_stylebox("down_background_hovered", "SpinBox", button_hover); + theme->set_stylebox("down_background_pressed", "SpinBox", button_pressed); + theme->set_stylebox("down_background_disabled", "SpinBox", make_empty_stylebox()); + + theme->set_color("up_icon_modulate", "SpinBox", control_font_color); + theme->set_color("up_hover_icon_modulate", "SpinBox", control_font_hover_color); + theme->set_color("up_pressed_icon_modulate", "SpinBox", control_font_hover_color); + theme->set_color("up_disabled_icon_modulate", "SpinBox", control_font_disabled_color); + theme->set_color("down_icon_modulate", "SpinBox", control_font_color); + theme->set_color("down_hover_icon_modulate", "SpinBox", control_font_hover_color); + theme->set_color("down_pressed_icon_modulate", "SpinBox", control_font_hover_color); + theme->set_color("down_disabled_icon_modulate", "SpinBox", control_font_disabled_color); + + theme->set_stylebox("field_and_buttons_separator", "SpinBox", make_empty_stylebox()); + theme->set_stylebox("up_down_buttons_separator", "SpinBox", make_empty_stylebox()); + + theme->set_constant("buttons_vertical_separation", "SpinBox", 0); + theme->set_constant("field_and_buttons_separation", "SpinBox", 2); + theme->set_constant("buttons_width", "SpinBox", 16); +#ifndef DISABLE_DEPRECATED + theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1); +#endif // ScrollContainer @@ -1174,6 +1211,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("v_separation", "VFlowContainer", Math::round(4 * scale)); theme->set_stylebox(SceneStringName(panel), "PanelContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0)); + theme->set_stylebox("split_bar_background", "SplitContainer", make_empty_stylebox(0, 0, 0, 0)); + theme->set_stylebox("split_bar_background", "VSplitContainer", make_empty_stylebox(0, 0, 0, 0)); + theme->set_stylebox("split_bar_background", "HSplitContainer", make_empty_stylebox(0, 0, 0, 0)); theme->set_icon("zoom_out", "GraphEdit", icons["zoom_less"]); theme->set_icon("zoom_in", "GraphEdit", icons["zoom_more"]); diff --git a/scene/theme/icons/default_theme_icons_builders.py b/scene/theme/icons/default_theme_icons_builders.py index 49c0a93191..3a673af92e 100644 --- a/scene/theme/icons/default_theme_icons_builders.py +++ b/scene/theme/icons/default_theme_icons_builders.py @@ -3,6 +3,8 @@ import os from io import StringIO +from methods import to_raw_cstring + # See also `editor/icons/editor_icons_builders.py`. def make_default_theme_icons_action(target, source, env): @@ -10,21 +12,9 @@ def make_default_theme_icons_action(target, source, env): svg_icons = [str(x) for x in source] with StringIO() as icons_string, StringIO() as s: - for f in svg_icons: - fname = str(f) - - icons_string.write('\t"') - - with open(fname, "rb") as svgf: - b = svgf.read(1) - while len(b) == 1: - icons_string.write("\\" + str(hex(ord(b)))[1:]) - b = svgf.read(1) - - icons_string.write('"') - if fname != svg_icons[-1]: - icons_string.write(",") - icons_string.write("\n") + for svg in svg_icons: + with open(svg, "r") as svgf: + icons_string.write("\t%s,\n" % to_raw_cstring(svgf.read())) s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n\n") s.write('#include "modules/modules_enabled.gen.h"\n\n') diff --git a/scene/theme/icons/value_down.svg b/scene/theme/icons/value_down.svg new file mode 100644 index 0000000000..57837d03fd --- /dev/null +++ b/scene/theme/icons/value_down.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#fff" stroke-width="2" d="m12 2-4 3.5L4 2"/></svg>
\ No newline at end of file diff --git a/scene/theme/icons/value_up.svg b/scene/theme/icons/value_up.svg new file mode 100644 index 0000000000..53fb102fe2 --- /dev/null +++ b/scene/theme/icons/value_up.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#fff" stroke-width="2" d="m4 6 4-3.5L12 6"/></svg>
\ No newline at end of file diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp index 7249fd7ba8..c33c31558f 100644 --- a/scene/theme/theme_db.cpp +++ b/scene/theme/theme_db.cpp @@ -198,21 +198,21 @@ Ref<StyleBox> ThemeDB::get_fallback_stylebox() { return fallback_stylebox; } -void ThemeDB::get_native_type_dependencies(const StringName &p_base_type, List<StringName> *p_list) { - ERR_FAIL_NULL(p_list); +void ThemeDB::get_native_type_dependencies(const StringName &p_base_type, Vector<StringName> &r_result) { + if (p_base_type == StringName()) { + return; + } // TODO: It may make sense to stop at Control/Window, because their parent classes cannot be used in // a meaningful way. - StringName class_name = p_base_type; - while (class_name != StringName()) { - p_list->push_back(class_name); - class_name = ClassDB::get_parent_class_nocheck(class_name); + if (!ClassDB::get_inheritance_chain_nocheck(p_base_type, r_result)) { + r_result.push_back(p_base_type); } } // Global theme contexts. -ThemeContext *ThemeDB::create_theme_context(Node *p_node, List<Ref<Theme>> &p_themes) { +ThemeContext *ThemeDB::create_theme_context(Node *p_node, Vector<Ref<Theme>> &p_themes) { ERR_FAIL_COND_V(!p_node->is_inside_tree(), nullptr); ERR_FAIL_COND_V(theme_contexts.has(p_node), nullptr); ERR_FAIL_COND_V(p_themes.is_empty(), nullptr); @@ -270,7 +270,7 @@ void ThemeDB::_propagate_theme_context(Node *p_from_node, ThemeContext *p_contex void ThemeDB::_init_default_theme_context() { default_theme_context = memnew(ThemeContext); - List<Ref<Theme>> themes; + Vector<Ref<Theme>> themes; // Only add the project theme to the default context when running projects. @@ -365,7 +365,7 @@ void ThemeDB::update_class_instance_items(Node *p_instance) { HashMap<StringName, HashMap<StringName, ThemeItemBind>>::Iterator E = theme_item_binds.find(class_name); if (E) { for (const KeyValue<StringName, ThemeItemBind> &F : E->value) { - F.value.setter(p_instance); + F.value.setter(p_instance, F.value.item_name, F.value.type_name); } } @@ -475,7 +475,7 @@ void ThemeContext::_emit_changed() { emit_signal(CoreStringName(changed)); } -void ThemeContext::set_themes(List<Ref<Theme>> &p_themes) { +void ThemeContext::set_themes(Vector<Ref<Theme>> &p_themes) { for (const Ref<Theme> &theme : themes) { theme->disconnect_changed(callable_mp(this, &ThemeContext::_emit_changed)); } @@ -494,7 +494,7 @@ void ThemeContext::set_themes(List<Ref<Theme>> &p_themes) { _emit_changed(); } -List<Ref<Theme>> ThemeContext::get_themes() const { +const Vector<Ref<Theme>> ThemeContext::get_themes() const { return themes; } @@ -504,7 +504,7 @@ Ref<Theme> ThemeContext::get_fallback_theme() const { return ThemeDB::get_singleton()->get_default_theme(); } - return themes.back()->get(); + return themes[themes.size() - 1]; } void ThemeContext::_bind_methods() { diff --git a/scene/theme/theme_db.h b/scene/theme/theme_db.h index f403c1ef20..353894f41e 100644 --- a/scene/theme/theme_db.h +++ b/scene/theme/theme_db.h @@ -46,26 +46,29 @@ class ThemeContext; // Macros for binding theme items of this class. This information is used for the documentation, theme // overrides, etc. This is also the basis for theme cache. -#define BIND_THEME_ITEM(m_data_type, m_class, m_prop) \ - ThemeDB::get_singleton()->bind_class_item(m_data_type, get_class_static(), #m_prop, #m_prop, [](Node *p_instance) { \ - m_class *p_cast = Object::cast_to<m_class>(p_instance); \ - p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(#m_prop)); \ - }) - -#define BIND_THEME_ITEM_CUSTOM(m_data_type, m_class, m_prop, m_item_name) \ - ThemeDB::get_singleton()->bind_class_item(m_data_type, get_class_static(), #m_prop, m_item_name, [](Node *p_instance) { \ - m_class *p_cast = Object::cast_to<m_class>(p_instance); \ - p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(m_item_name)); \ - }) +#define BIND_THEME_ITEM(m_data_type, m_class, m_prop) \ + ThemeDB::get_singleton()->bind_class_item(m_data_type, get_class_static(), #m_prop, #m_prop, \ + [](Node *p_instance, const StringName &p_item_name, const StringName &p_type_name) { \ + m_class *p_cast = Object::cast_to<m_class>(p_instance); \ + p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, p_item_name, p_type_name); \ + }) + +#define BIND_THEME_ITEM_CUSTOM(m_data_type, m_class, m_prop, m_item_name) \ + ThemeDB::get_singleton()->bind_class_item(m_data_type, get_class_static(), #m_prop, m_item_name, \ + [](Node *p_instance, const StringName &p_item_name, const StringName &p_type_name) { \ + m_class *p_cast = Object::cast_to<m_class>(p_instance); \ + p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, p_item_name, p_type_name); \ + }) // Macro for binding theme items used by this class, but defined/binded by other classes. This is primarily used for // the theme cache. Can also be used to list such items in documentation. -#define BIND_THEME_ITEM_EXT(m_data_type, m_class, m_prop, m_item_name, m_type_name) \ - ThemeDB::get_singleton()->bind_class_external_item(m_data_type, get_class_static(), #m_prop, m_item_name, m_type_name, [](Node *p_instance) { \ - m_class *p_cast = Object::cast_to<m_class>(p_instance); \ - p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, _scs_create(m_item_name), _scs_create(m_type_name)); \ - }) +#define BIND_THEME_ITEM_EXT(m_data_type, m_class, m_prop, m_item_name, m_type_name) \ + ThemeDB::get_singleton()->bind_class_external_item(m_data_type, get_class_static(), #m_prop, m_item_name, m_type_name, \ + [](Node *p_instance, const StringName &p_item_name, const StringName &p_type_name) { \ + m_class *p_cast = Object::cast_to<m_class>(p_instance); \ + p_cast->theme_cache.m_prop = p_cast->get_theme_item(m_data_type, p_item_name, p_type_name); \ + }) class ThemeDB : public Object { GDCLASS(ThemeDB, Object); @@ -97,7 +100,7 @@ class ThemeDB : public Object { // Binding of theme items to Node classes. public: - typedef std::function<void(Node *)> ThemeItemSetter; + typedef std::function<void(Node *, const StringName &, const StringName &)> ThemeItemSetter; struct ThemeItemBind { Theme::DataType data_type; @@ -154,11 +157,11 @@ public: void set_fallback_stylebox(const Ref<StyleBox> &p_stylebox); Ref<StyleBox> get_fallback_stylebox(); - void get_native_type_dependencies(const StringName &p_base_type, List<StringName> *p_list); + void get_native_type_dependencies(const StringName &p_base_type, Vector<StringName> &r_result); // Global theme contexts. - ThemeContext *create_theme_context(Node *p_node, List<Ref<Theme>> &p_themes); + ThemeContext *create_theme_context(Node *p_node, Vector<Ref<Theme>> &p_themes); void destroy_theme_context(Node *p_node); ThemeContext *get_theme_context(Node *p_node) const; @@ -191,7 +194,7 @@ class ThemeContext : public Object { // Themes are stacked in the order of relevance, for easy iteration. // This means that the first theme is the one you should check first, // and the last theme is the fallback theme where every lookup ends. - List<Ref<Theme>> themes; + Vector<Ref<Theme>> themes; void _emit_changed(); @@ -199,8 +202,8 @@ protected: static void _bind_methods(); public: - void set_themes(List<Ref<Theme>> &p_themes); - List<Ref<Theme>> get_themes() const; + void set_themes(Vector<Ref<Theme>> &p_themes); + const Vector<Ref<Theme>> get_themes() const; Ref<Theme> get_fallback_theme() const; }; diff --git a/scene/theme/theme_owner.cpp b/scene/theme/theme_owner.cpp index a25adddc09..b3d51e71c0 100644 --- a/scene/theme/theme_owner.cpp +++ b/scene/theme/theme_owner.cpp @@ -199,7 +199,7 @@ void ThemeOwner::propagate_theme_changed(Node *p_to_node, Node *p_owner_node, bo // Theme lookup. -void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, List<StringName> *r_list) const { +void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, Vector<StringName> &r_result) const { const Control *for_c = Object::cast_to<Control>(p_for_node); const Window *for_w = Object::cast_to<Window>(p_for_node); ERR_FAIL_COND_MSG(!for_c && !for_w, "Only Control and Window nodes and derivatives can be polled for theming."); @@ -224,7 +224,7 @@ void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const Strin while (owner_node) { Ref<Theme> owner_theme = _get_owner_node_theme(owner_node); if (owner_theme.is_valid() && owner_theme->get_type_variation_base(type_variation) != StringName()) { - owner_theme->get_type_dependencies(type_name, type_variation, r_list); + owner_theme->get_type_dependencies(type_name, type_variation, r_result); return; } @@ -235,21 +235,21 @@ void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const Strin ThemeContext *global_context = _get_active_owner_context(); for (const Ref<Theme> &theme : global_context->get_themes()) { if (theme.is_valid() && theme->get_type_variation_base(type_variation) != StringName()) { - theme->get_type_dependencies(type_name, type_variation, r_list); + theme->get_type_dependencies(type_name, type_variation, r_result); return; } } // If nothing was found, get the native dependencies for the current class. - ThemeDB::get_singleton()->get_native_type_dependencies(type_name, r_list); + ThemeDB::get_singleton()->get_native_type_dependencies(type_name, r_result); return; } // Otherwise, get the native dependencies for the provided theme type. - ThemeDB::get_singleton()->get_native_type_dependencies(p_theme_type, r_list); + ThemeDB::get_singleton()->get_native_type_dependencies(p_theme_type, r_result); } -Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types) { +Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types) { ERR_FAIL_COND_V_MSG(p_theme_types.is_empty(), Variant(), "At least one theme type must be specified."); // First, look through each control or window node in the branch, until no valid parent can be found. @@ -285,7 +285,7 @@ Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const S return global_context->get_fallback_theme()->get_theme_item(p_data_type, p_name, StringName()); } -bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types) { +bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types) { ERR_FAIL_COND_V_MSG(p_theme_types.is_empty(), false, "At least one theme type must be specified."); // First, look through each control or window node in the branch, until no valid parent can be found. diff --git a/scene/theme/theme_owner.h b/scene/theme/theme_owner.h index 7e19279c2a..14bfa35587 100644 --- a/scene/theme/theme_owner.h +++ b/scene/theme/theme_owner.h @@ -69,10 +69,10 @@ public: // Theme lookup. - void get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, List<StringName> *r_list) const; + void get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, Vector<StringName> &r_result) const; - Variant get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types); - bool has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types); + Variant get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types); + bool has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types); float get_theme_default_base_scale(); Ref<Font> get_theme_default_font(); |