summaryrefslogtreecommitdiffstats
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/animated_sprite_2d.cpp4
-rw-r--r--scene/2d/audio_stream_player_2d.cpp13
-rw-r--r--scene/2d/canvas_modulate.cpp2
-rw-r--r--scene/2d/gpu_particles_2d.cpp23
-rw-r--r--scene/2d/light_2d.cpp2
-rw-r--r--scene/2d/light_occluder_2d.cpp2
-rw-r--r--scene/2d/navigation_link_2d.cpp135
-rw-r--r--scene/2d/navigation_link_2d.h13
-rw-r--r--scene/2d/navigation_region_2d.cpp1
-rw-r--r--scene/2d/parallax_2d.cpp18
-rw-r--r--scene/2d/parallax_2d.h1
-rw-r--r--scene/2d/parallax_layer.cpp10
-rw-r--r--scene/2d/path_2d.cpp9
-rw-r--r--scene/2d/physics/collision_object_2d.cpp2
-rw-r--r--scene/2d/physics/collision_polygon_2d.cpp2
-rw-r--r--scene/2d/physics/collision_shape_2d.cpp2
-rw-r--r--scene/2d/physics/physical_bone_2d.cpp2
-rw-r--r--scene/2d/physics/shape_cast_2d.cpp6
-rw-r--r--scene/2d/physics/shape_cast_2d.h2
-rw-r--r--scene/2d/polygon_2d.cpp2
-rw-r--r--scene/2d/remote_transform_2d.cpp12
-rw-r--r--scene/2d/skeleton_2d.cpp42
-rw-r--r--scene/2d/tile_map.cpp20
-rw-r--r--scene/2d/tile_map.h4
-rw-r--r--scene/2d/tile_map_layer.cpp137
-rw-r--r--scene/2d/tile_map_layer.h12
-rw-r--r--scene/3d/audio_stream_player_3d.cpp13
-rw-r--r--scene/3d/bone_attachment_3d.cpp20
-rw-r--r--scene/3d/bone_attachment_3d.h3
-rw-r--r--scene/3d/camera_3d.cpp138
-rw-r--r--scene/3d/camera_3d.h32
-rw-r--r--scene/3d/cpu_particles_3d.cpp29
-rw-r--r--scene/3d/cpu_particles_3d.h3
-rw-r--r--scene/3d/decal.cpp2
-rw-r--r--scene/3d/fog_volume.cpp2
-rw-r--r--scene/3d/gpu_particles_3d.cpp22
-rw-r--r--scene/3d/gpu_particles_collision_3d.cpp2
-rw-r--r--scene/3d/label_3d.cpp22
-rw-r--r--scene/3d/lightmap_gi.cpp24
-rw-r--r--scene/3d/lightmap_gi.h5
-rw-r--r--scene/3d/mesh_instance_3d.cpp171
-rw-r--r--scene/3d/mesh_instance_3d.h1
-rw-r--r--scene/3d/multimesh_instance_3d.cpp19
-rw-r--r--scene/3d/multimesh_instance_3d.h5
-rw-r--r--scene/3d/navigation_link_3d.cpp108
-rw-r--r--scene/3d/navigation_link_3d.h9
-rw-r--r--scene/3d/navigation_region_3d.cpp19
-rw-r--r--scene/3d/node_3d.cpp147
-rw-r--r--scene/3d/node_3d.h45
-rw-r--r--scene/3d/occluder_instance_3d.cpp5
-rw-r--r--scene/3d/occluder_instance_3d.h1
-rw-r--r--scene/3d/path_3d.cpp61
-rw-r--r--scene/3d/path_3d.h3
-rw-r--r--scene/3d/physical_bone_simulator_3d.cpp57
-rw-r--r--scene/3d/physical_bone_simulator_3d.h2
-rw-r--r--scene/3d/physics/collision_object_3d.cpp2
-rw-r--r--scene/3d/physics/collision_polygon_3d.cpp2
-rw-r--r--scene/3d/physics/collision_shape_3d.cpp2
-rw-r--r--scene/3d/physics/shape_cast_3d.cpp6
-rw-r--r--scene/3d/physics/shape_cast_3d.h3
-rw-r--r--scene/3d/physics/vehicle_body_3d.cpp18
-rw-r--r--scene/3d/physics/vehicle_body_3d.h4
-rw-r--r--scene/3d/remote_transform_3d.cpp12
-rw-r--r--scene/3d/skeleton_3d.cpp79
-rw-r--r--scene/3d/skeleton_3d.h9
-rw-r--r--scene/3d/skeleton_ik_3d.cpp6
-rw-r--r--scene/3d/skeleton_modifier_3d.cpp2
-rw-r--r--scene/3d/soft_body_3d.compat.inc (renamed from scene/main/window.compat.inc)23
-rw-r--r--scene/3d/soft_body_3d.cpp38
-rw-r--r--scene/3d/soft_body_3d.h9
-rw-r--r--scene/3d/sprite_3d.cpp6
-rw-r--r--scene/3d/visual_instance_3d.cpp80
-rw-r--r--scene/3d/visual_instance_3d.h3
-rw-r--r--scene/3d/voxel_gi.cpp2
-rw-r--r--scene/3d/voxelizer.h3
-rw-r--r--scene/3d/xr_hand_modifier_3d.cpp17
-rw-r--r--scene/3d/xr_hand_modifier_3d.h2
-rw-r--r--scene/3d/xr_nodes.cpp26
-rw-r--r--scene/3d/xr_nodes.h2
-rw-r--r--scene/animation/animation_blend_tree.cpp31
-rw-r--r--scene/animation/animation_blend_tree.h21
-rw-r--r--scene/animation/animation_mixer.cpp137
-rw-r--r--scene/animation/animation_mixer.h1
-rw-r--r--scene/animation/animation_node_state_machine.cpp7
-rw-r--r--scene/animation/animation_node_state_machine.h2
-rw-r--r--scene/animation/animation_player.compat.inc10
-rw-r--r--scene/animation/animation_player.cpp7
-rw-r--r--scene/animation/animation_tree.cpp15
-rw-r--r--scene/animation/animation_tree.h6
-rw-r--r--scene/animation/tween.cpp20
-rw-r--r--scene/animation/tween.h1
-rw-r--r--scene/audio/audio_stream_player.cpp13
-rw-r--r--scene/audio/audio_stream_player_internal.cpp14
-rw-r--r--scene/audio/audio_stream_player_internal.h5
-rw-r--r--scene/gui/base_button.cpp12
-rw-r--r--scene/gui/code_edit.cpp108
-rw-r--r--scene/gui/code_edit.h4
-rw-r--r--scene/gui/color_picker.cpp26
-rw-r--r--scene/gui/control.cpp92
-rw-r--r--scene/gui/control.h4
-rw-r--r--scene/gui/file_dialog.cpp10
-rw-r--r--scene/gui/graph_edit.cpp50
-rw-r--r--scene/gui/line_edit.cpp730
-rw-r--r--scene/gui/line_edit.h14
-rw-r--r--scene/gui/menu_bar.cpp25
-rw-r--r--scene/gui/menu_button.cpp5
-rw-r--r--scene/gui/option_button.cpp2
-rw-r--r--scene/gui/popup_menu.cpp14
-rw-r--r--scene/gui/popup_menu.h2
-rw-r--r--scene/gui/range.cpp2
-rw-r--r--scene/gui/rich_text_label.cpp947
-rw-r--r--scene/gui/rich_text_label.h16
-rw-r--r--scene/gui/scroll_container.cpp28
-rw-r--r--scene/gui/slider.cpp2
-rw-r--r--scene/gui/spin_box.cpp280
-rw-r--r--scene/gui/spin_box.h71
-rw-r--r--scene/gui/split_container.cpp244
-rw-r--r--scene/gui/split_container.h31
-rw-r--r--scene/gui/subviewport_container.cpp2
-rw-r--r--scene/gui/tab_bar.cpp2
-rw-r--r--scene/gui/text_edit.cpp714
-rw-r--r--scene/gui/text_edit.h67
-rw-r--r--scene/gui/tree.cpp130
-rw-r--r--scene/gui/tree.h8
-rw-r--r--scene/main/node.cpp54
-rw-r--r--scene/main/node.h31
-rw-r--r--scene/main/scene_tree.cpp73
-rw-r--r--scene/main/scene_tree.h16
-rw-r--r--scene/main/viewport.cpp314
-rw-r--r--scene/main/viewport.h27
-rw-r--r--scene/main/window.cpp68
-rw-r--r--scene/main/window.h6
-rw-r--r--scene/property_list_helper.cpp2
-rw-r--r--scene/register_scene_types.cpp4
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp72
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.h5
-rw-r--r--scene/resources/2d/navigation_polygon.cpp84
-rw-r--r--scene/resources/2d/navigation_polygon.h24
-rw-r--r--scene/resources/2d/tile_set.compat.inc2
-rw-r--r--scene/resources/2d/tile_set.cpp206
-rw-r--r--scene/resources/2d/tile_set.h17
-rw-r--r--scene/resources/3d/fog_material.cpp3
-rw-r--r--scene/resources/3d/importer_mesh.cpp99
-rw-r--r--scene/resources/3d/importer_mesh.h8
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp54
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.h5
-rw-r--r--scene/resources/3d/primitive_meshes.cpp50
-rw-r--r--scene/resources/3d/primitive_meshes.h5
-rw-r--r--scene/resources/3d/sky_material.cpp11
-rw-r--r--scene/resources/animation.cpp9
-rw-r--r--scene/resources/animation.h19
-rw-r--r--scene/resources/audio_stream_polyphonic.cpp12
-rw-r--r--scene/resources/audio_stream_wav.cpp93
-rw-r--r--scene/resources/audio_stream_wav.h6
-rw-r--r--scene/resources/bit_map.cpp5
-rw-r--r--scene/resources/font.cpp48
-rw-r--r--scene/resources/font.h5
-rw-r--r--scene/resources/material.cpp10
-rw-r--r--scene/resources/mesh.cpp1
-rw-r--r--scene/resources/multimesh.cpp28
-rw-r--r--scene/resources/multimesh.h16
-rw-r--r--scene/resources/navigation_mesh.cpp41
-rw-r--r--scene/resources/navigation_mesh.h14
-rw-r--r--scene/resources/packed_scene.cpp150
-rw-r--r--scene/resources/packed_scene.h2
-rw-r--r--scene/resources/particle_process_material.cpp32
-rw-r--r--scene/resources/particle_process_material.h4
-rw-r--r--scene/resources/portable_compressed_texture.cpp2
-rw-r--r--scene/resources/resource_format_text.cpp79
-rw-r--r--scene/resources/resource_format_text.h12
-rw-r--r--scene/resources/shader.compat.inc (renamed from scene/gui/control.compat.inc)28
-rw-r--r--scene/resources/shader.cpp11
-rw-r--r--scene/resources/shader.h12
-rw-r--r--scene/resources/skeleton_profile.cpp5
-rw-r--r--scene/resources/sprite_frames.cpp7
-rw-r--r--scene/resources/sprite_frames.h1
-rw-r--r--scene/resources/style_box_flat.cpp4
-rw-r--r--scene/resources/surface_tool.cpp8
-rw-r--r--scene/resources/text_paragraph.cpp47
-rw-r--r--scene/resources/theme.cpp8
-rw-r--r--scene/resources/theme.h2
-rw-r--r--scene/resources/visual_shader.cpp373
-rw-r--r--scene/resources/visual_shader.h11
-rw-r--r--scene/resources/visual_shader_nodes.compat.inc63
-rw-r--r--scene/resources/visual_shader_nodes.cpp49
-rw-r--r--scene/resources/visual_shader_nodes.h30
-rw-r--r--scene/scene_string_names.cpp1
-rw-r--r--scene/scene_string_names.h1
-rw-r--r--scene/theme/default_theme.cpp42
-rw-r--r--scene/theme/icons/default_theme_icons_builders.py20
-rw-r--r--scene/theme/icons/value_down.svg1
-rw-r--r--scene/theme/icons/value_up.svg1
-rw-r--r--scene/theme/theme_db.cpp24
-rw-r--r--scene/theme/theme_db.h47
-rw-r--r--scene/theme/theme_owner.cpp14
-rw-r--r--scene/theme/theme_owner.h6
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();