summaryrefslogtreecommitdiffstats
path: root/scene
diff options
context:
space:
mode:
Diffstat (limited to 'scene')
-rw-r--r--scene/2d/animated_sprite_2d.cpp21
-rw-r--r--scene/2d/audio_listener_2d.cpp8
-rw-r--r--scene/2d/audio_stream_player_2d.cpp6
-rw-r--r--scene/2d/camera_2d.cpp6
-rw-r--r--scene/2d/cpu_particles_2d.cpp3
-rw-r--r--scene/2d/gpu_particles_2d.cpp3
-rw-r--r--scene/2d/line_builder.cpp15
-rw-r--r--scene/2d/mesh_instance_2d.cpp4
-rw-r--r--scene/2d/multimesh_instance_2d.cpp4
-rw-r--r--scene/2d/navigation_agent_2d.cpp2
-rw-r--r--scene/2d/navigation_region_2d.cpp160
-rw-r--r--scene/2d/navigation_region_2d.h17
-rw-r--r--scene/2d/parallax_2d.cpp9
-rw-r--r--scene/2d/physics/area_2d.cpp75
-rw-r--r--scene/2d/physics/character_body_2d.cpp10
-rw-r--r--scene/2d/physics/character_body_2d.h1
-rw-r--r--scene/2d/physics/collision_object_2d.cpp11
-rw-r--r--scene/2d/physics/collision_polygon_2d.cpp2
-rw-r--r--scene/2d/physics/joints/joint_2d.cpp13
-rw-r--r--scene/2d/physics/kinematic_collision_2d.cpp1
-rw-r--r--scene/2d/physics/kinematic_collision_2d.h2
-rw-r--r--scene/2d/physics/physics_body_2d.cpp10
-rw-r--r--scene/2d/physics/physics_body_2d.h2
-rw-r--r--scene/2d/physics/rigid_body_2d.cpp32
-rw-r--r--scene/2d/skeleton_2d.cpp65
-rw-r--r--scene/2d/sprite_2d.cpp5
-rw-r--r--scene/2d/tile_map.cpp313
-rw-r--r--scene/2d/tile_map.h15
-rw-r--r--scene/2d/tile_map_layer.cpp152
-rw-r--r--scene/2d/tile_map_layer.h4
-rw-r--r--scene/2d/touch_screen_button.cpp15
-rw-r--r--scene/2d/visible_on_screen_notifier_2d.cpp6
-rw-r--r--scene/3d/audio_listener_3d.cpp8
-rw-r--r--scene/3d/audio_stream_player_3d.cpp1
-rw-r--r--scene/3d/bone_attachment_3d.compat.inc43
-rw-r--r--scene/3d/bone_attachment_3d.cpp13
-rw-r--r--scene/3d/bone_attachment_3d.h6
-rw-r--r--scene/3d/camera_3d.cpp6
-rw-r--r--scene/3d/cpu_particles_3d.cpp11
-rw-r--r--scene/3d/decal.cpp2
-rw-r--r--scene/3d/fog_volume.cpp2
-rw-r--r--scene/3d/gpu_particles_3d.cpp3
-rw-r--r--scene/3d/gpu_particles_collision_3d.cpp4
-rw-r--r--scene/3d/label_3d.cpp5
-rw-r--r--scene/3d/lightmap_gi.cpp76
-rw-r--r--scene/3d/lightmap_gi.h4
-rw-r--r--scene/3d/lightmapper.h4
-rw-r--r--scene/3d/mesh_instance_3d.cpp159
-rw-r--r--scene/3d/mesh_instance_3d.h2
-rw-r--r--scene/3d/navigation_agent_3d.cpp2
-rw-r--r--scene/3d/navigation_region_3d.cpp6
-rw-r--r--scene/3d/node_3d.cpp32
-rw-r--r--scene/3d/node_3d.h1
-rw-r--r--scene/3d/occluder_instance_3d.cpp4
-rw-r--r--scene/3d/physical_bone_simulator_3d.cpp8
-rw-r--r--scene/3d/physics/area_3d.cpp79
-rw-r--r--scene/3d/physics/area_3d.h5
-rw-r--r--scene/3d/physics/character_body_3d.cpp12
-rw-r--r--scene/3d/physics/character_body_3d.h1
-rw-r--r--scene/3d/physics/collision_object_3d.cpp7
-rw-r--r--scene/3d/physics/joints/cone_twist_joint_3d.cpp2
-rw-r--r--scene/3d/physics/joints/joint_3d.cpp14
-rw-r--r--scene/3d/physics/joints/slider_joint_3d.cpp2
-rw-r--r--scene/3d/physics/kinematic_collision_3d.cpp1
-rw-r--r--scene/3d/physics/kinematic_collision_3d.h2
-rw-r--r--scene/3d/physics/physics_body_3d.cpp10
-rw-r--r--scene/3d/physics/physics_body_3d.h2
-rw-r--r--scene/3d/physics/ray_cast_3d.cpp6
-rw-r--r--scene/3d/physics/rigid_body_3d.cpp32
-rw-r--r--scene/3d/skeleton_3d.cpp63
-rw-r--r--scene/3d/skeleton_3d.h38
-rw-r--r--scene/3d/skeleton_ik_3d.cpp16
-rw-r--r--scene/3d/skeleton_ik_3d.h5
-rw-r--r--scene/3d/skeleton_modifier_3d.cpp3
-rw-r--r--scene/3d/skeleton_modifier_3d.h1
-rw-r--r--scene/3d/sprite_3d.cpp29
-rw-r--r--scene/3d/visible_on_screen_notifier_3d.cpp6
-rw-r--r--scene/3d/visual_instance_3d.cpp21
-rw-r--r--scene/3d/voxel_gi.cpp3
-rw-r--r--scene/3d/xr_body_modifier_3d.cpp59
-rw-r--r--scene/3d/xr_body_modifier_3d.h8
-rw-r--r--scene/3d/xr_face_modifier_3d.cpp8
-rw-r--r--scene/3d/xr_face_modifier_3d.h2
-rw-r--r--scene/3d/xr_hand_modifier_3d.cpp133
-rw-r--r--scene/3d/xr_hand_modifier_3d.h7
-rw-r--r--scene/3d/xr_nodes.cpp37
-rw-r--r--scene/3d/xr_nodes.h4
-rw-r--r--scene/animation/animation_blend_tree.cpp29
-rw-r--r--scene/animation/animation_mixer.cpp48
-rw-r--r--scene/animation/animation_node_state_machine.cpp10
-rw-r--r--scene/animation/animation_player.cpp168
-rw-r--r--scene/animation/animation_player.h19
-rw-r--r--scene/animation/animation_tree.cpp13
-rw-r--r--scene/animation/tween.cpp10
-rw-r--r--scene/audio/audio_stream_player.cpp1
-rw-r--r--scene/audio/audio_stream_player_internal.cpp7
-rw-r--r--scene/debugger/scene_debugger.cpp8
-rw-r--r--scene/gui/aspect_ratio_container.cpp13
-rw-r--r--scene/gui/base_button.cpp9
-rw-r--r--scene/gui/box_container.cpp30
-rw-r--r--scene/gui/button.cpp119
-rw-r--r--scene/gui/button.h12
-rw-r--r--scene/gui/center_container.cpp14
-rw-r--r--scene/gui/check_box.cpp8
-rw-r--r--scene/gui/check_button.cpp8
-rw-r--r--scene/gui/code_edit.cpp632
-rw-r--r--scene/gui/code_edit.h8
-rw-r--r--scene/gui/color_picker.cpp83
-rw-r--r--scene/gui/container.cpp26
-rw-r--r--scene/gui/container.h2
-rw-r--r--scene/gui/control.cpp108
-rw-r--r--scene/gui/control.h4
-rw-r--r--scene/gui/dialogs.cpp51
-rw-r--r--scene/gui/dialogs.h2
-rw-r--r--scene/gui/file_dialog.cpp68
-rw-r--r--scene/gui/file_dialog.h13
-rw-r--r--scene/gui/flow_container.cpp95
-rw-r--r--scene/gui/flow_container.h11
-rw-r--r--scene/gui/graph_edit.cpp64
-rw-r--r--scene/gui/graph_edit_arranger.cpp2
-rw-r--r--scene/gui/graph_element.cpp13
-rw-r--r--scene/gui/graph_frame.cpp14
-rw-r--r--scene/gui/graph_node.cpp53
-rw-r--r--scene/gui/graph_node.h5
-rw-r--r--scene/gui/grid_container.cpp15
-rw-r--r--scene/gui/item_list.cpp3
-rw-r--r--scene/gui/line_edit.cpp10
-rw-r--r--scene/gui/margin_container.cpp13
-rw-r--r--scene/gui/menu_button.cpp55
-rw-r--r--scene/gui/menu_button.h8
-rw-r--r--scene/gui/nine_patch_rect.cpp3
-rw-r--r--scene/gui/option_button.cpp67
-rw-r--r--scene/gui/option_button.h11
-rw-r--r--scene/gui/panel_container.cpp14
-rw-r--r--scene/gui/popup.cpp29
-rw-r--r--scene/gui/popup.h14
-rw-r--r--scene/gui/popup_menu.cpp55
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/progress_bar.cpp2
-rw-r--r--scene/gui/range.cpp2
-rw-r--r--scene/gui/rich_text_label.compat.inc5
-rw-r--r--scene/gui/rich_text_label.cpp1147
-rw-r--r--scene/gui/rich_text_label.h16
-rw-r--r--scene/gui/scroll_bar.cpp75
-rw-r--r--scene/gui/scroll_bar.h3
-rw-r--r--scene/gui/scroll_container.cpp47
-rw-r--r--scene/gui/spin_box.cpp6
-rw-r--r--scene/gui/split_container.cpp25
-rw-r--r--scene/gui/split_container.h3
-rw-r--r--scene/gui/tab_bar.cpp93
-rw-r--r--scene/gui/tab_bar.h21
-rw-r--r--scene/gui/tab_container.cpp68
-rw-r--r--scene/gui/tab_container.h6
-rw-r--r--scene/gui/text_edit.compat.inc41
-rw-r--r--scene/gui/text_edit.cpp2596
-rw-r--r--scene/gui/text_edit.h98
-rw-r--r--scene/gui/texture_button.cpp33
-rw-r--r--scene/gui/texture_progress_bar.cpp16
-rw-r--r--scene/gui/tree.cpp316
-rw-r--r--scene/gui/tree.h4
-rw-r--r--scene/gui/video_stream_player.cpp5
-rw-r--r--scene/main/canvas_item.compat.inc61
-rw-r--r--scene/main/canvas_item.cpp79
-rw-r--r--scene/main/canvas_item.h20
-rw-r--r--scene/main/canvas_layer.cpp2
-rw-r--r--scene/main/http_request.cpp2
-rw-r--r--scene/main/instance_placeholder.cpp131
-rw-r--r--scene/main/instance_placeholder.h4
-rw-r--r--scene/main/node.cpp137
-rw-r--r--scene/main/node.h5
-rw-r--r--scene/main/scene_tree.cpp12
-rw-r--r--scene/main/scene_tree.h6
-rw-r--r--scene/main/shader_globals_override.cpp13
-rw-r--r--scene/main/status_indicator.cpp65
-rw-r--r--scene/main/status_indicator.h12
-rw-r--r--scene/main/viewport.cpp99
-rw-r--r--scene/main/viewport.h14
-rw-r--r--scene/main/window.cpp26
-rw-r--r--scene/property_list_helper.cpp82
-rw-r--r--scene/property_list_helper.h13
-rw-r--r--scene/register_scene_types.cpp17
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp (renamed from scene/resources/navigation_mesh_source_geometry_data_2d.cpp)21
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.h (renamed from scene/resources/navigation_mesh_source_geometry_data_2d.h)5
-rw-r--r--scene/resources/2d/navigation_polygon.cpp (renamed from scene/resources/navigation_polygon.cpp)0
-rw-r--r--scene/resources/2d/navigation_polygon.h (renamed from scene/resources/navigation_polygon.h)0
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d.cpp5
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d.h2
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp13
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp15
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp11
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp9
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp4
-rw-r--r--scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp14
-rw-r--r--scene/resources/2d/tile_set.cpp67
-rw-r--r--scene/resources/2d/tile_set.h2
-rw-r--r--scene/resources/3d/importer_mesh.cpp2
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp (renamed from scene/resources/navigation_mesh_source_geometry_data_3d.cpp)35
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.h (renamed from scene/resources/navigation_mesh_source_geometry_data_3d.h)2
-rw-r--r--scene/resources/3d/world_3d.cpp1
-rw-r--r--scene/resources/animation.cpp30
-rw-r--r--scene/resources/animation.h7
-rw-r--r--scene/resources/animation_library.cpp4
-rw-r--r--scene/resources/atlas_texture.cpp2
-rw-r--r--scene/resources/audio_stream_wav.cpp145
-rw-r--r--scene/resources/audio_stream_wav.h23
-rw-r--r--scene/resources/canvas_item_material.cpp26
-rw-r--r--scene/resources/canvas_item_material.h3
-rw-r--r--scene/resources/curve_texture.cpp2
-rw-r--r--scene/resources/gradient_texture.cpp3
-rw-r--r--scene/resources/material.cpp46
-rw-r--r--scene/resources/material.h5
-rw-r--r--scene/resources/mesh.cpp4
-rw-r--r--scene/resources/packed_scene.cpp22
-rw-r--r--scene/resources/particle_process_material.cpp98
-rw-r--r--scene/resources/particle_process_material.h3
-rw-r--r--scene/resources/portable_compressed_texture.cpp6
-rw-r--r--scene/resources/resource_format_text.cpp48
-rw-r--r--scene/resources/resource_format_text.h2
-rw-r--r--scene/resources/shader.cpp40
-rw-r--r--scene/resources/sprite_frames.cpp2
-rw-r--r--scene/resources/surface_tool.cpp15
-rw-r--r--scene/resources/surface_tool.h3
-rw-r--r--scene/resources/syntax_highlighter.cpp8
-rw-r--r--scene/resources/syntax_highlighter.h4
-rw-r--r--scene/resources/visual_shader.cpp181
-rw-r--r--scene/resources/visual_shader.h27
-rw-r--r--scene/resources/visual_shader_nodes.cpp79
-rw-r--r--scene/resources/visual_shader_nodes.h31
-rw-r--r--scene/resources/visual_shader_particle_nodes.cpp6
-rw-r--r--scene/scene_string_names.cpp91
-rw-r--r--scene/scene_string_names.h88
-rw-r--r--scene/theme/SCsub1
-rw-r--r--scene/theme/default_theme.cpp23
-rw-r--r--scene/theme/icons/SCsub1
-rw-r--r--scene/theme/theme_db.cpp6
-rw-r--r--scene/theme/theme_owner.cpp12
236 files changed, 6114 insertions, 4712 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index 6eaf31d701..6d380aed3c 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -31,7 +31,6 @@
#include "animated_sprite_2d.h"
#include "scene/main/viewport.h"
-#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
Dictionary AnimatedSprite2D::_edit_get_state() const {
@@ -202,7 +201,7 @@ void AnimatedSprite2D::_notification(int p_what) {
} else {
frame = last_frame;
pause();
- emit_signal(SceneStringNames::get_singleton()->animation_finished);
+ emit_signal(SceneStringName(animation_finished));
return;
}
} else {
@@ -211,7 +210,7 @@ void AnimatedSprite2D::_notification(int p_what) {
_calc_frame_speed_scale();
frame_progress = 0.0;
queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
double to_process = MIN((1.0 - frame_progress) / abs_speed, remaining);
frame_progress += to_process * abs_speed;
@@ -226,7 +225,7 @@ void AnimatedSprite2D::_notification(int p_what) {
} else {
frame = 0;
pause();
- emit_signal(SceneStringNames::get_singleton()->animation_finished);
+ emit_signal(SceneStringName(animation_finished));
return;
}
} else {
@@ -235,7 +234,7 @@ void AnimatedSprite2D::_notification(int p_what) {
_calc_frame_speed_scale();
frame_progress = 1.0;
queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
double to_process = MIN(frame_progress / abs_speed, remaining);
frame_progress -= to_process * abs_speed;
@@ -291,12 +290,12 @@ void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) {
}
if (frames.is_valid()) {
- frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed));
+ frames->disconnect(CoreStringName(changed), callable_mp(this, &AnimatedSprite2D::_res_changed));
}
stop();
frames = p_frames;
if (frames.is_valid()) {
- frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed));
+ frames->connect(CoreStringName(changed), callable_mp(this, &AnimatedSprite2D::_res_changed));
List<StringName> al;
frames->get_animation_list(&al);
@@ -305,7 +304,7 @@ void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) {
autoplay = String();
} else {
if (!frames->has_animation(animation)) {
- set_animation(al[0]);
+ set_animation(al.front()->get());
}
if (!frames->has_animation(autoplay)) {
autoplay = String();
@@ -363,7 +362,7 @@ void AnimatedSprite2D::set_frame_and_progress(int p_frame, real_t p_progress) {
return; // No change, don't redraw.
}
queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
void AnimatedSprite2D::set_speed_scale(float p_speed_scale) {
@@ -482,7 +481,7 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
} else {
set_frame_and_progress(0, 0.0);
}
- emit_signal("animation_changed");
+ emit_signal(SceneStringName(animation_changed));
} else {
bool is_backward = signbit(speed_scale * custom_speed_scale);
if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) {
@@ -537,7 +536,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) {
animation = p_name;
- emit_signal("animation_changed");
+ emit_signal(SceneStringName(animation_changed));
if (frames == nullptr) {
animation = StringName();
diff --git a/scene/2d/audio_listener_2d.cpp b/scene/2d/audio_listener_2d.cpp
index b4484694a5..cff0654ecc 100644
--- a/scene/2d/audio_listener_2d.cpp
+++ b/scene/2d/audio_listener_2d.cpp
@@ -45,7 +45,7 @@ bool AudioListener2D::_set(const StringName &p_name, const Variant &p_value) {
bool AudioListener2D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "current") {
- if (is_inside_tree() && get_tree()->is_node_being_edited(this)) {
+ if (is_part_of_edited_scene()) {
r_ret = current;
} else {
r_ret = is_current();
@@ -63,13 +63,13 @@ void AudioListener2D::_get_property_list(List<PropertyInfo> *p_list) const {
void AudioListener2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- if (!get_tree()->is_node_being_edited(this) && current) {
+ if (!is_part_of_edited_scene() && current) {
make_current();
}
} break;
case NOTIFICATION_EXIT_TREE: {
- if (!get_tree()->is_node_being_edited(this)) {
+ if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; // Keep it true.
@@ -98,7 +98,7 @@ void AudioListener2D::clear_current() {
}
bool AudioListener2D::is_current() const {
- if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) {
+ if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_audio_listener_2d() == this;
} else {
return current;
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index cbd3c244d9..f88db0e3f4 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -37,7 +37,6 @@
#include "scene/audio/audio_stream_player_internal.h"
#include "scene/main/viewport.h"
#include "scene/resources/world_2d.h"
-#include "scene/scene_string_names.h"
#include "servers/audio/audio_stream.h"
#include "servers/audio_server.h"
@@ -81,10 +80,10 @@ StringName AudioStreamPlayer2D::_get_actual_bus() {
//check if any area is diverting sound into a bus
Ref<World2D> world_2d = get_world_2d();
- ERR_FAIL_COND_V(world_2d.is_null(), SceneStringNames::get_singleton()->Master);
+ ERR_FAIL_COND_V(world_2d.is_null(), SceneStringName(Master));
PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
- ERR_FAIL_NULL_V(space_state, SceneStringNames::get_singleton()->Master);
+ ERR_FAIL_NULL_V(space_state, SceneStringName(Master));
PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS];
PhysicsDirectSpaceState2D::PointParameters point_params;
@@ -196,6 +195,7 @@ Ref<AudioStream> AudioStreamPlayer2D::get_stream() const {
}
void AudioStreamPlayer2D::set_volume_db(float p_volume) {
+ ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN.");
internal->volume_db = p_volume;
}
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 4b866fc6de..18ef2d8505 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -54,16 +54,18 @@ void Camera2D::_update_scroll() {
if (is_current()) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
+ Size2 screen_size = _get_camera_screen_size();
+
Transform2D xform;
if (is_physics_interpolated_and_enabled()) {
xform = _interpolation_data.xform_prev.interpolate_with(_interpolation_data.xform_curr, Engine::get_singleton()->get_physics_interpolation_fraction());
+ camera_screen_center = xform.affine_inverse().xform(0.5 * screen_size);
} else {
xform = get_camera_transform();
}
viewport->set_canvas_transform(xform);
- Size2 screen_size = _get_camera_screen_size();
Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2());
Point2 adj_screen_pos = camera_screen_center - (screen_size * 0.5);
@@ -877,7 +879,7 @@ void Camera2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_margin_drawing_enabled"), &Camera2D::is_margin_drawing_enabled);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed TopLeft,Drag Center"), "set_anchor_mode", "get_anchor_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed Top Left,Drag Center"), "set_anchor_mode", "get_anchor_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_rotation"), "set_ignore_rotation", "is_ignoring_rotation");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom", PROPERTY_HINT_LINK), "set_zoom", "get_zoom");
diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp
index 2cd59776ec..9c9ba93b41 100644
--- a/scene/2d/cpu_particles_2d.cpp
+++ b/scene/2d/cpu_particles_2d.cpp
@@ -35,7 +35,6 @@
#include "scene/resources/curve_texture.h"
#include "scene/resources/gradient_texture.h"
#include "scene/resources/particle_process_material.h"
-#include "scene/scene_string_names.h"
void CPUParticles2D::set_emitting(bool p_emitting) {
if (emitting == p_emitting) {
@@ -997,7 +996,7 @@ void CPUParticles2D::_particles_process(double p_delta) {
}
if (!Math::is_equal_approx(time, 0.0) && active && !should_be_active) {
active = false;
- emit_signal(SceneStringNames::get_singleton()->finished);
+ emit_signal(SceneStringName(finished));
}
}
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index bc39513c03..1d3f1ceada 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -35,7 +35,6 @@
#include "scene/resources/curve_texture.h"
#include "scene/resources/gradient_texture.h"
#include "scene/resources/particle_process_material.h"
-#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
#include "core/config/engine.h"
@@ -731,7 +730,7 @@ void GPUParticles2D::_notification(int p_what) {
}
if (time > active_time) {
if (active && !signal_canceled) {
- emit_signal(SceneStringNames::get_singleton()->finished);
+ emit_signal(SceneStringName(finished));
}
active = false;
if (!emitting) {
diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp
index e898dc7fab..f8a8aa487c 100644
--- a/scene/2d/line_builder.cpp
+++ b/scene/2d/line_builder.cpp
@@ -353,7 +353,20 @@ void LineBuilder::build() {
} else if (current_joint_mode == Line2D::LINE_JOINT_ROUND && !(wrap_around && i == segments_count)) {
Vector2 vbegin = cbegin - pos1;
Vector2 vend = cend - pos1;
- strip_add_arc(pos1, vbegin.angle_to(vend), orientation);
+ // We want to use vbegin.angle_to(vend) below, which evaluates to
+ // Math::atan2(vbegin.cross(vend), vbegin.dot(vend)) but we need to
+ // calculate this ourselves as we need to check if the cross product
+ // in that calculation ends up being -0.f and flip it if so, effectively
+ // flipping the resulting angle_delta to not return -PI but +PI instead
+ float cross_product = vbegin.cross(vend);
+ float dot_product = vbegin.dot(vend);
+ // Note that we're comparing against -0.f for clarity but 0.f would
+ // match as well, therefore we need the explicit signbit check too.
+ if (cross_product == -0.f && signbit(cross_product)) {
+ cross_product = 0.f;
+ }
+ float angle_delta = Math::atan2(cross_product, dot_product);
+ strip_add_arc(pos1, angle_delta, orientation);
}
if (!is_intersecting) {
diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp
index 4fc375ff8d..ae45d431fe 100644
--- a/scene/2d/mesh_instance_2d.cpp
+++ b/scene/2d/mesh_instance_2d.cpp
@@ -30,8 +30,6 @@
#include "mesh_instance_2d.h"
-#include "scene/scene_string_names.h"
-
void MeshInstance2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
@@ -70,7 +68,7 @@ void MeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) {
}
texture = p_texture;
queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->texture_changed);
+ emit_signal(SceneStringName(texture_changed));
}
Ref<Texture2D> MeshInstance2D::get_texture() const {
diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp
index 9631b2cc4e..417e628517 100644
--- a/scene/2d/multimesh_instance_2d.cpp
+++ b/scene/2d/multimesh_instance_2d.cpp
@@ -30,8 +30,6 @@
#include "multimesh_instance_2d.h"
-#include "scene/scene_string_names.h"
-
void MultiMeshInstance2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
@@ -79,7 +77,7 @@ void MultiMeshInstance2D::set_texture(const Ref<Texture2D> &p_texture) {
}
texture = p_texture;
queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->texture_changed);
+ emit_signal(SceneStringName(texture_changed));
}
Ref<Texture2D> MultiMeshInstance2D::get_texture() const {
diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp
index 5d14358120..9e3e6ea583 100644
--- a/scene/2d/navigation_agent_2d.cpp
+++ b/scene/2d/navigation_agent_2d.cpp
@@ -796,7 +796,7 @@ void NavigationAgent2D::_trigger_waypoint_reached() {
Dictionary details;
const Vector2 waypoint = navigation_path[navigation_path_index];
- details[SNAME("position")] = waypoint;
+ details[CoreStringName(position)] = waypoint;
int waypoint_type = -1;
if (path_metadata_flags.has_flag(NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) {
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index 5510b59903..ab44e57d05 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -31,7 +31,6 @@
#include "navigation_region_2d.h"
#include "core/math/geometry_2d.h"
-#include "scene/2d/navigation_obstacle_2d.h"
#include "scene/resources/world_2d.h"
#include "servers/navigation_server_2d.h"
@@ -212,11 +211,6 @@ void NavigationRegion2D::set_navigation_map(RID p_navigation_map) {
map_override = p_navigation_map;
NavigationServer2D::get_singleton()->region_set_map(region, map_override);
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], map_override);
- }
- }
}
RID NavigationRegion2D::get_navigation_map() const {
@@ -265,7 +259,6 @@ void NavigationRegion2D::_navigation_polygon_changed() {
if (navigation_polygon.is_valid()) {
NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon);
}
- _update_avoidance_constrain();
}
#ifdef DEBUG_ENABLED
@@ -317,13 +310,6 @@ void NavigationRegion2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion2D::set_navigation_layer_value);
ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion2D::get_navigation_layer_value);
- ClassDB::bind_method(D_METHOD("set_constrain_avoidance", "enabled"), &NavigationRegion2D::set_constrain_avoidance);
- ClassDB::bind_method(D_METHOD("get_constrain_avoidance"), &NavigationRegion2D::get_constrain_avoidance);
- ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationRegion2D::set_avoidance_layers);
- ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationRegion2D::get_avoidance_layers);
- ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationRegion2D::set_avoidance_layer_value);
- ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationRegion2D::get_avoidance_layer_value);
-
ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion2D::get_region_rid);
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion2D::set_enter_cost);
@@ -343,8 +329,6 @@ void NavigationRegion2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constrain_avoidance"), "set_constrain_avoidance", "get_constrain_avoidance");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers");
ADD_SIGNAL(MethodInfo("navigation_polygon_changed"));
ADD_SIGNAL(MethodInfo("bake_finished"));
@@ -391,131 +375,12 @@ NavigationRegion2D::~NavigationRegion2D() {
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
NavigationServer2D::get_singleton()->free(region);
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]);
- }
- }
- constrain_avoidance_obstacles.clear();
-
#ifdef DEBUG_ENABLED
NavigationServer2D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed));
NavigationServer2D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed));
#endif // DEBUG_ENABLED
}
-void NavigationRegion2D::_update_avoidance_constrain() {
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->free(constrain_avoidance_obstacles[i]);
- constrain_avoidance_obstacles[i] = RID();
- }
- }
- constrain_avoidance_obstacles.clear();
-
- if (!constrain_avoidance) {
- return;
- }
-
- if (get_navigation_polygon() == nullptr) {
- return;
- }
-
- Ref<NavigationPolygon> _navpoly = get_navigation_polygon();
- int _outline_count = _navpoly->get_outline_count();
- if (_outline_count == 0) {
- return;
- }
-
- for (int outline_index(0); outline_index < _outline_count; outline_index++) {
- const Vector<Vector2> &_outline = _navpoly->get_outline(outline_index);
-
- const int outline_size = _outline.size();
- if (outline_size < 3) {
- ERR_FAIL_COND_MSG(_outline.size() < 3, "NavigationPolygon outline needs to have at least 3 vertex to create avoidance obstacles to constrain avoidance agent's");
- continue;
- }
-
- RID obstacle_rid = NavigationServer2D::get_singleton()->obstacle_create();
- constrain_avoidance_obstacles.push_back(obstacle_rid);
-
- Vector<Vector2> new_obstacle_outline;
-
- if (outline_index == 0) {
- for (int i(0); i < outline_size; i++) {
- new_obstacle_outline.push_back(_outline[outline_size - i - 1]);
- }
- ERR_FAIL_COND_MSG(Geometry2D::is_polygon_clockwise(_outline), "Outer most outline needs to be clockwise to push avoidance agent inside");
- } else {
- for (int i(0); i < outline_size; i++) {
- new_obstacle_outline.push_back(_outline[i]);
- }
- }
- new_obstacle_outline.resize(outline_size);
-
- NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle_rid, new_obstacle_outline);
- NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle_rid, avoidance_layers);
- if (is_inside_tree()) {
- if (map_override.is_valid()) {
- NavigationServer2D::get_singleton()->obstacle_set_map(obstacle_rid, map_override);
- } else {
- NavigationServer2D::get_singleton()->obstacle_set_map(obstacle_rid, get_world_2d()->get_navigation_map());
- }
- NavigationServer2D::get_singleton()->obstacle_set_position(obstacle_rid, get_global_position());
- }
- }
- constrain_avoidance_obstacles.resize(_outline_count);
-}
-
-void NavigationRegion2D::set_constrain_avoidance(bool p_enabled) {
- constrain_avoidance = p_enabled;
- _update_avoidance_constrain();
- notify_property_list_changed();
-}
-
-bool NavigationRegion2D::get_constrain_avoidance() const {
- return constrain_avoidance;
-}
-
-void NavigationRegion2D::_validate_property(PropertyInfo &p_property) const {
- if (p_property.name == "avoidance_layers") {
- if (!constrain_avoidance) {
- p_property.usage = PROPERTY_USAGE_NO_EDITOR;
- }
- }
-}
-
-void NavigationRegion2D::set_avoidance_layers(uint32_t p_layers) {
- avoidance_layers = p_layers;
- if (constrain_avoidance_obstacles.size() > 0) {
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(constrain_avoidance_obstacles[i], avoidance_layers);
- }
- }
-}
-
-uint32_t NavigationRegion2D::get_avoidance_layers() const {
- return avoidance_layers;
-}
-
-void NavigationRegion2D::set_avoidance_layer_value(int p_layer_number, bool p_value) {
- ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive.");
- ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive.");
- uint32_t avoidance_layers_new = get_avoidance_layers();
- if (p_value) {
- avoidance_layers_new |= 1 << (p_layer_number - 1);
- } else {
- avoidance_layers_new &= ~(1 << (p_layer_number - 1));
- }
- set_avoidance_layers(avoidance_layers_new);
-}
-
-bool NavigationRegion2D::get_avoidance_layer_value(int p_layer_number) const {
- ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive.");
- ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive.");
- return get_avoidance_layers() & (1 << (p_layer_number - 1));
-}
-
void NavigationRegion2D::_region_enter_navigation_map() {
if (!is_inside_tree()) {
return;
@@ -523,27 +388,12 @@ void NavigationRegion2D::_region_enter_navigation_map() {
if (map_override.is_valid()) {
NavigationServer2D::get_singleton()->region_set_map(region, map_override);
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], map_override);
- }
- }
} else {
NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map());
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], get_world_2d()->get_navigation_map());
- }
- }
}
current_global_transform = get_global_transform();
NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform);
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position());
- }
- }
NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
@@ -552,11 +402,6 @@ void NavigationRegion2D::_region_enter_navigation_map() {
void NavigationRegion2D::_region_exit_navigation_map() {
NavigationServer2D::get_singleton()->region_set_map(region, RID());
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->obstacle_set_map(constrain_avoidance_obstacles[i], RID());
- }
- }
}
void NavigationRegion2D::_region_update_transform() {
@@ -568,11 +413,6 @@ void NavigationRegion2D::_region_update_transform() {
if (current_global_transform != new_global_transform) {
current_global_transform = new_global_transform;
NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform);
- for (uint32_t i = 0; i < constrain_avoidance_obstacles.size(); i++) {
- if (constrain_avoidance_obstacles[i].is_valid()) {
- NavigationServer2D::get_singleton()->obstacle_set_position(constrain_avoidance_obstacles[i], get_global_position());
- }
- }
}
queue_redraw();
diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h
index 87c2365b15..5a86dd607d 100644
--- a/scene/2d/navigation_region_2d.h
+++ b/scene/2d/navigation_region_2d.h
@@ -31,7 +31,7 @@
#ifndef NAVIGATION_REGION_2D_H
#define NAVIGATION_REGION_2D_H
-#include "scene/resources/navigation_polygon.h"
+#include "scene/resources/2d/navigation_polygon.h"
class NavigationRegion2D : public Node2D {
GDCLASS(NavigationRegion2D, Node2D);
@@ -46,10 +46,6 @@ class NavigationRegion2D : public Node2D {
real_t travel_cost = 1.0;
Ref<NavigationPolygon> navigation_polygon;
- bool constrain_avoidance = false;
- LocalVector<RID> constrain_avoidance_obstacles;
- uint32_t avoidance_layers = 1;
-
Transform2D current_global_transform;
void _navigation_polygon_changed();
@@ -65,7 +61,6 @@ private:
protected:
void _notification(int p_what);
- void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
@@ -106,15 +101,6 @@ public:
void set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon);
Ref<NavigationPolygon> get_navigation_polygon() const;
- void set_constrain_avoidance(bool p_enabled);
- bool get_constrain_avoidance() const;
-
- void set_avoidance_layers(uint32_t p_layers);
- uint32_t get_avoidance_layers() const;
-
- void set_avoidance_layer_value(int p_layer_number, bool p_value);
- bool get_avoidance_layer_value(int p_layer_number) const;
-
PackedStringArray get_configuration_warnings() const override;
void bake_navigation_polygon(bool p_on_thread);
@@ -125,7 +111,6 @@ public:
~NavigationRegion2D();
private:
- void _update_avoidance_constrain();
void _region_enter_navigation_map();
void _region_exit_navigation_map();
void _region_update_transform();
diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp
index 7e47e0f061..aacab3213d 100644
--- a/scene/2d/parallax_2d.cpp
+++ b/scene/2d/parallax_2d.cpp
@@ -102,14 +102,14 @@ void Parallax2D::_update_scroll() {
scroll_ofs *= scroll_scale;
if (repeat_size.x) {
- real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x);
+ real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x * get_scale().x);
scroll_ofs.x = screen_offset.x - mod;
} else {
scroll_ofs.x = screen_offset.x + scroll_offset.x - scroll_ofs.x;
}
if (repeat_size.y) {
- real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y);
+ real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y * get_scale().y);
scroll_ofs.y = screen_offset.y - mod;
} else {
scroll_ofs.y = screen_offset.y + scroll_offset.y - scroll_ofs.y;
@@ -127,8 +127,7 @@ void Parallax2D::_update_repeat() {
return;
}
- Point2 repeat_scale = repeat_size * get_scale();
- RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_scale, repeat_times);
+ RenderingServer::get_singleton()->canvas_set_item_repeat(get_canvas_item(), repeat_size, repeat_times);
RenderingServer::get_singleton()->canvas_item_set_interpolated(get_canvas_item(), false);
}
@@ -145,7 +144,7 @@ void Parallax2D::set_repeat_size(const Size2 &p_repeat_size) {
return;
}
- repeat_size = p_repeat_size.max(Vector2(0, 0));
+ repeat_size = p_repeat_size.maxf(0);
_update_process();
_update_repeat();
diff --git a/scene/2d/physics/area_2d.cpp b/scene/2d/physics/area_2d.cpp
index b1ff94dda4..305ac8248e 100644
--- a/scene/2d/physics/area_2d.cpp
+++ b/scene/2d/physics/area_2d.cpp
@@ -30,7 +30,6 @@
#include "area_2d.h"
-#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
void Area2D::set_gravity_space_override_mode(SpaceOverride p_mode) {
@@ -142,9 +141,9 @@ void Area2D::_body_enter_tree(ObjectID p_id) {
ERR_FAIL_COND(E->value.in_tree);
E->value.in_tree = true;
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
+ emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
}
}
@@ -156,9 +155,9 @@ void Area2D::_body_exit_tree(ObjectID p_id) {
ERR_FAIL_COND(!E);
ERR_FAIL_COND(!E->value.in_tree);
E->value.in_tree = false;
- emit_signal(SceneStringNames::get_singleton()->body_exited, node);
+ emit_signal(SceneStringName(body_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
+ emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
}
}
@@ -172,9 +171,9 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
lock_callback();
locked = true;
if (body_in) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_entered), p_body, (Node *)nullptr, p_body_shape, p_area_shape);
} else {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_exited), p_body, (Node *)nullptr, p_body_shape, p_area_shape);
}
locked = false;
unlock_callback();
@@ -200,10 +199,10 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
E->value.rc = 0;
E->value.in_tree = node && node->is_inside_tree();
if (node) {
- node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree).bind(objid));
- node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree).bind(objid));
+ node->connect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_body_enter_tree).bind(objid));
+ node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_body_exit_tree).bind(objid));
if (E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
}
}
}
@@ -213,7 +212,7 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
}
if (!node || E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_area_shape);
}
} else {
@@ -227,15 +226,15 @@ void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
if (E->value.rc == 0) {
body_map.remove(E);
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_body_exit_tree));
if (in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_exited, obj);
+ emit_signal(SceneStringName(body_exited), obj);
}
}
}
if (!node || in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_exited), p_body, obj, p_body_shape, p_area_shape);
}
}
@@ -253,9 +252,9 @@ void Area2D::_area_enter_tree(ObjectID p_id) {
ERR_FAIL_COND(E->value.in_tree);
E->value.in_tree = true;
- emit_signal(SceneStringNames::get_singleton()->area_entered, node);
+ emit_signal(SceneStringName(area_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
+ emit_signal(SceneStringName(area_shape_entered), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
}
}
@@ -267,9 +266,9 @@ void Area2D::_area_exit_tree(ObjectID p_id) {
ERR_FAIL_COND(!E);
ERR_FAIL_COND(!E->value.in_tree);
E->value.in_tree = false;
- emit_signal(SceneStringNames::get_singleton()->area_exited, node);
+ emit_signal(SceneStringName(area_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
+ emit_signal(SceneStringName(area_shape_exited), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
}
}
@@ -283,9 +282,9 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
lock_callback();
locked = true;
if (area_in) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_entered), p_area, (Node *)nullptr, p_area_shape, p_self_shape);
} else {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_exited), p_area, (Node *)nullptr, p_area_shape, p_self_shape);
}
locked = false;
unlock_callback();
@@ -311,10 +310,10 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
E->value.rc = 0;
E->value.in_tree = node && node->is_inside_tree();
if (node) {
- node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree).bind(objid));
- node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree).bind(objid));
+ node->connect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_area_enter_tree).bind(objid));
+ node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_area_exit_tree).bind(objid));
if (E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_entered, node);
+ emit_signal(SceneStringName(area_entered), node);
}
}
}
@@ -324,7 +323,7 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
}
if (!node || E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, node, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_entered), p_area, node, p_area_shape, p_self_shape);
}
} else {
@@ -338,15 +337,15 @@ void Area2D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
if (E->value.rc == 0) {
area_map.remove(E);
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_area_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_area_exit_tree));
if (in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_exited, obj);
+ emit_signal(SceneStringName(area_exited), obj);
}
}
}
if (!node || in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, obj, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_exited), p_area, obj, p_area_shape, p_self_shape);
}
}
@@ -370,18 +369,18 @@ void Area2D::_clear_monitoring() {
continue;
}
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_body_exit_tree));
if (!E.value.in_tree) {
continue;
}
for (int i = 0; i < E.value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape);
+ emit_signal(SceneStringName(body_shape_exited), E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape);
}
- emit_signal(SceneStringNames::get_singleton()->body_exited, obj);
+ emit_signal(SceneStringName(body_exited), obj);
}
}
@@ -398,18 +397,18 @@ void Area2D::_clear_monitoring() {
continue;
}
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_area_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_area_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area2D::_area_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area2D::_area_exit_tree));
if (!E.value.in_tree) {
continue;
}
for (int i = 0; i < E.value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape);
+ emit_signal(SceneStringName(area_shape_exited), E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape);
}
- emit_signal(SceneStringNames::get_singleton()->area_exited, obj);
+ emit_signal(SceneStringName(area_exited), obj);
}
}
}
@@ -538,7 +537,7 @@ StringName Area2D::get_audio_bus_name() const {
return audio_bus;
}
}
- return SceneStringNames::get_singleton()->Master;
+ return SceneStringName(Master);
}
void Area2D::_validate_property(PropertyInfo &p_property) const {
diff --git a/scene/2d/physics/character_body_2d.cpp b/scene/2d/physics/character_body_2d.cpp
index e5d575a159..a503f3cb78 100644
--- a/scene/2d/physics/character_body_2d.cpp
+++ b/scene/2d/physics/character_body_2d.cpp
@@ -501,7 +501,7 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) {
// Create a new instance when the cached reference is invalid or still in use in script.
if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->get_reference_count() > 1) {
slide_colliders.write[p_bounce].instantiate();
- slide_colliders.write[p_bounce]->owner = this;
+ slide_colliders.write[p_bounce]->owner_id = get_instance_id();
}
slide_colliders.write[p_bounce]->result = motion_results[p_bounce];
@@ -745,11 +745,3 @@ void CharacterBody2D::_bind_methods() {
CharacterBody2D::CharacterBody2D() :
PhysicsBody2D(PhysicsServer2D::BODY_MODE_KINEMATIC) {
}
-
-CharacterBody2D::~CharacterBody2D() {
- for (int i = 0; i < slide_colliders.size(); i++) {
- if (slide_colliders[i].is_valid()) {
- slide_colliders.write[i]->owner = nullptr;
- }
- }
-}
diff --git a/scene/2d/physics/character_body_2d.h b/scene/2d/physics/character_body_2d.h
index 395438a1f1..536d0a1ebd 100644
--- a/scene/2d/physics/character_body_2d.h
+++ b/scene/2d/physics/character_body_2d.h
@@ -111,7 +111,6 @@ public:
PlatformOnLeave get_platform_on_leave() const;
CharacterBody2D();
- ~CharacterBody2D();
private:
real_t margin = 0.08;
diff --git a/scene/2d/physics/collision_object_2d.cpp b/scene/2d/physics/collision_object_2d.cpp
index 4e5852984b..00b6085f0c 100644
--- a/scene/2d/physics/collision_object_2d.cpp
+++ b/scene/2d/physics/collision_object_2d.cpp
@@ -31,7 +31,6 @@
#include "collision_object_2d.h"
#include "scene/resources/world_2d.h"
-#include "scene/scene_string_names.h"
void CollisionObject2D::_notification(int p_what) {
switch (p_what) {
@@ -519,27 +518,27 @@ bool CollisionObject2D::is_pickable() const {
void CollisionObject2D::_input_event_call(Viewport *p_viewport, const Ref<InputEvent> &p_input_event, int p_shape) {
GDVIRTUAL_CALL(_input_event, p_viewport, p_input_event, p_shape);
- emit_signal(SceneStringNames::get_singleton()->input_event, p_viewport, p_input_event, p_shape);
+ emit_signal(SceneStringName(input_event), p_viewport, p_input_event, p_shape);
}
void CollisionObject2D::_mouse_enter() {
GDVIRTUAL_CALL(_mouse_enter);
- emit_signal(SceneStringNames::get_singleton()->mouse_entered);
+ emit_signal(SceneStringName(mouse_entered));
}
void CollisionObject2D::_mouse_exit() {
GDVIRTUAL_CALL(_mouse_exit);
- emit_signal(SceneStringNames::get_singleton()->mouse_exited);
+ emit_signal(SceneStringName(mouse_exited));
}
void CollisionObject2D::_mouse_shape_enter(int p_shape) {
GDVIRTUAL_CALL(_mouse_shape_enter, p_shape);
- emit_signal(SceneStringNames::get_singleton()->mouse_shape_entered, p_shape);
+ emit_signal(SceneStringName(mouse_shape_entered), p_shape);
}
void CollisionObject2D::_mouse_shape_exit(int p_shape) {
GDVIRTUAL_CALL(_mouse_shape_exit, p_shape);
- emit_signal(SceneStringNames::get_singleton()->mouse_shape_exited, p_shape);
+ emit_signal(SceneStringName(mouse_shape_exited), p_shape);
}
void CollisionObject2D::set_only_update_transform_changes(bool p_enable) {
diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp
index 72ee4d52c5..a9b47ef4d4 100644
--- a/scene/2d/physics/collision_polygon_2d.cpp
+++ b/scene/2d/physics/collision_polygon_2d.cpp
@@ -144,7 +144,7 @@ void CollisionPolygon2D::_notification(int p_what) {
}
#endif
- const Color stroke_color = Color(0.9, 0.2, 0.0);
+ const Color stroke_color = get_tree()->get_debug_collisions_color();
draw_polyline(polygon, stroke_color);
// Draw the last segment.
draw_line(polygon[polygon.size() - 1], polygon[0], stroke_color);
diff --git a/scene/2d/physics/joints/joint_2d.cpp b/scene/2d/physics/joints/joint_2d.cpp
index 1afac7c150..a32bcbae78 100644
--- a/scene/2d/physics/joints/joint_2d.cpp
+++ b/scene/2d/physics/joints/joint_2d.cpp
@@ -31,19 +31,18 @@
#include "joint_2d.h"
#include "scene/2d/physics/physics_body_2d.h"
-#include "scene/scene_string_names.h"
void Joint2D::_disconnect_signals() {
Node *node_a = get_node_or_null(a);
PhysicsBody2D *body_a = Object::cast_to<PhysicsBody2D>(node_a);
if (body_a) {
- body_a->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
+ body_a->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
}
Node *node_b = get_node_or_null(b);
PhysicsBody2D *body_b = Object::cast_to<PhysicsBody2D>(node_b);
if (body_b) {
- body_b->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
+ body_b->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
}
}
@@ -117,8 +116,12 @@ void Joint2D::_update_joint(bool p_only_free) {
ba = body_a->get_rid();
bb = body_b->get_rid();
- body_a->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
- body_b->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint2D::_body_exit_tree));
+ if (!body_a->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree))) {
+ body_a->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
+ }
+ if (!body_b->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree))) {
+ body_b->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint2D::_body_exit_tree));
+ }
PhysicsServer2D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision);
}
diff --git a/scene/2d/physics/kinematic_collision_2d.cpp b/scene/2d/physics/kinematic_collision_2d.cpp
index 7e7c33b259..18b0254769 100644
--- a/scene/2d/physics/kinematic_collision_2d.cpp
+++ b/scene/2d/physics/kinematic_collision_2d.cpp
@@ -59,6 +59,7 @@ real_t KinematicCollision2D::get_depth() const {
}
Object *KinematicCollision2D::get_local_shape() const {
+ PhysicsBody2D *owner = Object::cast_to<PhysicsBody2D>(ObjectDB::get_instance(owner_id));
if (!owner) {
return nullptr;
}
diff --git a/scene/2d/physics/kinematic_collision_2d.h b/scene/2d/physics/kinematic_collision_2d.h
index 0d187b87a5..8d3d6ca8c1 100644
--- a/scene/2d/physics/kinematic_collision_2d.h
+++ b/scene/2d/physics/kinematic_collision_2d.h
@@ -40,7 +40,7 @@ class PhysicsBody2D;
class KinematicCollision2D : public RefCounted {
GDCLASS(KinematicCollision2D, RefCounted);
- PhysicsBody2D *owner = nullptr;
+ ObjectID owner_id;
friend class PhysicsBody2D;
friend class CharacterBody2D;
PhysicsServer2D::MotionResult result;
diff --git a/scene/2d/physics/physics_body_2d.cpp b/scene/2d/physics/physics_body_2d.cpp
index 81120d0b01..fc14e6ed62 100644
--- a/scene/2d/physics/physics_body_2d.cpp
+++ b/scene/2d/physics/physics_body_2d.cpp
@@ -30,8 +30,6 @@
#include "physics_body_2d.h"
-#include "scene/scene_string_names.h"
-
void PhysicsBody2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("move_and_collide", "motion", "test_only", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08), DEFVAL(false));
ClassDB::bind_method(D_METHOD("test_move", "from", "motion", "collision", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08), DEFVAL(false));
@@ -48,12 +46,6 @@ PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) :
set_pickable(false);
}
-PhysicsBody2D::~PhysicsBody2D() {
- if (motion_cache.is_valid()) {
- motion_cache->owner = nullptr;
- }
-}
-
Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_test_only, real_t p_margin, bool p_recovery_as_collision) {
PhysicsServer2D::MotionParameters parameters(get_global_transform(), p_motion, p_margin);
parameters.recovery_as_collision = p_recovery_as_collision;
@@ -64,7 +56,7 @@ Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_motion, bool p_t
// Create a new instance when the cached reference is invalid or still in use in script.
if (motion_cache.is_null() || motion_cache->get_reference_count() > 1) {
motion_cache.instantiate();
- motion_cache->owner = this;
+ motion_cache->owner_id = get_instance_id();
}
motion_cache->result = result;
diff --git a/scene/2d/physics/physics_body_2d.h b/scene/2d/physics/physics_body_2d.h
index 43bc479881..d44eebabee 100644
--- a/scene/2d/physics/physics_body_2d.h
+++ b/scene/2d/physics/physics_body_2d.h
@@ -56,8 +56,6 @@ public:
TypedArray<PhysicsBody2D> get_collision_exceptions();
void add_collision_exception_with(Node *p_node); //must be physicsbody
void remove_collision_exception_with(Node *p_node);
-
- virtual ~PhysicsBody2D();
};
#endif // PHYSICS_BODY_2D_H
diff --git a/scene/2d/physics/rigid_body_2d.cpp b/scene/2d/physics/rigid_body_2d.cpp
index 5e05c563a4..402e5c8b95 100644
--- a/scene/2d/physics/rigid_body_2d.cpp
+++ b/scene/2d/physics/rigid_body_2d.cpp
@@ -30,8 +30,6 @@
#include "rigid_body_2d.h"
-#include "scene/scene_string_names.h"
-
void RigidBody2D::_body_enter_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
@@ -44,10 +42,10 @@ void RigidBody2D::_body_enter_tree(ObjectID p_id) {
contact_monitor->locked = true;
E->value.in_scene = true;
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
+ emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
}
contact_monitor->locked = false;
@@ -65,10 +63,10 @@ void RigidBody2D::_body_exit_tree(ObjectID p_id) {
contact_monitor->locked = true;
- emit_signal(SceneStringNames::get_singleton()->body_exited, node);
+ emit_signal(SceneStringName(body_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
+ emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
}
contact_monitor->locked = false;
@@ -93,10 +91,10 @@ void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan
//E->value.rc=0;
E->value.in_scene = node && node->is_inside_tree();
if (node) {
- node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody2D::_body_enter_tree).bind(objid));
- node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody2D::_body_exit_tree).bind(objid));
+ node->connect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree).bind(objid));
+ node->connect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree).bind(objid));
if (E->value.in_scene) {
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
}
}
@@ -108,7 +106,7 @@ void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan
}
if (E->value.in_scene) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_local_shape);
+ emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_local_shape);
}
} else {
@@ -122,17 +120,17 @@ void RigidBody2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan
if (E->value.shapes.is_empty()) {
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody2D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody2D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree));
if (in_scene) {
- emit_signal(SceneStringNames::get_singleton()->body_exited, node);
+ emit_signal(SceneStringName(body_exited), node);
}
}
contact_monitor->body_map.remove(E);
}
if (node && in_scene) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, node, p_body_shape, p_local_shape);
+ emit_signal(SceneStringName(body_shape_exited), p_body, node, p_body_shape, p_local_shape);
}
}
}
@@ -158,7 +156,7 @@ void RigidBody2D::_sync_body_state(PhysicsDirectBodyState2D *p_state) {
if (sleeping != p_state->is_sleeping()) {
sleeping = p_state->is_sleeping();
- emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed);
+ emit_signal(SceneStringName(sleeping_state_changed));
}
}
@@ -605,8 +603,8 @@ void RigidBody2D::set_contact_monitor(bool p_enabled) {
Node *node = Object::cast_to<Node>(obj);
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody2D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody2D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody2D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody2D::_body_exit_tree));
}
}
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 69e0414855..fe21c7f21b 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -635,36 +635,47 @@ Bone2D *Skeleton2D::get_bone(int p_idx) {
}
void Skeleton2D::_notification(int p_what) {
- if (p_what == NOTIFICATION_READY) {
- if (bone_setup_dirty) {
- _update_bone_setup();
- }
- if (transform_dirty) {
- _update_transform();
- }
- request_ready();
- }
+ switch (p_what) {
+ case NOTIFICATION_READY: {
+ if (bone_setup_dirty) {
+ _update_bone_setup();
+ }
+ if (transform_dirty) {
+ _update_transform();
+ }
+ request_ready();
+ } break;
- if (p_what == NOTIFICATION_TRANSFORM_CHANGED) {
- RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform());
- } else if (p_what == NOTIFICATION_INTERNAL_PROCESS) {
- if (modification_stack.is_valid()) {
- execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process);
- }
- } else if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
- if (modification_stack.is_valid()) {
- execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process);
- }
- }
-#ifdef TOOLS_ENABLED
- else if (p_what == NOTIFICATION_DRAW) {
- if (Engine::get_singleton()->is_editor_hint()) {
+ case NOTIFICATION_TRANSFORM_CHANGED: {
+ RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform());
+ } break;
+
+ case NOTIFICATION_INTERNAL_PROCESS: {
if (modification_stack.is_valid()) {
- modification_stack->draw_editor_gizmos();
+ execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process);
}
- }
- }
+ } break;
+
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ if (modification_stack.is_valid()) {
+ execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process);
+ }
+ } break;
+
+ case NOTIFICATION_POST_ENTER_TREE: {
+ set_modification_stack(modification_stack);
+ } break;
+
+#ifdef TOOLS_ENABLED
+ case NOTIFICATION_DRAW: {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ if (modification_stack.is_valid()) {
+ modification_stack->draw_editor_gizmos();
+ }
+ }
+ } break;
#endif // TOOLS_ENABLED
+ }
}
RID Skeleton2D::get_skeleton() const {
@@ -692,7 +703,7 @@ void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack
set_physics_process_internal(false);
}
modification_stack = p_stack;
- if (modification_stack.is_valid()) {
+ if (modification_stack.is_valid() && is_inside_tree()) {
modification_stack->set_skeleton(this);
modification_stack->setup();
diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp
index 5745a59297..efb5029ac4 100644
--- a/scene/2d/sprite_2d.cpp
+++ b/scene/2d/sprite_2d.cpp
@@ -31,7 +31,6 @@
#include "sprite_2d.h"
#include "scene/main/window.h"
-#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
Dictionary Sprite2D::_edit_get_state() const {
@@ -146,7 +145,7 @@ void Sprite2D::set_texture(const Ref<Texture2D> &p_texture) {
}
queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->texture_changed);
+ emit_signal(SceneStringName(texture_changed));
item_rect_changed();
}
@@ -260,7 +259,7 @@ void Sprite2D::set_frame(int p_frame) {
frame = p_frame;
item_rect_changed();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
int Sprite2D::get_frame() const {
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index bbf1d09bbc..f7d672620d 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -31,7 +31,6 @@
#include "tile_map.h"
#include "tile_map.compat.inc"
-#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "scene/gui/control.h"
@@ -54,7 +53,7 @@ void TileMap::_tile_set_changed() {
}
void TileMap::_emit_changed() {
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMap::_set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data) {
@@ -161,6 +160,10 @@ Vector<int> TileMap::_get_tile_map_data_using_compatibility_format(int p_layer)
return tile_data;
}
+void TileMap::_set_layer_tile_data(int p_layer, const PackedInt32Array &p_data) {
+ _set_tile_map_data_using_compatibility_format(p_layer, format, p_data);
+}
+
void TileMap::_notification(int p_what) {
switch (p_what) {
case TileMap::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
@@ -222,91 +225,6 @@ int TileMap::get_rendering_quadrant_size() const {
return rendering_quadrant_size;
}
-void TileMap::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_normalized_animation_offset) {
- ERR_FAIL_COND(!p_tile_set.is_valid());
- ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id));
- ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords));
- ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile));
- TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id);
- TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
- if (atlas_source) {
- // Check for the frame.
- if (p_frame >= 0) {
- ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords));
- }
-
- // Get the texture.
- Ref<Texture2D> tex = atlas_source->get_runtime_texture();
- if (!tex.is_valid()) {
- return;
- }
-
- // Check if we are in the texture, return otherwise.
- Vector2i grid_size = atlas_source->get_atlas_grid_size();
- if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) {
- return;
- }
-
- // Get tile data.
- const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile);
-
- // Get the tile modulation.
- Color modulate = tile_data->get_modulate() * p_modulation;
-
- // Compute the offset.
- Vector2 tile_offset = tile_data->get_texture_origin();
-
- // Get destination rect.
- Rect2 dest_rect;
- dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size;
- dest_rect.size.x += FP_ADJUST;
- dest_rect.size.y += FP_ADJUST;
-
- bool transpose = tile_data->get_transpose() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
- if (transpose) {
- dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset);
- } else {
- dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset);
- }
-
- if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) {
- dest_rect.size.x = -dest_rect.size.x;
- }
-
- if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) {
- dest_rect.size.y = -dest_rect.size.y;
- }
-
- // Draw the tile.
- if (p_frame >= 0) {
- Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame);
- tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
- } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) {
- Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0);
- tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
- } else {
- real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords);
- real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed;
- real_t animation_offset = p_normalized_animation_offset * animation_duration;
- // Accumulate durations unaffected by the speed to avoid accumulating floating point division errors.
- // Aka do `sum(duration[i]) / speed` instead of `sum(duration[i] / speed)`.
- real_t time_unscaled = 0.0;
- for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) {
- real_t frame_duration_unscaled = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame);
- real_t slice_start = time_unscaled / speed;
- real_t slice_end = (time_unscaled + frame_duration_unscaled) / speed;
- RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, slice_start, slice_end, animation_offset);
-
- Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame);
- tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
-
- time_unscaled += frame_duration_unscaled;
- }
- RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0);
- }
- }
-}
-
void TileMap::set_tileset(const Ref<TileSet> &p_tileset) {
if (p_tileset == tile_set) {
return;
@@ -356,7 +274,7 @@ void TileMap::add_layer(int p_to_pos) {
for (uint32_t i = 0; i < layers.size(); i++) {
layers[i]->set_as_tile_map_internal_node(i);
}
- new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
+ new_layer->connect(CoreStringName(changed), callable_mp(this, &TileMap::_emit_changed));
notify_property_list_changed();
@@ -429,6 +347,7 @@ Color TileMap::get_layer_modulate(int p_layer) const {
void TileMap::set_layer_y_sort_enabled(int p_layer, bool p_y_sort_enabled) {
TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_enabled, p_y_sort_enabled);
+ update_configuration_warnings();
}
bool TileMap::is_layer_y_sort_enabled(int p_layer) const {
@@ -437,6 +356,7 @@ bool TileMap::is_layer_y_sort_enabled(int p_layer) const {
void TileMap::set_layer_y_sort_origin(int p_layer, int p_y_sort_origin) {
TILEMAP_CALL_FOR_LAYER(p_layer, set_y_sort_origin, p_y_sort_origin);
+ update_configuration_warnings();
}
int TileMap::get_layer_y_sort_origin(int p_layer) const {
@@ -518,9 +438,6 @@ void TileMap::set_y_sort_enabled(bool p_enable) {
return;
}
Node2D::set_y_sort_enabled(p_enable);
- for (TileMapLayer *layer : layers) {
- layer->set_y_sort_enabled(p_enable);
- }
_emit_changed();
update_configuration_warnings();
}
@@ -741,41 +658,23 @@ Rect2 TileMap::_edit_get_rect() const {
#endif
bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
+ int index;
+ const String sname = p_name;
+
Vector<String> components = String(p_name).split("/", true, 2);
- if (p_name == "format") {
+ if (sname == "format") {
if (p_value.get_type() == Variant::INT) {
format = (TileMapDataFormat)(p_value.operator int64_t()); // Set format used for loading.
return true;
}
}
#ifndef DISABLE_DEPRECATED
- else if (p_name == "tile_data") { // Kept for compatibility reasons.
- if (p_value.is_array()) {
- if (layers.size() == 0) {
- TileMapLayer *new_layer = memnew(TileMapLayer);
- add_child(new_layer, false, INTERNAL_MODE_FRONT);
- new_layer->set_as_tile_map_internal_node(0);
- new_layer->set_name("Layer0");
- new_layer->set_tile_set(tile_set);
- new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
- layers.push_back(new_layer);
- }
- _set_tile_map_data_using_compatibility_format(0, format, p_value);
- _emit_changed();
- return true;
- }
- return false;
- } else if (p_name == "cell_quadrant_size") {
+ else if (sname == "cell_quadrant_size") {
set_rendering_quadrant_size(p_value);
return true;
}
#endif // DISABLE_DEPRECATED
- else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
- int index = components[0].trim_prefix("layer_").to_int();
- if (index < 0) {
- return false;
- }
-
+ else if (property_helper.is_property_valid(sname, &index)) {
if (index >= (int)layers.size()) {
while (index >= (int)layers.size()) {
TileMapLayer *new_layer = memnew(TileMapLayer);
@@ -783,7 +682,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
new_layer->set_as_tile_map_internal_node(index);
new_layer->set_name(vformat("Layer%d", index));
new_layer->set_tile_set(tile_set);
- new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
+ new_layer->connect(CoreStringName(changed), callable_mp(this, &TileMap::_emit_changed));
layers.push_back(new_layer);
}
@@ -792,172 +691,38 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) {
update_configuration_warnings();
}
- if (components[1] == "name") {
- set_layer_name(index, p_value);
- return true;
- } else if (components[1] == "enabled") {
- set_layer_enabled(index, p_value);
- return true;
- } else if (components[1] == "modulate") {
- set_layer_modulate(index, p_value);
- return true;
- } else if (components[1] == "y_sort_enabled") {
- set_layer_y_sort_enabled(index, p_value);
- return true;
- } else if (components[1] == "y_sort_origin") {
- set_layer_y_sort_origin(index, p_value);
- return true;
- } else if (components[1] == "z_index") {
- set_layer_z_index(index, p_value);
- return true;
- } else if (components[1] == "navigation_enabled") {
- set_layer_navigation_enabled(index, p_value);
- return true;
- } else if (components[1] == "tile_data") {
- _set_tile_map_data_using_compatibility_format(index, format, p_value);
- _emit_changed();
+ if (property_helper.property_set_value(sname, p_value)) {
+ if (components[1] == "tile_data") {
+ _emit_changed();
+ }
return true;
- } else {
- return false;
}
}
return false;
}
bool TileMap::_get(const StringName &p_name, Variant &r_ret) const {
+ const String sname = p_name;
+
Vector<String> components = String(p_name).split("/", true, 2);
if (p_name == "format") {
r_ret = TileMapDataFormat::TILE_MAP_DATA_FORMAT_MAX - 1; // When saving, always save highest format.
return true;
}
#ifndef DISABLE_DEPRECATED
- else if (p_name == "cell_quadrant_size") { // Kept for compatibility reasons.
+ else if (sname == "cell_quadrant_size") { // Kept for compatibility reasons.
r_ret = get_rendering_quadrant_size();
return true;
}
#endif
- else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
- int index = components[0].trim_prefix("layer_").to_int();
- if (index < 0 || index >= (int)layers.size()) {
- return false;
- }
-
- if (components[1] == "name") {
- r_ret = get_layer_name(index);
- return true;
- } else if (components[1] == "enabled") {
- r_ret = is_layer_enabled(index);
- return true;
- } else if (components[1] == "modulate") {
- r_ret = get_layer_modulate(index);
- return true;
- } else if (components[1] == "y_sort_enabled") {
- r_ret = is_layer_y_sort_enabled(index);
- return true;
- } else if (components[1] == "y_sort_origin") {
- r_ret = get_layer_y_sort_origin(index);
- return true;
- } else if (components[1] == "z_index") {
- r_ret = get_layer_z_index(index);
- return true;
- } else if (components[1] == "navigation_enabled") {
- r_ret = is_layer_navigation_enabled(index);
- return true;
- } else if (components[1] == "tile_data") {
- r_ret = _get_tile_map_data_using_compatibility_format(index);
- return true;
- } else {
- return false;
- }
+ else {
+ return property_helper.property_get_value(sname, r_ret);
}
- return false;
}
void TileMap::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
- p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
-
-#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \
- { \
- const String property_name = vformat("layer_%d/" m_name, i); \
- p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \
- }
-
- for (uint32_t i = 0; i < layers.size(); i++) {
- MAKE_LAYER_PROPERTY(Variant::STRING, "name", "");
- MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", "");
- MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", "");
- MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", "");
- MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px");
- MAKE_LAYER_PROPERTY(Variant::INT, "z_index", "");
- MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", "");
- p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
- }
-
-#undef MAKE_LAYER_PROPERTY
-}
-
-bool TileMap::_property_can_revert(const StringName &p_name) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("layer_")) {
- int index = components[0].trim_prefix("layer_").to_int();
- if (index <= 0 || index >= (int)layers.size()) {
- return false;
- }
-
- if (components[1] == "name") {
- return layers[index]->get_name() != default_layer->get_name();
- } else if (components[1] == "enabled") {
- return layers[index]->is_enabled() != default_layer->is_enabled();
- } else if (components[1] == "modulate") {
- return layers[index]->get_modulate() != default_layer->get_modulate();
- } else if (components[1] == "y_sort_enabled") {
- return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled();
- } else if (components[1] == "y_sort_origin") {
- return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin();
- } else if (components[1] == "z_index") {
- return layers[index]->get_z_index() != default_layer->get_z_index();
- } else if (components[1] == "navigation_enabled") {
- return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled();
- }
- }
-
- return false;
-}
-
-bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("layer_")) {
- int index = components[0].trim_prefix("layer_").to_int();
- if (index <= 0 || index >= (int)layers.size()) {
- return false;
- }
-
- if (components[1] == "name") {
- r_property = default_layer->get_name();
- return true;
- } else if (components[1] == "enabled") {
- r_property = default_layer->is_enabled();
- return true;
- } else if (components[1] == "modulate") {
- r_property = default_layer->get_modulate();
- return true;
- } else if (components[1] == "y_sort_enabled") {
- r_property = default_layer->is_y_sort_enabled();
- return true;
- } else if (components[1] == "y_sort_origin") {
- r_property = default_layer->get_y_sort_origin();
- return true;
- } else if (components[1] == "z_index") {
- r_property = default_layer->get_z_index();
- return true;
- } else if (components[1] == "navigation_enabled") {
- r_property = default_layer->is_navigation_enabled();
- return true;
- }
- }
-
- return false;
+ property_helper.get_property_list(p_list, layers.size());
}
Vector2 TileMap::map_to_local(const Vector2i &p_pos) const {
@@ -1200,7 +965,7 @@ void TileMap::_bind_methods() {
ADD_PROPERTY_DEFAULT("format", TileMapDataFormat::TILE_MAP_DATA_FORMAT_1);
- ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed));
+ ADD_SIGNAL(MethodInfo(CoreStringName(changed)));
BIND_ENUM_CONSTANT(VISIBILITY_MODE_DEFAULT);
BIND_ENUM_CONSTANT(VISIBILITY_MODE_FORCE_HIDE);
@@ -1213,13 +978,29 @@ TileMap::TileMap() {
new_layer->set_as_tile_map_internal_node(0);
new_layer->set_name("Layer0");
new_layer->set_tile_set(tile_set);
- new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed));
+ new_layer->connect(CoreStringName(changed), callable_mp(this, &TileMap::_emit_changed));
layers.push_back(new_layer);
- default_layer = memnew(TileMapLayer);
-}
-TileMap::~TileMap() {
- memdelete(default_layer);
+ if (!base_property_helper.is_initialized()) {
+ // Initialize static PropertyListHelper if it wasn't yet. This has to be done here,
+ // because creating TileMapLayer in a static context is not always safe.
+ TileMapLayer *defaults = memnew(TileMapLayer);
+
+ base_property_helper.set_prefix("layer_");
+ base_property_helper.set_array_length_getter(&TileMap::get_layers_count);
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults->get_name(), &TileMap::set_layer_name, &TileMap::get_layer_name);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "enabled"), defaults->is_enabled(), &TileMap::set_layer_enabled, &TileMap::is_layer_enabled);
+ base_property_helper.register_property(PropertyInfo(Variant::COLOR, "modulate"), defaults->get_modulate(), &TileMap::set_layer_modulate, &TileMap::get_layer_modulate);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "y_sort_enabled"), defaults->is_y_sort_enabled(), &TileMap::set_layer_y_sort_enabled, &TileMap::is_layer_y_sort_enabled);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "y_sort_origin", PROPERTY_HINT_NONE, "suffix:px"), defaults->get_y_sort_origin(), &TileMap::set_layer_y_sort_origin, &TileMap::get_layer_y_sort_origin);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "z_index"), defaults->get_z_index(), &TileMap::set_layer_z_index, &TileMap::get_layer_z_index);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "navigation_enabled"), defaults->is_navigation_enabled(), &TileMap::set_layer_navigation_enabled, &TileMap::is_layer_navigation_enabled);
+ base_property_helper.register_property(PropertyInfo(Variant::PACKED_INT32_ARRAY, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Vector<int>(), &TileMap::_set_layer_tile_data, &TileMap::_get_tile_map_data_using_compatibility_format);
+
+ memdelete(defaults);
+ }
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
#undef TILEMAP_CALL_FOR_LAYER
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 41068ea978..690102f730 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -32,6 +32,7 @@
#define TILE_MAP_H
#include "scene/2d/tile_map_layer.h"
+#include "scene/property_list_helper.h"
#include "scene/resources/2d/tile_set.h"
class Control;
@@ -62,8 +63,6 @@ private:
// A compatibility enum to specify how is the data if formatted.
mutable TileMapDataFormat format = TileMapDataFormat::TILE_MAP_DATA_FORMAT_3;
- static constexpr float FP_ADJUST = 0.00001;
-
// Properties.
Ref<TileSet> tile_set;
int rendering_quadrant_size = 16;
@@ -73,7 +72,9 @@ private:
// Layers.
LocalVector<TileMapLayer *> layers;
- TileMapLayer *default_layer; // Dummy layer to fetch default values.
+
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
// Transforms for collision_animatable.
Transform2D last_valid_transform;
@@ -86,13 +87,14 @@ private:
// Kept for compatibility with TileMap. With TileMapLayers as individual nodes, the format is stored directly in the array.
void _set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data);
Vector<int> _get_tile_map_data_using_compatibility_format(int p_layer) const;
+ void _set_layer_tile_data(int p_layer, const PackedInt32Array &p_data);
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
- bool _property_can_revert(const StringName &p_name) const;
- bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
void _notification(int p_what);
static void _bind_methods();
@@ -119,8 +121,6 @@ public:
void set_rendering_quadrant_size(int p_size);
int get_rendering_quadrant_size() const;
- static void draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr, real_t p_normalized_animation_offset = 0.0);
-
// Accessors.
void set_tileset(const Ref<TileSet> &p_tileset);
Ref<TileSet> get_tileset() const;
@@ -235,7 +235,6 @@ public:
PackedStringArray get_configuration_warnings() const override;
TileMap();
- ~TileMap();
};
VARIANT_ENUM_CAST(TileMap::VisibilityMode);
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index 873cfeebda..0ac236eaa7 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -30,7 +30,6 @@
#include "tile_map_layer.h"
-#include "core/core_string_names.h"
#include "core/io/marshalls.h"
#include "scene/2d/tile_map.h"
#include "scene/gui/control.h"
@@ -212,8 +211,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) {
// Free all quadrants.
if (forced_cleanup || quandrant_shape_changed) {
for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) {
- for (int i = 0; i < kv.value->canvas_items.size(); i++) {
- const RID &ci = kv.value->canvas_items[i];
+ for (const RID &ci : kv.value->canvas_items) {
if (ci.is_valid()) {
rs->free(ci);
}
@@ -342,7 +340,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) {
}
// Drawing the tile in the canvas item.
- TileMap::draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset);
+ draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, get_self_modulate(), tile_data, random_animation_offset);
}
// Reset physics interpolation for any recreated canvas items.
@@ -354,8 +352,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) {
} else {
// Free the quadrant.
- for (int i = 0; i < rendering_quadrant->canvas_items.size(); i++) {
- const RID &ci = rendering_quadrant->canvas_items[i];
+ for (const RID &ci : rendering_quadrant->canvas_items) {
if (ci.is_valid()) {
rs->free(ci);
}
@@ -606,6 +603,7 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid());
rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
+ rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
} else {
// Clear occluder.
if (occluder.is_valid()) {
@@ -1600,11 +1598,11 @@ RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cel
void TileMapLayer::_tile_set_changed() {
dirty.flags[DIRTY_FLAGS_TILE_SET] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMapLayer::_renamed() {
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMapLayer::_update_notify_local_transform() {
@@ -1698,18 +1696,18 @@ void TileMapLayer::_notification(int p_what) {
_internal_update(true);
} break;
- case TileMap::NOTIFICATION_ENTER_CANVAS: {
+ case NOTIFICATION_ENTER_CANVAS: {
dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true;
_queue_internal_update();
} break;
- case TileMap::NOTIFICATION_EXIT_CANVAS: {
+ case NOTIFICATION_EXIT_CANVAS: {
dirty.flags[DIRTY_FLAGS_LAYER_IN_CANVAS] = true;
// Update immediately on exiting, and force cleanup.
_internal_update(true);
} break;
- case TileMap::NOTIFICATION_VISIBILITY_CHANGED: {
+ case NOTIFICATION_VISIBILITY_CHANGED: {
dirty.flags[DIRTY_FLAGS_LAYER_VISIBILITY] = true;
_queue_internal_update();
} break;
@@ -1807,7 +1805,7 @@ void TileMapLayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "navigation_enabled"), "set_navigation_enabled", "is_navigation_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode");
- ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed));
+ ADD_SIGNAL(MethodInfo(CoreStringName(changed)));
ADD_PROPERTY_DEFAULT("tile_map_data_format", TileMapDataFormat::TILE_MAP_DATA_FORMAT_1);
@@ -1821,7 +1819,7 @@ void TileMapLayer::_update_self_texture_filter(RS::CanvasItemTextureFilter p_tex
CanvasItem::_update_self_texture_filter(p_texture_filter);
dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_FILTER] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMapLayer::_update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat) {
@@ -1829,7 +1827,7 @@ void TileMapLayer::_update_self_texture_repeat(RS::CanvasItemTextureRepeat p_tex
CanvasItem::_update_self_texture_repeat(p_texture_repeat);
dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_REPEAT] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMapLayer::set_as_tile_map_internal_node(int p_index) {
@@ -2164,6 +2162,91 @@ TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords) const {
}
}
+void TileMapLayer::draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame, Color p_modulation, const TileData *p_tile_data_override, real_t p_normalized_animation_offset) {
+ ERR_FAIL_COND(p_tile_set.is_null());
+ ERR_FAIL_COND(!p_tile_set->has_source(p_atlas_source_id));
+ ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_tile(p_atlas_coords));
+ ERR_FAIL_COND(!p_tile_set->get_source(p_atlas_source_id)->has_alternative_tile(p_atlas_coords, p_alternative_tile));
+ TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id);
+ TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
+ if (atlas_source) {
+ // Check for the frame.
+ if (p_frame >= 0) {
+ ERR_FAIL_INDEX(p_frame, atlas_source->get_tile_animation_frames_count(p_atlas_coords));
+ }
+
+ // Get the texture.
+ Ref<Texture2D> tex = atlas_source->get_runtime_texture();
+ if (tex.is_null()) {
+ return;
+ }
+
+ // Check if we are in the texture, return otherwise.
+ Vector2i grid_size = atlas_source->get_atlas_grid_size();
+ if (p_atlas_coords.x >= grid_size.x || p_atlas_coords.y >= grid_size.y) {
+ return;
+ }
+
+ // Get tile data.
+ const TileData *tile_data = p_tile_data_override ? p_tile_data_override : atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile);
+
+ // Get the tile modulation.
+ Color modulate = tile_data->get_modulate() * p_modulation;
+
+ // Compute the offset.
+ Vector2 tile_offset = tile_data->get_texture_origin();
+
+ // Get destination rect.
+ Rect2 dest_rect;
+ dest_rect.size = atlas_source->get_runtime_tile_texture_region(p_atlas_coords).size;
+ dest_rect.size.x += FP_ADJUST;
+ dest_rect.size.y += FP_ADJUST;
+
+ bool transpose = tile_data->get_transpose() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE);
+ if (transpose) {
+ dest_rect.position = (p_position - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset);
+ } else {
+ dest_rect.position = (p_position - dest_rect.size / 2 - tile_offset);
+ }
+
+ if (tile_data->get_flip_h() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H)) {
+ dest_rect.size.x = -dest_rect.size.x;
+ }
+
+ if (tile_data->get_flip_v() ^ bool(p_alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V)) {
+ dest_rect.size.y = -dest_rect.size.y;
+ }
+
+ // Draw the tile.
+ if (p_frame >= 0) {
+ Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, p_frame);
+ tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
+ } else if (atlas_source->get_tile_animation_frames_count(p_atlas_coords) == 1) {
+ Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, 0);
+ tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
+ } else {
+ real_t speed = atlas_source->get_tile_animation_speed(p_atlas_coords);
+ real_t animation_duration = atlas_source->get_tile_animation_total_duration(p_atlas_coords) / speed;
+ real_t animation_offset = p_normalized_animation_offset * animation_duration;
+ // Accumulate durations unaffected by the speed to avoid accumulating floating point division errors.
+ // Aka do `sum(duration[i]) / speed` instead of `sum(duration[i] / speed)`.
+ real_t time_unscaled = 0.0;
+ for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(p_atlas_coords); frame++) {
+ real_t frame_duration_unscaled = atlas_source->get_tile_animation_frame_duration(p_atlas_coords, frame);
+ real_t slice_start = time_unscaled / speed;
+ real_t slice_end = (time_unscaled + frame_duration_unscaled) / speed;
+ RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, animation_duration, slice_start, slice_end, animation_offset);
+
+ Rect2i source_rect = atlas_source->get_runtime_tile_texture_region(p_atlas_coords, frame);
+ tex->draw_rect_region(p_canvas_item, dest_rect, source_rect, modulate, transpose, p_tile_set->is_uv_clipping());
+
+ time_unscaled += frame_duration_unscaled;
+ }
+ RenderingServer::get_singleton()->canvas_item_add_animation_slice(p_canvas_item, 1.0, 0.0, 1.0, 0.0);
+ }
+ }
+}
+
void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile) {
// Set the current cell tile (using integer position).
Vector2i pk(p_coords);
@@ -2214,7 +2297,7 @@ void TileMapLayer::erase_cell(const Vector2i &p_coords) {
}
void TileMapLayer::fix_invalid_tiles() {
- ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet.");
+ ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot call fix_invalid_tiles() on a TileMapLayer without a valid TileSet.");
RBSet<Vector2i> coords;
for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) {
@@ -2504,7 +2587,7 @@ void TileMapLayer::update_internals() {
void TileMapLayer::notify_runtime_tile_data_update() {
dirty.flags[TileMapLayer::DIRTY_FLAGS_LAYER_RUNTIME_UPDATE] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
Vector2i TileMapLayer::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref<TileMapPattern> p_pattern) {
@@ -2539,7 +2622,7 @@ void TileMapLayer::set_enabled(bool p_enabled) {
enabled = p_enabled;
dirty.flags[DIRTY_FLAGS_LAYER_ENABLED] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
bool TileMapLayer::is_enabled() const {
@@ -2565,7 +2648,7 @@ void TileMapLayer::set_tile_set(const Ref<TileSet> &p_tile_set) {
tile_set->connect_changed(callable_mp(this, &TileMapLayer::_tile_set_changed));
}
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
// Trigger updates for TileSet's read-only status.
notify_property_list_changed();
@@ -2588,6 +2671,11 @@ TileMapLayer::HighlightMode TileMapLayer::get_highlight_mode() const {
}
void TileMapLayer::set_tile_map_data_from_array(const Vector<uint8_t> &p_data) {
+ if (p_data.is_empty()) {
+ clear();
+ return;
+ }
+
const int cell_data_struct_size = 12;
int size = p_data.size();
@@ -2630,6 +2718,10 @@ Vector<uint8_t> TileMapLayer::get_tile_map_data_as_array() const {
const int cell_data_struct_size = 12;
Vector<uint8_t> tile_map_data_array;
+ if (tile_map_layer_data.is_empty()) {
+ return tile_map_data_array;
+ }
+
tile_map_data_array.resize(2 + tile_map_layer_data.size() * cell_data_struct_size);
uint8_t *ptr = tile_map_data_array.ptrw();
@@ -2668,7 +2760,7 @@ void TileMapLayer::set_self_modulate(const Color &p_self_modulate) {
CanvasItem::set_self_modulate(p_self_modulate);
dirty.flags[DIRTY_FLAGS_LAYER_SELF_MODULATE] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) {
@@ -2678,7 +2770,7 @@ void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) {
CanvasItem::set_y_sort_enabled(p_y_sort_enabled);
dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
_update_notify_local_transform();
}
@@ -2690,7 +2782,7 @@ void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) {
y_sort_origin = p_y_sort_origin;
dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
int TileMapLayer::get_y_sort_origin() const {
@@ -2704,7 +2796,7 @@ void TileMapLayer::set_z_index(int p_z_index) {
CanvasItem::set_z_index(p_z_index);
dirty.flags[DIRTY_FLAGS_LAYER_Z_INDEX] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMapLayer::set_light_mask(int p_light_mask) {
@@ -2714,7 +2806,7 @@ void TileMapLayer::set_light_mask(int p_light_mask) {
CanvasItem::set_light_mask(p_light_mask);
dirty.flags[DIRTY_FLAGS_LAYER_LIGHT_MASK] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
void TileMapLayer::set_rendering_quadrant_size(int p_size) {
@@ -2726,7 +2818,7 @@ void TileMapLayer::set_rendering_quadrant_size(int p_size) {
rendering_quadrant_size = p_size;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
int TileMapLayer::get_rendering_quadrant_size() const {
@@ -2740,7 +2832,7 @@ void TileMapLayer::set_collision_enabled(bool p_enabled) {
collision_enabled = p_enabled;
dirty.flags[DIRTY_FLAGS_LAYER_COLLISION_ENABLED] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
bool TileMapLayer::is_collision_enabled() const {
@@ -2751,7 +2843,7 @@ void TileMapLayer::set_use_kinematic_bodies(bool p_use_kinematic_bodies) {
use_kinematic_bodies = p_use_kinematic_bodies;
dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] = p_use_kinematic_bodies;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
bool TileMapLayer::is_using_kinematic_bodies() const {
@@ -2765,7 +2857,7 @@ void TileMapLayer::set_collision_visibility_mode(TileMapLayer::DebugVisibilityMo
collision_visibility_mode = p_show_collision;
dirty.flags[DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
TileMapLayer::DebugVisibilityMode TileMapLayer::get_collision_visibility_mode() const {
@@ -2779,7 +2871,7 @@ void TileMapLayer::set_navigation_enabled(bool p_enabled) {
navigation_enabled = p_enabled;
dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
bool TileMapLayer::is_navigation_enabled() const {
@@ -2793,7 +2885,7 @@ void TileMapLayer::set_navigation_map(RID p_map) {
navigation_map_override = p_map;
dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_MAP] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
RID TileMapLayer::get_navigation_map() const {
@@ -2812,7 +2904,7 @@ void TileMapLayer::set_navigation_visibility_mode(TileMapLayer::DebugVisibilityM
navigation_visibility_mode = p_show_navigation;
dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_VISIBILITY_MODE] = true;
_queue_internal_update();
- emit_signal(CoreStringNames::get_singleton()->changed);
+ emit_signal(CoreStringName(changed));
}
TileMapLayer::DebugVisibilityMode TileMapLayer::get_navigation_visibility_mode() const {
diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h
index 5861433c8a..57c83d7c4c 100644
--- a/scene/2d/tile_map_layer.h
+++ b/scene/2d/tile_map_layer.h
@@ -269,6 +269,8 @@ public:
};
private:
+ static constexpr float FP_ADJUST = 0.00001;
+
// Properties.
HashMap<Vector2i, CellData> tile_map_layer_data;
@@ -407,6 +409,8 @@ public:
// Not exposed to users.
TileMapCell get_cell(const Vector2i &p_coords) const;
+ static void draw_tile(RID p_canvas_item, const Vector2 &p_position, const Ref<TileSet> p_tile_set, int p_atlas_source_id, const Vector2i &p_atlas_coords, int p_alternative_tile, int p_frame = -1, Color p_modulation = Color(1.0, 1.0, 1.0, 1.0), const TileData *p_tile_data_override = nullptr, real_t p_normalized_animation_offset = 0.0);
+
////////////// Exposed functions //////////////
// --- Cells manipulation ---
diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp
index 5ed7fadb2a..ff409272c5 100644
--- a/scene/2d/touch_screen_button.cpp
+++ b/scene/2d/touch_screen_button.cpp
@@ -31,18 +31,17 @@
#include "touch_screen_button.h"
#include "scene/main/window.h"
-#include "scene/scene_string_names.h"
void TouchScreenButton::set_texture_normal(const Ref<Texture2D> &p_texture) {
if (texture_normal == p_texture) {
return;
}
if (texture_normal.is_valid()) {
- texture_normal->disconnect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
+ texture_normal->disconnect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
}
texture_normal = p_texture;
if (texture_normal.is_valid()) {
- texture_normal->connect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED);
+ texture_normal->connect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED);
}
queue_redraw();
}
@@ -56,11 +55,11 @@ void TouchScreenButton::set_texture_pressed(const Ref<Texture2D> &p_texture_pres
return;
}
if (texture_pressed.is_valid()) {
- texture_pressed->disconnect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
+ texture_pressed->disconnect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
}
texture_pressed = p_texture_pressed;
if (texture_pressed.is_valid()) {
- texture_pressed->connect(SceneStringNames::get_singleton()->changed, callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED);
+ texture_pressed->connect(CoreStringName(changed), callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw), CONNECT_REFERENCE_COUNTED);
}
queue_redraw();
}
@@ -306,7 +305,7 @@ void TouchScreenButton::_press(int p_finger_pressed) {
get_viewport()->push_input(iea, true);
}
- emit_signal(SNAME("pressed"));
+ emit_signal(SceneStringName(pressed));
queue_redraw();
}
@@ -371,10 +370,10 @@ bool TouchScreenButton::is_passby_press_enabled() const {
#ifndef DISABLE_DEPRECATED
bool TouchScreenButton::_set(const StringName &p_name, const Variant &p_value) {
- if (p_name == SNAME("normal")) { // Compatibility with Godot 3.x.
+ if (p_name == CoreStringName(normal)) { // Compatibility with Godot 3.x.
set_texture_normal(p_value);
return true;
- } else if (p_name == SNAME("pressed")) { // Compatibility with Godot 3.x.
+ } else if (p_name == SceneStringName(pressed)) { // Compatibility with Godot 3.x.
set_texture_pressed(p_value);
return true;
}
diff --git a/scene/2d/visible_on_screen_notifier_2d.cpp b/scene/2d/visible_on_screen_notifier_2d.cpp
index 89b2c20b20..c64507fe32 100644
--- a/scene/2d/visible_on_screen_notifier_2d.cpp
+++ b/scene/2d/visible_on_screen_notifier_2d.cpp
@@ -30,8 +30,6 @@
#include "visible_on_screen_notifier_2d.h"
-#include "scene/scene_string_names.h"
-
#ifdef TOOLS_ENABLED
Rect2 VisibleOnScreenNotifier2D::_edit_get_rect() const {
return rect;
@@ -48,7 +46,7 @@ void VisibleOnScreenNotifier2D::_visibility_enter() {
}
on_screen = true;
- emit_signal(SceneStringNames::get_singleton()->screen_entered);
+ emit_signal(SceneStringName(screen_entered));
_screen_enter();
}
void VisibleOnScreenNotifier2D::_visibility_exit() {
@@ -57,7 +55,7 @@ void VisibleOnScreenNotifier2D::_visibility_exit() {
}
on_screen = false;
- emit_signal(SceneStringNames::get_singleton()->screen_exited);
+ emit_signal(SceneStringName(screen_exited));
_screen_exit();
}
diff --git a/scene/3d/audio_listener_3d.cpp b/scene/3d/audio_listener_3d.cpp
index 437d840d5f..4a18447b3b 100644
--- a/scene/3d/audio_listener_3d.cpp
+++ b/scene/3d/audio_listener_3d.cpp
@@ -55,7 +55,7 @@ bool AudioListener3D::_set(const StringName &p_name, const Variant &p_value) {
bool AudioListener3D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "current") {
- if (is_inside_tree() && get_tree()->is_node_being_edited(this)) {
+ if (is_part_of_edited_scene()) {
r_ret = current;
} else {
r_ret = is_current();
@@ -81,7 +81,7 @@ void AudioListener3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
bool first_listener = get_viewport()->_audio_listener_3d_add(this);
- if (!get_tree()->is_node_being_edited(this) && (current || first_listener)) {
+ if (!is_part_of_edited_scene() && (current || first_listener)) {
make_current();
}
} break;
@@ -91,7 +91,7 @@ void AudioListener3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_WORLD: {
- if (!get_tree()->is_node_being_edited(this)) {
+ if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; //keep it true
@@ -133,7 +133,7 @@ void AudioListener3D::clear_current() {
}
bool AudioListener3D::is_current() const {
- if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) {
+ if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_audio_listener_3d() == this;
} else {
return current;
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index f1f9a04ea0..0cef56dbf2 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -496,6 +496,7 @@ Ref<AudioStream> AudioStreamPlayer3D::get_stream() const {
}
void AudioStreamPlayer3D::set_volume_db(float p_volume) {
+ ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN.");
internal->volume_db = p_volume;
}
diff --git a/scene/3d/bone_attachment_3d.compat.inc b/scene/3d/bone_attachment_3d.compat.inc
new file mode 100644
index 0000000000..1b21612d5f
--- /dev/null
+++ b/scene/3d/bone_attachment_3d.compat.inc
@@ -0,0 +1,43 @@
+/**************************************************************************/
+/* bone_attachment_3d.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+#include "bone_attachment_3d.h"
+
+void BoneAttachment3D::_on_bone_pose_update_bind_compat_90575(int p_bone_index) {
+ return on_skeleton_update();
+}
+
+void BoneAttachment3D::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment3D::_on_bone_pose_update_bind_compat_90575);
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 221200284d..2716738684 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "bone_attachment_3d.h"
+#include "bone_attachment_3d.compat.inc"
void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "bone_name") {
@@ -148,9 +149,9 @@ void BoneAttachment3D::_check_bind() {
bone_idx = sk->find_bone(bone_name);
}
if (bone_idx != -1) {
- sk->connect(SNAME("bone_pose_changed"), callable_mp(this, &BoneAttachment3D::on_bone_pose_update));
+ sk->connect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update));
bound = true;
- callable_mp(this, &BoneAttachment3D::on_bone_pose_update).call_deferred(bone_idx);
+ callable_mp(this, &BoneAttachment3D::on_skeleton_update);
}
}
}
@@ -176,7 +177,7 @@ void BoneAttachment3D::_check_unbind() {
Skeleton3D *sk = _get_skeleton3d();
if (sk) {
- sk->disconnect(SNAME("bone_pose_changed"), callable_mp(this, &BoneAttachment3D::on_bone_pose_update));
+ sk->disconnect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update));
}
bound = false;
}
@@ -308,12 +309,12 @@ void BoneAttachment3D::_notification(int p_what) {
}
}
-void BoneAttachment3D::on_bone_pose_update(int p_bone_index) {
+void BoneAttachment3D::on_skeleton_update() {
if (updating) {
return;
}
updating = true;
- if (bone_idx == p_bone_index) {
+ if (bone_idx >= 0) {
Skeleton3D *sk = _get_skeleton3d();
if (sk) {
if (!override_pose) {
@@ -371,7 +372,7 @@ void BoneAttachment3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_idx", "bone_idx"), &BoneAttachment3D::set_bone_idx);
ClassDB::bind_method(D_METHOD("get_bone_idx"), &BoneAttachment3D::get_bone_idx);
- ClassDB::bind_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment3D::on_bone_pose_update);
+ ClassDB::bind_method(D_METHOD("on_skeleton_update"), &BoneAttachment3D::on_skeleton_update);
ClassDB::bind_method(D_METHOD("set_override_pose", "override_pose"), &BoneAttachment3D::set_override_pose);
ClassDB::bind_method(D_METHOD("get_override_pose"), &BoneAttachment3D::get_override_pose);
diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h
index ec0f7344d7..e19180b0ea 100644
--- a/scene/3d/bone_attachment_3d.h
+++ b/scene/3d/bone_attachment_3d.h
@@ -67,6 +67,10 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ virtual void _on_bone_pose_update_bind_compat_90575(int p_bone_index);
+ static void _bind_compatibility_methods();
+#endif
public:
#ifdef TOOLS_ENABLED
@@ -89,7 +93,7 @@ public:
void set_external_skeleton(NodePath p_skeleton);
NodePath get_external_skeleton() const;
- virtual void on_bone_pose_update(int p_bone_index);
+ virtual void on_skeleton_update();
#ifdef TOOLS_ENABLED
virtual void notify_rebind_required();
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index e8bd498e1f..8515aacba7 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -90,7 +90,7 @@ void Camera3D::_update_camera() {
RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
- if (get_tree()->is_node_being_edited(this) || !is_current()) {
+ if (is_part_of_edited_scene() || !is_current()) {
return;
}
@@ -126,7 +126,7 @@ void Camera3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_WORLD: {
- if (!get_tree()->is_node_being_edited(this)) {
+ if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; //keep it true
@@ -286,7 +286,7 @@ void Camera3D::set_current(bool p_enabled) {
}
bool Camera3D::is_current() const {
- if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) {
+ if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_camera_3d() == this;
} else {
return current;
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index e7b6aa1dba..03fe5e1fad 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -37,7 +37,6 @@
#include "scene/resources/gradient_texture.h"
#include "scene/resources/image_texture.h"
#include "scene/resources/particle_process_material.h"
-#include "scene/scene_string_names.h"
AABB CPUParticles3D::get_aabb() const {
return AABB();
@@ -880,10 +879,10 @@ void CPUParticles3D::_particles_process(double p_delta) {
} break;
case EMISSION_SHAPE_RING: {
real_t ring_random_angle = Math::randf() * Math_TAU;
- real_t ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;
- Vector3 axis = emission_ring_axis.normalized();
+ real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized();
Vector3 ortho_axis;
- if (axis == Vector3(1.0, 0.0, 0.0)) {
+ if (axis.abs() == Vector3(1.0, 0.0, 0.0)) {
ortho_axis = Vector3(0.0, 1.0, 0.0).cross(axis);
} else {
ortho_axis = Vector3(1.0, 0.0, 0.0).cross(axis);
@@ -1125,7 +1124,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
//turn particle by rotation in Y
if (particle_flags[PARTICLE_FLAG_ROTATE_Y]) {
Basis rot_y(Vector3(0, 1, 0), p.custom[0]);
- p.transform.basis = p.transform.basis * rot_y;
+ p.transform.basis = rot_y;
}
}
@@ -1156,7 +1155,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
}
if (!Math::is_equal_approx(time, 0.0) && active && !should_be_active) {
active = false;
- emit_signal(SceneStringNames::get_singleton()->finished);
+ emit_signal(SceneStringName(finished));
}
}
diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp
index 8415fb38cb..485599d0fb 100644
--- a/scene/3d/decal.cpp
+++ b/scene/3d/decal.cpp
@@ -31,7 +31,7 @@
#include "decal.h"
void Decal::set_size(const Vector3 &p_size) {
- size = p_size.max(Vector3(0.001, 0.001, 0.001));
+ size = p_size.maxf(0.001);
RS::get_singleton()->decal_set_size(decal, size);
update_gizmos();
}
diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp
index 8af386f282..54631a8dff 100644
--- a/scene/3d/fog_volume.cpp
+++ b/scene/3d/fog_volume.cpp
@@ -73,7 +73,7 @@ bool FogVolume::_get(const StringName &p_name, Variant &r_property) const {
void FogVolume::set_size(const Vector3 &p_size) {
size = p_size;
- size = size.max(Vector3());
+ size = size.maxf(0);
RS::get_singleton()->fog_volume_set_size(_get_volume(), size);
update_gizmos();
}
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 16813b9017..3771b385e5 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -34,7 +34,6 @@
#include "scene/resources/curve_texture.h"
#include "scene/resources/gradient_texture.h"
#include "scene/resources/particle_process_material.h"
-#include "scene/scene_string_names.h"
AABB GPUParticles3D::get_aabb() const {
return AABB();
@@ -478,7 +477,7 @@ void GPUParticles3D::_notification(int p_what) {
}
if (time > active_time) {
if (active && !signal_canceled) {
- emit_signal(SceneStringNames::get_singleton()->finished);
+ emit_signal(SceneStringName(finished));
}
active = false;
if (!emitting) {
diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp
index 8fd5f25749..3a05ec9c9e 100644
--- a/scene/3d/gpu_particles_collision_3d.cpp
+++ b/scene/3d/gpu_particles_collision_3d.cpp
@@ -382,7 +382,7 @@ Vector3i GPUParticlesCollisionSDF3D::get_estimated_cell_size() const {
float cell_size = aabb.get_longest_axis_size() / float(subdiv);
Vector3i sdf_size = Vector3i(aabb.size / cell_size);
- sdf_size = sdf_size.max(Vector3i(1, 1, 1));
+ sdf_size = sdf_size.maxi(1);
return sdf_size;
}
@@ -395,7 +395,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() {
float cell_size = aabb.get_longest_axis_size() / float(subdiv);
Vector3i sdf_size = Vector3i(aabb.size / cell_size);
- sdf_size = sdf_size.max(Vector3i(1, 1, 1));
+ sdf_size = sdf_size.maxi(1);
if (bake_begin_function) {
bake_begin_function(100);
diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp
index 9f71c881a9..0f2ce829eb 100644
--- a/scene/3d/label_3d.cpp
+++ b/scene/3d/label_3d.cpp
@@ -32,7 +32,6 @@
#include "scene/main/viewport.h"
#include "scene/resources/theme.h"
-#include "scene/scene_string_names.h"
#include "scene/theme/theme_db.h"
void Label3D::_bind_methods() {
@@ -636,6 +635,10 @@ void Label3D::_shape() {
}
void Label3D::set_text(const String &p_string) {
+ if (text == p_string) {
+ return;
+ }
+
text = p_string;
xl_text = atr(p_string);
dirty_text = true;
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index 86ff6d15dd..ef492a6994 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -397,7 +397,10 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc
const BSPSimplex &s = p_simplices[p_simplex];
for (int i = 0; i < 4; i++) {
const Vector3 v = p_points[s.vertices[i]];
- if (p_plane.has_point(v)) {
+ // The tolerance used here comes from experiments on scenes up to
+ // 1000x1000x100 meters. If it's any smaller, some simplices will
+ // appear to self-intersect due to a lack of precision in Plane.
+ if (p_plane.has_point(v, 1.0 / (1 << 13))) {
// Coplanar.
} else if (p_plane.is_point_over(v)) {
over++;
@@ -419,7 +422,8 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc
//#define DEBUG_BSP
int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const LocalVector<Plane> &p_planes, LocalVector<int32_t> &planes_tested, const LocalVector<BSPSimplex> &p_simplices, const LocalVector<int32_t> &p_simplex_indices, LocalVector<BSPNode> &bsp_nodes) {
- //if we reach here, it means there is more than one simplex
+ ERR_FAIL_COND_V(p_simplex_indices.size() < 2, -1);
+
int32_t node_index = (int32_t)bsp_nodes.size();
bsp_nodes.push_back(BSPNode());
@@ -477,13 +481,14 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc
float score = 0; //by default, score is 0 (worst)
if (over_count > 0) {
- //give score mainly based on ratio (under / over), this means that this plane is splitting simplices a lot, but its balanced
- score = float(under_count) / over_count;
+ // Simplices that are intersected by the plane are moved into both the over
+ // and under subtrees which makes the entire tree deeper, so the best plane
+ // will have the least intersections while separating the simplices evenly.
+ float balance = float(under_count) / over_count;
+ float separation = float(over_count + under_count) / p_simplex_indices.size();
+ score = balance * separation * separation;
}
- //adjusting priority over least splits, probably not a great idea
- //score *= Math::sqrt(float(over_count + under_count) / p_simplex_indices.size()); //also multiply score
-
if (score > best_plane_score) {
best_plane = plane;
best_plane_score = score;
@@ -491,6 +496,44 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc
}
}
+ // We often end up with two (or on rare occasions, three) simplices that are
+ // either disjoint or share one vertex and don't have a separating plane
+ // among their faces. The fallback is to loop through new planes created
+ // with one vertex of the first simplex and two vertices of the second until
+ // we find a winner.
+ if (best_plane_score == 0) {
+ const BSPSimplex &simplex0 = p_simplices[p_simplex_indices[0]];
+ const BSPSimplex &simplex1 = p_simplices[p_simplex_indices[1]];
+
+ for (uint32_t i = 0; i < 4 && !best_plane_score; i++) {
+ Vector3 v0 = p_points[simplex0.vertices[i]];
+ for (uint32_t j = 0; j < 3 && !best_plane_score; j++) {
+ if (simplex0.vertices[i] == simplex1.vertices[j]) {
+ break;
+ }
+ Vector3 v1 = p_points[simplex1.vertices[j]];
+ for (uint32_t k = j + 1; k < 4; k++) {
+ if (simplex0.vertices[i] == simplex1.vertices[k]) {
+ break;
+ }
+ Vector3 v2 = p_points[simplex1.vertices[k]];
+
+ Plane plane = Plane(v0, v1, v2);
+ if (plane == Plane()) { // When v0, v1, and v2 are collinear, they can't form a plane.
+ continue;
+ }
+ int32_t side0 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[0]);
+ int32_t side1 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[1]);
+ if ((side0 == 1 && side1 == -1) || (side0 == -1 && side1 == 1)) {
+ best_plane = plane;
+ best_plane_score = 1.0;
+ break;
+ }
+ }
+ }
+ }
+ }
+
LocalVector<int32_t> indices_over;
LocalVector<int32_t> indices_under;
@@ -515,8 +558,6 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc
#endif
if (best_plane_score < 0.0 || indices_over.size() == p_simplex_indices.size() || indices_under.size() == p_simplex_indices.size()) {
- ERR_FAIL_COND_V(p_simplex_indices.size() <= 1, 0); //should not happen, this is a bug
-
// Failed to separate the tetrahedrons using planes
// this means Delaunay broke at some point.
// Luckily, because we are using tetrahedrons, we can resort to
@@ -1061,7 +1102,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
}
}
- Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
+ Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL) {
return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
@@ -1409,6 +1450,14 @@ float LightmapGI::get_denoiser_strength() const {
return denoiser_strength;
}
+void LightmapGI::set_denoiser_range(int p_denoiser_range) {
+ denoiser_range = p_denoiser_range;
+}
+
+int LightmapGI::get_denoiser_range() const {
+ return denoiser_range;
+}
+
void LightmapGI::set_directional(bool p_enable) {
directional = p_enable;
}
@@ -1552,6 +1601,9 @@ void LightmapGI::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "denoiser_strength" && !use_denoiser) {
p_property.usage = PROPERTY_USAGE_NONE;
}
+ if (p_property.name == "denoiser_range" && !use_denoiser) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
}
void LightmapGI::_bind_methods() {
@@ -1597,6 +1649,9 @@ void LightmapGI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_denoiser_strength", "denoiser_strength"), &LightmapGI::set_denoiser_strength);
ClassDB::bind_method(D_METHOD("get_denoiser_strength"), &LightmapGI::get_denoiser_strength);
+ ClassDB::bind_method(D_METHOD("set_denoiser_range", "denoiser_range"), &LightmapGI::set_denoiser_range);
+ ClassDB::bind_method(D_METHOD("get_denoiser_range"), &LightmapGI::get_denoiser_range);
+
ClassDB::bind_method(D_METHOD("set_interior", "enable"), &LightmapGI::set_interior);
ClassDB::bind_method(D_METHOD("is_interior"), &LightmapGI::is_interior);
@@ -1620,6 +1675,7 @@ void LightmapGI::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "denoiser_strength", PROPERTY_HINT_RANGE, "0.001,0.2,0.001,or_greater"), "set_denoiser_strength", "get_denoiser_strength");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "denoiser_range", PROPERTY_HINT_RANGE, "1,20"), "set_denoiser_range", "get_denoiser_range");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texel_scale", PROPERTY_HINT_RANGE, "0.01,100.0,0.01"), "set_texel_scale", "get_texel_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size", PROPERTY_HINT_RANGE, "2048,16384,1"), "set_max_texture_size", "get_max_texture_size");
diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h
index 765e4a731d..527667177b 100644
--- a/scene/3d/lightmap_gi.h
+++ b/scene/3d/lightmap_gi.h
@@ -156,6 +156,7 @@ private:
BakeQuality bake_quality = BAKE_QUALITY_MEDIUM;
bool use_denoiser = true;
float denoiser_strength = 0.1f;
+ int denoiser_range = 10;
int bounces = 3;
float bounce_indirect_energy = 1.0;
float bias = 0.0005;
@@ -256,6 +257,9 @@ public:
void set_denoiser_strength(float p_denoiser_strength);
float get_denoiser_strength() const;
+ void set_denoiser_range(int p_denoiser_range);
+ int get_denoiser_range() const;
+
void set_directional(bool p_enable);
bool is_directional() const;
diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h
index 446ed794ed..39181ad9a2 100644
--- a/scene/3d/lightmapper.h
+++ b/scene/3d/lightmapper.h
@@ -61,7 +61,7 @@ protected:
static LightmapRaycaster *(*create_function)();
public:
- // compatible with embree3 rays
+ // Compatible with embree4 rays.
struct __aligned(16) Ray {
const static unsigned int INVALID_GEOMETRY_ID = ((unsigned int)-1); // from rtcore_common.h
@@ -180,7 +180,7 @@ public:
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0;
virtual void add_probe(const Vector3 &p_position) = 0;
- virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0;
+ virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0;
virtual int get_bake_texture_count() const = 0;
virtual Ref<Image> get_bake_texture(int p_index) const = 0;
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index 616fb18d53..85bf8846b9 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -515,6 +515,162 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p
return false;
}
+Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) {
+ Ref<ArrayMesh> source_mesh = get_mesh();
+ ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
+
+ Ref<ArrayMesh> bake_mesh;
+
+ if (p_existing.is_valid()) {
+ ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
+
+ bake_mesh = p_existing;
+ } else {
+ bake_mesh.instantiate();
+ }
+
+ Mesh::BlendShapeMode blend_shape_mode = source_mesh->get_blend_shape_mode();
+ int mesh_surface_count = source_mesh->get_surface_count();
+
+ bake_mesh->clear_surfaces();
+ bake_mesh->set_blend_shape_mode(blend_shape_mode);
+
+ for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) {
+ uint32_t surface_format = source_mesh->surface_get_format(surface_index);
+
+ ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX));
+
+ const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index);
+
+ ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>());
+
+ const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX];
+ const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL];
+ const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT];
+
+ Array new_mesh_arrays;
+ new_mesh_arrays.resize(Mesh::ARRAY_MAX);
+ for (int i = 0; i < source_mesh_arrays.size(); i++) {
+ if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT) {
+ continue;
+ }
+ new_mesh_arrays[i] = source_mesh_arrays[i];
+ }
+
+ bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size();
+ bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size();
+
+ Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array;
+ Vector<Vector3> lerped_normal_array = source_mesh_normal_array;
+ Vector<float> lerped_tangent_array = source_mesh_tangent_array;
+
+ const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr();
+ const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr();
+ const float *source_tangents_ptr = source_mesh_tangent_array.ptr();
+
+ Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw();
+ Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw();
+ float *lerped_tangents_ptrw = lerped_tangent_array.ptrw();
+
+ const Array &blendshapes_mesh_arrays = source_mesh->surface_get_blend_shape_arrays(surface_index);
+ int blend_shape_count = source_mesh->get_blend_shape_count();
+ ERR_FAIL_COND_V(blendshapes_mesh_arrays.size() != blend_shape_count, Ref<ArrayMesh>());
+
+ for (int blendshape_index = 0; blendshape_index < blend_shape_count; blendshape_index++) {
+ float blend_weight = get_blend_shape_value(blendshape_index);
+ if (abs(blend_weight) <= 0.0001) {
+ continue;
+ }
+
+ const Array &blendshape_mesh_arrays = blendshapes_mesh_arrays[blendshape_index];
+
+ const Vector<Vector3> &blendshape_vertex_array = blendshape_mesh_arrays[Mesh::ARRAY_VERTEX];
+ const Vector<Vector3> &blendshape_normal_array = blendshape_mesh_arrays[Mesh::ARRAY_NORMAL];
+ const Vector<float> &blendshape_tangent_array = blendshape_mesh_arrays[Mesh::ARRAY_TANGENT];
+
+ ERR_FAIL_COND_V(source_mesh_vertex_array.size() != blendshape_vertex_array.size(), Ref<ArrayMesh>());
+ ERR_FAIL_COND_V(source_mesh_normal_array.size() != blendshape_normal_array.size(), Ref<ArrayMesh>());
+ ERR_FAIL_COND_V(source_mesh_tangent_array.size() != blendshape_tangent_array.size(), Ref<ArrayMesh>());
+
+ const Vector3 *blendshape_vertices_ptr = blendshape_vertex_array.ptr();
+ const Vector3 *blendshape_normals_ptr = blendshape_normal_array.ptr();
+ const float *blendshape_tangents_ptr = blendshape_tangent_array.ptr();
+
+ if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_NORMALIZED) {
+ for (int i = 0; i < source_mesh_vertex_array.size(); i++) {
+ const Vector3 &source_vertex = source_vertices_ptr[i];
+ const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i];
+ Vector3 lerped_vertex = source_vertex.lerp(blendshape_vertex, blend_weight) - source_vertex;
+ lerped_vertices_ptrw[i] += lerped_vertex;
+
+ if (use_normal_array) {
+ const Vector3 &source_normal = source_normals_ptr[i];
+ const Vector3 &blendshape_normal = blendshape_normals_ptr[i];
+ Vector3 lerped_normal = source_normal.lerp(blendshape_normal, blend_weight) - source_normal;
+ lerped_normals_ptrw[i] += lerped_normal;
+ }
+
+ if (use_tangent_array) {
+ int tangent_index = i * 4;
+ const Vector4 source_tangent = Vector4(
+ source_tangents_ptr[tangent_index],
+ source_tangents_ptr[tangent_index + 1],
+ source_tangents_ptr[tangent_index + 2],
+ source_tangents_ptr[tangent_index + 3]);
+ const Vector4 blendshape_tangent = Vector4(
+ blendshape_tangents_ptr[tangent_index],
+ blendshape_tangents_ptr[tangent_index + 1],
+ blendshape_tangents_ptr[tangent_index + 2],
+ blendshape_tangents_ptr[tangent_index + 3]);
+ Vector4 lerped_tangent = source_tangent.lerp(blendshape_tangent, blend_weight);
+ lerped_tangents_ptrw[tangent_index] += lerped_tangent.x;
+ lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y;
+ lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z;
+ lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w;
+ }
+ }
+ } else if (blend_shape_mode == Mesh::BLEND_SHAPE_MODE_RELATIVE) {
+ for (int i = 0; i < source_mesh_vertex_array.size(); i++) {
+ const Vector3 &blendshape_vertex = blendshape_vertices_ptr[i];
+ lerped_vertices_ptrw[i] += blendshape_vertex * blend_weight;
+
+ if (use_normal_array) {
+ const Vector3 &blendshape_normal = blendshape_normals_ptr[i];
+ lerped_normals_ptrw[i] += blendshape_normal * blend_weight;
+ }
+
+ if (use_tangent_array) {
+ int tangent_index = i * 4;
+ const Vector4 blendshape_tangent = Vector4(
+ blendshape_tangents_ptr[tangent_index],
+ blendshape_tangents_ptr[tangent_index + 1],
+ blendshape_tangents_ptr[tangent_index + 2],
+ blendshape_tangents_ptr[tangent_index + 3]);
+ Vector4 lerped_tangent = blendshape_tangent * blend_weight;
+ lerped_tangents_ptrw[tangent_index] += lerped_tangent.x;
+ lerped_tangents_ptrw[tangent_index + 1] += lerped_tangent.y;
+ lerped_tangents_ptrw[tangent_index + 2] += lerped_tangent.z;
+ lerped_tangents_ptrw[tangent_index + 3] += lerped_tangent.w;
+ }
+ }
+ }
+ }
+
+ new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array;
+ if (use_normal_array) {
+ new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array;
+ }
+ if (use_tangent_array) {
+ new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array;
+ }
+
+ bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format);
+ }
+
+ return bake_mesh;
+}
+
void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh);
ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh);
@@ -542,6 +698,9 @@ void MeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value);
ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents);
+
+ ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>()));
+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
ADD_GROUP("Skeleton", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin");
diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h
index d6ae1291d3..8a7e03c5b3 100644
--- a/scene/3d/mesh_instance_3d.h
+++ b/scene/3d/mesh_instance_3d.h
@@ -101,6 +101,8 @@ public:
virtual AABB get_aabb() const override;
+ Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>());
+
MeshInstance3D();
~MeshInstance3D();
};
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index 43c41c1b5b..dff413f5d2 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -865,7 +865,7 @@ void NavigationAgent3D::_trigger_waypoint_reached() {
Dictionary details;
const Vector3 waypoint = navigation_path[navigation_path_index];
- details[SNAME("position")] = waypoint;
+ details[CoreStringName(position)] = waypoint;
int waypoint_type = -1;
if (path_metadata_flags.has_flag(NavigationPathQueryParameters3D::PathMetadataFlags::PATH_METADATA_INCLUDE_TYPES)) {
diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp
index d8a63c60a2..40e04f0fb4 100644
--- a/scene/3d/navigation_region_3d.cpp
+++ b/scene/3d/navigation_region_3d.cpp
@@ -30,7 +30,7 @@
#include "navigation_region_3d.h"
-#include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
+#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
#include "servers/navigation_server_3d.h"
RID NavigationRegion3D::get_rid() const {
@@ -474,7 +474,7 @@ void NavigationRegion3D::_update_debug_mesh() {
return;
}
- if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
+ if (!NavigationServer3D::get_singleton()->get_debug_enabled() || !NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
if (debug_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_instance, false);
}
@@ -640,7 +640,7 @@ void NavigationRegion3D::_update_debug_mesh() {
#ifdef DEBUG_ENABLED
void NavigationRegion3D::_update_debug_edge_connections_mesh() {
- if (!NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
+ if (!NavigationServer3D::get_singleton()->get_debug_enabled() || !NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) {
if (debug_edge_connections_instance.is_valid()) {
RS::get_singleton()->instance_set_visible(debug_edge_connections_instance, false);
}
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 5c081a0b47..2e08afb30d 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -33,7 +33,6 @@
#include "scene/3d/visual_instance_3d.h"
#include "scene/main/viewport.h"
#include "scene/property_utils.h"
-#include "scene/scene_string_names.h"
/*
@@ -193,12 +192,12 @@ void Node3D::_notification(int p_what) {
ERR_FAIL_NULL(data.viewport);
if (get_script_instance()) {
- get_script_instance()->call(SceneStringNames::get_singleton()->_enter_world);
+ get_script_instance()->call(SNAME("_enter_world"));
}
#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
- get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SNAME("_request_gizmo_for_id"), get_instance_id());
+ if (is_part_of_edited_scene()) {
+ get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_request_gizmo_for_id"), get_instance_id());
}
#endif
} break;
@@ -211,7 +210,7 @@ void Node3D::_notification(int p_what) {
#endif
if (get_script_instance()) {
- get_script_instance()->call(SceneStringNames::get_singleton()->_exit_world);
+ get_script_instance()->call(SNAME("_exit_world"));
}
data.viewport = nullptr;
@@ -293,7 +292,7 @@ Vector3 Node3D::get_global_rotation_degrees() const {
void Node3D::set_global_rotation(const Vector3 &p_euler_rad) {
ERR_THREAD_GUARD;
Transform3D transform = get_global_transform();
- transform.basis = Basis::from_euler(p_euler_rad);
+ transform.basis = Basis::from_euler(p_euler_rad) * Basis::from_scale(transform.basis.get_scale());
set_global_transform(transform);
}
@@ -564,7 +563,7 @@ void Node3D::update_gizmos() {
}
if (data.gizmos.is_empty()) {
- get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SNAME("_request_gizmo_for_id"), get_instance_id());
+ get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_request_gizmo_for_id"), get_instance_id());
return;
}
if (data.gizmos_dirty) {
@@ -582,8 +581,8 @@ void Node3D::set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transfor
return;
}
- if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
- get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_set_subgizmo_selection, this, p_gizmo, p_id, p_transform);
+ if (is_part_of_edited_scene()) {
+ get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_set_subgizmo_selection"), this, p_gizmo, p_id, p_transform);
}
#endif
}
@@ -599,8 +598,8 @@ void Node3D::clear_subgizmo_selection() {
return;
}
- if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
- get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_clear_subgizmo_selection, this);
+ if (is_part_of_edited_scene()) {
+ get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(_spatial_editor_group), SNAME("_clear_subgizmo_selection"), this);
}
#endif
}
@@ -753,6 +752,15 @@ void Node3D::set_as_top_level(bool p_enabled) {
data.top_level = p_enabled;
}
+void Node3D::set_as_top_level_keep_local(bool p_enabled) {
+ ERR_THREAD_GUARD;
+ if (data.top_level == p_enabled) {
+ return;
+ }
+ data.top_level = p_enabled;
+ _propagate_transform_changed(this);
+}
+
bool Node3D::is_set_as_top_level() const {
ERR_READ_THREAD_GUARD_V(false);
return data.top_level;
@@ -768,7 +776,7 @@ Ref<World3D> Node3D::get_world_3d() const {
void Node3D::_propagate_visibility_changed() {
notification(NOTIFICATION_VISIBILITY_CHANGED);
- emit_signal(SceneStringNames::get_singleton()->visibility_changed);
+ emit_signal(SceneStringName(visibility_changed));
#ifdef TOOLS_ENABLED
if (!data.gizmos.is_empty()) {
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index 7f8c3e6e68..c1667221df 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -227,6 +227,7 @@ public:
void clear_gizmos();
void set_as_top_level(bool p_enabled);
+ void set_as_top_level_keep_local(bool p_enabled);
bool is_set_as_top_level() const;
void set_disable_scale(bool p_enabled);
diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp
index 2f77185d0d..150771545b 100644
--- a/scene/3d/occluder_instance_3d.cpp
+++ b/scene/3d/occluder_instance_3d.cpp
@@ -192,7 +192,7 @@ void QuadOccluder3D::set_size(const Size2 &p_size) {
return;
}
- size = p_size.max(Size2());
+ size = p_size.maxf(0);
_update();
}
@@ -236,7 +236,7 @@ void BoxOccluder3D::set_size(const Vector3 &p_size) {
return;
}
- size = p_size.max(Vector3());
+ size = p_size.maxf(0);
_update();
}
diff --git a/scene/3d/physical_bone_simulator_3d.cpp b/scene/3d/physical_bone_simulator_3d.cpp
index aba052165c..ef3c51b032 100644
--- a/scene/3d/physical_bone_simulator_3d.cpp
+++ b/scene/3d/physical_bone_simulator_3d.cpp
@@ -35,16 +35,16 @@ void PhysicalBoneSimulator3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p
if (p_old->is_connected(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed))) {
p_old->disconnect(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed));
}
- if (p_old->is_connected(SNAME("pose_updated"), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) {
- p_old->disconnect(SNAME("pose_updated"), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated));
+ if (p_old->is_connected(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) {
+ p_old->disconnect(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated));
}
}
if (p_new) {
if (!p_new->is_connected(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed))) {
p_new->connect(SNAME("bone_list_changed"), callable_mp(this, &PhysicalBoneSimulator3D::_bone_list_changed));
}
- if (!p_new->is_connected(SNAME("pose_updated"), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) {
- p_new->connect(SNAME("pose_updated"), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated));
+ if (!p_new->is_connected(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated))) {
+ p_new->connect(SceneStringName(pose_updated), callable_mp(this, &PhysicalBoneSimulator3D::_pose_updated));
}
}
_bone_list_changed();
diff --git a/scene/3d/physics/area_3d.cpp b/scene/3d/physics/area_3d.cpp
index 014c33cad0..be95512bea 100644
--- a/scene/3d/physics/area_3d.cpp
+++ b/scene/3d/physics/area_3d.cpp
@@ -30,7 +30,6 @@
#include "area_3d.h"
-#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
void Area3D::set_gravity_space_override_mode(SpaceOverride p_mode) {
@@ -199,9 +198,9 @@ void Area3D::_body_enter_tree(ObjectID p_id) {
ERR_FAIL_COND(E->value.in_tree);
E->value.in_tree = true;
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
+ emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
}
}
@@ -213,9 +212,9 @@ void Area3D::_body_exit_tree(ObjectID p_id) {
ERR_FAIL_COND(!E);
ERR_FAIL_COND(!E->value.in_tree);
E->value.in_tree = false;
- emit_signal(SceneStringNames::get_singleton()->body_exited, node);
+ emit_signal(SceneStringName(body_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
+ emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].area_shape);
}
}
@@ -229,9 +228,9 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
locked = true;
// Emit the appropriate signals.
if (body_in) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_entered), p_body, (Node *)nullptr, p_body_shape, p_area_shape);
} else {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, (Node *)nullptr, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_exited), p_body, (Node *)nullptr, p_body_shape, p_area_shape);
}
locked = false;
unlock_callback();
@@ -257,10 +256,10 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
E->value.rc = 0;
E->value.in_tree = node && node->is_inside_tree();
if (node) {
- node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree).bind(objid));
- node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree).bind(objid));
+ node->connect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree).bind(objid));
+ node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree).bind(objid));
if (E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
}
}
}
@@ -270,7 +269,7 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
}
if (!node || E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_area_shape);
}
} else {
@@ -284,15 +283,15 @@ void Area3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, i
if (E->value.rc == 0) {
body_map.remove(E);
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree));
if (in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_exited, obj);
+ emit_signal(SceneStringName(body_exited), obj);
}
}
}
if (!node || in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape);
+ emit_signal(SceneStringName(body_shape_exited), p_body, obj, p_body_shape, p_area_shape);
}
}
@@ -317,18 +316,18 @@ void Area3D::_clear_monitoring() {
}
//ERR_CONTINUE(!node);
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_body_exit_tree));
if (!E.value.in_tree) {
continue;
}
for (int i = 0; i < E.value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape);
+ emit_signal(SceneStringName(body_shape_exited), E.value.rid, node, E.value.shapes[i].body_shape, E.value.shapes[i].area_shape);
}
- emit_signal(SceneStringNames::get_singleton()->body_exited, node);
+ emit_signal(SceneStringName(body_exited), node);
}
}
@@ -346,18 +345,18 @@ void Area3D::_clear_monitoring() {
}
//ERR_CONTINUE(!node);
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree));
if (!E.value.in_tree) {
continue;
}
for (int i = 0; i < E.value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape);
+ emit_signal(SceneStringName(area_shape_exited), E.value.rid, node, E.value.shapes[i].area_shape, E.value.shapes[i].self_shape);
}
- emit_signal(SceneStringNames::get_singleton()->area_exited, obj);
+ emit_signal(SceneStringName(area_exited), obj);
}
}
}
@@ -405,9 +404,9 @@ void Area3D::_area_enter_tree(ObjectID p_id) {
ERR_FAIL_COND(E->value.in_tree);
E->value.in_tree = true;
- emit_signal(SceneStringNames::get_singleton()->area_entered, node);
+ emit_signal(SceneStringName(area_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_entered, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
+ emit_signal(SceneStringName(area_shape_entered), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
}
}
@@ -419,9 +418,9 @@ void Area3D::_area_exit_tree(ObjectID p_id) {
ERR_FAIL_COND(!E);
ERR_FAIL_COND(!E->value.in_tree);
E->value.in_tree = false;
- emit_signal(SceneStringNames::get_singleton()->area_exited, node);
+ emit_signal(SceneStringName(area_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
+ emit_signal(SceneStringName(area_shape_exited), E->value.rid, node, E->value.shapes[i].area_shape, E->value.shapes[i].self_shape);
}
}
@@ -435,9 +434,9 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
locked = true;
// Emit the appropriate signals.
if (area_in) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_entered), p_area, (Node *)nullptr, p_area_shape, p_self_shape);
} else {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, (Node *)nullptr, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_exited), p_area, (Node *)nullptr, p_area_shape, p_self_shape);
}
locked = false;
unlock_callback();
@@ -463,10 +462,10 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
E->value.rc = 0;
E->value.in_tree = node && node->is_inside_tree();
if (node) {
- node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree).bind(objid));
- node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree).bind(objid));
+ node->connect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree).bind(objid));
+ node->connect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree).bind(objid));
if (E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_entered, node);
+ emit_signal(SceneStringName(area_entered), node);
}
}
}
@@ -476,7 +475,7 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
}
if (!node || E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_entered, p_area, node, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_entered), p_area, node, p_area_shape, p_self_shape);
}
} else {
@@ -490,15 +489,15 @@ void Area3D::_area_inout(int p_status, const RID &p_area, ObjectID p_instance, i
if (E->value.rc == 0) {
area_map.remove(E);
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area3D::_area_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area3D::_area_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &Area3D::_area_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Area3D::_area_exit_tree));
if (in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_exited, obj);
+ emit_signal(SceneStringName(area_exited), obj);
}
}
}
if (!node || in_tree) {
- emit_signal(SceneStringNames::get_singleton()->area_shape_exited, p_area, obj, p_area_shape, p_self_shape);
+ emit_signal(SceneStringName(area_shape_exited), p_area, obj, p_area_shape, p_self_shape);
}
}
@@ -605,7 +604,7 @@ StringName Area3D::get_audio_bus_name() const {
return audio_bus;
}
}
- return SceneStringNames::get_singleton()->Master;
+ return SceneStringName(Master);
}
void Area3D::set_use_reverb_bus(bool p_enable) {
@@ -626,7 +625,7 @@ StringName Area3D::get_reverb_bus_name() const {
return reverb_bus;
}
}
- return SceneStringNames::get_singleton()->Master;
+ return SceneStringName(Master);
}
void Area3D::set_reverb_amount(float p_amount) {
@@ -812,6 +811,8 @@ void Area3D::_bind_methods() {
Area3D::Area3D() :
CollisionObject3D(PhysicsServer3D::get_singleton()->area_create(), true) {
+ audio_bus = SceneStringName(Master);
+ reverb_bus = SceneStringName(Master);
set_gravity(9.8);
set_gravity_direction(Vector3(0, -1, 0));
set_monitoring(true);
diff --git a/scene/3d/physics/area_3d.h b/scene/3d/physics/area_3d.h
index 41382b6128..8848f9c23a 100644
--- a/scene/3d/physics/area_3d.h
+++ b/scene/3d/physics/area_3d.h
@@ -33,7 +33,6 @@
#include "core/templates/vset.h"
#include "scene/3d/physics/collision_object_3d.h"
-#include "scene/scene_string_names.h"
class Area3D : public CollisionObject3D {
GDCLASS(Area3D, CollisionObject3D);
@@ -135,10 +134,10 @@ private:
void _clear_monitoring();
bool audio_bus_override = false;
- StringName audio_bus = SceneStringNames::get_singleton()->Master;
+ StringName audio_bus;
bool use_reverb_bus = false;
- StringName reverb_bus = SceneStringNames::get_singleton()->Master;
+ StringName reverb_bus;
float reverb_amount = 0.0;
float reverb_uniformity = 0.0;
diff --git a/scene/3d/physics/character_body_3d.cpp b/scene/3d/physics/character_body_3d.cpp
index 6759033358..dda3ea9cca 100644
--- a/scene/3d/physics/character_body_3d.cpp
+++ b/scene/3d/physics/character_body_3d.cpp
@@ -232,7 +232,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
} else {
// Travel is too high to be safely canceled, we take it into account.
result.travel = result.travel.slide(up_direction);
- motion = motion.normalized() * result.travel.length();
+ motion = result.remainder;
}
set_global_transform(gt);
// Determines if you are on the ground, and limits the possibility of climbing on the walls because of the approximations.
@@ -704,7 +704,7 @@ Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) {
// Create a new instance when the cached reference is invalid or still in use in script.
if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->get_reference_count() > 1) {
slide_colliders.write[p_bounce].instantiate();
- slide_colliders.write[p_bounce]->owner = this;
+ slide_colliders.write[p_bounce]->owner_id = get_instance_id();
}
slide_colliders.write[p_bounce]->result = motion_results[p_bounce];
@@ -936,11 +936,3 @@ void CharacterBody3D::_validate_property(PropertyInfo &p_property) const {
CharacterBody3D::CharacterBody3D() :
PhysicsBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) {
}
-
-CharacterBody3D::~CharacterBody3D() {
- for (int i = 0; i < slide_colliders.size(); i++) {
- if (slide_colliders[i].is_valid()) {
- slide_colliders.write[i]->owner = nullptr;
- }
- }
-}
diff --git a/scene/3d/physics/character_body_3d.h b/scene/3d/physics/character_body_3d.h
index cffc0f8e63..430d9c35cb 100644
--- a/scene/3d/physics/character_body_3d.h
+++ b/scene/3d/physics/character_body_3d.h
@@ -113,7 +113,6 @@ public:
PlatformOnLeave get_platform_on_leave() const;
CharacterBody3D();
- ~CharacterBody3D();
private:
real_t margin = 0.001;
diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp
index 54752b1281..dddaf7eb4a 100644
--- a/scene/3d/physics/collision_object_3d.cpp
+++ b/scene/3d/physics/collision_object_3d.cpp
@@ -31,7 +31,6 @@
#include "collision_object_3d.h"
#include "scene/resources/3d/shape_3d.h"
-#include "scene/scene_string_names.h"
void CollisionObject3D::_notification(int p_what) {
switch (p_what) {
@@ -291,17 +290,17 @@ void CollisionObject3D::_apply_enabled() {
void CollisionObject3D::_input_event_call(Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) {
GDVIRTUAL_CALL(_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape);
- emit_signal(SceneStringNames::get_singleton()->input_event, p_camera, p_input_event, p_pos, p_normal, p_shape);
+ emit_signal(SceneStringName(input_event), p_camera, p_input_event, p_pos, p_normal, p_shape);
}
void CollisionObject3D::_mouse_enter() {
GDVIRTUAL_CALL(_mouse_enter);
- emit_signal(SceneStringNames::get_singleton()->mouse_entered);
+ emit_signal(SceneStringName(mouse_entered));
}
void CollisionObject3D::_mouse_exit() {
GDVIRTUAL_CALL(_mouse_exit);
- emit_signal(SceneStringNames::get_singleton()->mouse_exited);
+ emit_signal(SceneStringName(mouse_exited));
}
void CollisionObject3D::set_body_mode(PhysicsServer3D::BodyMode p_mode) {
diff --git a/scene/3d/physics/joints/cone_twist_joint_3d.cpp b/scene/3d/physics/joints/cone_twist_joint_3d.cpp
index 404c074911..3da0cbee71 100644
--- a/scene/3d/physics/joints/cone_twist_joint_3d.cpp
+++ b/scene/3d/physics/joints/cone_twist_joint_3d.cpp
@@ -30,8 +30,6 @@
#include "cone_twist_joint_3d.h"
-#include "scene/scene_string_names.h"
-
void ConeTwistJoint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &ConeTwistJoint3D::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &ConeTwistJoint3D::get_param);
diff --git a/scene/3d/physics/joints/joint_3d.cpp b/scene/3d/physics/joints/joint_3d.cpp
index a9c2526bd0..47c89f37e2 100644
--- a/scene/3d/physics/joints/joint_3d.cpp
+++ b/scene/3d/physics/joints/joint_3d.cpp
@@ -30,19 +30,17 @@
#include "joint_3d.h"
-#include "scene/scene_string_names.h"
-
void Joint3D::_disconnect_signals() {
Node *node_a = get_node_or_null(a);
PhysicsBody3D *body_a = Object::cast_to<PhysicsBody3D>(node_a);
if (body_a) {
- body_a->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree));
+ body_a->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
}
Node *node_b = get_node_or_null(b);
PhysicsBody3D *body_b = Object::cast_to<PhysicsBody3D>(node_b);
if (body_b) {
- body_b->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree));
+ body_b->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
}
}
@@ -108,12 +106,16 @@ void Joint3D::_update_joint(bool p_only_free) {
if (body_a) {
ba = body_a->get_rid();
- body_a->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree));
+ if (!body_a->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree))) {
+ body_a->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
+ }
}
if (body_b) {
bb = body_b->get_rid();
- body_b->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Joint3D::_body_exit_tree));
+ if (!body_b->is_connected(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree))) {
+ body_b->connect(SceneStringName(tree_exiting), callable_mp(this, &Joint3D::_body_exit_tree));
+ }
}
PhysicsServer3D::get_singleton()->joint_disable_collisions_between_bodies(joint, exclude_from_collision);
diff --git a/scene/3d/physics/joints/slider_joint_3d.cpp b/scene/3d/physics/joints/slider_joint_3d.cpp
index 2e87ae1e83..df6b1cc045 100644
--- a/scene/3d/physics/joints/slider_joint_3d.cpp
+++ b/scene/3d/physics/joints/slider_joint_3d.cpp
@@ -30,8 +30,6 @@
#include "slider_joint_3d.h"
-#include "scene/scene_string_names.h"
-
void SliderJoint3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_param", "param", "value"), &SliderJoint3D::set_param);
ClassDB::bind_method(D_METHOD("get_param", "param"), &SliderJoint3D::get_param);
diff --git a/scene/3d/physics/kinematic_collision_3d.cpp b/scene/3d/physics/kinematic_collision_3d.cpp
index 54371425bc..de13831ac3 100644
--- a/scene/3d/physics/kinematic_collision_3d.cpp
+++ b/scene/3d/physics/kinematic_collision_3d.cpp
@@ -67,6 +67,7 @@ real_t KinematicCollision3D::get_angle(int p_collision_index, const Vector3 &p_u
Object *KinematicCollision3D::get_local_shape(int p_collision_index) const {
ERR_FAIL_INDEX_V(p_collision_index, result.collision_count, nullptr);
+ PhysicsBody3D *owner = Object::cast_to<PhysicsBody3D>(ObjectDB::get_instance(owner_id));
if (!owner) {
return nullptr;
}
diff --git a/scene/3d/physics/kinematic_collision_3d.h b/scene/3d/physics/kinematic_collision_3d.h
index 656531c82b..0573af0c21 100644
--- a/scene/3d/physics/kinematic_collision_3d.h
+++ b/scene/3d/physics/kinematic_collision_3d.h
@@ -40,7 +40,7 @@ class PhysicsBody3D;
class KinematicCollision3D : public RefCounted {
GDCLASS(KinematicCollision3D, RefCounted);
- PhysicsBody3D *owner = nullptr;
+ ObjectID owner_id;
friend class PhysicsBody3D;
friend class CharacterBody3D;
PhysicsServer3D::MotionResult result;
diff --git a/scene/3d/physics/physics_body_3d.cpp b/scene/3d/physics/physics_body_3d.cpp
index 711ca60a81..b723b452c1 100644
--- a/scene/3d/physics/physics_body_3d.cpp
+++ b/scene/3d/physics/physics_body_3d.cpp
@@ -30,8 +30,6 @@
#include "physics_body_3d.h"
-#include "scene/scene_string_names.h"
-
void PhysicsBody3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("move_and_collide", "motion", "test_only", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(false), DEFVAL(1));
ClassDB::bind_method(D_METHOD("test_move", "from", "motion", "collision", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(false), DEFVAL(1));
@@ -58,12 +56,6 @@ PhysicsBody3D::PhysicsBody3D(PhysicsServer3D::BodyMode p_mode) :
set_body_mode(p_mode);
}
-PhysicsBody3D::~PhysicsBody3D() {
- if (motion_cache.is_valid()) {
- motion_cache->owner = nullptr;
- }
-}
-
TypedArray<PhysicsBody3D> PhysicsBody3D::get_collision_exceptions() {
List<RID> exceptions;
PhysicsServer3D::get_singleton()->body_get_collision_exceptions(get_rid(), &exceptions);
@@ -102,7 +94,7 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_motion, bool p_t
// Create a new instance when the cached reference is invalid or still in use in script.
if (motion_cache.is_null() || motion_cache->get_reference_count() > 1) {
motion_cache.instantiate();
- motion_cache->owner = this;
+ motion_cache->owner_id = get_instance_id();
}
motion_cache->result = result;
diff --git a/scene/3d/physics/physics_body_3d.h b/scene/3d/physics/physics_body_3d.h
index 92b3850085..71253be0b8 100644
--- a/scene/3d/physics/physics_body_3d.h
+++ b/scene/3d/physics/physics_body_3d.h
@@ -65,8 +65,6 @@ public:
TypedArray<PhysicsBody3D> get_collision_exceptions();
void add_collision_exception_with(Node *p_node); //must be physicsbody
void remove_collision_exception_with(Node *p_node);
-
- virtual ~PhysicsBody3D();
};
#endif // PHYSICS_BODY_3D_H
diff --git a/scene/3d/physics/ray_cast_3d.cpp b/scene/3d/physics/ray_cast_3d.cpp
index 0cb722a77e..a9272388c1 100644
--- a/scene/3d/physics/ray_cast_3d.cpp
+++ b/scene/3d/physics/ray_cast_3d.cpp
@@ -204,8 +204,10 @@ void RayCast3D::_notification(int p_what) {
bool prev_collision_state = collided;
_update_raycast_state();
- if (prev_collision_state != collided && get_tree()->is_debugging_collisions_hint()) {
- _update_debug_shape_material(true);
+ if (get_tree()->is_debugging_collisions_hint()) {
+ if (prev_collision_state != collided) {
+ _update_debug_shape_material(true);
+ }
if (is_inside_tree() && debug_instance.is_valid()) {
RenderingServer::get_singleton()->instance_set_transform(debug_instance, get_global_transform());
}
diff --git a/scene/3d/physics/rigid_body_3d.cpp b/scene/3d/physics/rigid_body_3d.cpp
index 6cd621c1c7..5ea413f2c4 100644
--- a/scene/3d/physics/rigid_body_3d.cpp
+++ b/scene/3d/physics/rigid_body_3d.cpp
@@ -30,8 +30,6 @@
#include "rigid_body_3d.h"
-#include "scene/scene_string_names.h"
-
void RigidBody3D::_body_enter_tree(ObjectID p_id) {
Object *obj = ObjectDB::get_instance(p_id);
Node *node = Object::cast_to<Node>(obj);
@@ -45,10 +43,10 @@ void RigidBody3D::_body_enter_tree(ObjectID p_id) {
contact_monitor->locked = true;
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
+ emit_signal(SceneStringName(body_shape_entered), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
}
contact_monitor->locked = false;
@@ -66,10 +64,10 @@ void RigidBody3D::_body_exit_tree(ObjectID p_id) {
contact_monitor->locked = true;
- emit_signal(SceneStringNames::get_singleton()->body_exited, node);
+ emit_signal(SceneStringName(body_exited), node);
for (int i = 0; i < E->value.shapes.size(); i++) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
+ emit_signal(SceneStringName(body_shape_exited), E->value.rid, node, E->value.shapes[i].body_shape, E->value.shapes[i].local_shape);
}
contact_monitor->locked = false;
@@ -94,10 +92,10 @@ void RigidBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan
//E->value.rc=0;
E->value.in_tree = node && node->is_inside_tree();
if (node) {
- node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody3D::_body_enter_tree).bind(objid));
- node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody3D::_body_exit_tree).bind(objid));
+ node->connect(SceneStringName(tree_entered), callable_mp(this, &RigidBody3D::_body_enter_tree).bind(objid));
+ node->connect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody3D::_body_exit_tree).bind(objid));
if (E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_entered, node);
+ emit_signal(SceneStringName(body_entered), node);
}
}
}
@@ -107,7 +105,7 @@ void RigidBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan
}
if (E->value.in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_local_shape);
+ emit_signal(SceneStringName(body_shape_entered), p_body, node, p_body_shape, p_local_shape);
}
} else {
@@ -121,17 +119,17 @@ void RigidBody3D::_body_inout(int p_status, const RID &p_body, ObjectID p_instan
if (E->value.shapes.is_empty()) {
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody3D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody3D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody3D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody3D::_body_exit_tree));
if (in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_exited, node);
+ emit_signal(SceneStringName(body_exited), node);
}
}
contact_monitor->body_map.remove(E);
}
if (node && in_tree) {
- emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_local_shape);
+ emit_signal(SceneStringName(body_shape_exited), p_body, obj, p_body_shape, p_local_shape);
}
}
}
@@ -157,7 +155,7 @@ void RigidBody3D::_sync_body_state(PhysicsDirectBodyState3D *p_state) {
if (sleeping != p_state->is_sleeping()) {
sleeping = p_state->is_sleeping();
- emit_signal(SceneStringNames::get_singleton()->sleeping_state_changed);
+ emit_signal(SceneStringName(sleeping_state_changed));
}
}
@@ -613,8 +611,8 @@ void RigidBody3D::set_contact_monitor(bool p_enabled) {
Node *node = Object::cast_to<Node>(obj);
if (node) {
- node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &RigidBody3D::_body_enter_tree));
- node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &RigidBody3D::_body_exit_tree));
+ node->disconnect(SceneStringName(tree_entered), callable_mp(this, &RigidBody3D::_body_enter_tree));
+ node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &RigidBody3D::_body_exit_tree));
}
}
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 95e94de0e3..a4804e928a 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -34,7 +34,6 @@
#include "core/variant/type_info.h"
#include "scene/3d/skeleton_modifier_3d.h"
#include "scene/resources/surface_tool.h"
-#include "scene/scene_string_names.h"
#ifndef DISABLE_DEPRECATED
#include "scene/3d/physical_bone_simulator_3d.h"
#endif // _DISABLE_DEPRECATED
@@ -253,7 +252,7 @@ void Skeleton3D::_update_process_order() {
int parent_bone_idx = bonesptr[i].parent;
// Check to see if this node is already added to the parent.
- if (bonesptr[parent_bone_idx].child_bones.find(i) < 0) {
+ if (!bonesptr[parent_bone_idx].child_bones.has(i)) {
// Add the child node.
bonesptr[parent_bone_idx].child_bones.push_back(i);
} else {
@@ -264,6 +263,8 @@ void Skeleton3D::_update_process_order() {
}
}
+ bones_backup.resize(bones.size());
+
process_order_dirty = false;
emit_signal("bone_list_changed");
@@ -271,16 +272,15 @@ void Skeleton3D::_update_process_order() {
#ifndef DISABLE_DEPRECATED
void Skeleton3D::setup_simulator() {
+ if (simulator && simulator->get_parent() == this) {
+ remove_child(simulator);
+ simulator->queue_free();
+ }
PhysicalBoneSimulator3D *sim = memnew(PhysicalBoneSimulator3D);
simulator = sim;
sim->is_compat = true;
sim->set_active(false); // Don't run unneeded process.
- add_child(sim);
-}
-
-void Skeleton3D::remove_simulator() {
- remove_child(simulator);
- memdelete(simulator);
+ add_child(simulator);
}
#endif // _DISABLE_DEPRECATED
@@ -294,11 +294,6 @@ void Skeleton3D::_notification(int p_what) {
setup_simulator();
#endif // _DISABLE_DEPRECATED
} break;
-#ifndef DISABLE_DEPRECATED
- case NOTIFICATION_EXIT_TREE: {
- remove_simulator();
- } break;
-#endif // _DISABLE_DEPRECATED
case NOTIFICATION_UPDATE_SKELETON: {
// Update bone transforms to apply unprocessed poses.
force_update_all_dirty_bones();
@@ -310,23 +305,16 @@ void Skeleton3D::_notification(int p_what) {
// Process modifiers.
_find_modifiers();
- LocalVector<Transform3D> current_bone_poses;
- LocalVector<Vector3> current_pose_positions;
- LocalVector<Quaternion> current_pose_rotations;
- LocalVector<Vector3> current_pose_scales;
- LocalVector<Transform3D> current_bone_global_poses;
if (!modifiers.is_empty()) {
// Store unmodified bone poses.
- for (int i = 0; i < len; i++) {
- current_bone_poses.push_back(bones[i].pose_cache);
- current_pose_positions.push_back(bones[i].pose_position);
- current_pose_rotations.push_back(bones[i].pose_rotation);
- current_pose_scales.push_back(bones[i].pose_scale);
- current_bone_global_poses.push_back(bones[i].global_pose);
+ for (int i = 0; i < bones.size(); i++) {
+ bones_backup[i].save(bones[i]);
}
_process_modifiers();
}
+ emit_signal(SceneStringName(skeleton_updated));
+
// Update skins.
RenderingServer *rs = RenderingServer::get_singleton();
for (SkinReference *E : skin_bindings) {
@@ -386,12 +374,8 @@ void Skeleton3D::_notification(int p_what) {
if (!modifiers.is_empty()) {
// Restore unmodified bone poses.
- for (int i = 0; i < len; i++) {
- bonesptr[i].pose_cache = current_bone_poses[i];
- bonesptr[i].pose_position = current_pose_positions[i];
- bonesptr[i].pose_rotation = current_pose_rotations[i];
- bonesptr[i].pose_scale = current_pose_scales[i];
- bonesptr[i].global_pose = current_bone_global_poses[i];
+ for (int i = 0; i < bones.size(); i++) {
+ bones_backup[i].restore(bones.write[i]);
}
}
@@ -620,7 +604,7 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) {
ERR_FAIL_INDEX(p_bone, bone_size);
bones.write[p_bone].enabled = p_enabled;
- emit_signal(SceneStringNames::get_singleton()->bone_enabled_changed, p_bone);
+ emit_signal(SceneStringName(bone_enabled_changed), p_bone);
_make_dirty();
}
@@ -632,7 +616,7 @@ bool Skeleton3D::is_bone_enabled(int p_bone) const {
void Skeleton3D::set_show_rest_only(bool p_enabled) {
show_rest_only = p_enabled;
- emit_signal(SceneStringNames::get_singleton()->show_rest_only_changed);
+ emit_signal(SceneStringName(show_rest_only_changed));
_make_dirty();
}
@@ -855,7 +839,7 @@ void Skeleton3D::force_update_all_bone_transforms() {
if (updating) {
return;
}
- emit_signal(SceneStringNames::get_singleton()->pose_updated);
+ emit_signal(SceneStringName(pose_updated));
}
void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
@@ -863,12 +847,13 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
ERR_FAIL_INDEX(p_bone_idx, bone_size);
Bone *bonesptr = bones.ptrw();
- List<int> bones_to_process = List<int>();
+ thread_local LocalVector<int> bones_to_process;
+ bones_to_process.clear();
bones_to_process.push_back(p_bone_idx);
- while (bones_to_process.size() > 0) {
- int current_bone_idx = bones_to_process[0];
- bones_to_process.erase(current_bone_idx);
+ uint32_t index = 0;
+ while (index < bones_to_process.size()) {
+ int current_bone_idx = bones_to_process[index];
Bone &b = bonesptr[current_bone_idx];
bool bone_enabled = b.enabled && !show_rest_only;
@@ -922,7 +907,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
bones_to_process.push_back(b.child_bones[i]);
}
- emit_signal(SceneStringNames::get_singleton()->bone_pose_changed, current_bone_idx);
+ index++;
}
}
@@ -1059,7 +1044,7 @@ void Skeleton3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "modifier_callback_mode_process", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_modifier_callback_mode_process", "get_modifier_callback_mode_process");
ADD_SIGNAL(MethodInfo("pose_updated"));
- ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx")));
+ ADD_SIGNAL(MethodInfo("skeleton_updated"));
ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx")));
ADD_SIGNAL(MethodInfo("bone_list_changed"));
ADD_SIGNAL(MethodInfo("show_rest_only_changed"));
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 5b6f60dbd4..23b9423993 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -69,7 +69,6 @@ class Skeleton3D : public Node3D {
#ifndef DISABLE_DEPRECATED
Node *simulator = nullptr;
void setup_simulator();
- void remove_simulator();
#endif // _DISABLE_DEPRECATED
public:
@@ -88,15 +87,15 @@ private:
struct Bone {
String name;
- int parent;
+ int parent = -1;
Vector<int> child_bones;
Transform3D rest;
Transform3D global_rest;
- bool enabled;
- Transform3D pose_cache;
+ bool enabled = true;
bool pose_cache_dirty = true;
+ Transform3D pose_cache;
Vector3 pose_position;
Quaternion pose_rotation;
Vector3 pose_scale = Vector3(1, 1, 1);
@@ -116,15 +115,29 @@ private:
bool global_pose_override_reset = false;
Transform3D global_pose_override;
#endif // _DISABLE_DEPRECATED
+ };
- Bone() {
- parent = -1;
- child_bones = Vector<int>();
- enabled = true;
-#ifndef DISABLE_DEPRECATED
- global_pose_override_amount = 0;
- global_pose_override_reset = false;
-#endif // _DISABLE_DEPRECATED
+ struct BonePoseBackup {
+ Transform3D pose_cache;
+ Vector3 pose_position;
+ Quaternion pose_rotation;
+ Vector3 pose_scale = Vector3(1, 1, 1);
+ Transform3D global_pose;
+
+ void save(const Bone &p_bone) {
+ pose_cache = p_bone.pose_cache;
+ pose_position = p_bone.pose_position;
+ pose_rotation = p_bone.pose_rotation;
+ pose_scale = p_bone.pose_scale;
+ global_pose = p_bone.global_pose;
+ }
+
+ void restore(Bone &r_bone) {
+ r_bone.pose_cache = pose_cache;
+ r_bone.pose_position = pose_position;
+ r_bone.pose_rotation = pose_rotation;
+ r_bone.pose_scale = pose_scale;
+ r_bone.global_pose = global_pose;
}
};
@@ -156,6 +169,7 @@ private:
void _process_modifiers();
void _process_changed();
void _make_modifiers_dirty();
+ LocalVector<BonePoseBackup> bones_backup;
#ifndef DISABLE_DEPRECATED
void _add_bone_bind_compat_88791(const String &p_name);
diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp
index 9581ae58d8..78a21ba9e1 100644
--- a/scene/3d/skeleton_ik_3d.cpp
+++ b/scene/3d/skeleton_ik_3d.cpp
@@ -366,6 +366,12 @@ void SkeletonIK3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_min_distance", "get_min_distance");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations");
+
+#ifndef DISABLE_DEPRECATED
+ ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK3D::_set_interpolation);
+ ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK3D::_get_interpolation);
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001", PROPERTY_USAGE_NONE), "set_interpolation", "get_interpolation");
+#endif
}
void SkeletonIK3D::_process_modification() {
@@ -415,6 +421,16 @@ StringName SkeletonIK3D::get_tip_bone() const {
return tip_bone;
}
+#ifndef DISABLE_DEPRECATED
+void SkeletonIK3D::_set_interpolation(real_t p_interpolation) {
+ set_influence(p_interpolation);
+}
+
+real_t SkeletonIK3D::_get_interpolation() const {
+ return get_influence();
+}
+#endif
+
void SkeletonIK3D::set_target_transform(const Transform3D &p_target) {
target = p_target;
reload_goal();
diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h
index eff018f2cc..5d6020194e 100644
--- a/scene/3d/skeleton_ik_3d.h
+++ b/scene/3d/skeleton_ik_3d.h
@@ -134,6 +134,11 @@ class SkeletonIK3D : public SkeletonModifier3D {
Variant target_node_override_ref = Variant();
FabrikInverseKinematic::Task *task = nullptr;
+#ifndef DISABLE_DEPRECATED
+ void _set_interpolation(real_t p_interpolation);
+ real_t _get_interpolation() const;
+#endif // DISABLE_DEPRECATED
+
protected:
void _validate_property(PropertyInfo &p_property) const;
diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp
index 96e3e33841..8d806ef5fc 100644
--- a/scene/3d/skeleton_modifier_3d.cpp
+++ b/scene/3d/skeleton_modifier_3d.cpp
@@ -110,7 +110,7 @@ void SkeletonModifier3D::process_modification() {
}
void SkeletonModifier3D::_process_modification() {
- //
+ GDVIRTUAL_CALL(_process_modification);
}
void SkeletonModifier3D::_notification(int p_what) {
@@ -133,6 +133,7 @@ void SkeletonModifier3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "influence", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_influence", "get_influence");
ADD_SIGNAL(MethodInfo("modification_processed"));
+ GDVIRTUAL_BIND(_process_modification);
}
SkeletonModifier3D::SkeletonModifier3D() {
diff --git a/scene/3d/skeleton_modifier_3d.h b/scene/3d/skeleton_modifier_3d.h
index 25c09f3b93..d00a1e94a9 100644
--- a/scene/3d/skeleton_modifier_3d.h
+++ b/scene/3d/skeleton_modifier_3d.h
@@ -60,6 +60,7 @@ protected:
virtual void _set_active(bool p_active);
virtual void _process_modification();
+ GDVIRTUAL0(_process_modification);
public:
virtual PackedStringArray get_configuration_warnings() const override;
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index a7ac278bc1..ba3b32a031 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -31,7 +31,6 @@
#include "sprite_3d.h"
#include "scene/resources/atlas_texture.h"
-#include "scene/scene_string_names.h"
Color SpriteBase3D::_get_color_accum() {
if (!color_dirty) {
@@ -796,15 +795,15 @@ void Sprite3D::set_texture(const Ref<Texture2D> &p_texture) {
return;
}
if (texture.is_valid()) {
- texture->disconnect(SceneStringNames::get_singleton()->changed, callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw));
+ texture->disconnect(CoreStringName(changed), callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw));
}
texture = p_texture;
if (texture.is_valid()) {
- texture->connect(SceneStringNames::get_singleton()->changed, callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw));
+ texture->connect(CoreStringName(changed), callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw));
}
_queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->texture_changed);
+ emit_signal(SceneStringName(texture_changed));
}
Ref<Texture2D> Sprite3D::get_texture() const {
@@ -849,7 +848,7 @@ void Sprite3D::set_frame(int p_frame) {
frame = p_frame;
_queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
int Sprite3D::get_frame() const {
@@ -1122,7 +1121,7 @@ void AnimatedSprite3D::_notification(int p_what) {
} else {
frame = last_frame;
pause();
- emit_signal(SceneStringNames::get_singleton()->animation_finished);
+ emit_signal(SceneStringName(animation_finished));
return;
}
} else {
@@ -1131,7 +1130,7 @@ void AnimatedSprite3D::_notification(int p_what) {
_calc_frame_speed_scale();
frame_progress = 0.0;
_queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
double to_process = MIN((1.0 - frame_progress) / abs_speed, remaining);
frame_progress += to_process * abs_speed;
@@ -1146,7 +1145,7 @@ void AnimatedSprite3D::_notification(int p_what) {
} else {
frame = 0;
pause();
- emit_signal(SceneStringNames::get_singleton()->animation_finished);
+ emit_signal(SceneStringName(animation_finished));
return;
}
} else {
@@ -1155,7 +1154,7 @@ void AnimatedSprite3D::_notification(int p_what) {
_calc_frame_speed_scale();
frame_progress = 1.0;
_queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
double to_process = MIN(frame_progress / abs_speed, remaining);
frame_progress -= to_process * abs_speed;
@@ -1177,12 +1176,12 @@ void AnimatedSprite3D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) {
}
if (frames.is_valid()) {
- frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed));
+ frames->disconnect(CoreStringName(changed), callable_mp(this, &AnimatedSprite3D::_res_changed));
}
stop();
frames = p_frames;
if (frames.is_valid()) {
- frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed));
+ frames->connect(CoreStringName(changed), callable_mp(this, &AnimatedSprite3D::_res_changed));
List<StringName> al;
frames->get_animation_list(&al);
@@ -1191,7 +1190,7 @@ void AnimatedSprite3D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) {
autoplay = String();
} else {
if (!frames->has_animation(animation)) {
- set_animation(al[0]);
+ set_animation(al.front()->get());
}
if (!frames->has_animation(autoplay)) {
autoplay = String();
@@ -1249,7 +1248,7 @@ void AnimatedSprite3D::set_frame_and_progress(int p_frame, real_t p_progress) {
return; // No change, don't redraw.
}
_queue_redraw();
- emit_signal(SceneStringNames::get_singleton()->frame_changed);
+ emit_signal(SceneStringName(frame_changed));
}
void AnimatedSprite3D::set_speed_scale(float p_speed_scale) {
@@ -1343,7 +1342,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
} else {
set_frame_and_progress(0, 0.0);
}
- emit_signal("animation_changed");
+ emit_signal(SceneStringName(animation_changed));
} else {
bool is_backward = signbit(speed_scale * custom_speed_scale);
if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) {
@@ -1398,7 +1397,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) {
animation = p_name;
- emit_signal("animation_changed");
+ emit_signal(SceneStringName(animation_changed));
if (frames == nullptr) {
animation = StringName();
diff --git a/scene/3d/visible_on_screen_notifier_3d.cpp b/scene/3d/visible_on_screen_notifier_3d.cpp
index 272852e8fa..a510540e4e 100644
--- a/scene/3d/visible_on_screen_notifier_3d.cpp
+++ b/scene/3d/visible_on_screen_notifier_3d.cpp
@@ -30,15 +30,13 @@
#include "visible_on_screen_notifier_3d.h"
-#include "scene/scene_string_names.h"
-
void VisibleOnScreenNotifier3D::_visibility_enter() {
if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) {
return;
}
on_screen = true;
- emit_signal(SceneStringNames::get_singleton()->screen_entered);
+ emit_signal(SceneStringName(screen_entered));
_screen_enter();
}
void VisibleOnScreenNotifier3D::_visibility_exit() {
@@ -47,7 +45,7 @@ void VisibleOnScreenNotifier3D::_visibility_exit() {
}
on_screen = false;
- emit_signal(SceneStringNames::get_singleton()->screen_exited);
+ emit_signal(SceneStringName(screen_exited));
_screen_exit();
}
diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp
index 503c39ae3e..f14ae3a285 100644
--- a/scene/3d/visual_instance_3d.cpp
+++ b/scene/3d/visual_instance_3d.cpp
@@ -30,9 +30,6 @@
#include "visual_instance_3d.h"
-#include "core/core_string_names.h"
-#include "scene/scene_string_names.h"
-
AABB VisualInstance3D::get_aabb() const {
AABB ret;
GDVIRTUAL_CALL(_get_aabb, ret);
@@ -169,11 +166,11 @@ VisualInstance3D::~VisualInstance3D() {
void GeometryInstance3D::set_material_override(const Ref<Material> &p_material) {
if (material_override.is_valid()) {
- material_override->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ material_override->disconnect(CoreStringName(property_list_changed), callable_mp((Object *)this, &Object::notify_property_list_changed));
}
material_override = p_material;
if (material_override.is_valid()) {
- material_override->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed));
+ material_override->connect(CoreStringName(property_list_changed), callable_mp((Object *)this, &Object::notify_property_list_changed));
}
RS::get_singleton()->instance_geometry_set_material_override(get_instance(), p_material.is_valid() ? p_material->get_rid() : RID());
}
@@ -194,6 +191,7 @@ Ref<Material> GeometryInstance3D::get_material_overlay() const {
void GeometryInstance3D::set_transparency(float p_transparency) {
transparency = CLAMP(p_transparency, 0.0f, 1.0f);
RS::get_singleton()->instance_geometry_set_transparency(get_instance(), transparency);
+ update_configuration_warnings();
}
float GeometryInstance3D::get_transparency() const {
@@ -278,12 +276,12 @@ bool GeometryInstance3D::_set(const StringName &p_name, const Variant &p_value)
return true;
}
#ifndef DISABLE_DEPRECATED
- if (p_name == SceneStringNames::get_singleton()->use_in_baked_light && bool(p_value)) {
+ if (p_name == SNAME("use_in_baked_light") && bool(p_value)) {
set_gi_mode(GI_MODE_STATIC);
return true;
}
- if (p_name == SceneStringNames::get_singleton()->use_dynamic_gi && bool(p_value)) {
+ if (p_name == SNAME("use_dynamic_gi") && bool(p_value)) {
set_gi_mode(GI_MODE_DYNAMIC);
return true;
}
@@ -377,6 +375,7 @@ void GeometryInstance3D::set_custom_aabb(AABB p_aabb) {
}
custom_aabb = p_aabb;
RS::get_singleton()->instance_set_custom_aabb(get_instance(), custom_aabb);
+ update_gizmos();
}
AABB GeometryInstance3D::get_custom_aabb() const {
@@ -440,6 +439,14 @@ PackedStringArray GeometryInstance3D::get_configuration_warnings() const {
warnings.push_back(RTR("The GeometryInstance3D is configured to fade out smoothly over distance, but the fade transition distance is set to 0.\nTo resolve this, increase Visibility Range End Margin above 0."));
}
+ if (!Math::is_zero_approx(transparency) && OS::get_singleton()->get_current_rendering_method() != "forward_plus") {
+ warnings.push_back(RTR("GeometryInstance3D transparency is only available when using the Forward+ rendering method."));
+ }
+
+ if ((visibility_range_fade_mode == VISIBILITY_RANGE_FADE_SELF || visibility_range_fade_mode == VISIBILITY_RANGE_FADE_DEPENDENCIES) && OS::get_singleton()->get_current_rendering_method() != "forward_plus") {
+ warnings.push_back(RTR("GeometryInstance3D visibility range transparency fade is only available when using the Forward+ rendering method."));
+ }
+
return warnings;
}
diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp
index 938d6e5699..ffca856fba 100644
--- a/scene/3d/voxel_gi.cpp
+++ b/scene/3d/voxel_gi.cpp
@@ -31,7 +31,6 @@
#include "voxel_gi.h"
#include "core/config/project_settings.h"
-#include "core/core_string_names.h"
#include "mesh_instance_3d.h"
#include "multimesh_instance_3d.h"
#include "scene/resources/camera_attributes.h"
@@ -294,7 +293,7 @@ VoxelGI::Subdiv VoxelGI::get_subdiv() const {
void VoxelGI::set_size(const Vector3 &p_size) {
// Prevent very small size dimensions as these breaks baking if other size dimensions are set very high.
- size = p_size.max(Vector3(1.0, 1.0, 1.0));
+ size = p_size.maxf(1.0);
update_gizmos();
}
diff --git a/scene/3d/xr_body_modifier_3d.cpp b/scene/3d/xr_body_modifier_3d.cpp
index 0099784a05..cf73882a7b 100644
--- a/scene/3d/xr_body_modifier_3d.cpp
+++ b/scene/3d/xr_body_modifier_3d.cpp
@@ -44,13 +44,9 @@ void XRBodyModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRBodyModifier3D::set_bone_update);
ClassDB::bind_method(D_METHOD("get_bone_update"), &XRBodyModifier3D::get_bone_update);
- ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRBodyModifier3D::set_show_when_tracked);
- ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRBodyModifier3D::get_show_when_tracked);
-
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body"), "set_body_tracker", "get_body_tracker");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/body_tracker"), "set_body_tracker", "get_body_tracker");
ADD_PROPERTY(PropertyInfo(Variant::INT, "body_update", PROPERTY_HINT_FLAGS, "Upper Body,Lower Body,Hands"), "set_body_update", "get_body_update");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked");
BIND_BITFIELD_FLAG(BODY_UPDATE_UPPER_BODY);
BIND_BITFIELD_FLAG(BODY_UPDATE_LOWER_BODY);
@@ -86,14 +82,6 @@ XRBodyModifier3D::BoneUpdate XRBodyModifier3D::get_bone_update() const {
return bone_update;
}
-void XRBodyModifier3D::set_show_when_tracked(bool p_show_when_tracked) {
- show_when_tracked = p_show_when_tracked;
-}
-
-bool XRBodyModifier3D::get_show_when_tracked() const {
- return show_when_tracked;
-}
-
void XRBodyModifier3D::_get_joint_data() {
// Table of Godot Humanoid bone names.
static const String bone_names[XRBodyTracker::JOINT_MAX] = {
@@ -189,7 +177,7 @@ void XRBodyModifier3D::_get_joint_data() {
joints[i].parent_joint = -1;
}
- Skeleton3D *skeleton = get_skeleton();
+ const Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
@@ -257,27 +245,22 @@ void XRBodyModifier3D::_process_modification() {
return;
}
- XRServer *xr_server = XRServer::get_singleton();
+ const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
- Ref<XRBodyTracker> tracker = xr_server->get_body_tracker(tracker_name);
- if (tracker.is_null()) {
+ const Ref<XRBodyTracker> tracker = xr_server->get_tracker(tracker_name);
+ if (!tracker.is_valid()) {
return;
}
- // Handle no tracking data.
+ // Skip if no tracking data.
if (!tracker->get_has_tracking_data()) {
- // If tracking-state determines visibility then hide the node.
- if (show_when_tracked) {
- set_visible(false);
- }
return;
}
// Get the world and skeleton scale.
- const float ws = xr_server->get_world_scale();
const float ss = skeleton->get_motion_scale();
// Read the relevant tracking data. This applies the skeleton motion scale to
@@ -296,12 +279,8 @@ void XRBodyModifier3D::_process_modification() {
}
}
- // Handle root joint not tracked.
+ // Skip if root joint not tracked.
if (!has_valid_data[XRBodyTracker::JOINT_ROOT]) {
- // If tracking-state determines visibility then hide the node.
- if (show_when_tracked) {
- set_visible(false);
- }
return;
}
@@ -331,19 +310,9 @@ void XRBodyModifier3D::_process_modification() {
// Always update the bone rotation.
skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis));
}
-
- // Transform to the tracking data root pose. This also applies the XR world-scale to allow
- // scaling the avatars mesh and skeleton appropriately (if they are child nodes).
- set_transform(
- transforms[XRBodyTracker::JOINT_ROOT] * ws);
-
- // If tracking-state determines visibility then show the node.
- if (show_when_tracked) {
- set_visible(true);
- }
}
-void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker) {
+void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type) {
if (tracker_name == p_tracker_name) {
_get_joint_data();
}
@@ -358,18 +327,18 @@ void XRBodyModifier3D::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->connect("body_tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->connect("body_tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->connect("body_tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed).bind(Ref<XRBodyTracker>()));
+ xr_server->connect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->connect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->connect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
}
_get_joint_data();
} break;
case NOTIFICATION_EXIT_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->disconnect("body_tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->disconnect("body_tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->disconnect("body_tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed).bind(Ref<XRBodyTracker>()));
+ xr_server->disconnect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
}
for (int i = 0; i < XRBodyTracker::JOINT_MAX; i++) {
joints[i].bone = -1;
diff --git a/scene/3d/xr_body_modifier_3d.h b/scene/3d/xr_body_modifier_3d.h
index 03b1c07d53..78d70146ee 100644
--- a/scene/3d/xr_body_modifier_3d.h
+++ b/scene/3d/xr_body_modifier_3d.h
@@ -66,9 +66,6 @@ public:
void set_bone_update(BoneUpdate p_bone_update);
BoneUpdate get_bone_update() const;
- void set_show_when_tracked(bool p_show_when_tracked);
- bool get_show_when_tracked() const;
-
void _notification(int p_what);
protected:
@@ -83,14 +80,13 @@ private:
int parent_joint = -1;
};
- StringName tracker_name = "/user/body";
+ StringName tracker_name = "/user/body_tracker";
BitField<BodyUpdate> body_update = BODY_UPDATE_UPPER_BODY | BODY_UPDATE_LOWER_BODY | BODY_UPDATE_HANDS;
BoneUpdate bone_update = BONE_UPDATE_FULL;
- bool show_when_tracked = true;
JointData joints[XRBodyTracker::JOINT_MAX];
void _get_joint_data();
- void _tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker);
+ void _tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type);
};
VARIANT_BITFIELD_CAST(XRBodyModifier3D::BodyUpdate)
diff --git a/scene/3d/xr_face_modifier_3d.cpp b/scene/3d/xr_face_modifier_3d.cpp
index be92a587b0..43cef95fb9 100644
--- a/scene/3d/xr_face_modifier_3d.cpp
+++ b/scene/3d/xr_face_modifier_3d.cpp
@@ -495,7 +495,7 @@ static void remove_driven_unified_blend_shapes(RBMap<int, int> &p_blend_mapping)
void XRFaceModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_face_tracker", "tracker_name"), &XRFaceModifier3D::set_face_tracker);
ClassDB::bind_method(D_METHOD("get_face_tracker"), &XRFaceModifier3D::get_face_tracker);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/head"), "set_face_tracker", "get_face_tracker");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "face_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/face_tracker"), "set_face_tracker", "get_face_tracker");
ClassDB::bind_method(D_METHOD("set_target", "target"), &XRFaceModifier3D::set_target);
ClassDB::bind_method(D_METHOD("get_target"), &XRFaceModifier3D::get_target);
@@ -576,8 +576,8 @@ void XRFaceModifier3D::_update_face_blends() const {
}
// Get the face tracker.
- const Ref<XRFaceTracker> p = xr_server->get_face_tracker(tracker_name);
- if (!p.is_valid()) {
+ const Ref<XRFaceTracker> tracker = xr_server->get_tracker(tracker_name);
+ if (!tracker.is_valid()) {
return;
}
@@ -588,7 +588,7 @@ void XRFaceModifier3D::_update_face_blends() const {
}
// Get the blend weights.
- const PackedFloat32Array weights = p->get_blend_shapes();
+ const PackedFloat32Array weights = tracker->get_blend_shapes();
// Apply all the face blend weights to the mesh.
for (const KeyValue<int, int> &it : blend_mapping) {
diff --git a/scene/3d/xr_face_modifier_3d.h b/scene/3d/xr_face_modifier_3d.h
index 147c374e95..e5e59afe1d 100644
--- a/scene/3d/xr_face_modifier_3d.h
+++ b/scene/3d/xr_face_modifier_3d.h
@@ -47,7 +47,7 @@ class XRFaceModifier3D : public Node3D {
GDCLASS(XRFaceModifier3D, Node3D);
private:
- StringName tracker_name = "/user/head";
+ StringName tracker_name = "/user/face_tracker";
NodePath target;
// Map from XRFaceTracker blend shape index to mesh blend shape index.
diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp
index 7fecb53008..baaa9eee48 100644
--- a/scene/3d/xr_hand_modifier_3d.cpp
+++ b/scene/3d/xr_hand_modifier_3d.cpp
@@ -40,7 +40,7 @@ void XRHandModifier3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &XRHandModifier3D::set_bone_update);
ClassDB::bind_method(D_METHOD("get_bone_update"), &XRHandModifier3D::get_bone_update);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/left,/user/right"), "set_hand_tracker", "get_hand_tracker");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "hand_tracker", PROPERTY_HINT_ENUM_SUGGESTION, "/user/hand_tracker/left,/user/hand_tracker/right"), "set_hand_tracker", "get_hand_tracker");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update");
BIND_ENUM_CONSTANT(BONE_UPDATE_FULL);
@@ -70,6 +70,11 @@ void XRHandModifier3D::_get_joint_data() {
return;
}
+ if (has_stored_previous_transforms) {
+ previous_relative_transforms.clear();
+ has_stored_previous_transforms = false;
+ }
+
// Table of bone names for different rig types.
static const String bone_names[XRHandTracker::HAND_JOINT_MAX] = {
"Palm",
@@ -111,22 +116,30 @@ void XRHandModifier3D::_get_joint_data() {
joints[i].parent_joint = -1;
}
- Skeleton3D *skeleton = get_skeleton();
+ const Skeleton3D *skeleton = get_skeleton();
if (!skeleton) {
return;
}
- XRServer *xr_server = XRServer::get_singleton();
+ const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
- Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name);
+ const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
- XRHandTracker::Hand hand = tracker->get_hand();
+ // Verify we have a left or right hand tracker.
+ const XRPositionalTracker::TrackerHand tracker_hand = tracker->get_tracker_hand();
+ if (tracker_hand != XRPositionalTracker::TRACKER_HAND_LEFT &&
+ tracker_hand != XRPositionalTracker::TRACKER_HAND_RIGHT) {
+ return;
+ }
+
+ // Get the hand index (0 = left, 1 = right).
+ const int hand = tracker_hand == XRPositionalTracker::TRACKER_HAND_LEFT ? 0 : 1;
// Find the skeleton-bones associated with each joint.
int bones[XRHandTracker::HAND_JOINT_MAX];
@@ -176,18 +189,34 @@ void XRHandModifier3D::_process_modification() {
return;
}
- XRServer *xr_server = XRServer::get_singleton();
+ const XRServer *xr_server = XRServer::get_singleton();
if (!xr_server) {
return;
}
- Ref<XRHandTracker> tracker = xr_server->get_hand_tracker(tracker_name);
+ const Ref<XRHandTracker> tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
return;
}
+ // Skip if no tracking data
+ if (!tracker->get_has_tracking_data()) {
+ if (!has_stored_previous_transforms) {
+ return;
+ }
+
+ // Apply previous relative transforms if they are stored.
+ for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
+ if (bone_update == BONE_UPDATE_FULL) {
+ skeleton->set_bone_pose_position(joints[joint].bone, previous_relative_transforms[joint].origin);
+ }
+
+ skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(previous_relative_transforms[joint].basis));
+ }
+ return;
+ }
+
// Get the world and skeleton scale.
- const float ws = xr_server->get_world_scale();
const float ss = skeleton->get_motion_scale();
// We cache our transforms so we can quickly calculate local transforms.
@@ -195,55 +224,51 @@ void XRHandModifier3D::_process_modification() {
Transform3D transforms[XRHandTracker::HAND_JOINT_MAX];
Transform3D inv_transforms[XRHandTracker::HAND_JOINT_MAX];
- if (tracker->get_has_tracking_data()) {
- for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
- BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint);
- has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID);
+ for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
+ BitField<XRHandTracker::HandJointFlags> flags = tracker->get_hand_joint_flags((XRHandTracker::HandJoint)joint);
+ has_valid_data[joint] = flags.has_flag(XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID);
- if (has_valid_data[joint]) {
- transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint);
- transforms[joint].origin *= ss;
- inv_transforms[joint] = transforms[joint].inverse();
- }
+ if (has_valid_data[joint]) {
+ transforms[joint] = tracker->get_hand_joint_transform((XRHandTracker::HandJoint)joint);
+ transforms[joint].origin *= ss;
+ inv_transforms[joint] = transforms[joint].inverse();
}
+ }
- if (has_valid_data[XRHandTracker::HAND_JOINT_PALM]) {
- for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
- // Get the skeleton bone (skip if none).
- const int bone = joints[joint].bone;
- if (bone == -1) {
- continue;
- }
-
- // Calculate the relative relationship to the parent bone joint.
- const int parent_joint = joints[joint].parent_joint;
- const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint];
-
- // Update the bone position if enabled by update mode.
- if (bone_update == BONE_UPDATE_FULL) {
- skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin);
- }
-
- // Always update the bone rotation.
- skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis));
- }
+ // Skip if palm has no tracking data
+ if (!has_valid_data[XRHandTracker::HAND_JOINT_PALM]) {
+ return;
+ }
- // Transform to the skeleton pose. This uses the HAND_JOINT_PALM position without skeleton-scaling, as it
- // must be positioned to match the physical hand location. It is scaled with the world space to match
- // the scaling done to the camera and eyes.
- set_transform(
- tracker->get_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM) * ws);
+ if (!has_stored_previous_transforms) {
+ previous_relative_transforms.resize(XRHandTracker::HAND_JOINT_MAX);
+ has_stored_previous_transforms = true;
+ }
+ Transform3D *previous_relative_transforms_ptr = previous_relative_transforms.ptrw();
- set_visible(true);
- } else {
- set_visible(false);
+ for (int joint = 0; joint < XRHandTracker::HAND_JOINT_MAX; joint++) {
+ // Get the skeleton bone (skip if none).
+ const int bone = joints[joint].bone;
+ if (bone == -1) {
+ continue;
}
- } else {
- set_visible(false);
+
+ // Calculate the relative relationship to the parent bone joint.
+ const int parent_joint = joints[joint].parent_joint;
+ const Transform3D relative_transform = inv_transforms[parent_joint] * transforms[joint];
+ previous_relative_transforms_ptr[joint] = relative_transform;
+
+ // Update the bone position if enabled by update mode.
+ if (bone_update == BONE_UPDATE_FULL) {
+ skeleton->set_bone_pose_position(joints[joint].bone, relative_transform.origin);
+ }
+
+ // Always update the bone rotation.
+ skeleton->set_bone_pose_rotation(joints[joint].bone, Quaternion(relative_transform.basis));
}
}
-void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker) {
+void XRHandModifier3D::_tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type) {
if (tracker_name == p_tracker_name) {
_get_joint_data();
}
@@ -258,9 +283,9 @@ void XRHandModifier3D::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->connect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->connect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->connect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>()));
+ xr_server->connect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->connect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->connect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed));
}
_get_joint_data();
@@ -268,9 +293,9 @@ void XRHandModifier3D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->disconnect("hand_tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->disconnect("hand_tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
- xr_server->disconnect("hand_tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed).bind(Ref<XRHandTracker>()));
+ xr_server->disconnect("tracker_added", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_updated", callable_mp(this, &XRHandModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_removed", callable_mp(this, &XRHandModifier3D::_tracker_changed));
}
for (int i = 0; i < XRHandTracker::HAND_JOINT_MAX; i++) {
diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h
index 9f7ce45c9d..3d78f32b64 100644
--- a/scene/3d/xr_hand_modifier_3d.h
+++ b/scene/3d/xr_hand_modifier_3d.h
@@ -69,12 +69,15 @@ private:
int parent_joint = -1;
};
- StringName tracker_name = "/user/left";
+ StringName tracker_name = "/user/hand_tracker/left";
BoneUpdate bone_update = BONE_UPDATE_FULL;
JointData joints[XRHandTracker::HAND_JOINT_MAX];
+ bool has_stored_previous_transforms = false;
+ Vector<Transform3D> previous_relative_transforms;
+
void _get_joint_data();
- void _tracker_changed(StringName p_tracker_name, const Ref<XRHandTracker> &p_tracker);
+ void _tracker_changed(StringName p_tracker_name, XRServer::TrackerType p_tracker_type);
};
VARIANT_ENUM_CAST(XRHandModifier3D::BoneUpdate)
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 12a9f50ed7..3f4b962641 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -80,10 +80,11 @@ PackedStringArray XRCamera3D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
- // must be child node of XROrigin3D!
- XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
- if (origin == nullptr) {
- warnings.push_back(RTR("XRCamera3D must have an XROrigin3D node as its parent."));
+ // Warn if the node has a parent which isn't an XROrigin3D!
+ Node *parent = get_parent();
+ XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent);
+ if (parent && origin == nullptr) {
+ warnings.push_back(RTR("XRCamera3D may not function as expected without an XROrigin3D node as its parent."));
};
}
@@ -229,6 +230,10 @@ void XRNode3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pose_name"), &XRNode3D::get_pose_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "pose", PROPERTY_HINT_ENUM_SUGGESTION), "set_pose_name", "get_pose_name");
+ ClassDB::bind_method(D_METHOD("set_show_when_tracked", "show"), &XRNode3D::set_show_when_tracked);
+ ClassDB::bind_method(D_METHOD("get_show_when_tracked"), &XRNode3D::get_show_when_tracked);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_when_tracked"), "set_show_when_tracked", "get_show_when_tracked");
+
ClassDB::bind_method(D_METHOD("get_is_active"), &XRNode3D::get_is_active);
ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRNode3D::get_has_tracking_data);
ClassDB::bind_method(D_METHOD("get_pose"), &XRNode3D::get_pose);
@@ -296,6 +301,14 @@ StringName XRNode3D::get_pose_name() const {
return pose_name;
}
+void XRNode3D::set_show_when_tracked(bool p_show) {
+ show_when_tracked = p_show;
+}
+
+bool XRNode3D::get_show_when_tracked() const {
+ return show_when_tracked;
+}
+
bool XRNode3D::get_is_active() const {
if (tracker.is_null()) {
return false;
@@ -402,6 +415,11 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
// Handle change of has_tracking_data.
has_tracking_data = p_has_tracking_data;
emit_signal(SNAME("tracking_changed"), has_tracking_data);
+
+ // If configured, show or hide the node based on tracking data.
+ if (show_when_tracked) {
+ set_visible(has_tracking_data);
+ }
}
XRNode3D::XRNode3D() {
@@ -428,11 +446,12 @@ PackedStringArray XRNode3D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
- // must be child node of XROrigin!
- XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
- if (origin == nullptr) {
- warnings.push_back(RTR("XRController3D must have an XROrigin3D node as its parent."));
- }
+ // Warn if the node has a parent which isn't an XROrigin3D!
+ Node *parent = get_parent();
+ XROrigin3D *origin = Object::cast_to<XROrigin3D>(parent);
+ if (parent && origin == nullptr) {
+ warnings.push_back(RTR("XRNode3D may not function as expected without an XROrigin3D node as its parent."));
+ };
if (tracker_name == "") {
warnings.push_back(RTR("No tracker name is set."));
diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h
index bdcccd51ea..a42f6d470f 100644
--- a/scene/3d/xr_nodes.h
+++ b/scene/3d/xr_nodes.h
@@ -79,6 +79,7 @@ private:
StringName tracker_name;
StringName pose_name = "default";
bool has_tracking_data = false;
+ bool show_when_tracked = false;
protected:
Ref<XRPositionalTracker> tracker;
@@ -105,6 +106,9 @@ public:
bool get_is_active() const;
bool get_has_tracking_data() const;
+ void set_show_when_tracked(bool p_show);
+ bool get_show_when_tracked() const;
+
void trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0);
Ref<XRPose> get_pose();
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index 71f9c45eea..6e33a1b27c 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -31,7 +31,6 @@
#include "animation_blend_tree.h"
#include "scene/resources/animation.h"
-#include "scene/scene_string_names.h"
void AnimationNodeAnimation::set_animation(const StringName &p_name) {
animation = p_name;
@@ -194,8 +193,8 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
nti.is_just_looped = is_just_looped;
// 3. Progress for Animation.
- double prev_playback_time = prev_time - start_offset;
- double cur_playback_time = cur_time - start_offset;
+ double prev_playback_time = prev_time + start_offset;
+ double cur_playback_time = cur_time + start_offset;
if (stretch_time_scale) {
double mlt = anim_size / cur_len;
cur_playback_time *= mlt;
@@ -215,7 +214,7 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
} else if (cur_loop_mode == Animation::LOOP_PINGPONG) {
if (!Math::is_zero_approx(anim_size)) {
if (Math::fposmod(cur_playback_time, anim_size * 2.0) >= anim_size) {
- cur_delta = -cur_delta; // Needed for retrieveing discrete keys correctly.
+ cur_delta = -cur_delta; // Needed for retrieving discrete keys correctly.
}
prev_playback_time = Math::pingpong(prev_playback_time, anim_size);
cur_playback_time = Math::pingpong(cur_playback_time, anim_size);
@@ -238,11 +237,11 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
if (process_state->tree && !p_test_only) {
// AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection.
if (p_seek && !p_is_external_seeking && cur_playback_time == 0) {
- process_state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation);
+ process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_started), animation);
}
// Finished.
- if (prev_time - start_offset < anim_size && cur_playback_time >= anim_size) {
- process_state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation);
+ if (prev_time + start_offset < anim_size && cur_playback_time >= anim_size) {
+ process_state->tree->call_deferred(SNAME("emit_signal"), SceneStringName(animation_finished), animation);
}
}
}
@@ -1421,7 +1420,7 @@ AnimationNodeOutput::AnimationNodeOutput() {
void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position) {
ERR_FAIL_COND(nodes.has(p_name));
ERR_FAIL_COND(p_node.is_null());
- ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(p_name == SceneStringName(output));
ERR_FAIL_COND(String(p_name).contains("/"));
Node n;
@@ -1491,7 +1490,7 @@ Vector<StringName> AnimationNodeBlendTree::get_node_connection_array(const Strin
void AnimationNodeBlendTree::remove_node(const StringName &p_name) {
ERR_FAIL_COND(!nodes.has(p_name));
- ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); //can't delete output
+ ERR_FAIL_COND(p_name == SceneStringName(output)); //can't delete output
{
Ref<AnimationNode> node = nodes[p_name].node;
@@ -1520,8 +1519,8 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) {
void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringName &p_new_name) {
ERR_FAIL_COND(!nodes.has(p_name));
ERR_FAIL_COND(nodes.has(p_new_name));
- ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
- ERR_FAIL_COND(p_new_name == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(p_name == SceneStringName(output));
+ ERR_FAIL_COND(p_new_name == SceneStringName(output));
nodes[p_name].node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed));
@@ -1546,7 +1545,7 @@ void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringN
void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) {
ERR_FAIL_COND(!nodes.has(p_output_node));
ERR_FAIL_COND(!nodes.has(p_input_node));
- ERR_FAIL_COND(p_output_node == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(p_output_node == SceneStringName(output));
ERR_FAIL_COND(p_input_node == p_output_node);
Ref<AnimationNode> input = nodes[p_input_node].node;
@@ -1574,7 +1573,7 @@ void AnimationNodeBlendTree::disconnect_node(const StringName &p_node, int p_inp
}
AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const {
- if (!nodes.has(p_output_node) || p_output_node == SceneStringNames::get_singleton()->output) {
+ if (!nodes.has(p_output_node) || p_output_node == SceneStringName(output)) {
return CONNECTION_ERROR_NO_OUTPUT;
}
@@ -1627,8 +1626,8 @@ String AnimationNodeBlendTree::get_caption() const {
}
AnimationNode::NodeTimeInfo AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
- Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node;
- node_state.connections = nodes[SceneStringNames::get_singleton()->output].connections;
+ Ref<AnimationNodeOutput> output = nodes[SceneStringName(output)].node;
+ node_state.connections = nodes[SceneStringName(output)].connections;
ERR_FAIL_COND_V(output.is_null(), NodeTimeInfo());
AnimationMixer::PlaybackInfo pi = p_playback_info;
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 01e1de8f23..d4c6ca3ea0 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -35,7 +35,6 @@
#include "core/config/project_settings.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/animation.h"
-#include "scene/scene_string_names.h"
#include "servers/audio/audio_stream.h"
#ifndef _3D_DISABLED
@@ -301,7 +300,7 @@ Error AnimationMixer::add_animation_library(const StringName &p_name, const Ref<
ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added).bind(p_name));
ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed).bind(p_name));
ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed).bind(p_name));
- ald.library->connect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed));
+ ald.library->connect(SceneStringName(animation_changed), callable_mp(this, &AnimationMixer::_animation_changed));
_animation_set_cache_update();
@@ -325,7 +324,7 @@ void AnimationMixer::remove_animation_library(const StringName &p_name) {
animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added));
animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed));
animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed));
- animation_libraries[at_pos].library->disconnect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed));
+ animation_libraries[at_pos].library->disconnect(SceneStringName(animation_changed), callable_mp(this, &AnimationMixer::_animation_changed));
animation_libraries.remove_at(at_pos);
_animation_set_cache_update();
@@ -628,9 +627,9 @@ bool AnimationMixer::_update_caches() {
#endif
Ref<Animation> reset_anim;
- bool has_reset_anim = has_animation(SceneStringNames::get_singleton()->RESET);
+ bool has_reset_anim = has_animation(SceneStringName(RESET));
if (has_reset_anim) {
- reset_anim = get_animation(SceneStringNames::get_singleton()->RESET);
+ reset_anim = get_animation(SceneStringName(RESET));
}
for (const StringName &E : sname) {
Ref<Animation> anim = get_animation(E);
@@ -698,9 +697,15 @@ bool AnimationMixer::_update_caches() {
if (has_reset_anim) {
int rt = reset_anim->find_track(path, track_src_type);
if (rt >= 0) {
- track_value->init_use_continuous = track_value->init_use_continuous || (reset_anim->value_track_get_update_mode(rt) != Animation::UPDATE_DISCRETE); // Take precedence Force Continuous.
- if (reset_anim->track_get_key_count(rt) > 0) {
- track_value->init_value = track_src_type == Animation::TYPE_VALUE ? reset_anim->track_get_key_value(rt, 0) : (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
+ if (track_src_type == Animation::TYPE_VALUE) {
+ track_value->init_use_continuous = track_value->init_use_continuous || (reset_anim->value_track_get_update_mode(rt) != Animation::UPDATE_DISCRETE); // Take precedence Force Continuous.
+ if (reset_anim->track_get_key_count(rt) > 0) {
+ track_value->init_value = reset_anim->track_get_key_value(rt, 0);
+ }
+ } else {
+ if (reset_anim->track_get_key_count(rt) > 0) {
+ track_value->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
+ }
}
}
}
@@ -875,7 +880,7 @@ bool AnimationMixer::_update_caches() {
if (track_value->init_value.is_string() && anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE) {
WARN_PRINT_ONCE_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' blends String types. This is an experimental algorithm.");
}
- track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
+ track_value->is_using_angle = track_value->is_using_angle || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
}
if (check_angle_interpolation && (was_using_angle != track_value->is_using_angle)) {
WARN_PRINT_ED(mixer_name + ": '" + String(E) + "', Value Track: '" + String(path) + "' has different interpolation types for rotation between some animations which may be blended together. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
@@ -1611,7 +1616,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
if (seeked) {
// Seek.
- int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true);
+ int idx = a->track_find_key(i, time, Animation::FIND_MODE_NEAREST, true);
if (idx < 0) {
continue;
}
@@ -1624,6 +1629,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
double at_anim_pos = 0.0;
switch (anim->get_loop_mode()) {
case Animation::LOOP_NONE: {
+ if (!is_external_seeking && ((!backward && time >= pos + (double)anim->get_length()) || (backward && time <= pos))) {
+ continue; // Do nothing if current time is outside of length when started.
+ }
at_anim_pos = MIN((double)anim->get_length(), time - pos); // Seek to end.
} break;
case Animation::LOOP_LINEAR: {
@@ -1635,7 +1643,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
default:
break;
}
- if (player2->is_playing()) {
+ if (player2->is_playing() || !is_external_seeking) {
player2->seek(at_anim_pos, false, p_update_only);
player2->play(anim_name);
t->playing = true;
@@ -1917,7 +1925,7 @@ bool AnimationMixer::is_reset_on_save_enabled() const {
}
bool AnimationMixer::can_apply_reset() const {
- return has_animation(SceneStringNames::get_singleton()->RESET);
+ return has_animation(SceneStringName(RESET));
}
void AnimationMixer::_build_backup_track_cache() {
@@ -2004,7 +2012,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() {
Ref<AnimatedValuesBackup> backup;
backup.instantiate();
- Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation;
+ Ref<Animation> reset_anim = animation_set[SceneStringName(RESET)].animation;
ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>());
_blend_init();
@@ -2013,7 +2021,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() {
pi.delta = 0;
pi.seeked = true;
pi.weight = 1.0;
- make_animation_instance(SceneStringNames::get_singleton()->RESET, pi);
+ make_animation_instance(SceneStringName(RESET), pi);
_build_backup_track_cache();
backup->set_data(track_cache);
@@ -2025,7 +2033,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() {
void AnimationMixer::reset() {
ERR_FAIL_COND(!can_apply_reset());
- Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation;
+ Ref<Animation> reset_anim = animation_set[SceneStringName(RESET)].animation;
ERR_FAIL_COND(reset_anim.is_null());
Node *root_node_object = get_node_or_null(root_node);
@@ -2035,11 +2043,11 @@ void AnimationMixer::reset() {
root_node_object->add_child(aux_player);
Ref<AnimationLibrary> al;
al.instantiate();
- al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim);
+ al->add_animation(SceneStringName(RESET), reset_anim);
aux_player->set_reset_on_save_enabled(false);
aux_player->set_root_node(aux_player->get_path_to(root_node_object));
aux_player->add_animation_library("", al);
- aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET);
+ aux_player->set_assigned_animation(SceneStringName(RESET));
aux_player->seek(0.0f, true);
aux_player->queue_free();
}
@@ -2059,7 +2067,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
}
ERR_FAIL_COND_V(!can_apply_reset(), Ref<AnimatedValuesBackup>());
- Ref<Animation> reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation;
+ Ref<Animation> reset_anim = animation_set[SceneStringName(RESET)].animation;
ERR_FAIL_COND_V(reset_anim.is_null(), Ref<AnimatedValuesBackup>());
Ref<AnimatedValuesBackup> backup_current = make_backup();
@@ -2084,7 +2092,7 @@ Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
ERR_FAIL_COND(!active);
ERR_FAIL_COND(!has_animation(p_name));
- ERR_FAIL_COND(Math::is_zero_approx(p_duration));
+ ERR_FAIL_COND(p_duration <= 0);
Ref<Animation> reference_animation = get_animation(p_name);
if (!cache_valid) {
@@ -2277,7 +2285,7 @@ void AnimationMixer::_bind_methods() {
}
AnimationMixer::AnimationMixer() {
- root_node = SceneStringNames::get_singleton()->path_pp;
+ root_node = SceneStringName(path_pp);
}
AnimationMixer::~AnimationMixer() {
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index f5bef899da..87574a66ed 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -350,18 +350,18 @@ float AnimationNodeStateMachinePlayback::get_fading_pos() const {
void AnimationNodeStateMachinePlayback::_clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) {
List<AnimationNode::ChildNode> child_nodes;
p_state_machine->get_child_nodes(&child_nodes);
- for (int i = 0; i < child_nodes.size(); i++) {
- Ref<AnimationNodeStateMachine> anodesm = child_nodes[i].node;
+ for (const AnimationNode::ChildNode &child_node : child_nodes) {
+ Ref<AnimationNodeStateMachine> anodesm = child_node.node;
if (anodesm.is_valid() && anodesm->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
- Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + child_nodes[i].name + "/playback");
+ Ref<AnimationNodeStateMachinePlayback> playback = p_tree->get(base_path + child_node.name + "/playback");
ERR_FAIL_COND(!playback.is_valid());
- playback->_set_base_path(base_path + child_nodes[i].name + "/");
+ playback->_set_base_path(base_path + child_node.name + "/");
if (p_test_only) {
playback = playback->duplicate();
}
playback->path.clear();
playback->_clear_path_children(p_tree, anodesm.ptr(), p_test_only);
- if (current != child_nodes[i].name) {
+ if (current != child_node.name) {
playback->_start(anodesm.ptr()); // Can restart.
}
}
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index d46470282f..435776843c 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -32,7 +32,6 @@
#include "animation_player.compat.inc"
#include "core/config/engine.h"
-#include "scene/scene_string_names.h"
bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
@@ -41,7 +40,7 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) {
} else if (name.begins_with("next/")) {
String which = name.get_slicec('/', 1);
animation_set_next(which, p_value);
- } else if (p_name == SceneStringNames::get_singleton()->blend_times) {
+ } else if (p_name == SceneStringName(blend_times)) {
Array array = p_value;
int len = array.size();
ERR_FAIL_COND_V(len % 3, false);
@@ -77,7 +76,7 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const {
String which = name.get_slicec('/', 1);
r_ret = animation_get_next(which);
- } else if (name == "blend_times") {
+ } else if (p_name == SceneStringName(blend_times)) {
Vector<BlendKey> keys;
for (const KeyValue<BlendKey, double> &E : blend_times) {
keys.ordered_insert(E.key);
@@ -125,6 +124,8 @@ void AnimationPlayer::_validate_property(PropertyInfo &p_property) const {
}
p_property.hint_string = hint;
+ } else if (!auto_capture && p_property.name.begins_with("playback_auto_capture_")) {
+ p_property.usage = PROPERTY_USAGE_NONE;
}
}
@@ -149,7 +150,7 @@ void AnimationPlayer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (!Engine::get_singleton()->is_editor_hint() && animation_set.has(autoplay)) {
- set_active(true);
+ set_active(active);
play(autoplay);
_check_immediately_after_start();
}
@@ -324,14 +325,14 @@ void AnimationPlayer::_blend_post_process() {
String new_name = playback.assigned;
playback_queue.pop_front();
if (end_notify) {
- emit_signal(SceneStringNames::get_singleton()->animation_changed, old, new_name);
+ emit_signal(SceneStringName(animation_changed), old, new_name);
}
} else {
_clear_caches();
playing = false;
_set_process(false);
if (end_notify) {
- emit_signal(SceneStringNames::get_singleton()->animation_finished, playback.assigned);
+ emit_signal(SceneStringName(animation_finished), playback.assigned);
if (movie_quit_on_finish && OS::get_singleton()->has_feature("movie")) {
print_line(vformat("Movie Maker mode is enabled. Quitting on animation finish as requested by: %s", get_path()));
get_tree()->quit();
@@ -370,73 +371,21 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b
play(p_name, p_custom_blend, -1, true);
}
-void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
- StringName name = p_name;
- if (name == StringName()) {
- name = playback.assigned;
- }
-
- if (signbit(p_duration)) {
- double max_dur = 0;
- Ref<Animation> anim = get_animation(name);
- if (anim.is_valid()) {
- double current_pos = playback.current.pos;
- if (playback.assigned != name) {
- current_pos = p_from_end ? anim->get_length() : 0;
- }
- for (int i = 0; i < anim->get_track_count(); i++) {
- if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
- continue;
- }
- if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
- continue;
- }
- if (anim->track_get_key_count(i) == 0) {
- continue;
- }
- max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos);
- }
- }
- p_duration = max_dur;
+void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
+ if (auto_capture) {
+ play_with_capture(p_name, auto_capture_duration, p_custom_blend, p_custom_scale, p_from_end, auto_capture_transition_type, auto_capture_ease_type);
+ } else {
+ _play(p_name, p_custom_blend, p_custom_scale, p_from_end);
}
-
- capture(name, p_duration, p_trans_type, p_ease_type);
- play(name, p_custom_blend, p_custom_scale, p_from_end);
}
-void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
+void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
StringName name = p_name;
if (name == StringName()) {
name = playback.assigned;
}
-#ifdef TOOLS_ENABLED
- if (!Engine::get_singleton()->is_editor_hint()) {
- bool warn_enabled = false;
- if (capture_cache.animation.is_null()) {
- Ref<Animation> anim = get_animation(name);
- if (anim.is_valid()) {
- for (int i = 0; i < anim->get_track_count(); i++) {
- if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
- continue;
- }
- if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
- continue;
- }
- if (anim->track_get_key_count(i) == 0) {
- continue;
- }
- warn_enabled = true;
- }
- }
- }
- if (warn_enabled) {
- WARN_PRINT_ONCE_ED("Capture track found. If you want to interpolate animation with captured frame, you can use play_with_capture() instead of play().");
- }
- }
-#endif
-
ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
Playback &c = playback;
@@ -513,7 +462,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
_set_process(true); // Always process when starting an animation.
playing = true;
- emit_signal(SceneStringNames::get_singleton()->animation_started, c.assigned);
+ emit_signal(SceneStringName(animation_started), c.assigned);
if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) {
return; // No next in this case.
@@ -525,6 +474,47 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
}
}
+void AnimationPlayer::_capture(const StringName &p_name, bool p_from_end, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
+ StringName name = p_name;
+ if (name == StringName()) {
+ name = playback.assigned;
+ }
+
+ Ref<Animation> anim = get_animation(name);
+ if (anim.is_null() || !anim->is_capture_included()) {
+ return;
+ }
+ if (signbit(p_duration)) {
+ double max_dur = 0;
+ double current_pos = playback.current.pos;
+ if (playback.assigned != name) {
+ current_pos = p_from_end ? anim->get_length() : 0;
+ }
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
+ continue;
+ }
+ if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
+ continue;
+ }
+ if (anim->track_get_key_count(i) == 0) {
+ continue;
+ }
+ max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos);
+ }
+ p_duration = max_dur;
+ }
+ if (Math::is_zero_approx(p_duration)) {
+ return;
+ }
+ capture(name, p_duration, p_trans_type, p_ease_type);
+}
+
+void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
+ _capture(p_name, p_from_end, p_duration, p_trans_type, p_ease_type);
+ _play(p_name, p_custom_blend, p_custom_scale, p_from_end);
+}
+
bool AnimationPlayer::is_playing() const {
return playing;
}
@@ -725,6 +715,39 @@ double AnimationPlayer::get_blend_time(const StringName &p_animation1, const Str
}
}
+void AnimationPlayer::set_auto_capture(bool p_auto_capture) {
+ auto_capture = p_auto_capture;
+ notify_property_list_changed();
+}
+
+bool AnimationPlayer::is_auto_capture() const {
+ return auto_capture;
+}
+
+void AnimationPlayer::set_auto_capture_duration(double p_auto_capture_duration) {
+ auto_capture_duration = p_auto_capture_duration;
+}
+
+double AnimationPlayer::get_auto_capture_duration() const {
+ return auto_capture_duration;
+}
+
+void AnimationPlayer::set_auto_capture_transition_type(Tween::TransitionType p_auto_capture_transition_type) {
+ auto_capture_transition_type = p_auto_capture_transition_type;
+}
+
+Tween::TransitionType AnimationPlayer::get_auto_capture_transition_type() const {
+ return auto_capture_transition_type;
+}
+
+void AnimationPlayer::set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type) {
+ auto_capture_ease_type = p_auto_capture_ease_type;
+}
+
+Tween::EaseType AnimationPlayer::get_auto_capture_ease_type() const {
+ return auto_capture_ease_type;
+}
+
#ifdef TOOLS_ENABLED
void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
const String pf = p_function;
@@ -815,9 +838,18 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_default_blend_time", "sec"), &AnimationPlayer::set_default_blend_time);
ClassDB::bind_method(D_METHOD("get_default_blend_time"), &AnimationPlayer::get_default_blend_time);
+ ClassDB::bind_method(D_METHOD("set_auto_capture", "auto_capture"), &AnimationPlayer::set_auto_capture);
+ ClassDB::bind_method(D_METHOD("is_auto_capture"), &AnimationPlayer::is_auto_capture);
+ ClassDB::bind_method(D_METHOD("set_auto_capture_duration", "auto_capture_duration"), &AnimationPlayer::set_auto_capture_duration);
+ ClassDB::bind_method(D_METHOD("get_auto_capture_duration"), &AnimationPlayer::get_auto_capture_duration);
+ ClassDB::bind_method(D_METHOD("set_auto_capture_transition_type", "auto_capture_transition_type"), &AnimationPlayer::set_auto_capture_transition_type);
+ ClassDB::bind_method(D_METHOD("get_auto_capture_transition_type"), &AnimationPlayer::get_auto_capture_transition_type);
+ ClassDB::bind_method(D_METHOD("set_auto_capture_ease_type", "auto_capture_ease_type"), &AnimationPlayer::set_auto_capture_ease_type);
+ ClassDB::bind_method(D_METHOD("get_auto_capture_ease_type"), &AnimationPlayer::get_auto_capture_ease_type);
+
ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(StringName()), DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
+ ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(StringName()), DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause);
ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false));
ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing);
@@ -855,6 +887,10 @@ void AnimationPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_position");
ADD_GROUP("Playback Options", "playback_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_auto_capture"), "set_auto_capture", "is_auto_capture");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_auto_capture_duration", PROPERTY_HINT_NONE, "suffix:s"), "set_auto_capture_duration", "get_auto_capture_duration");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Expo,Elastic,Cubic,Circ,Bounce,Back,Spring"), "set_auto_capture_transition_type", "get_auto_capture_transition_type");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_ease_type", PROPERTY_HINT_ENUM, "In,Out,InOut,OutIn"), "set_auto_capture_ease_type", "get_auto_capture_ease_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-4,4,0.001,or_less,or_greater"), "set_speed_scale", "get_speed_scale");
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 13e1e37ca9..f270f32193 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -56,6 +56,12 @@ private:
float speed_scale = 1.0;
double default_blend_time = 0.0;
+
+ bool auto_capture = true;
+ double auto_capture_duration = -1.0;
+ Tween::TransitionType auto_capture_transition_type = Tween::TRANS_LINEAR;
+ Tween::EaseType auto_capture_ease_type = Tween::EASE_IN;
+
bool is_stopping = false;
struct PlaybackData {
@@ -108,6 +114,8 @@ private:
bool reset_on_save = true;
bool movie_quit_on_finish = false;
+ void _play(const StringName &p_name, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
+ void _capture(const StringName &p_name, bool p_from_end = false, double p_duration = -1.0, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current = false);
void _blend_playback_data(double p_delta, bool p_started);
void _stop_internal(bool p_reset, bool p_keep_state);
@@ -158,9 +166,18 @@ public:
void set_default_blend_time(double p_default);
double get_default_blend_time() const;
+ void set_auto_capture(bool p_auto_capture);
+ bool is_auto_capture() const;
+ void set_auto_capture_duration(double p_auto_capture_duration);
+ double get_auto_capture_duration() const;
+ void set_auto_capture_transition_type(Tween::TransitionType p_auto_capture_transition_type);
+ Tween::TransitionType get_auto_capture_transition_type() const;
+ void set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type);
+ Tween::EaseType get_auto_capture_ease_type() const;
+
void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1);
- void play_with_capture(const StringName &p_name, double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
+ void play_with_capture(const StringName &p_name = StringName(), double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
void queue(const StringName &p_name);
Vector<String> get_queue();
void clear_queue();
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 4880a0f6ed..d4061ab167 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -34,7 +34,6 @@
#include "animation_blend_tree.h"
#include "core/config/engine.h"
#include "scene/animation/animation_player.h"
-#include "scene/scene_string_names.h"
void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
Array parameters;
@@ -627,7 +626,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
for (int i = 0; i < p_track_count; i++) {
src_blendsw[i] = 1.0; // By default all go to 1 for the root input.
}
- root_animation_node->node_state.base_path = SceneStringNames::get_singleton()->parameters_base_path;
+ root_animation_node->node_state.base_path = SceneStringName(parameters_base_path);
root_animation_node->node_state.parent = nullptr;
}
@@ -788,7 +787,7 @@ void AnimationTree::_update_properties() {
input_activity_map_get.clear();
if (root_animation_node.is_valid()) {
- _update_properties_for_node(SceneStringNames::get_singleton()->parameters_base_path, root_animation_node);
+ _update_properties_for_node(SceneStringName(parameters_base_path), root_animation_node);
}
properties_dirty = false;
@@ -810,7 +809,7 @@ void AnimationTree::_notification(int p_what) {
void AnimationTree::set_animation_player(const NodePath &p_path) {
animation_player = p_path;
if (p_path.is_empty()) {
- set_root_node(SceneStringNames::get_singleton()->path_pp);
+ set_root_node(SceneStringName(path_pp));
while (animation_libraries.size()) {
remove_animation_library(animation_libraries[0].name);
}
@@ -854,10 +853,10 @@ void AnimationTree::_setup_animation_player() {
}
List<StringName> list;
player->get_animation_library_list(&list);
- for (int i = 0; i < list.size(); i++) {
- Ref<AnimationLibrary> lib = player->get_animation_library(list[i]);
+ for (const StringName &E : list) {
+ Ref<AnimationLibrary> lib = player->get_animation_library(E);
if (lib.is_valid()) {
- add_animation_library(list[i], lib);
+ add_animation_library(E, lib);
}
}
}
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 56c582e2d7..6a61e8693d 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -351,7 +351,7 @@ bool Tween::step(double p_delta) {
if (loops_done == loops) {
running = false;
dead = true;
- emit_signal(SNAME("finished"));
+ emit_signal(SceneStringName(finished));
break;
} else {
emit_signal(SNAME("loop_finished"), loops_done);
@@ -614,7 +614,7 @@ bool PropertyTweener::step(double &r_delta) {
target_instance->set_indexed(property, final_val);
finished = true;
r_delta = elapsed_time - delay - duration;
- emit_signal(SNAME("finished"));
+ emit_signal(SceneStringName(finished));
return false;
}
}
@@ -674,7 +674,7 @@ bool IntervalTweener::step(double &r_delta) {
} else {
finished = true;
r_delta = elapsed_time - duration;
- emit_signal(SNAME("finished"));
+ emit_signal(SceneStringName(finished));
return false;
}
}
@@ -717,7 +717,7 @@ bool CallbackTweener::step(double &r_delta) {
finished = true;
r_delta = elapsed_time - delay;
- emit_signal(SNAME("finished"));
+ emit_signal(SceneStringName(finished));
return false;
}
@@ -803,7 +803,7 @@ bool MethodTweener::step(double &r_delta) {
} else {
finished = true;
r_delta = elapsed_time - delay - duration;
- emit_signal(SNAME("finished"));
+ emit_signal(SceneStringName(finished));
return false;
}
}
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index dadcfab69f..0c2bd64e84 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -59,6 +59,7 @@ Ref<AudioStream> AudioStreamPlayer::get_stream() const {
}
void AudioStreamPlayer::set_volume_db(float p_volume) {
+ ERR_FAIL_COND_MSG(Math::is_nan(p_volume), "Volume can't be set to NaN.");
internal->volume_db = p_volume;
Vector<AudioFrame> volume_vector = _get_volume_vector();
diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp
index 19b3ec481b..a7b8faaaae 100644
--- a/scene/audio/audio_stream_player_internal.cpp
+++ b/scene/audio/audio_stream_player_internal.cpp
@@ -31,7 +31,6 @@
#include "audio_stream_player_internal.h"
#include "scene/main/node.h"
-#include "scene/scene_string_names.h"
#include "servers/audio/audio_stream.h"
void AudioStreamPlayerInternal::_set_process(bool p_enabled) {
@@ -78,7 +77,7 @@ void AudioStreamPlayerInternal::process() {
_set_process(false);
}
if (!playbacks_to_remove.is_empty()) {
- node->emit_signal(SNAME("finished"));
+ node->emit_signal(SceneStringName(finished));
}
}
@@ -307,14 +306,14 @@ StringName AudioStreamPlayerInternal::get_bus() const {
return bus;
}
}
- return SceneStringNames::get_singleton()->Master;
+ return SceneStringName(Master);
}
AudioStreamPlayerInternal::AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical) {
node = p_node;
play_callable = p_play_callable;
physical = p_physical;
- bus = SceneStringNames::get_singleton()->Master;
+ bus = SceneStringName(Master);
AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp((Object *)node, &Object::notify_property_list_changed));
AudioServer::get_singleton()->connect("bus_renamed", callable_mp((Object *)node, &Object::notify_property_list_changed).unbind(3));
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 19e5693736..07c32eef13 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -421,9 +421,9 @@ void SceneDebuggerObject::_parse_script_properties(Script *p_script, ScriptInsta
void SceneDebuggerObject::serialize(Array &r_arr, int p_max_size) {
Array send_props;
- for (int i = 0; i < properties.size(); i++) {
- const PropertyInfo &pi = properties[i].first;
- Variant &var = properties[i].second;
+ for (SceneDebuggerObject::SceneDebuggerProperty &property : properties) {
+ const PropertyInfo &pi = property.first;
+ Variant &var = property.second;
Ref<Resource> res = var;
@@ -510,7 +510,7 @@ SceneDebuggerTree::SceneDebuggerTree(Node *p_root) {
const StringName &is_visible_sn = SNAME("is_visible");
const StringName &is_visible_in_tree_sn = SNAME("is_visible_in_tree");
while (stack.size()) {
- Node *n = stack[0];
+ Node *n = stack.front()->get();
stack.pop_front();
int count = n->get_child_count();
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp
index 711dd13781..1663a7d602 100644
--- a/scene/gui/aspect_ratio_container.cpp
+++ b/scene/gui/aspect_ratio_container.cpp
@@ -35,16 +35,10 @@
Size2 AspectRatioContainer::get_minimum_size() const {
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible()) {
- continue;
- }
Size2 minsize = c->get_combined_minimum_size();
ms = ms.max(minsize);
}
@@ -107,13 +101,10 @@ void AspectRatioContainer::_notification(int p_what) {
bool rtl = is_layout_rtl();
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
// Temporary fix for editor crash.
TextureRect *trect = Object::cast_to<TextureRect>(c);
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 66b14dc967..01e3cce78b 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -33,7 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "scene/main/window.h"
-#include "scene/scene_string_names.h"
void BaseButton::_unpress_group() {
if (!button_group.is_valid()) {
@@ -135,7 +134,7 @@ void BaseButton::_notification(int p_what) {
void BaseButton::_pressed() {
GDVIRTUAL_CALL(_pressed);
pressed();
- emit_signal(SNAME("pressed"));
+ emit_signal(SceneStringName(pressed));
}
void BaseButton::_toggled(bool p_pressed) {
@@ -162,7 +161,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
status.pressed = !status.pressed;
_unpress_group();
if (button_group.is_valid()) {
- button_group->emit_signal(SNAME("pressed"), this);
+ button_group->emit_signal(SceneStringName(pressed), this);
}
_toggled(status.pressed);
_pressed();
@@ -226,7 +225,7 @@ void BaseButton::set_pressed(bool p_pressed) {
if (p_pressed) {
_unpress_group();
if (button_group.is_valid()) {
- button_group->emit_signal(SNAME("pressed"), this);
+ button_group->emit_signal(SceneStringName(pressed), this);
}
}
_toggled(status.pressed);
@@ -368,7 +367,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
_unpress_group();
if (button_group.is_valid()) {
- button_group->emit_signal(SNAME("pressed"), this);
+ button_group->emit_signal(SceneStringName(pressed), this);
}
_toggled(status.pressed);
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 2728126e64..d8fcbbb883 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -55,11 +55,8 @@ void BoxContainer::_resort() {
HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -109,11 +106,8 @@ void BoxContainer::_resort() {
float error = 0.0; // Keep track of accumulated error in pixels
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -201,11 +195,8 @@ void BoxContainer::_resort() {
}
for (int i = start; i != end; i += delta) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -253,14 +244,7 @@ Size2 BoxContainer::get_minimum_size() const {
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
- if (!c) {
- continue;
- }
- if (c->is_set_as_top_level()) {
- continue;
- }
-
- if (!c->is_visible()) {
+ if (!c || !c->is_visible() || c->is_set_as_top_level()) {
continue;
}
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index b1870eea55..3bf9d79c7f 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -50,6 +50,83 @@ void Button::_set_internal_margin(Side p_side, float p_value) {
void Button::_queue_update_size_cache() {
}
+void Button::_update_theme_item_cache() {
+ Control::_update_theme_item_cache();
+
+ const bool rtl = is_layout_rtl();
+ if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) {
+ theme_cache.max_style_size = theme_cache.normal_mirrored->get_minimum_size();
+ theme_cache.style_margin_left = theme_cache.normal_mirrored->get_margin(SIDE_LEFT);
+ theme_cache.style_margin_right = theme_cache.normal_mirrored->get_margin(SIDE_RIGHT);
+ theme_cache.style_margin_top = theme_cache.normal_mirrored->get_margin(SIDE_TOP);
+ theme_cache.style_margin_bottom = theme_cache.normal_mirrored->get_margin(SIDE_BOTTOM);
+ } else {
+ theme_cache.max_style_size = theme_cache.normal->get_minimum_size();
+ theme_cache.style_margin_left = theme_cache.normal->get_margin(SIDE_LEFT);
+ theme_cache.style_margin_right = theme_cache.normal->get_margin(SIDE_RIGHT);
+ theme_cache.style_margin_top = theme_cache.normal->get_margin(SIDE_TOP);
+ theme_cache.style_margin_bottom = theme_cache.normal->get_margin(SIDE_BOTTOM);
+ }
+ if (has_theme_stylebox("hover_pressed")) {
+ if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_pressed_mirrored->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_pressed_mirrored->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_pressed_mirrored->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_pressed_mirrored->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_pressed_mirrored->get_margin(SIDE_BOTTOM));
+ } else {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_pressed->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_pressed->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_pressed->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_pressed->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_pressed->get_margin(SIDE_BOTTOM));
+ }
+ }
+ if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.pressed_mirrored->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.pressed_mirrored->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.pressed_mirrored->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.pressed_mirrored->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.pressed_mirrored->get_margin(SIDE_BOTTOM));
+ } else {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.pressed->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.pressed->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.pressed->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.pressed->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.pressed->get_margin(SIDE_BOTTOM));
+ }
+ if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_mirrored->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_mirrored->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_mirrored->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_mirrored->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_mirrored->get_margin(SIDE_BOTTOM));
+ } else {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover->get_margin(SIDE_BOTTOM));
+ }
+ if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.disabled_mirrored->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.disabled_mirrored->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.disabled_mirrored->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.disabled_mirrored->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.disabled_mirrored->get_margin(SIDE_BOTTOM));
+ } else {
+ theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.disabled->get_minimum_size());
+ theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.disabled->get_margin(SIDE_LEFT));
+ theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.disabled->get_margin(SIDE_RIGHT));
+ theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.disabled->get_margin(SIDE_TOP));
+ theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.disabled->get_margin(SIDE_BOTTOM));
+ }
+}
+
+Size2 Button::_get_largest_stylebox_size() const {
+ return theme_cache.max_style_size;
+}
+
Ref<StyleBox> Button::_get_current_stylebox() const {
Ref<StyleBox> stylebox = theme_cache.normal;
const bool rtl = is_layout_rtl();
@@ -137,16 +214,13 @@ void Button::_notification(int p_what) {
const RID ci = get_canvas_item();
const Size2 size = get_size();
- const Ref<StyleBox> style = _get_current_stylebox();
- { // Draws the stylebox in the current state.
- if (!flat) {
- style->draw(ci, Rect2(Point2(), size));
- }
+ // Draws the stylebox in the current state.
+ if (!flat) {
+ _get_current_stylebox()->draw(ci, Rect2(Point2(), size));
+ }
- if (has_focus()) {
- Ref<StyleBox> style2 = theme_cache.focus;
- style2->draw(ci, Rect2(Point2(), size));
- }
+ if (has_focus()) {
+ theme_cache.focus->draw(ci, Rect2(Point2(), size));
}
Ref<Texture2D> _icon = icon;
@@ -158,16 +232,11 @@ void Button::_notification(int p_what) {
break;
}
- const float style_margin_left = style->get_margin(SIDE_LEFT);
- const float style_margin_right = style->get_margin(SIDE_RIGHT);
- const float style_margin_top = style->get_margin(SIDE_TOP);
- const float style_margin_bottom = style->get_margin(SIDE_BOTTOM);
-
Size2 drawable_size_remained = size;
{ // The size after the stelybox is stripped.
- drawable_size_remained.width -= style_margin_left + style_margin_right;
- drawable_size_remained.height -= style_margin_top + style_margin_bottom;
+ drawable_size_remained.width -= theme_cache.style_margin_left + theme_cache.style_margin_right;
+ drawable_size_remained.height -= theme_cache.style_margin_top + theme_cache.style_margin_bottom;
}
const int h_separation = MAX(0, theme_cache.h_separation);
@@ -298,6 +367,7 @@ void Button::_notification(int p_what) {
icon_size = Size2(icon_width, icon_height);
}
icon_size = _fit_icon_size(icon_size);
+ icon_size = icon_size.round();
}
if (icon_size.width > 0.0f) {
@@ -311,12 +381,12 @@ void Button::_notification(int p_what) {
[[fallthrough]];
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
- icon_ofs.x += style_margin_left;
+ icon_ofs.x += theme_cache.style_margin_left;
icon_ofs.x += left_internal_margin_with_h_separation;
} break;
case HORIZONTAL_ALIGNMENT_RIGHT: {
- icon_ofs.x = size.x - style_margin_right;
+ icon_ofs.x = size.x - theme_cache.style_margin_right;
icon_ofs.x -= right_internal_margin_with_h_separation;
icon_ofs.x -= icon_size.width;
} break;
@@ -329,13 +399,14 @@ void Button::_notification(int p_what) {
[[fallthrough]];
case VERTICAL_ALIGNMENT_FILL:
case VERTICAL_ALIGNMENT_TOP: {
- icon_ofs.y += style_margin_top;
+ icon_ofs.y += theme_cache.style_margin_top;
} break;
case VERTICAL_ALIGNMENT_BOTTOM: {
- icon_ofs.y = size.y - style_margin_bottom - icon_size.height;
+ icon_ofs.y = size.y - theme_cache.style_margin_bottom - icon_size.height;
} break;
}
+ icon_ofs = icon_ofs.floor();
Rect2 icon_region = Rect2(icon_ofs, icon_size);
draw_texture_rect(_icon, icon_region, false, icon_modulate_color);
@@ -358,7 +429,7 @@ void Button::_notification(int p_what) {
if (!xl_text.is_empty()) {
text_buf->set_alignment(align_rtl_checked);
- float text_buf_width = MAX(1.0f, drawable_size_remained.width); // The space's width filled by the text_buf.
+ float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf.
text_buf->set_width(text_buf_width);
Point2 text_ofs;
@@ -371,7 +442,7 @@ void Button::_notification(int p_what) {
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT:
case HORIZONTAL_ALIGNMENT_RIGHT: {
- text_ofs.x += style_margin_left;
+ text_ofs.x += theme_cache.style_margin_left;
text_ofs.x += left_internal_margin_with_h_separation;
if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
// Offset by the space's width that occupied by icon and h_separation together.
@@ -380,7 +451,7 @@ void Button::_notification(int p_what) {
} break;
}
- text_ofs.y = (drawable_size_remained.height - text_buf->get_size().height) / 2.0f + style_margin_top;
+ text_ofs.y = (drawable_size_remained.height - text_buf->get_size().height) / 2.0f + theme_cache.style_margin_top;
if (vertical_icon_alignment == VERTICAL_ALIGNMENT_TOP) {
text_ofs.y += custom_element_size.height - drawable_size_remained.height; // Offset by the icon's height.
}
@@ -450,7 +521,7 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu
}
}
- return _get_current_stylebox()->get_minimum_size() + minsize;
+ return _get_largest_stylebox_size() + minsize;
}
void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) {
diff --git a/scene/gui/button.h b/scene/gui/button.h
index 86fdbd35d5..eefb690913 100644
--- a/scene/gui/button.h
+++ b/scene/gui/button.h
@@ -69,6 +69,12 @@ private:
Ref<StyleBox> disabled_mirrored;
Ref<StyleBox> focus;
+ Size2 max_style_size;
+ float style_margin_left = 0;
+ float style_margin_right = 0;
+ float style_margin_top = 0;
+ float style_margin_bottom = 0;
+
Color font_color;
Color font_focus_color;
Color font_pressed_color;
@@ -94,16 +100,18 @@ private:
int icon_max_width = 0;
} theme_cache;
- Size2 _fit_icon_size(const Size2 &p_size) const;
-
void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = "");
void _texture_changed();
protected:
+ virtual void _update_theme_item_cache() override;
+
void _set_internal_margin(Side p_side, float p_value);
virtual void _queue_update_size_cache();
+ Size2 _fit_icon_size(const Size2 &p_size) const;
Ref<StyleBox> _get_current_stylebox() const;
+ Size2 _get_largest_stylebox_size() const;
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp
index acdeae289b..1af33d2814 100644
--- a/scene/gui/center_container.cpp
+++ b/scene/gui/center_container.cpp
@@ -36,16 +36,10 @@ Size2 CenterContainer::get_minimum_size() const {
}
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible()) {
- continue;
- }
Size2 minsize = c->get_combined_minimum_size();
ms = ms.max(minsize);
}
@@ -81,14 +75,10 @@ void CenterContainer::_notification(int p_what) {
case NOTIFICATION_SORT_CHILDREN: {
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
-
Size2 minsize = c->get_combined_minimum_size();
Point2 ofs = use_top_left ? (-minsize * 0.5).floor() : ((size - minsize) / 2.0).floor();
fit_child_in_rect(c, Rect2(ofs, minsize));
diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp
index af6696834e..99937aaf41 100644
--- a/scene/gui/check_box.cpp
+++ b/scene/gui/check_box.cpp
@@ -59,14 +59,14 @@ Size2 CheckBox::get_icon_size() const {
if (!theme_cache.radio_unchecked_disabled.is_null()) {
tex_size = tex_size.max(theme_cache.radio_unchecked_disabled->get_size());
}
- return tex_size;
+ return _fit_icon_size(tex_size);
}
Size2 CheckBox::get_minimum_size() const {
Size2 minsize = Button::get_minimum_size();
const Size2 tex_size = get_icon_size();
if (tex_size.width > 0 || tex_size.height > 0) {
- const Size2 padding = _get_current_stylebox()->get_minimum_size();
+ const Size2 padding = _get_largest_stylebox_size();
Size2 content_size = minsize - padding;
if (content_size.width > 0 && tex_size.width > 0) {
content_size.width += MAX(0, theme_cache.h_separation);
@@ -127,9 +127,9 @@ void CheckBox::_notification(int p_what) {
ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_offset;
if (is_pressed()) {
- on_tex->draw(ci, ofs);
+ on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())));
} else {
- off_tex->draw(ci, ofs);
+ off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())));
}
} break;
}
diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp
index ab3b74a3c3..29b9504776 100644
--- a/scene/gui/check_button.cpp
+++ b/scene/gui/check_button.cpp
@@ -63,14 +63,14 @@ Size2 CheckButton::get_icon_size() const {
tex_size = tex_size.max(off_tex->get_size());
}
- return tex_size;
+ return _fit_icon_size(tex_size);
}
Size2 CheckButton::get_minimum_size() const {
Size2 minsize = Button::get_minimum_size();
const Size2 tex_size = get_icon_size();
if (tex_size.width > 0 || tex_size.height > 0) {
- const Size2 padding = _get_current_stylebox()->get_minimum_size();
+ const Size2 padding = _get_largest_stylebox_size();
Size2 content_size = minsize - padding;
if (content_size.width > 0 && tex_size.width > 0) {
content_size.width += MAX(0, theme_cache.h_separation);
@@ -134,9 +134,9 @@ void CheckButton::_notification(int p_what) {
ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_offset;
if (is_pressed()) {
- on_tex->draw(ci, ofs);
+ on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())));
} else {
- off_tex->draw(ci, ofs);
+ off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())));
}
} break;
}
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 4f90504e35..c843bb8c44 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -624,16 +624,31 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
return TextEdit::get_cursor_shape(p_pos);
}
+void CodeEdit::_unhide_carets() {
+ // Unfold caret and selection origin.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_is_line_hidden(get_caret_line(i))) {
+ unfold_line(get_caret_line(i));
+ }
+ if (has_selection(i) && _is_line_hidden(get_selection_origin_line(i))) {
+ unfold_line(get_selection_origin_line(i));
+ }
+ }
+}
+
/* Text manipulation */
// Overridable actions
void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
start_action(EditAction::ACTION_TYPING);
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
bool had_selection = has_selection(i);
String selection_text = (had_selection ? get_selected_text(i) : "");
@@ -691,6 +706,7 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_ca
insert_text_at_caret(chr, i);
}
}
+ end_multicaret_edit();
end_action();
}
@@ -705,66 +721,80 @@ void CodeEdit::_backspace_internal(int p_caret) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
- int cc = get_caret_column(i);
- int cl = get_caret_line(i);
+ int to_line = get_caret_line(i);
+ int to_column = get_caret_column(i);
- if (cc == 0 && cl == 0) {
+ if (to_column == 0 && to_line == 0) {
continue;
}
- if (cl > 0 && _is_line_hidden(cl - 1)) {
- unfold_line(get_caret_line(i) - 1);
+ if (to_line > 0 && _is_line_hidden(to_line - 1)) {
+ unfold_line(to_line - 1);
}
- int prev_line = cc ? cl : cl - 1;
- int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
+ int from_line = to_column > 0 ? to_line : to_line - 1;
+ int from_column = to_column > 0 ? (to_column - 1) : (get_line(to_line - 1).length());
- merge_gutters(prev_line, cl);
+ merge_gutters(from_line, to_line);
- if (auto_brace_completion_enabled && cc > 0) {
- int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
+ if (auto_brace_completion_enabled && to_column > 0) {
+ int idx = _get_auto_brace_pair_open_at_pos(to_line, to_column);
if (idx != -1) {
- prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
+ from_column = to_column - auto_brace_completion_pairs[idx].open_key.length();
- if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) {
- cc += auto_brace_completion_pairs[idx].close_key.length();
+ if (_get_auto_brace_pair_close_at_pos(to_line, to_column) == idx) {
+ to_column += auto_brace_completion_pairs[idx].close_key.length();
}
-
- remove_text(prev_line, prev_column, cl, cc);
-
- set_caret_line(prev_line, false, true, 0, i);
- set_caret_column(prev_column, i == 0, i);
-
- adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
- continue;
}
}
// For space indentation we need to do a basic unindent if there are no chars to the left, acting the same way as tabs.
- if (indent_using_spaces && cc != 0) {
- if (get_first_non_whitespace_column(cl) >= cc) {
- prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
- prev_line = cl;
+ if (indent_using_spaces && to_column != 0) {
+ if (get_first_non_whitespace_column(to_line) >= to_column) {
+ from_column = to_column - _calculate_spaces_till_next_left_indent(to_column);
+ from_line = to_line;
}
}
- remove_text(prev_line, prev_column, cl, cc);
-
- set_caret_line(prev_line, false, true, 0, i);
- set_caret_column(prev_column, i == 0, i);
+ remove_text(from_line, from_column, to_line, to_column);
- adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
+ set_caret_line(from_line, false, true, -1, i);
+ set_caret_column(from_column, i == 0, i);
}
- merge_overlapping_carets();
+
+ end_multicaret_edit();
end_complex_operation();
}
+void CodeEdit::_cut_internal(int p_caret) {
+ // Overridden to unfold lines.
+ _copy_internal(p_caret);
+
+ if (!is_editable()) {
+ return;
+ }
+
+ if (has_selection(p_caret)) {
+ delete_selection(p_caret);
+ return;
+ }
+ if (p_caret == -1) {
+ delete_lines();
+ } else {
+ unfold_line(get_caret_line(p_caret));
+ remove_line_at(get_caret_line(p_caret));
+ }
+}
+
/* Indent management */
void CodeEdit::set_indent_size(const int p_size) {
ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
@@ -838,13 +868,17 @@ void CodeEdit::do_indent() {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column(i));
if (spaces_to_add > 0) {
insert_text_at_caret(String(" ").repeat(spaces_to_add), i);
}
}
+ end_multicaret_edit();
end_complex_operation();
}
@@ -854,51 +888,28 @@ void CodeEdit::indent_lines() {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &c : caret_edit_order) {
- // This value informs us by how much we changed selection position by indenting right.
- // Default is 1 for tab indentation.
- int selection_offset = 1;
-
- int start_line = get_caret_line(c);
- int end_line = start_line;
- if (has_selection(c)) {
- start_line = get_selection_from_line(c);
- end_line = get_selection_to_line(c);
+ begin_multicaret_edit();
- // Ignore the last line if the selection is not past the first column.
- if (get_selection_to_column(c) == 0) {
- selection_offset = 0;
- end_line--;
- }
- }
-
- for (int i = start_line; i <= end_line; i++) {
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ for (Point2i line_range : line_ranges) {
+ for (int i = line_range.x; i <= line_range.y; i++) {
const String line_text = get_line(i);
- if (line_text.size() == 0 && has_selection(c)) {
+ if (line_text.size() == 0) {
+ // Ignore empty lines.
continue;
}
- if (!indent_using_spaces) {
- set_line(i, '\t' + line_text);
- continue;
+ if (indent_using_spaces) {
+ int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
+ insert_text(String(" ").repeat(spaces_to_add), i, 0, false);
+ } else {
+ insert_text("\t", i, 0, false);
}
-
- // We don't really care where selection is - we just need to know indentation level at the beginning of the line.
- // Since we will add this many spaces, we want to move the whole selection and caret by this much.
- int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
- set_line(i, String(" ").repeat(spaces_to_add) + line_text);
- selection_offset = spaces_to_add;
}
-
- // Fix selection and caret being off after shifting selection right.
- if (has_selection(c)) {
- select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c);
- }
- set_caret_column(get_caret_column(c) + selection_offset, false, c);
}
+
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
void CodeEdit::unindent_lines() {
@@ -907,76 +918,25 @@ void CodeEdit::unindent_lines() {
}
begin_complex_operation();
+ begin_multicaret_edit();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &c : caret_edit_order) {
- // Moving caret and selection after unindenting can get tricky because
- // changing content of line can move caret and selection on its own (if new line ends before previous position of either)
- // therefore we just remember initial values and at the end of the operation offset them by number of removed characters.
- int removed_characters = 0;
- int initial_selection_end_column = 0;
- int initial_cursor_column = get_caret_column(c);
-
- int start_line = get_caret_line(c);
- int end_line = start_line;
- if (has_selection(c)) {
- start_line = get_selection_from_line(c);
- end_line = get_selection_to_line(c);
-
- // Ignore the last line if the selection is not past the first column.
- initial_selection_end_column = get_selection_to_column(c);
- if (initial_selection_end_column == 0) {
- end_line--;
- }
- }
-
- bool first_line_edited = false;
- bool last_line_edited = false;
-
- for (int i = start_line; i <= end_line; i++) {
- String line_text = get_line(i);
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ for (Point2i line_range : line_ranges) {
+ for (int i = line_range.x; i <= line_range.y; i++) {
+ const String line_text = get_line(i);
if (line_text.begins_with("\t")) {
- line_text = line_text.substr(1, line_text.length());
-
- set_line(i, line_text);
- removed_characters = 1;
-
- first_line_edited = (i == start_line) ? true : first_line_edited;
- last_line_edited = (i == end_line) ? true : last_line_edited;
- continue;
- }
-
- if (line_text.begins_with(" ")) {
- // When unindenting we aim to remove spaces before line that has selection no matter what is selected.
- // Here we remove only enough spaces to align text to nearest full multiple of indentation_size.
- // In case where selection begins at the start of indentation_size multiple we remove whole indentation level.
+ remove_text(i, 0, i, 1);
+ } else if (line_text.begins_with(" ")) {
+ // Remove only enough spaces to align text to nearest full multiple of indentation_size.
int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i));
- line_text = line_text.substr(spaces_to_remove, line_text.length());
-
- set_line(i, line_text);
- removed_characters = spaces_to_remove;
-
- first_line_edited = (i == start_line) ? true : first_line_edited;
- last_line_edited = (i == end_line) ? true : last_line_edited;
+ remove_text(i, 0, i, spaces_to_remove);
}
}
-
- if (has_selection(c)) {
- // Fix selection being off by one on the first line.
- if (first_line_edited) {
- select(get_selection_from_line(c), get_selection_from_column(c) - removed_characters, get_selection_to_line(c), initial_selection_end_column, c);
- }
-
- // Fix selection being off by one on the last line.
- if (last_line_edited) {
- select(get_selection_from_line(c), get_selection_from_column(c), get_selection_to_line(c), initial_selection_end_column - removed_characters, c);
- }
- }
- set_caret_column(initial_cursor_column - removed_characters, false, c);
}
+
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
@@ -992,27 +952,6 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
ERR_FAIL_COND(p_to_line >= get_line_count());
ERR_FAIL_COND(p_to_line < p_from_line);
- // Store caret states.
- Vector<int> caret_columns;
- Vector<Pair<int, int>> from_selections;
- Vector<Pair<int, int>> to_selections;
- caret_columns.resize(get_caret_count());
- from_selections.resize(get_caret_count());
- to_selections.resize(get_caret_count());
- for (int c = 0; c < get_caret_count(); c++) {
- caret_columns.write[c] = get_caret_column(c);
-
- // Set "selection_from_line" to -1 to allow checking if there was a selection later.
- if (!has_selection(c)) {
- from_selections.write[c].first = -1;
- continue;
- }
- from_selections.write[c].first = get_selection_from_line(c);
- from_selections.write[c].second = get_selection_from_column(c);
- to_selections.write[c].first = get_selection_to_line(c);
- to_selections.write[c].second = get_selection_to_column(c);
- }
-
// Check lines within range.
const char32_t from_indent_char = indent_using_spaces ? '\t' : ' ';
int size_diff = indent_using_spaces ? indent_size - 1 : -(indent_size - 1);
@@ -1044,23 +983,10 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
line_changed = true;
if (!changed_indentation) {
begin_complex_operation();
+ begin_multicaret_edit();
changed_indentation = true;
}
- // Calculate new caret state.
- for (int c = 0; c < get_caret_count(); c++) {
- if (get_caret_line(c) != i || caret_columns[c] <= j) {
- continue;
- }
- caret_columns.write[c] += size_diff;
-
- if (from_selections.write[c].first == -1) {
- continue;
- }
- from_selections.write[c].second = from_selections[c].first == i ? from_selections[c].second + size_diff : from_selections[c].second;
- to_selections.write[c].second = to_selections[c].first == i ? to_selections[c].second + size_diff : to_selections[c].second;
- }
-
// Calculate new line.
line = line.left(j + ((size_diff < 0) ? size_diff : 0)) + indent_text + line.substr(j + 1);
@@ -1069,6 +995,7 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
}
if (line_changed) {
+ // Use set line to preserve carets visual position.
set_line(i, line);
}
}
@@ -1077,16 +1004,9 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
return;
}
- // Restore caret states.
- for (int c = 0; c < get_caret_count(); c++) {
- set_caret_column(caret_columns[c], c == 0, c);
- if (from_selections.write[c].first != -1) {
- select(from_selections.write[c].first, from_selections.write[c].second, to_selections.write[c].first, to_selections.write[c].second, c);
- }
- }
merge_overlapping_carets();
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const {
@@ -1107,15 +1027,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
// When not splitting the line, we need to factor in indentation from the end of the current line.
const int cc = p_split_current_line ? get_caret_column(i) : get_line(get_caret_line(i)).length();
const int cl = get_caret_line(i);
const String line = get_line(cl);
- String ins = "\n";
+ String ins = "";
+ if (!p_above) {
+ ins = "\n";
+ }
// Append current indentation.
int space_count = 0;
@@ -1138,6 +1065,9 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
break;
}
+ if (p_above) {
+ ins += "\n";
+ }
if (is_line_folded(cl)) {
unfold_line(cl);
@@ -1183,33 +1113,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
}
- bool first_line = false;
- if (!p_split_current_line) {
+ if (p_split_current_line) {
+ insert_text_at_caret(ins, i);
+ } else {
+ insert_text(ins, cl, p_above ? 0 : get_line(cl).length(), p_above, p_above);
deselect(i);
-
- if (p_above) {
- if (cl > 0) {
- set_caret_line(cl - 1, false, true, 0, i);
- set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
- } else {
- set_caret_column(0, i == 0, i);
- first_line = true;
- }
- } else {
- set_caret_column(line.length(), i == 0, i);
- }
+ set_caret_line(p_above ? cl : cl + 1, false, true, -1, i);
+ set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
}
-
- insert_text_at_caret(ins, i);
-
- if (first_line) {
- set_caret_line(0, i == 0, true, 0, i);
- } else if (brace_indent) {
+ if (brace_indent) {
+ // Move to inner indented line.
set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
}
}
+ end_multicaret_edit();
end_complex_operation();
}
@@ -1700,27 +1619,8 @@ void CodeEdit::fold_line(int p_line) {
_set_line_as_hidden(i, true);
}
- for (int i = 0; i < get_caret_count(); i++) {
- // Fix selection.
- if (has_selection(i)) {
- if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) {
- deselect(i);
- } else if (_is_line_hidden(get_selection_from_line(i))) {
- select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i);
- } else if (_is_line_hidden(get_selection_to_line(i))) {
- select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i);
- }
- }
-
- // Reset caret.
- if (_is_line_hidden(get_caret_line(i))) {
- set_caret_line(p_line, false, false, 0, i);
- set_caret_column(get_line(p_line).length(), false, i);
- }
- }
-
- merge_overlapping_carets();
- queue_redraw();
+ // Collapse any carets in the hidden area.
+ collapse_carets(p_line, get_line(p_line).length(), end_line, get_line(end_line).length(), true);
}
void CodeEdit::unfold_line(int p_line) {
@@ -1769,6 +1669,23 @@ void CodeEdit::toggle_foldable_line(int p_line) {
fold_line(p_line);
}
+void CodeEdit::toggle_foldable_lines_at_carets() {
+ begin_multicaret_edit();
+ int previous_line = -1;
+ Vector<int> sorted = get_sorted_carets();
+ for (int caret_idx : sorted) {
+ if (multicaret_edit_ignore_caret(caret_idx)) {
+ continue;
+ }
+ int line_idx = get_caret_line(caret_idx);
+ if (line_idx != previous_line) {
+ toggle_foldable_line(line_idx);
+ previous_line = line_idx;
+ }
+ }
+ end_multicaret_edit();
+}
+
bool CodeEdit::is_line_folded(int p_line) const {
ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
return p_line + 1 < get_line_count() && !_is_line_hidden(p_line) && _is_line_hidden(p_line + 1);
@@ -1795,49 +1712,29 @@ void CodeEdit::create_code_region() {
WARN_PRINT_ONCE("Cannot create code region without any one line comment delimiters");
return;
}
+ String region_name = atr(ETR("New Code Region"));
+
begin_complex_operation();
- // Merge selections if selection starts on the same line the previous one ends.
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- Vector<int> carets_to_remove;
- for (int i = 1; i < caret_edit_order.size(); i++) {
- int current_caret = caret_edit_order[i - 1];
- int next_caret = caret_edit_order[i];
- if (get_selection_from_line(current_caret) == get_selection_to_line(next_caret)) {
- select(get_selection_from_line(next_caret), get_selection_from_column(next_caret), get_selection_to_line(current_caret), get_selection_to_column(current_caret), next_caret);
- carets_to_remove.append(current_caret);
- }
- }
- // Sort and remove backwards to preserve indices.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
- }
-
- // Adding start and end region tags.
- int first_region_start = -1;
- for (int caret_idx : get_caret_index_edit_order()) {
- if (!has_selection(caret_idx)) {
- continue;
- }
- int from_line = get_selection_from_line(caret_idx);
- if (first_region_start == -1 || from_line < first_region_start) {
- first_region_start = from_line;
- }
- int to_line = get_selection_to_line(caret_idx);
- set_line(to_line, get_line(to_line) + "\n" + code_region_end_string);
- insert_line_at(from_line, code_region_start_string + " " + atr(ETR("New Code Region")));
- fold_line(from_line);
+ begin_multicaret_edit();
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets(true, false);
+
+ // Add start and end region tags.
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ insert_text("\n" + code_region_end_string, line_range.y + line_offset, get_line(line_range.y + line_offset).length());
+ insert_line_at(line_range.x + line_offset, code_region_start_string + " " + region_name);
+ fold_line(line_range.x + line_offset);
+ line_offset += 2;
}
+ int first_region_start = line_ranges[0].x;
// Select name of the first region to allow quick edit.
remove_secondary_carets();
- set_caret_line(first_region_start);
- int tag_length = code_region_start_string.length() + atr(ETR("New Code Region")).length() + 1;
- set_caret_column(tag_length);
+ int tag_length = code_region_start_string.length() + region_name.length() + 1;
select(first_region_start, code_region_start_string.length() + 1, first_region_start, tag_length);
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
String CodeEdit::get_code_region_start_tag() const {
@@ -2236,8 +2133,12 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
char32_t caret_last_completion_char = 0;
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
int caret_line = get_caret_line(i);
const String &insert_text = code_completion_options[code_completion_current_selected].insert_text;
@@ -2270,8 +2171,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
// Replace.
remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_remove_line, caret_col);
- adjust_carets_after_edit(i, caret_line, caret_col - code_completion_base.length(), caret_remove_line, caret_col);
- set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i);
insert_text_at_caret(insert_text, i);
} else {
// Get first non-matching char.
@@ -2287,8 +2186,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
// Remove base completion text.
remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i));
- adjust_carets_after_edit(i, caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i));
- set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i);
// Merge with text.
insert_text_at_caret(insert_text.substr(0, code_completion_base.length()), i);
@@ -2313,12 +2210,10 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
if (has_string_delimiter(String::chr(last_completion_char))) {
if (post_brace_pair != -1 && last_char_matches) {
remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
- adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
}
} else {
if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && last_char_matches) {
remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
- adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
} else if (auto_brace_completion_enabled && pre_brace_pair != -1) {
insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i);
set_caret_column(get_caret_column(i) - auto_brace_completion_pairs[pre_brace_pair].close_key.length(), i == 0, i);
@@ -2329,13 +2224,16 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i) + 1);
if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1)) {
remove_text(caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i));
- adjust_carets_after_edit(i, caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i));
- if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1) != pre_brace_pair) {
- set_caret_column(get_caret_column(i) - 1, i == 0, i);
+ if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) + 1) != pre_brace_pair) {
+ set_caret_column(get_caret_column(i) + 1, i == 0, i);
+ } else {
+ set_caret_column(get_caret_column(i) + 2, i == 0, i);
}
}
}
}
+
+ end_multicaret_edit();
end_complex_operation();
cancel_code_completion();
@@ -2418,65 +2316,154 @@ void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) {
}
/* Text manipulation */
-void CodeEdit::duplicate_lines() {
+void CodeEdit::move_lines_up() {
begin_complex_operation();
+ begin_multicaret_edit();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &caret_index : caret_edit_order) {
- // The text that will be inserted. All lines in one string.
- String insert_text;
-
- // The new line position of the caret after the operation.
- int new_caret_line = get_caret_line(caret_index);
- // The new column position of the caret after the operation.
- int new_caret_column = get_caret_column(caret_index);
- // The caret positions of the selection. Stays -1 if there is no selection.
- int select_from_line = -1;
- int select_to_line = -1;
- int select_from_column = -1;
- int select_to_column = -1;
- // Number of lines of the selection.
- int select_num_lines = -1;
-
- if (has_selection(caret_index)) {
- select_from_line = get_selection_from_line(caret_index);
- select_to_line = get_selection_to_line(caret_index);
- select_from_column = get_selection_from_column(caret_index);
- select_to_column = get_selection_to_column(caret_index);
- select_num_lines = select_to_line - select_from_line + 1;
-
- for (int i = select_from_line; i <= select_to_line; i++) {
- insert_text += "\n" + get_line(i);
- unfold_line(i);
- }
- new_caret_line = select_to_line + select_num_lines;
- } else {
- insert_text = "\n" + get_line(new_caret_line);
- new_caret_line++;
+ // Move lines up by swapping each line with the one above it.
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ for (Point2i line_range : line_ranges) {
+ if (line_range.x == 0) {
+ continue;
+ }
+ unfold_line(line_range.x - 1);
+ for (int line = line_range.x; line <= line_range.y; line++) {
+ unfold_line(line);
+ swap_lines(line - 1, line);
+ }
+ }
+
+ // Fix selection if it ended at column 0, since it wasn't moved.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != 0) {
+ if (is_caret_after_selection_origin(i)) {
+ set_caret_line(get_caret_line(i) - 1, false, true, -1, i);
+ } else {
+ set_selection_origin_line(get_selection_origin_line(i) - 1, true, -1, i);
+ }
+ }
+ }
+
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void CodeEdit::move_lines_down() {
+ begin_complex_operation();
+ begin_multicaret_edit();
+
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+
+ // Fix selection if it ended at column 0, since it won't be moved.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != get_line_count() - 1) {
+ if (is_caret_after_selection_origin(i)) {
+ set_caret_line(get_caret_line(i) + 1, false, true, -1, i);
+ } else {
+ set_selection_origin_line(get_selection_origin_line(i) + 1, true, -1, i);
+ }
+ }
+ }
- unfold_line(get_caret_line(caret_index));
+ // Move lines down by swapping each line with the one below it.
+ for (Point2i line_range : line_ranges) {
+ if (line_range.y == get_line_count() - 1) {
+ continue;
+ }
+ unfold_line(line_range.y + 1);
+ for (int line = line_range.y; line >= line_range.x; line--) {
+ unfold_line(line);
+ swap_lines(line + 1, line);
}
+ }
- // The text will be inserted at the end of the current line.
- set_caret_column(get_line(get_caret_line(caret_index)).length(), false, caret_index);
+ end_multicaret_edit();
+ end_complex_operation();
+}
- deselect(caret_index);
+void CodeEdit::delete_lines() {
+ begin_complex_operation();
+ begin_multicaret_edit();
- insert_text_at_caret(insert_text, caret_index);
- set_caret_line(new_caret_line, false, true, 0, caret_index);
- set_caret_column(new_caret_column, true, caret_index);
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets();
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ // Remove last line of range separately to preserve carets.
+ unfold_line(line_range.y + line_offset);
+ remove_line_at(line_range.y + line_offset);
+ if (line_range.x != line_range.y) {
+ remove_text(line_range.x + line_offset, 0, line_range.y + line_offset, 0);
+ }
+ line_offset += line_range.x - line_range.y - 1;
+ }
- if (select_from_line != -1) {
- // Advance the selection by the number of duplicated lines.
- select_from_line += select_num_lines;
- select_to_line += select_num_lines;
+ // Deselect all.
+ deselect();
- select(select_from_line, select_from_column, select_to_line, select_to_column, caret_index);
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void CodeEdit::duplicate_selection() {
+ begin_complex_operation();
+ begin_multicaret_edit();
+
+ // Duplicate lines from carets without selections first.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
+ for (int l = get_selection_from_line(i); l <= get_selection_to_line(i); l++) {
+ unfold_line(l);
+ }
+ if (has_selection(i)) {
+ continue;
}
+
+ String text_to_insert = get_line(get_caret_line(i)) + "\n";
+ // Insert new text before the line, so the caret is on the second one.
+ insert_text(text_to_insert, get_caret_line(i), 0);
}
+ // Duplicate selections.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
+ if (!has_selection(i)) {
+ continue;
+ }
+
+ // Insert new text before the selection, so the caret is on the second one.
+ insert_text(get_selected_text(i), get_selection_from_line(i), get_selection_from_column(i));
+ }
+
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void CodeEdit::duplicate_lines() {
+ begin_complex_operation();
+ begin_multicaret_edit();
+
+ Vector<Point2i> line_ranges = get_line_ranges_from_carets(false, false);
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ // The text that will be inserted. All lines in one string.
+ String text_to_insert;
+
+ for (int i = line_range.x + line_offset; i <= line_range.y + line_offset; i++) {
+ text_to_insert += get_line(i) + "\n";
+ unfold_line(i);
+ }
+
+ // Insert new text before the line.
+ insert_text(text_to_insert, line_range.x + line_offset, 0);
+ line_offset += line_range.y - line_range.x + 1;
+ }
+
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
/* Visual */
@@ -2578,6 +2565,7 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("fold_all_lines"), &CodeEdit::fold_all_lines);
ClassDB::bind_method(D_METHOD("unfold_all_lines"), &CodeEdit::unfold_all_lines);
ClassDB::bind_method(D_METHOD("toggle_foldable_line", "line"), &CodeEdit::toggle_foldable_line);
+ ClassDB::bind_method(D_METHOD("toggle_foldable_lines_at_carets"), &CodeEdit::toggle_foldable_lines_at_carets);
ClassDB::bind_method(D_METHOD("is_line_folded", "line"), &CodeEdit::is_line_folded);
ClassDB::bind_method(D_METHOD("get_folded_lines"), &CodeEdit::get_folded_lines);
@@ -2679,6 +2667,10 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid);
/* Text manipulation */
+ ClassDB::bind_method(D_METHOD("move_lines_up"), &CodeEdit::move_lines_up);
+ ClassDB::bind_method(D_METHOD("move_lines_down"), &CodeEdit::move_lines_down);
+ ClassDB::bind_method(D_METHOD("delete_lines"), &CodeEdit::delete_lines);
+ ClassDB::bind_method(D_METHOD("duplicate_selection"), &CodeEdit::duplicate_selection);
ClassDB::bind_method(D_METHOD("duplicate_lines"), &CodeEdit::duplicate_lines);
/* Inspector */
@@ -2846,10 +2838,12 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
if (p_gutter == line_number_gutter) {
remove_secondary_carets();
- set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0);
- select(p_line, 0, p_line + 1, 0);
- set_caret_line(p_line + 1);
- set_caret_column(0);
+ set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE);
+ if (p_line == get_line_count() - 1) {
+ select(p_line, 0, p_line, INT_MAX);
+ } else {
+ select(p_line, 0, p_line + 1, 0);
+ }
return;
}
@@ -3056,7 +3050,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) {
}
int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const {
- if (delimiters.size() == 0) {
+ if (delimiters.size() == 0 || p_line >= delimiter_cache.size()) {
return -1;
}
ERR_FAIL_INDEX_V(p_line, get_line_count(), 0);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 1770d4f4d8..56f8cce548 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -309,11 +309,14 @@ protected:
static void _bind_compatibility_methods();
#endif
+ virtual void _unhide_carets() override;
+
/* Text manipulation */
// Overridable actions
virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override;
virtual void _backspace_internal(int p_caret) override;
+ virtual void _cut_internal(int p_caret) override;
GDVIRTUAL1(_confirm_code_completion, bool)
GDVIRTUAL1(_request_code_completion, bool)
@@ -409,6 +412,7 @@ public:
void fold_all_lines();
void unfold_all_lines();
void toggle_foldable_line(int p_line);
+ void toggle_foldable_lines_at_carets();
bool is_line_folded(int p_line) const;
TypedArray<int> get_folded_lines() const;
@@ -489,6 +493,10 @@ public:
void set_symbol_lookup_word_as_valid(bool p_valid);
/* Text manipulation */
+ void move_lines_up();
+ void move_lines_down();
+ void delete_lines();
+ void duplicate_selection();
void duplicate_lines();
CodeEdit();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 3b62f6c23d..245a086dda 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -95,8 +95,8 @@ void ColorPicker::_notification(int p_what) {
for (int i = 0; i < MODE_BUTTON_COUNT; i++) {
mode_btns[i]->begin_bulk_theme_override();
- mode_btns[i]->add_theme_style_override(SNAME("pressed"), theme_cache.mode_button_pressed);
- mode_btns[i]->add_theme_style_override(SNAME("normal"), theme_cache.mode_button_normal);
+ mode_btns[i]->add_theme_style_override(SceneStringName(pressed), theme_cache.mode_button_pressed);
+ mode_btns[i]->add_theme_style_override(CoreStringName(normal), theme_cache.mode_button_normal);
mode_btns[i]->add_theme_style_override(SNAME("hover"), theme_cache.mode_button_hover);
mode_btns[i]->end_bulk_theme_override();
}
@@ -403,8 +403,9 @@ void ColorPicker::add_mode(ColorMode *p_mode) {
}
void ColorPicker::create_slider(GridContainer *gc, int idx) {
- Label *lbl = memnew(Label());
+ Label *lbl = memnew(Label);
lbl->set_v_size_flags(SIZE_SHRINK_CENTER);
+ lbl->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
gc->add_child(lbl);
HSlider *slider = memnew(HSlider);
@@ -419,18 +420,18 @@ void ColorPicker::create_slider(GridContainer *gc, int idx) {
LineEdit *vle = val->get_line_edit();
vle->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed));
- vle->connect("gui_input", callable_mp(this, &ColorPicker::_line_edit_input));
+ vle->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_line_edit_input));
vle->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
- val->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input));
+ val->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_slider_or_spin_input));
slider->set_h_size_flags(SIZE_EXPAND_FILL);
slider->connect("drag_started", callable_mp(this, &ColorPicker::_slider_drag_started));
slider->connect("value_changed", callable_mp(this, &ColorPicker::_slider_value_changed).unbind(1));
slider->connect("drag_ended", callable_mp(this, &ColorPicker::_slider_drag_ended).unbind(1));
- slider->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx));
- slider->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input));
+ slider->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_slider_draw).bind(idx));
+ slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_slider_or_spin_input));
if (idx < SLIDER_COUNT) {
sliders[idx] = slider;
@@ -457,8 +458,8 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) {
}
}
- for (int i = 0; i < preset_cache.size(); i++) {
- presets.push_back(preset_cache[i]);
+ for (const Color &preset : preset_cache) {
+ presets.push_back(preset);
}
if (recent_preset_cache.is_empty()) {
@@ -468,8 +469,8 @@ void ColorPicker::set_editor_settings(Object *p_editor_settings) {
}
}
- for (int i = 0; i < recent_preset_cache.size(); i++) {
- recent_presets.push_back(recent_preset_cache[i]);
+ for (const Color &preset : recent_preset_cache) {
+ recent_presets.push_back(preset);
}
_update_presets();
@@ -659,8 +660,8 @@ void ColorPicker::_update_presets() {
for (int i = 1; i < preset_container->get_child_count(); i++) {
preset_container->get_child(i)->queue_free();
}
- for (int i = 0; i < preset_cache.size(); i++) {
- _add_preset_button(preset_size, preset_cache[i]);
+ for (const Color &preset : preset_cache) {
+ _add_preset_button(preset_size, preset);
}
_notification(NOTIFICATION_VISIBILITY_CHANGED);
}
@@ -676,13 +677,13 @@ void ColorPicker::_update_recent_presets() {
}
recent_presets.clear();
- for (int i = 0; i < recent_preset_cache.size(); i++) {
- recent_presets.push_back(recent_preset_cache[i]);
+ for (const Color &preset : recent_preset_cache) {
+ recent_presets.push_back(preset);
}
int preset_size = _get_preset_size();
- for (int i = 0; i < recent_presets.size(); i++) {
- _add_recent_preset_button(preset_size, recent_presets[i]);
+ for (const Color &preset : recent_presets) {
+ _add_recent_preset_button(preset_size, preset);
}
_notification(NOTIFICATION_VISIBILITY_CHANGED);
@@ -760,7 +761,7 @@ void ColorPicker::_add_preset_button(int p_size, const Color &p_color) {
btn_preset_new->set_button_group(preset_group);
preset_container->add_child(btn_preset_new);
btn_preset_new->set_pressed(true);
- btn_preset_new->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color));
+ btn_preset_new->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_preset_input).bind(p_color));
}
void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) {
@@ -936,8 +937,9 @@ void ColorPicker::erase_recent_preset(const Color &p_color) {
PackedColorArray ColorPicker::get_presets() const {
PackedColorArray arr;
arr.resize(presets.size());
- for (int i = 0; i < presets.size(); i++) {
- arr.set(i, presets[i]);
+ int i = 0;
+ for (List<Color>::ConstIterator itr = presets.begin(); itr != presets.end(); ++itr, ++i) {
+ arr.set(i, *itr);
}
return arr;
}
@@ -945,8 +947,9 @@ PackedColorArray ColorPicker::get_presets() const {
PackedColorArray ColorPicker::get_recent_presets() const {
PackedColorArray arr;
arr.resize(recent_presets.size());
- for (int i = 0; i < recent_presets.size(); i++) {
- arr.set(i, recent_presets[i]);
+ int i = 0;
+ for (List<Color>::ConstIterator itr = recent_presets.begin(); itr != recent_presets.end(); ++itr, ++i) {
+ arr.set(i, *itr);
}
return arr;
}
@@ -1507,7 +1510,7 @@ void ColorPicker::_pick_button_pressed() {
if (!picker_window) {
picker_window = memnew(Popup);
picker_window->set_size(Vector2i(1, 1));
- picker_window->connect("visibility_changed", callable_mp(this, &ColorPicker::_pick_finished));
+ picker_window->connect(SceneStringName(visibility_changed), callable_mp(this, &ColorPicker::_pick_finished));
add_child(picker_window, false, INTERNAL_MODE_FRONT);
}
picker_window->popup();
@@ -1543,7 +1546,7 @@ void ColorPicker::_pick_button_pressed_legacy() {
picker_texture_rect->set_anchors_preset(Control::PRESET_FULL_RECT);
picker_window->add_child(picker_texture_rect);
picker_texture_rect->set_default_cursor_shape(CURSOR_POINTING_HAND);
- picker_texture_rect->connect("gui_input", callable_mp(this, &ColorPicker::_picker_texture_input));
+ picker_texture_rect->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_picker_texture_input));
picker_preview = memnew(Panel);
picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP);
@@ -1820,11 +1823,11 @@ ColorPicker::ColorPicker() {
uv_edit = memnew(Control);
hb_edit->add_child(uv_edit);
- uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input).bind(uv_edit));
+ uv_edit->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_uv_input).bind(uv_edit));
uv_edit->set_mouse_filter(MOUSE_FILTER_PASS);
uv_edit->set_h_size_flags(SIZE_EXPAND_FILL);
uv_edit->set_v_size_flags(SIZE_EXPAND_FILL);
- uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(0, uv_edit));
+ uv_edit->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(0, uv_edit));
sample_hbc = memnew(HBoxContainer);
real_vbox->add_child(sample_hbc);
@@ -1833,18 +1836,18 @@ ColorPicker::ColorPicker() {
sample_hbc->add_child(btn_pick);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE)) {
btn_pick->set_tooltip_text(ETR("Pick a color from the screen."));
- btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed));
+ btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed));
} else {
// On unsupported platforms, use a legacy method for color picking.
btn_pick->set_tooltip_text(ETR("Pick a color from the application window."));
- btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy));
+ btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy));
}
sample = memnew(TextureRect);
sample_hbc->add_child(sample);
sample->set_h_size_flags(SIZE_EXPAND_FILL);
- sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input));
- sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw));
+ sample->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_sample_input));
+ sample->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_sample_draw));
btn_shape = memnew(MenuButton);
btn_shape->set_flat(false);
@@ -1880,7 +1883,7 @@ ColorPicker::ColorPicker() {
mode_btns[i]->set_toggle_mode(true);
mode_btns[i]->set_text(modes[i]->get_name());
mode_btns[i]->set_button_group(mode_group);
- mode_btns[i]->connect("pressed", callable_mp(this, &ColorPicker::set_color_mode).bind((ColorModeType)i));
+ mode_btns[i]->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::set_color_mode).bind((ColorModeType)i));
}
mode_btns[0]->set_pressed(true);
@@ -1933,7 +1936,7 @@ ColorPicker::ColorPicker() {
text_type->set_text("#");
text_type->set_tooltip_text(RTR("Switch between hexadecimal and code values."));
if (Engine::get_singleton()->is_editor_hint()) {
- text_type->connect("pressed", callable_mp(this, &ColorPicker::_text_type_toggled));
+ text_type->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_text_type_toggled));
} else {
text_type->set_flat(true);
text_type->set_mouse_filter(MOUSE_FILTER_IGNORE);
@@ -1947,7 +1950,7 @@ ColorPicker::ColorPicker() {
c_text->set_placeholder(ETR("Hex code or named color"));
c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted));
c_text->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed));
- c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit));
+ c_text->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPicker::_html_focus_exit));
wheel_edit = memnew(AspectRatioContainer);
wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL);
@@ -1966,19 +1969,19 @@ ColorPicker::ColorPicker() {
wheel = memnew(Control);
wheel_margin->add_child(wheel);
wheel->set_mouse_filter(MOUSE_FILTER_PASS);
- wheel->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(2, wheel));
+ wheel->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(2, wheel));
wheel_uv = memnew(Control);
wheel_margin->add_child(wheel_uv);
- wheel_uv->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input).bind(wheel_uv));
- wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(0, wheel_uv));
+ wheel_uv->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_uv_input).bind(wheel_uv));
+ wheel_uv->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(0, wheel_uv));
w_edit = memnew(Control);
hb_edit->add_child(w_edit);
w_edit->set_h_size_flags(SIZE_FILL);
w_edit->set_v_size_flags(SIZE_EXPAND_FILL);
- w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input));
- w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(1, w_edit));
+ w_edit->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_w_input));
+ w_edit->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(1, w_edit));
_update_controls();
updating = false;
@@ -2023,7 +2026,7 @@ ColorPicker::ColorPicker() {
btn_add_preset = memnew(Button);
btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
btn_add_preset->set_tooltip_text(ETR("Add current color as a preset."));
- btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed));
+ btn_add_preset->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_add_preset_pressed));
preset_container->add_child(btn_add_preset);
}
@@ -2168,7 +2171,7 @@ void ColorPickerButton::_update_picker() {
picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed));
popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup));
popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed));
- picker->connect("minimum_size_changed", callable_mp((Window *)popup, &Window::reset_size));
+ picker->connect(SceneStringName(minimum_size_changed), callable_mp((Window *)popup, &Window::reset_size));
picker->set_pick_color(color);
picker->set_edit_alpha(edit_alpha);
picker->set_display_old_color(true);
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index c6e66c95c6..f1faf3e899 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -30,8 +30,6 @@
#include "container.h"
-#include "scene/scene_string_names.h"
-
void Container::_child_minsize_changed() {
update_minimum_size();
queue_sort();
@@ -45,9 +43,9 @@ void Container::add_child_notify(Node *p_child) {
return;
}
- control->connect(SNAME("size_flags_changed"), callable_mp(this, &Container::queue_sort));
- control->connect(SNAME("minimum_size_changed"), callable_mp(this, &Container::_child_minsize_changed));
- control->connect(SNAME("visibility_changed"), callable_mp(this, &Container::_child_minsize_changed));
+ control->connect(SceneStringName(size_flags_changed), callable_mp(this, &Container::queue_sort));
+ control->connect(SceneStringName(minimum_size_changed), callable_mp(this, &Container::_child_minsize_changed));
+ control->connect(SceneStringName(visibility_changed), callable_mp(this, &Container::_child_minsize_changed));
update_minimum_size();
queue_sort();
@@ -72,9 +70,9 @@ void Container::remove_child_notify(Node *p_child) {
return;
}
- control->disconnect("size_flags_changed", callable_mp(this, &Container::queue_sort));
- control->disconnect("minimum_size_changed", callable_mp(this, &Container::_child_minsize_changed));
- control->disconnect("visibility_changed", callable_mp(this, &Container::_child_minsize_changed));
+ control->disconnect(SceneStringName(size_flags_changed), callable_mp(this, &Container::queue_sort));
+ control->disconnect(SceneStringName(minimum_size_changed), callable_mp(this, &Container::_child_minsize_changed));
+ control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Container::_child_minsize_changed));
update_minimum_size();
queue_sort();
@@ -86,10 +84,10 @@ void Container::_sort_children() {
}
notification(NOTIFICATION_PRE_SORT_CHILDREN);
- emit_signal(SceneStringNames::get_singleton()->pre_sort_children);
+ emit_signal(SceneStringName(pre_sort_children));
notification(NOTIFICATION_SORT_CHILDREN);
- emit_signal(SceneStringNames::get_singleton()->sort_children);
+ emit_signal(SceneStringName(sort_children));
pending_sort = false;
}
@@ -141,6 +139,14 @@ void Container::queue_sort() {
pending_sort = true;
}
+Control *Container::as_sortable_control(Node *p_node) const {
+ Control *c = Object::cast_to<Control>(p_node);
+ if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
+ return nullptr;
+ }
+ return c;
+}
+
Vector<int> Container::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
if (GDVIRTUAL_CALL(_get_allowed_size_flags_horizontal, flags)) {
diff --git a/scene/gui/container.h b/scene/gui/container.h
index 94c3c540d7..405220cee6 100644
--- a/scene/gui/container.h
+++ b/scene/gui/container.h
@@ -42,6 +42,8 @@ class Container : public Control {
protected:
void queue_sort();
+ Control *as_sortable_control(Node *p_node) const;
+
virtual void add_child_notify(Node *p_child) override;
virtual void move_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index f1a8c2f6ae..0d5c69b207 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -42,7 +42,6 @@
#include "scene/gui/panel.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/window.h"
-#include "scene/scene_string_names.h"
#include "scene/theme/theme_db.h"
#include "scene/theme/theme_owner.h"
#include "servers/rendering_server.h"
@@ -142,8 +141,8 @@ Size2 Control::_edit_get_scale() const {
void Control::_edit_set_rect(const Rect2 &p_edit_rect) {
ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be used from editor plugins.");
- set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
- set_size(p_edit_rect.size.snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
+ set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
+ set_size(p_edit_rect.size.snappedf(1), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled());
}
Rect2 Control::_edit_get_rect() const {
@@ -208,28 +207,42 @@ void Control::set_root_layout_direction(int p_root_dir) {
#ifdef TOOLS_ENABLED
void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
ERR_READ_THREAD_GUARD;
- CanvasItem::get_argument_options(p_function, p_idx, r_options);
-
if (p_idx == 0) {
- List<StringName> sn;
const String pf = p_function;
- if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") {
- ThemeDB::get_singleton()->get_default_theme()->get_color_list(get_class(), &sn);
- } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") {
- ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(get_class(), &sn);
- } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") {
- ThemeDB::get_singleton()->get_default_theme()->get_font_list(get_class(), &sn);
- } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") {
- ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(get_class(), &sn);
- } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") {
- ThemeDB::get_singleton()->get_default_theme()->get_constant_list(get_class(), &sn);
- }
+ Theme::DataType type = Theme::DATA_TYPE_MAX;
+
+ if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color" || pf == "remove_theme_color_override") {
+ type = Theme::DATA_TYPE_COLOR;
+ } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant" || pf == "remove_theme_constant_override") {
+ type = Theme::DATA_TYPE_CONSTANT;
+ } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font" || pf == "remove_theme_font_override") {
+ type = Theme::DATA_TYPE_FONT;
+ } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size" || pf == "remove_theme_font_size_override") {
+ type = Theme::DATA_TYPE_FONT_SIZE;
+ } else if (pf == "add_theme_icon_override" || pf == "has_theme_icon" || pf == "has_theme_icon_override" || pf == "get_theme_icon" || pf == "remove_theme_icon_override") {
+ type = Theme::DATA_TYPE_ICON;
+ } else if (pf == "add_theme_stylebox_override" || pf == "has_theme_stylebox" || pf == "has_theme_stylebox_override" || pf == "get_theme_stylebox" || pf == "remove_theme_stylebox_override") {
+ type = Theme::DATA_TYPE_STYLEBOX;
+ }
+
+ if (type != Theme::DATA_TYPE_MAX) {
+ List<ThemeDB::ThemeItemBind> theme_items;
+ ThemeDB::get_singleton()->get_class_items(get_class_name(), &theme_items, true, type);
+
+ List<StringName> sn;
+ for (const ThemeDB::ThemeItemBind &E : theme_items) {
+ if (E.data_type == type) {
+ sn.push_back(E.item_name);
+ }
+ }
- sn.sort_custom<StringName::AlphCompare>();
- for (const StringName &name : sn) {
- r_options->push_back(String(name).quote());
+ sn.sort_custom<StringName::AlphCompare>();
+ for (const StringName &name : sn) {
+ r_options->push_back(String(name).quote());
+ }
}
}
+ CanvasItem::get_argument_options(p_function, p_idx, r_options);
}
#endif
@@ -1383,6 +1396,15 @@ void Control::_set_position(const Point2 &p_point) {
void Control::set_position(const Point2 &p_point, bool p_keep_offsets) {
ERR_MAIN_THREAD_GUARD;
+
+#ifdef TOOLS_ENABLED
+ // Can't compute anchors, set position directly and return immediately.
+ if (saving && !is_inside_tree()) {
+ data.pos_cache = p_point;
+ return;
+ }
+#endif
+
if (p_keep_offsets) {
_compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor);
} else {
@@ -1441,6 +1463,14 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) {
new_size.y = min.y;
}
+#ifdef TOOLS_ENABLED
+ // Can't compute anchors, set size directly and return immediately.
+ if (saving && !is_inside_tree()) {
+ data.size_cache = new_size;
+ return;
+ }
+#endif
+
if (p_keep_offsets) {
_compute_anchors(Rect2(data.pos_cache, new_size), data.offset, data.anchor);
} else {
@@ -1575,7 +1605,7 @@ void Control::_update_minimum_size() {
if (minsize != data.last_minimum_size) {
data.last_minimum_size = minsize;
_size_changed();
- emit_signal(SceneStringNames::get_singleton()->minimum_size_changed);
+ emit_signal(SceneStringName(minimum_size_changed));
}
}
@@ -1739,7 +1769,7 @@ void Control::set_h_size_flags(BitField<SizeFlags> p_flags) {
return;
}
data.h_size_flags = p_flags;
- emit_signal(SceneStringNames::get_singleton()->size_flags_changed);
+ emit_signal(SceneStringName(size_flags_changed));
}
BitField<Control::SizeFlags> Control::get_h_size_flags() const {
@@ -1753,7 +1783,7 @@ void Control::set_v_size_flags(BitField<SizeFlags> p_flags) {
return;
}
data.v_size_flags = p_flags;
- emit_signal(SceneStringNames::get_singleton()->size_flags_changed);
+ emit_signal(SceneStringName(size_flags_changed));
}
BitField<Control::SizeFlags> Control::get_v_size_flags() const {
@@ -1768,7 +1798,7 @@ void Control::set_stretch_ratio(real_t p_ratio) {
}
data.expand = p_ratio;
- emit_signal(SceneStringNames::get_singleton()->size_flags_changed);
+ emit_signal(SceneStringName(size_flags_changed));
}
real_t Control::get_stretch_ratio() const {
@@ -1780,7 +1810,7 @@ real_t Control::get_stretch_ratio() const {
void Control::_call_gui_input(const Ref<InputEvent> &p_event) {
if (p_event->get_device() != InputEvent::DEVICE_ID_INTERNAL) {
- emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); // Signal should be first, so it's possible to override an event (and then accept it).
+ emit_signal(SceneStringName(gui_input), p_event); // Signal should be first, so it's possible to override an event (and then accept it).
}
if (!is_inside_tree() || get_viewport()->is_input_handled()) {
return; // Input was handled, abort.
@@ -3140,6 +3170,14 @@ Control *Control::make_custom_tooltip(const String &p_text) const {
void Control::_notification(int p_notification) {
ERR_MAIN_THREAD_GUARD;
switch (p_notification) {
+#ifdef TOOLS_ENABLED
+ case NOTIFICATION_EDITOR_PRE_SAVE: {
+ saving = true;
+ } break;
+ case NOTIFICATION_EDITOR_POST_SAVE: {
+ saving = false;
+ } break;
+#endif
case NOTIFICATION_POSTINITIALIZE: {
data.initialized = true;
@@ -3184,7 +3222,7 @@ void Control::_notification(int p_notification) {
case NOTIFICATION_READY: {
#ifdef DEBUG_ENABLED
- connect("ready", callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONE_SHOT);
+ connect(SceneStringName(ready), callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONE_SHOT);
#endif
} break;
@@ -3223,7 +3261,7 @@ void Control::_notification(int p_notification) {
data.parent_canvas_item = get_parent_item();
if (data.parent_canvas_item) {
- data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed));
+ data.parent_canvas_item->connect(SceneStringName(item_rect_changed), callable_mp(this, &Control::_size_changed));
} else {
// Connect viewport.
Viewport *viewport = get_viewport();
@@ -3234,7 +3272,7 @@ void Control::_notification(int p_notification) {
case NOTIFICATION_EXIT_CANVAS: {
if (data.parent_canvas_item) {
- data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed));
+ data.parent_canvas_item->disconnect(SceneStringName(item_rect_changed), callable_mp(this, &Control::_size_changed));
data.parent_canvas_item = nullptr;
} else {
// Disconnect viewport.
@@ -3260,7 +3298,7 @@ void Control::_notification(int p_notification) {
} break;
case NOTIFICATION_RESIZED: {
- emit_signal(SceneStringNames::get_singleton()->resized);
+ emit_signal(SceneStringName(resized));
} break;
case NOTIFICATION_DRAW: {
@@ -3270,25 +3308,25 @@ void Control::_notification(int p_notification) {
} break;
case NOTIFICATION_MOUSE_ENTER: {
- emit_signal(SceneStringNames::get_singleton()->mouse_entered);
+ emit_signal(SceneStringName(mouse_entered));
} break;
case NOTIFICATION_MOUSE_EXIT: {
- emit_signal(SceneStringNames::get_singleton()->mouse_exited);
+ emit_signal(SceneStringName(mouse_exited));
} break;
case NOTIFICATION_FOCUS_ENTER: {
- emit_signal(SceneStringNames::get_singleton()->focus_entered);
+ emit_signal(SceneStringName(focus_entered));
queue_redraw();
} break;
case NOTIFICATION_FOCUS_EXIT: {
- emit_signal(SceneStringNames::get_singleton()->focus_exited);
+ emit_signal(SceneStringName(focus_exited));
queue_redraw();
} break;
case NOTIFICATION_THEME_CHANGED: {
- emit_signal(SceneStringNames::get_singleton()->theme_changed);
+ emit_signal(SceneStringName(theme_changed));
_invalidate_theme_cache();
_update_theme_item_cache();
@@ -3549,7 +3587,7 @@ void Control::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "localize_numeral_system"), "set_localize_numeral_system", "is_localizing_numeral_system");
#ifndef DISABLE_DEPRECATED
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_translate", "is_auto_translating");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_auto_translate", "is_auto_translating");
#endif
ADD_GROUP("Tooltip", "tooltip_");
diff --git a/scene/gui/control.h b/scene/gui/control.h
index bdb908e089..c784d4330d 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -47,6 +47,10 @@ class ThemeContext;
class Control : public CanvasItem {
GDCLASS(Control, CanvasItem);
+#ifdef TOOLS_ENABLED
+ bool saving = false;
+#endif
+
public:
enum Anchor {
ANCHOR_BEGIN = 0,
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index c13a2e281a..58961d370c 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -68,11 +68,11 @@ void AcceptDialog::_notification(int p_what) {
parent_visible = get_parent_visible_window();
if (parent_visible) {
- parent_visible->connect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
+ parent_visible->connect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
}
} else {
if (parent_visible) {
- parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
+ parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
}
@@ -89,7 +89,7 @@ void AcceptDialog::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
if (parent_visible) {
- parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
+ parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
} break;
@@ -126,7 +126,7 @@ void AcceptDialog::_ok_pressed() {
void AcceptDialog::_cancel_pressed() {
Window *parent_window = parent_visible;
if (parent_visible) {
- parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused));
+ parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
@@ -202,7 +202,7 @@ void AcceptDialog::register_text_enter(LineEdit *p_line_edit) {
}
void AcceptDialog::_update_child_rects() {
- Size2 dlg_size = get_size();
+ Size2 dlg_size = Vector2(get_size()) / get_content_scale_factor();
float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT);
float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM);
@@ -210,6 +210,15 @@ void AcceptDialog::_update_child_rects() {
bg_panel->set_position(Point2());
bg_panel->set_size(dlg_size);
+ for (int i = 0; i < buttons_hbox->get_child_count(); i++) {
+ Button *b = Object::cast_to<Button>(buttons_hbox->get_child(i));
+ if (!b) {
+ continue;
+ }
+
+ b->set_custom_minimum_size(Size2(theme_cache.buttons_min_width, theme_cache.buttons_min_height));
+ }
+
// Place the buttons from the bottom edge to their minimum required size.
Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size();
Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y);
@@ -255,12 +264,6 @@ Size2 AcceptDialog::_get_contents_minimum_size() const {
content_minsize = child_minsize.max(content_minsize);
}
- // Then we take the background panel as it provides the offsets,
- // which are always added to the minimum size.
- if (theme_cache.panel_style.is_valid()) {
- content_minsize += theme_cache.panel_style->get_minimum_size();
- }
-
// Then we add buttons. Horizontally we're interested in whichever
// value is the biggest. Vertically buttons add to the overall size.
Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size();
@@ -269,6 +272,12 @@ Size2 AcceptDialog::_get_contents_minimum_size() const {
// Plus there is a separation size added on top.
content_minsize.y += theme_cache.buttons_separation;
+ // Then we take the background panel as it provides the offsets,
+ // which are always added to the minimum size.
+ if (theme_cache.panel_style.is_valid()) {
+ content_minsize += theme_cache.panel_style->get_minimum_size();
+ }
+
return content_minsize;
}
@@ -299,7 +308,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin
}
button->set_meta("__right_spacer", right_spacer);
- button->connect("visibility_changed", callable_mp(this, &AcceptDialog::_custom_button_visibility_changed).bind(button));
+ button->connect(SceneStringName(visibility_changed), callable_mp(this, &AcceptDialog::_custom_button_visibility_changed).bind(button));
child_controls_changed();
if (is_visible()) {
@@ -307,7 +316,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin
}
if (!p_action.is_empty()) {
- button->connect("pressed", callable_mp(this, &AcceptDialog::_custom_action).bind(p_action));
+ button->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action).bind(p_action));
}
return button;
@@ -321,7 +330,7 @@ Button *AcceptDialog::add_cancel_button(const String &p_cancel) {
Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c);
- b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed));
+ b->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed));
return b;
}
@@ -336,12 +345,12 @@ void AcceptDialog::remove_button(Button *p_button) {
ERR_FAIL_COND_MSG(right_spacer->get_parent() != buttons_hbox, vformat("Cannot remove button %s as its associated spacer does not belong to this dialog.", p_button->get_name()));
}
- p_button->disconnect("visibility_changed", callable_mp(this, &AcceptDialog::_custom_button_visibility_changed));
- if (p_button->is_connected("pressed", callable_mp(this, &AcceptDialog::_custom_action))) {
- p_button->disconnect("pressed", callable_mp(this, &AcceptDialog::_custom_action));
+ p_button->disconnect(SceneStringName(visibility_changed), callable_mp(this, &AcceptDialog::_custom_button_visibility_changed));
+ if (p_button->is_connected(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action))) {
+ p_button->disconnect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action));
}
- if (p_button->is_connected("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed))) {
- p_button->disconnect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed));
+ if (p_button->is_connected(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed))) {
+ p_button->disconnect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed));
}
if (right_spacer) {
@@ -389,6 +398,8 @@ void AcceptDialog::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, AcceptDialog, panel_style, "panel");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_separation);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_width);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_height);
}
bool AcceptDialog::swap_cancel_ok = false;
@@ -422,7 +433,7 @@ AcceptDialog::AcceptDialog() {
buttons_hbox->add_child(ok_button);
buttons_hbox->add_spacer();
- ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed));
+ ok_button->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_ok_pressed));
set_title(ETR("Alert!"));
}
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index 6f9f450778..12b48c903a 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -57,6 +57,8 @@ class AcceptDialog : public Window {
struct ThemeCache {
Ref<StyleBox> panel_style;
int buttons_separation = 0;
+ int buttons_min_width = 0;
+ int buttons_min_height = 0;
} theme_cache;
void _custom_action(const String &p_action);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 12b2364ddf..0c146ce173 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -53,7 +53,7 @@ void FileDialog::_focus_file_text() {
int lp = file->get_text().rfind(".");
if (lp != -1) {
file->select(0, lp);
- if (file->is_inside_tree() && !get_tree()->is_node_being_edited(file)) {
+ if (file->is_inside_tree() && !is_part_of_edited_scene()) {
file->grab_focus();
}
}
@@ -1255,52 +1255,6 @@ int FileDialog::get_option_count() const {
return options.size();
}
-bool FileDialog::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) {
- int item_index = components[0].trim_prefix("option_").to_int();
- String property = components[1];
- if (property == "name") {
- set_option_name(item_index, p_value);
- return true;
- } else if (property == "values") {
- set_option_values(item_index, p_value);
- return true;
- } else if (property == "default") {
- set_option_default(item_index, p_value);
- return true;
- }
- }
- return false;
-}
-
-bool FileDialog::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) {
- int item_index = components[0].trim_prefix("option_").to_int();
- String property = components[1];
- if (property == "name") {
- r_ret = get_option_name(item_index);
- return true;
- } else if (property == "values") {
- r_ret = get_option_values(item_index);
- return true;
- } else if (property == "default") {
- r_ret = get_option_default(item_index);
- return true;
- }
- }
- return false;
-}
-
-void FileDialog::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < options.size(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("option_%d/name", i)));
- p_list->push_back(PropertyInfo(Variant::PACKED_STRING_ARRAY, vformat("option_%d/values", i)));
- p_list->push_back(PropertyInfo(Variant::INT, vformat("option_%d/default", i)));
- }
-}
-
void FileDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed);
@@ -1386,6 +1340,14 @@ void FileDialog::_bind_methods() {
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color", "Button");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color", "Button");
BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color", "Button");
+
+ Option defaults;
+
+ base_property_helper.set_prefix("option_");
+ base_property_helper.set_array_length_getter(&FileDialog::get_option_count);
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name);
+ base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default);
}
void FileDialog::set_show_hidden_files(bool p_show) {
@@ -1447,9 +1409,9 @@ FileDialog::FileDialog() {
hbc->add_child(dir_prev);
hbc->add_child(dir_next);
hbc->add_child(dir_up);
- dir_prev->connect("pressed", callable_mp(this, &FileDialog::_go_back));
- dir_next->connect("pressed", callable_mp(this, &FileDialog::_go_forward));
- dir_up->connect("pressed", callable_mp(this, &FileDialog::_go_up));
+ dir_prev->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_back));
+ dir_next->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_forward));
+ dir_up->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_go_up));
hbc->add_child(memnew(Label(ETR("Path:"))));
@@ -1468,7 +1430,7 @@ FileDialog::FileDialog() {
refresh = memnew(Button);
refresh->set_theme_type_variation("FlatButton");
refresh->set_tooltip_text(ETR("Refresh files."));
- refresh->connect("pressed", callable_mp(this, &FileDialog::update_file_list));
+ refresh->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::update_file_list));
hbc->add_child(refresh);
show_hidden = memnew(Button);
@@ -1485,7 +1447,7 @@ FileDialog::FileDialog() {
makedir = memnew(Button);
makedir->set_theme_type_variation("FlatButton");
makedir->set_tooltip_text(ETR("Create a new folder."));
- makedir->connect("pressed", callable_mp(this, &FileDialog::_make_dir));
+ makedir->connect(SceneStringName(pressed), callable_mp(this, &FileDialog::_make_dir));
hbc->add_child(makedir);
vbox->add_child(hbc);
@@ -1563,6 +1525,8 @@ FileDialog::FileDialog() {
if (register_func) {
register_func(this);
}
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
FileDialog::~FileDialog() {
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 7caae7e216..4236f0a56b 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -37,6 +37,7 @@
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
#include "scene/gui/tree.h"
+#include "scene/property_list_helper.h"
class GridContainer;
@@ -137,6 +138,10 @@ private:
Vector<String> values;
int default_idx = 0;
};
+
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
Vector<Option> options;
Dictionary selected_options;
bool options_dirty = false;
@@ -187,9 +192,11 @@ private:
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
- bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); }
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, options.size()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
public:
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
index 9f79da2905..ceffd9d103 100644
--- a/scene/gui/flow_container.cpp
+++ b/scene/gui/flow_container.cpp
@@ -38,6 +38,7 @@ struct _LineData {
int min_line_length = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0;
+ bool is_filled = false;
};
void FlowContainer::_resort() {
@@ -58,14 +59,12 @@ void FlowContainer::_resort() {
float line_stretch_ratio_total = 0;
int current_container_size = vertical ? get_rect().size.y : get_rect().size.x;
int children_in_current_line = 0;
+ Control *last_child = nullptr;
// First pass for line wrapping and minimum size calculation.
for (int i = 0; i < get_child_count(); i++) {
- Control *child = Object::cast_to<Control>(get_child(i));
- if (!child || !child->is_visible()) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i));
+ if (!child) {
continue;
}
@@ -77,7 +76,7 @@ void FlowContainer::_resort() {
}
if (ofs.y + child_msc.y > current_container_size) {
line_length = ofs.y - theme_cache.v_separation;
- lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true });
// Move in new column (vertical line).
ofs.x += line_height + theme_cache.h_separation;
@@ -99,7 +98,7 @@ void FlowContainer::_resort() {
}
if (ofs.x + child_msc.x > current_container_size) {
line_length = ofs.x - theme_cache.h_separation;
- lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true });
// Move in new line.
ofs.y += line_height + theme_cache.v_separation;
@@ -116,11 +115,16 @@ void FlowContainer::_resort() {
ofs.x += child_msc.x;
}
+ last_child = child;
children_minsize_cache[child] = child_msc;
children_in_current_line++;
}
line_length = vertical ? (ofs.y) : (ofs.x);
- lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
+ bool is_filled = false;
+ if (last_child != nullptr) {
+ is_filled = vertical ? (ofs.y + last_child->get_combined_minimum_size().y > current_container_size ? true : false) : (ofs.x + last_child->get_combined_minimum_size().x > current_container_size ? true : false);
+ }
+ lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, is_filled });
// Second pass for in-line expansion and alignment.
@@ -131,11 +135,8 @@ void FlowContainer::_resort() {
ofs.y = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *child = Object::cast_to<Control>(get_child(i));
- if (!child || !child->is_visible()) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i));
+ if (!child) {
continue;
}
Size2i child_size = children_minsize_cache[child];
@@ -158,17 +159,43 @@ void FlowContainer::_resort() {
// but only if the line doesn't contain a child that expands.
if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) {
int alignment_ofs = 0;
+ bool is_not_first_line_and_not_filled = current_line_idx != 0 && !line_data.is_filled;
+ float prior_stretch_avail = is_not_first_line_and_not_filled ? lines_data[current_line_idx - 1].stretch_avail : 0.0;
switch (alignment) {
- case ALIGNMENT_CENTER:
- alignment_ofs = line_data.stretch_avail / 2;
- break;
- case ALIGNMENT_END:
- alignment_ofs = line_data.stretch_avail;
- break;
+ case ALIGNMENT_BEGIN: {
+ if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && is_not_first_line_and_not_filled) {
+ if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
+ alignment_ofs = line_data.stretch_avail - prior_stretch_avail;
+ } else if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_CENTER) {
+ alignment_ofs = (line_data.stretch_avail - prior_stretch_avail) * 0.5;
+ }
+ }
+ } break;
+ case ALIGNMENT_CENTER: {
+ if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_CENTER && is_not_first_line_and_not_filled) {
+ if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
+ alignment_ofs = line_data.stretch_avail - (prior_stretch_avail * 0.5);
+ } else { // Is LAST_WRAP_ALIGNMENT_BEGIN
+ alignment_ofs = prior_stretch_avail * 0.5;
+ }
+ } else {
+ alignment_ofs = line_data.stretch_avail * 0.5;
+ }
+ } break;
+ case ALIGNMENT_END: {
+ if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_END && is_not_first_line_and_not_filled) {
+ if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_BEGIN) {
+ alignment_ofs = prior_stretch_avail;
+ } else { // Is LAST_WRAP_ALIGNMENT_CENTER
+ alignment_ofs = prior_stretch_avail + (line_data.stretch_avail - prior_stretch_avail) * 0.5;
+ }
+ } else {
+ alignment_ofs = line_data.stretch_avail;
+ }
+ } break;
default:
break;
}
-
if (vertical) { /* VERTICAL */
ofs.y += alignment_ofs;
} else { /* HORIZONTAL */
@@ -223,17 +250,10 @@ Size2 FlowContainer::get_minimum_size() const {
Size2i minimum;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
-
- if (!c->is_visible()) {
- continue;
- }
Size2i size = c->get_combined_minimum_size();
@@ -314,6 +334,18 @@ FlowContainer::AlignmentMode FlowContainer::get_alignment() const {
return alignment;
}
+void FlowContainer::set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment) {
+ if (last_wrap_alignment == p_last_wrap_alignment) {
+ return;
+ }
+ last_wrap_alignment = p_last_wrap_alignment;
+ _resort();
+}
+
+FlowContainer::LastWrapAlignmentMode FlowContainer::get_last_wrap_alignment() const {
+ return last_wrap_alignment;
+}
+
void FlowContainer::set_vertical(bool p_vertical) {
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
vertical = p_vertical;
@@ -346,6 +378,8 @@ void FlowContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment);
ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment);
+ ClassDB::bind_method(D_METHOD("set_last_wrap_alignment", "last_wrap_alignment"), &FlowContainer::set_last_wrap_alignment);
+ ClassDB::bind_method(D_METHOD("get_last_wrap_alignment"), &FlowContainer::get_last_wrap_alignment);
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical);
ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical);
ClassDB::bind_method(D_METHOD("set_reverse_fill", "reverse_fill"), &FlowContainer::set_reverse_fill);
@@ -354,8 +388,13 @@ void FlowContainer::_bind_methods() {
BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
BIND_ENUM_CONSTANT(ALIGNMENT_END);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_INHERIT);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_BEGIN);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_CENTER);
+ BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_END);
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "last_wrap_alignment", PROPERTY_HINT_ENUM, "Inherit,Begin,Center,End"), "set_last_wrap_alignment", "get_last_wrap_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverse_fill"), "set_reverse_fill", "is_reverse_fill");
diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h
index 90da73aaab..65ebc89c78 100644
--- a/scene/gui/flow_container.h
+++ b/scene/gui/flow_container.h
@@ -42,6 +42,12 @@ public:
ALIGNMENT_CENTER,
ALIGNMENT_END
};
+ enum LastWrapAlignmentMode {
+ LAST_WRAP_ALIGNMENT_INHERIT,
+ LAST_WRAP_ALIGNMENT_BEGIN,
+ LAST_WRAP_ALIGNMENT_CENTER,
+ LAST_WRAP_ALIGNMENT_END
+ };
private:
int cached_size = 0;
@@ -50,6 +56,7 @@ private:
bool vertical = false;
bool reverse_fill = false;
AlignmentMode alignment = ALIGNMENT_BEGIN;
+ LastWrapAlignmentMode last_wrap_alignment = LAST_WRAP_ALIGNMENT_INHERIT;
struct ThemeCache {
int h_separation = 0;
@@ -71,6 +78,9 @@ public:
void set_alignment(AlignmentMode p_alignment);
AlignmentMode get_alignment() const;
+ void set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment);
+ LastWrapAlignmentMode get_last_wrap_alignment() const;
+
void set_vertical(bool p_vertical);
bool is_vertical() const;
@@ -102,5 +112,6 @@ public:
};
VARIANT_ENUM_CAST(FlowContainer::AlignmentMode);
+VARIANT_ENUM_CAST(FlowContainer::LastWrapAlignmentMode);
#endif // FLOW_CONTAINER_H
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index ef9c7e35ed..c9f3fc1dfe 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -501,7 +501,7 @@ void GraphEdit::_graph_element_resize_request(const Vector2 &p_new_minsize, Node
// Snap the new size to the grid if snapping is enabled.
Vector2 new_size = p_new_minsize;
if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
- new_size = new_size.snapped(Vector2(snapping_distance, snapping_distance));
+ new_size = new_size.snappedf(snapping_distance);
}
// Disallow resizing the frame to a size smaller than the minimum size of the attached nodes.
@@ -601,7 +601,7 @@ void GraphEdit::add_child_notify(Node *p_child) {
GraphNode *graph_node = Object::cast_to<GraphNode>(graph_element);
if (graph_node) {
graph_node->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(graph_element));
- graph_node->connect("item_rect_changed", callable_mp(this, &GraphEdit::_graph_node_rect_changed).bind(graph_node));
+ graph_node->connect(SceneStringName(item_rect_changed), callable_mp(this, &GraphEdit::_graph_node_rect_changed).bind(graph_node));
_ensure_node_order_from(graph_node);
}
@@ -618,8 +618,8 @@ void GraphEdit::add_child_notify(Node *p_child) {
}
graph_element->connect("raise_request", callable_mp(this, &GraphEdit::_ensure_node_order_from).bind(graph_element));
graph_element->connect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resize_request).bind(graph_element));
- graph_element->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw));
- graph_element->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw));
+ graph_element->connect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw));
+ graph_element->connect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw));
graph_element->set_scale(Vector2(zoom, zoom));
_graph_element_moved(graph_element);
@@ -651,7 +651,7 @@ void GraphEdit::remove_child_notify(Node *p_child) {
GraphNode *graph_node = Object::cast_to<GraphNode>(graph_element);
if (graph_node) {
graph_node->disconnect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated));
- graph_node->disconnect("item_rect_changed", callable_mp(this, &GraphEdit::_graph_node_rect_changed));
+ graph_node->disconnect(SceneStringName(item_rect_changed), callable_mp(this, &GraphEdit::_graph_node_rect_changed));
// Invalidate all adjacent connections, so that they are removed before the next redraw.
for (const Ref<Connection> &conn : connection_map[graph_node->get_name()]) {
@@ -692,7 +692,7 @@ void GraphEdit::remove_child_notify(Node *p_child) {
// In case of the whole GraphEdit being destroyed these references can already be freed.
if (minimap != nullptr && minimap->is_inside_tree()) {
- graph_element->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw));
+ graph_element->disconnect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw));
}
}
}
@@ -851,7 +851,7 @@ void GraphEdit::_set_position_of_frame_attached_nodes(GraphFrame *p_frame, const
Vector2 pos = (attached_node->get_drag_from() * zoom + drag_accum) / zoom;
if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
- pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
+ pos = pos.snappedf(snapping_distance);
}
// Recursively move graph frames.
@@ -898,6 +898,12 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
return true;
}
}
+
+ // This prevents interactions with a port hotzone that is behind another node.
+ Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom);
+ if (graph_node_rect.has_point(click_pos * zoom)) {
+ break;
+ }
}
return false;
@@ -1027,6 +1033,12 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
return;
}
}
+
+ // This prevents interactions with a port hotzone that is behind another node.
+ Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom);
+ if (graph_node_rect.has_point(click_pos * zoom)) {
+ break;
+ }
}
}
@@ -1059,7 +1071,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
int type = graph_node->get_output_port_type(j);
- if ((type == connecting_type ||
+ if ((type == connecting_type || graph_node->is_ignoring_valid_connection_type() ||
valid_connection_types.has(ConnectionType(type, connecting_type))) &&
is_in_output_hotzone(graph_node, j, mpos, port_size)) {
if (!is_node_hover_valid(graph_node->get_name(), j, connecting_from_node, connecting_from_port_index)) {
@@ -1084,7 +1096,7 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
int type = graph_node->get_input_port_type(j);
- if ((type == connecting_type || valid_connection_types.has(ConnectionType(connecting_type, type))) &&
+ if ((type == connecting_type || graph_node->is_ignoring_valid_connection_type() || valid_connection_types.has(ConnectionType(connecting_type, type))) &&
is_in_input_hotzone(graph_node, j, mpos, port_size)) {
if (!is_node_hover_valid(connecting_from_node, connecting_from_port_index, graph_node->get_name(), j)) {
continue;
@@ -1117,6 +1129,8 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) {
emit_signal(SNAME("connection_from_empty"), connecting_from_node, connecting_from_port_index, mb->get_position());
}
}
+ } else {
+ set_selected(get_node_or_null(NodePath(connecting_from_node)));
}
if (connecting) {
@@ -1636,12 +1650,12 @@ void GraphEdit::_draw_grid() {
void GraphEdit::set_selected(Node *p_child) {
for (int i = get_child_count() - 1; i >= 0; i--) {
- GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i));
- if (!graph_node) {
+ GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i));
+ if (!graph_element) {
continue;
}
- graph_node->set_selected(graph_node == p_child);
+ graph_element->set_selected(graph_element == p_child);
}
}
@@ -1678,7 +1692,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
// Snapping can be toggled temporarily by holding down Ctrl.
// This is done here as to not toggle the grid when holding down Ctrl.
if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
- pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
+ pos = pos.snappedf(snapping_distance);
}
graph_element->set_position_offset(pos);
@@ -2755,12 +2769,12 @@ GraphEdit::GraphEdit() {
add_child(top_layer, false, INTERNAL_MODE_BACK);
top_layer->set_mouse_filter(MOUSE_FILTER_IGNORE);
top_layer->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
- top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw));
- top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
+ top_layer->connect(SceneStringName(draw), callable_mp(this, &GraphEdit::_top_layer_draw));
+ top_layer->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
connections_layer = memnew(Control);
add_child(connections_layer, false);
- connections_layer->connect("draw", callable_mp(this, &GraphEdit::_update_connections));
+ connections_layer->connect(SceneStringName(draw), callable_mp(this, &GraphEdit::_update_connections));
connections_layer->set_name("_connection_layer");
connections_layer->set_disable_visibility_clip(true); // Necessary, so it can draw freely and be offset.
connections_layer->set_mouse_filter(MOUSE_FILTER_IGNORE);
@@ -2772,7 +2786,7 @@ GraphEdit::GraphEdit() {
top_connection_layer->set_mouse_filter(MOUSE_FILTER_PASS);
top_connection_layer->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
- top_connection_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_connection_layer_input));
+ top_connection_layer->connect(SceneStringName(gui_input), callable_mp(this, &GraphEdit::_top_connection_layer_input));
dragged_connection_line = memnew(Line2D);
dragged_connection_line->set_texture_mode(Line2D::LINE_TEXTURE_STRETCH);
@@ -2822,7 +2836,7 @@ GraphEdit::GraphEdit() {
zoom_minus_button->set_tooltip_text(ETR("Zoom Out"));
zoom_minus_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(zoom_minus_button);
- zoom_minus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus));
+ zoom_minus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_minus));
zoom_reset_button = memnew(Button);
zoom_reset_button->set_theme_type_variation("FlatButton");
@@ -2830,7 +2844,7 @@ GraphEdit::GraphEdit() {
zoom_reset_button->set_tooltip_text(ETR("Zoom Reset"));
zoom_reset_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(zoom_reset_button);
- zoom_reset_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset));
+ zoom_reset_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_reset));
zoom_plus_button = memnew(Button);
zoom_plus_button->set_theme_type_variation("FlatButton");
@@ -2838,7 +2852,7 @@ GraphEdit::GraphEdit() {
zoom_plus_button->set_tooltip_text(ETR("Zoom In"));
zoom_plus_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(zoom_plus_button);
- zoom_plus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus));
+ zoom_plus_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_zoom_plus));
// Grid controls.
@@ -2850,7 +2864,7 @@ GraphEdit::GraphEdit() {
toggle_grid_button->set_tooltip_text(ETR("Toggle the visual grid."));
toggle_grid_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(toggle_grid_button);
- toggle_grid_button->connect("pressed", callable_mp(this, &GraphEdit::_show_grid_toggled));
+ toggle_grid_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_show_grid_toggled));
toggle_snapping_button = memnew(Button);
toggle_snapping_button->set_theme_type_variation("FlatButton");
@@ -2860,7 +2874,7 @@ GraphEdit::GraphEdit() {
toggle_snapping_button->set_pressed(snapping_enabled);
toggle_snapping_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(toggle_snapping_button);
- toggle_snapping_button->connect("pressed", callable_mp(this, &GraphEdit::_snapping_toggled));
+ toggle_snapping_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_snapping_toggled));
snapping_distance_spinbox = memnew(SpinBox);
snapping_distance_spinbox->set_visible(show_grid_buttons);
@@ -2882,12 +2896,12 @@ GraphEdit::GraphEdit() {
minimap_button->set_pressed(show_grid);
minimap_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(minimap_button);
- minimap_button->connect("pressed", callable_mp(this, &GraphEdit::_minimap_toggled));
+ minimap_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::_minimap_toggled));
arrange_button = memnew(Button);
arrange_button->set_theme_type_variation("FlatButton");
arrange_button->set_visible(show_arrange_button);
- arrange_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes));
+ arrange_button->connect(SceneStringName(pressed), callable_mp(this, &GraphEdit::arrange_nodes));
arrange_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(arrange_button);
arrange_button->set_tooltip_text(ETR("Automatically arrange selected nodes."));
@@ -2909,7 +2923,7 @@ GraphEdit::GraphEdit() {
minimap->set_offset(Side::SIDE_TOP, -minimap_size.height - MINIMAP_OFFSET);
minimap->set_offset(Side::SIDE_RIGHT, -MINIMAP_OFFSET);
minimap->set_offset(Side::SIDE_BOTTOM, -MINIMAP_OFFSET);
- minimap->connect("draw", callable_mp(this, &GraphEdit::_minimap_draw));
+ minimap->connect(SceneStringName(draw), callable_mp(this, &GraphEdit::_minimap_draw));
set_clip_contents(true);
diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp
index 49998beb42..fa1059c667 100644
--- a/scene/gui/graph_edit_arranger.cpp
+++ b/scene/gui/graph_edit_arranger.cpp
@@ -180,7 +180,7 @@ void GraphEditArranger::arrange_nodes() {
if (graph_edit->is_snapping_enabled()) {
float snapping_distance = graph_edit->get_snapping_distance();
- pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
+ pos = pos.snappedf(snapping_distance);
}
graph_node->set_position_offset(pos);
graph_node->set_drag(false);
diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp
index 9c13a3ad9e..e231b05d7f 100644
--- a/scene/gui/graph_element.cpp
+++ b/scene/gui/graph_element.cpp
@@ -49,14 +49,10 @@ void GraphElement::_resort() {
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *child = Object::cast_to<Control>(get_child(i));
- if (!child || !child->is_visible_in_tree()) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i));
+ if (!child) {
continue;
}
-
fit_child_in_rect(child, Rect2(Point2(), size));
}
}
@@ -65,10 +61,7 @@ Size2 GraphElement::get_minimum_size() const {
Size2 minsize;
for (int i = 0; i < get_child_count(); i++) {
Control *child = Object::cast_to<Control>(get_child(i));
- if (!child) {
- continue;
- }
- if (child->is_set_as_top_level()) {
+ if (!child || child->is_set_as_top_level()) {
continue;
}
diff --git a/scene/gui/graph_frame.cpp b/scene/gui/graph_frame.cpp
index 288b8ba66d..8cd7dbbeb5 100644
--- a/scene/gui/graph_frame.cpp
+++ b/scene/gui/graph_frame.cpp
@@ -118,7 +118,7 @@ void GraphFrame::_notification(int p_what) {
sb_panel_flat->set_border_color(selected ? original_border_color : tint_color.lightened(0.3));
draw_style_box(sb_panel_flat, body_rect);
} else if (sb_panel_texture.is_valid()) {
- sb_panel_texture = sb_panel_flat->duplicate();
+ sb_panel_texture = sb_panel_texture->duplicate();
sb_panel_texture->set_modulate(tint_color);
draw_style_box(sb_panel_texture, body_rect);
}
@@ -160,14 +160,10 @@ void GraphFrame::_resort() {
Point2 offset = Point2(sb_panel->get_margin(SIDE_LEFT), sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height);
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible_in_tree()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
- if (child->is_set_as_top_level()) {
- continue;
- }
-
fit_child_in_rect(child, Rect2(offset, size));
}
}
@@ -325,8 +321,8 @@ Size2 GraphFrame::get_minimum_size() const {
Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size();
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index 1b960a9b62..d804f83e1c 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -175,9 +175,8 @@ void GraphNode::_resort() {
HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
-
- if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
@@ -218,8 +217,8 @@ void GraphNode::_resort() {
bool refit_successful = true;
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
@@ -256,8 +255,8 @@ void GraphNode::_resort() {
int width = new_size.width - sb_panel->get_minimum_size().width;
int valid_children_idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
@@ -336,7 +335,8 @@ void GraphNode::_notification(int p_what) {
int width = get_size().width - sb_panel->get_minimum_size().x;
- if (get_child_count() > 0) {
+ // Take the HboxContainer child into account.
+ if (get_child_count(false) > 0) {
int slot_index = 0;
for (const KeyValue<int, Slot> &E : slot_table) {
if (E.key < 0 || E.key >= slot_y_cache.size()) {
@@ -604,6 +604,14 @@ void GraphNode::set_slot_draw_stylebox(int p_slot_index, bool p_enable) {
emit_signal(SNAME("slot_updated"), p_slot_index);
}
+void GraphNode::set_ignore_invalid_connection_type(bool p_ignore) {
+ ignore_invalid_connection_type = p_ignore;
+}
+
+bool GraphNode::is_ignoring_valid_connection_type() const {
+ return ignore_invalid_connection_type;
+}
+
Size2 GraphNode::get_minimum_size() const {
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
@@ -613,8 +621,8 @@ Size2 GraphNode::get_minimum_size() const {
Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size();
for (int i = 0; i < get_child_count(false); i++) {
- Control *child = Object::cast_to<Control>(get_child(i, false));
- if (!child || !child->is_visible() || child->is_set_as_top_level()) {
+ Control *child = as_sortable_control(get_child(i, false));
+ if (!child) {
continue;
}
@@ -647,6 +655,7 @@ void GraphNode::_port_pos_update() {
left_port_cache.clear();
right_port_cache.clear();
int vertical_ofs = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height + sb_panel->get_margin(SIDE_TOP);
+ int slot_index = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = Object::cast_to<Control>(get_child(i, false));
@@ -656,27 +665,28 @@ void GraphNode::_port_pos_update() {
Size2i size = child->get_rect().size;
- if (slot_table.has(i)) {
- if (slot_table[i].enable_left) {
+ if (slot_table.has(slot_index)) {
+ if (slot_table[slot_index].enable_left) {
PortCache port_cache;
port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2);
- port_cache.type = slot_table[i].type_left;
- port_cache.color = slot_table[i].color_left;
- port_cache.slot_index = child->get_index(false);
+ port_cache.type = slot_table[slot_index].type_left;
+ port_cache.color = slot_table[slot_index].color_left;
+ port_cache.slot_index = slot_index;
left_port_cache.push_back(port_cache);
}
- if (slot_table[i].enable_right) {
+ if (slot_table[slot_index].enable_right) {
PortCache port_cache;
port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2);
- port_cache.type = slot_table[i].type_right;
- port_cache.color = slot_table[i].color_right;
- port_cache.slot_index = child->get_index(false);
+ port_cache.type = slot_table[slot_index].type_right;
+ port_cache.color = slot_table[slot_index].color_right;
+ port_cache.slot_index = slot_index;
right_port_cache.push_back(port_cache);
}
}
vertical_ofs += separation;
vertical_ofs += size.height;
+ slot_index++;
}
port_pos_dirty = false;
@@ -857,6 +867,9 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "slot_index"), &GraphNode::is_slot_draw_stylebox);
ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "slot_index", "enable"), &GraphNode::set_slot_draw_stylebox);
+ ClassDB::bind_method(D_METHOD("set_ignore_invalid_connection_type", "ignore"), &GraphNode::set_ignore_invalid_connection_type);
+ ClassDB::bind_method(D_METHOD("is_ignoring_valid_connection_type"), &GraphNode::is_ignoring_valid_connection_type);
+
ClassDB::bind_method(D_METHOD("get_input_port_count"), &GraphNode::get_input_port_count);
ClassDB::bind_method(D_METHOD("get_input_port_position", "port_idx"), &GraphNode::get_input_port_position);
ClassDB::bind_method(D_METHOD("get_input_port_type", "port_idx"), &GraphNode::get_input_port_type);
@@ -872,6 +885,8 @@ void GraphNode::_bind_methods() {
GDVIRTUAL_BIND(_draw_port, "slot_index", "position", "left", "color")
ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_invalid_connection_type"), "set_ignore_invalid_connection_type", "is_ignoring_valid_connection_type");
+
ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "slot_index")));
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel);
diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h
index 71cc322baa..27af3192c8 100644
--- a/scene/gui/graph_node.h
+++ b/scene/gui/graph_node.h
@@ -95,6 +95,8 @@ class GraphNode : public GraphElement {
bool port_pos_dirty = true;
+ bool ignore_invalid_connection_type = false;
+
void _port_pos_update();
protected:
@@ -147,6 +149,9 @@ public:
bool is_slot_draw_stylebox(int p_slot_index) const;
void set_slot_draw_stylebox(int p_slot_index, bool p_enable);
+ void set_ignore_invalid_connection_type(bool p_ignore);
+ bool is_ignoring_valid_connection_type() const;
+
int get_input_port_count();
Vector2 get_input_port_position(int p_port_idx);
int get_input_port_type(int p_port_idx);
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
index a4baf3bb8d..a67bba786b 100644
--- a/scene/gui/grid_container.cpp
+++ b/scene/gui/grid_container.cpp
@@ -44,11 +44,8 @@ void GridContainer::_notification(int p_what) {
// Compute the per-column/per-row data.
int valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -186,8 +183,8 @@ void GridContainer::_notification(int p_what) {
valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
int row = valid_controls_index / columns;
@@ -282,8 +279,8 @@ Size2 GridContainer::get_minimum_size() const {
int valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
int row = valid_controls_index / columns;
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 8376ef48b6..cfb46aebc8 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -1882,6 +1882,7 @@ void ItemList::_bind_methods() {
Item defaults(true);
base_property_helper.set_prefix("item_");
+ base_property_helper.set_array_length_getter(&ItemList::get_item_count);
base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &ItemList::set_item_text, &ItemList::get_item_text);
base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &ItemList::set_item_icon, &ItemList::get_item_icon);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "selectable"), defaults.selectable, &ItemList::set_item_selectable, &ItemList::is_item_selectable);
@@ -1893,7 +1894,7 @@ ItemList::ItemList() {
add_child(scroll_bar, false, INTERNAL_MODE_FRONT);
scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed));
- connect("mouse_exited", callable_mp(this, &ItemList::_mouse_exited));
+ connect(SceneStringName(mouse_exited), callable_mp(this, &ItemList::_mouse_exited));
set_focus_mode(FOCUS_ALL);
set_clip_contents(true);
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 1a94d92855..729e219825 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -771,7 +771,7 @@ void LineEdit::_notification(int p_what) {
switch (p_what) {
#ifdef TOOLS_ENABLED
case NOTIFICATION_ENTER_TREE: {
- if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) {
+ if (Engine::get_singleton()->is_editor_hint() && !is_part_of_edited_scene()) {
set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink"));
set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval"));
@@ -1805,7 +1805,6 @@ void LineEdit::clear_internal() {
}
Size2 LineEdit::get_minimum_size() const {
- Ref<StyleBox> style = theme_cache.normal;
Ref<Font> font = theme_cache.font;
int font_size = theme_cache.font_size;
@@ -1834,7 +1833,8 @@ Size2 LineEdit::get_minimum_size() const {
}
min_size.width += icon_max_width;
- return style->get_minimum_size() + min_size;
+ Size2 style_min_size = theme_cache.normal->get_minimum_size().max(theme_cache.read_only->get_minimum_size());
+ return style_min_size + min_size;
}
void LineEdit::deselect() {
@@ -2473,8 +2473,8 @@ void LineEdit::_generate_context_menu() {
menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option));
- menu->connect(SNAME("focus_entered"), callable_mp(this, &LineEdit::_validate_caret_can_draw));
- menu->connect(SNAME("focus_exited"), callable_mp(this, &LineEdit::_validate_caret_can_draw));
+ menu->connect(SceneStringName(focus_entered), callable_mp(this, &LineEdit::_validate_caret_can_draw));
+ menu->connect(SceneStringName(focus_exited), callable_mp(this, &LineEdit::_validate_caret_can_draw));
}
void LineEdit::_update_context_menu() {
diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp
index 6d331afbb5..06e4a7cc13 100644
--- a/scene/gui/margin_container.cpp
+++ b/scene/gui/margin_container.cpp
@@ -37,13 +37,7 @@ Size2 MarginContainer::get_minimum_size() const {
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
- if (!c) {
- continue;
- }
- if (c->is_set_as_top_level()) {
- continue;
- }
- if (!c->is_visible()) {
+ if (!c || !c->is_visible() || c->is_set_as_top_level()) {
continue;
}
@@ -103,13 +97,10 @@ void MarginContainer::_notification(int p_what) {
Size2 s = get_size();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
int w = s.width - theme_cache.margin_left - theme_cache.margin_right;
int h = s.height - theme_cache.margin_top - theme_cache.margin_bottom;
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 89c627a7a8..998f99b2f9 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -153,54 +153,25 @@ void MenuButton::_notification(int p_what) {
}
bool MenuButton::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
+ const String sname = p_name;
+ if (property_helper.is_property_valid(sname)) {
bool valid;
- popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid);
+ popup->set(sname.trim_prefix("popup/"), p_value, &valid);
return valid;
}
return false;
}
bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
+ const String sname = p_name;
+ if (property_helper.is_property_valid(sname)) {
bool valid;
- r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid);
+ r_ret = popup->get(sname.trim_prefix("popup/"), &valid);
return valid;
}
return false;
}
-void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < popup->get_item_count(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i)));
-
- PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
- pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button");
- pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i));
- pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i));
- pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i));
- pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
- }
-}
-
void MenuButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup);
@@ -215,6 +186,18 @@ void MenuButton::_bind_methods() {
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
ADD_SIGNAL(MethodInfo("about_to_popup"));
+
+ PopupMenu::Item defaults(true);
+
+ base_property_helper.set_prefix("popup/item_");
+ base_property_helper.set_array_length_getter(&MenuButton::get_item_count);
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text);
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator);
}
void MenuButton::set_disable_shortcuts(bool p_disabled) {
@@ -235,6 +218,8 @@ MenuButton::MenuButton(const String &p_text) :
add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true));
popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false));
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
MenuButton::~MenuButton() {
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
index eea6b8e877..2bd577ddd0 100644
--- a/scene/gui/menu_button.h
+++ b/scene/gui/menu_button.h
@@ -33,6 +33,7 @@
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
+#include "scene/property_list_helper.h"
class MenuButton : public Button {
GDCLASS(MenuButton, Button);
@@ -42,13 +43,18 @@ class MenuButton : public Button {
bool disable_shortcuts = false;
PopupMenu *popup = nullptr;
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
void _popup_visibility_changed(bool p_visible);
protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp
index e2ae824e60..f9181d2a2d 100644
--- a/scene/gui/nine_patch_rect.cpp
+++ b/scene/gui/nine_patch_rect.cpp
@@ -30,7 +30,6 @@
#include "nine_patch_rect.h"
-#include "scene/scene_string_names.h"
#include "servers/rendering_server.h"
void NinePatchRect::_notification(int p_what) {
@@ -111,7 +110,7 @@ void NinePatchRect::set_texture(const Ref<Texture2D> &p_tex) {
queue_redraw();
update_minimum_size();
- emit_signal(SceneStringNames::get_singleton()->texture_changed);
+ emit_signal(SceneStringName(texture_changed));
}
Ref<Texture2D> NinePatchRect::get_texture() const {
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 0e10652f07..68e72ea996 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -60,7 +60,7 @@ Size2 OptionButton::get_minimum_size() const {
}
if (has_theme_icon(SNAME("arrow"))) {
- const Size2 padding = _get_current_stylebox()->get_minimum_size();
+ const Size2 padding = _get_largest_stylebox_size();
const Size2 arrow_size = theme_cache.arrow_icon->get_size();
Size2 content_size = minsize - padding;
@@ -154,23 +154,20 @@ void OptionButton::_notification(int p_what) {
}
bool OptionButton::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
- const String &property = components[2];
- if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") {
- return false;
- }
+ int index;
+ const String sname = p_name;
+ if (property_helper.is_property_valid(sname, &index)) {
bool valid;
- popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid);
+ popup->set(sname.trim_prefix("popup/"), p_value, &valid);
- int idx = components[1].get_slice("_", 1).to_int();
- if (idx == current) {
+ if (index == current) {
// Force refreshing currently displayed item.
current = NONE_SELECTED;
- _select(idx, false);
+ _select(index, false);
}
+ const String property = sname.get_slice("/", 2);
if (property == "text" || property == "icon") {
_queue_update_size_cache();
}
@@ -180,42 +177,6 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
-bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0] == "popup") {
- const String &property = components[2];
- if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") {
- return false;
- }
-
- bool valid;
- r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid);
- return valid;
- }
- return false;
-}
-
-void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < popup->get_item_count(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i)));
-
- PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
- pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i));
- pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i));
- pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
- }
-}
-
void OptionButton::_focused(int p_which) {
emit_signal(SNAME("item_focused"), p_which);
}
@@ -606,6 +567,16 @@ void OptionButton::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, OptionButton, arrow_icon, "arrow");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, arrow_margin);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, modulate_arrow);
+
+ PopupMenu::Item defaults(true);
+
+ base_property_helper.set_prefix("popup/item_");
+ base_property_helper.set_array_length_getter(&OptionButton::get_item_count);
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text);
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon);
+ base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator);
}
void OptionButton::set_disable_shortcuts(bool p_disabled) {
@@ -625,6 +596,8 @@ OptionButton::OptionButton(const String &p_text) :
popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));
popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));
popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false));
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
OptionButton::~OptionButton() {
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 9c15b295a9..4b5164161a 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -33,6 +33,7 @@
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
+#include "scene/property_list_helper.h"
class OptionButton : public Button {
GDCLASS(OptionButton, Button);
@@ -64,11 +65,15 @@ class OptionButton : public Button {
int modulate_arrow = 0;
} theme_cache;
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
void _focused(int p_which);
void _selected(int p_which);
void _select(int p_which, bool p_emit = false);
void _select_int(int p_which);
void _refresh_size_cache();
+ void _dummy_setter() {} // Stub for PropertyListHelper (_set() doesn't use it).
virtual void pressed() override;
@@ -78,8 +83,10 @@ protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp
index ef85919859..76fde26b26 100644
--- a/scene/gui/panel_container.cpp
+++ b/scene/gui/panel_container.cpp
@@ -35,11 +35,8 @@
Size2 PanelContainer::get_minimum_size() const {
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
@@ -87,11 +84,8 @@ void PanelContainer::_notification(int p_what) {
}
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index 0927404947..38204af6d5 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -37,6 +37,7 @@
void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
+ hide_reason = HIDE_REASON_CANCELED; // ESC pressed, mark as canceled unconditionally.
_close_pressed();
}
Window::_input_from_window(p_event);
@@ -51,8 +52,8 @@ void Popup::_initialize_visible_parents() {
parent_window = parent_window->get_parent_visible_window();
if (parent_window) {
visible_parents.push_back(parent_window);
- parent_window->connect("focus_entered", callable_mp(this, &Popup::_parent_focused));
- parent_window->connect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents));
+ parent_window->connect(SceneStringName(focus_entered), callable_mp(this, &Popup::_parent_focused));
+ parent_window->connect(SceneStringName(tree_exited), callable_mp(this, &Popup::_deinitialize_visible_parents));
}
}
}
@@ -61,8 +62,8 @@ void Popup::_initialize_visible_parents() {
void Popup::_deinitialize_visible_parents() {
if (is_embedded()) {
for (Window *parent_window : visible_parents) {
- parent_window->disconnect("focus_entered", callable_mp(this, &Popup::_parent_focused));
- parent_window->disconnect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents));
+ parent_window->disconnect(SceneStringName(focus_entered), callable_mp(this, &Popup::_parent_focused));
+ parent_window->disconnect(SceneStringName(tree_exited), callable_mp(this, &Popup::_deinitialize_visible_parents));
}
visible_parents.clear();
@@ -77,6 +78,9 @@ void Popup::_notification(int p_what) {
_initialize_visible_parents();
} else {
_deinitialize_visible_parents();
+ if (hide_reason == HIDE_REASON_NONE) {
+ hide_reason = HIDE_REASON_CANCELED;
+ }
emit_signal(SNAME("popup_hide"));
popped_up = false;
}
@@ -87,6 +91,7 @@ void Popup::_notification(int p_what) {
if (!is_in_edited_scene_root()) {
if (has_focus()) {
popped_up = true;
+ hide_reason = HIDE_REASON_NONE;
}
}
} break;
@@ -100,12 +105,18 @@ void Popup::_notification(int p_what) {
case NOTIFICATION_WM_CLOSE_REQUEST: {
if (!is_in_edited_scene_root()) {
+ if (hide_reason == HIDE_REASON_NONE) {
+ hide_reason = HIDE_REASON_UNFOCUSED;
+ }
_close_pressed();
}
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
if (!is_in_edited_scene_root() && get_flag(FLAG_POPUP)) {
+ if (hide_reason == HIDE_REASON_NONE) {
+ hide_reason = HIDE_REASON_UNFOCUSED;
+ }
_close_pressed();
}
} break;
@@ -114,6 +125,9 @@ void Popup::_notification(int p_what) {
void Popup::_parent_focused() {
if (popped_up && get_flag(FLAG_POPUP)) {
+ if (hide_reason == HIDE_REASON_NONE) {
+ hide_reason = HIDE_REASON_UNFOCUSED;
+ }
_close_pressed();
}
}
@@ -194,8 +208,6 @@ Rect2i Popup::_popup_adjust_rect() const {
void Popup::_bind_methods() {
ADD_SIGNAL(MethodInfo("popup_hide"));
-
- BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Popup, panel_style, "panel");
}
Popup::Popup() {
@@ -232,7 +244,8 @@ Size2 PopupPanel::_get_contents_minimum_size() const {
void PopupPanel::_update_child_rects() {
Vector2 cpos(theme_cache.panel_style->get_offset());
- Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size());
+ Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor();
+ Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size();
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -246,7 +259,7 @@ void PopupPanel::_update_child_rects() {
if (c == panel) {
c->set_position(Vector2());
- c->set_size(get_size());
+ c->set_size(panel_size);
} else {
c->set_position(cpos);
c->set_size(csize);
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index 25edca3657..69a81ad98c 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -43,9 +43,15 @@ class Popup : public Window {
LocalVector<Window *> visible_parents;
bool popped_up = false;
- struct ThemeCache {
- Ref<StyleBox> panel_style;
- } theme_cache;
+public:
+ enum HideReason {
+ HIDE_REASON_NONE,
+ HIDE_REASON_CANCELED, // E.g., because of rupture of UI flow (app unfocused). Includes closed programmatically.
+ HIDE_REASON_UNFOCUSED, // E.g., user clicked outside.
+ };
+
+private:
+ HideReason hide_reason = HIDE_REASON_NONE;
void _initialize_visible_parents();
void _deinitialize_visible_parents();
@@ -64,6 +70,8 @@ protected:
virtual void _post_popup() override;
public:
+ HideReason get_hide_reason() const { return hide_reason; }
+
Popup();
~Popup();
};
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 56346f5edc..bdd0102b63 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -276,6 +276,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
}
}
+ minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing.
return minsize;
}
@@ -312,18 +313,17 @@ int PopupMenu::_get_items_total_height() const {
}
int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
- if (p_over.x < 0 || p_over.x >= get_size().width || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP)) {
+ float win_scale = get_content_scale_factor();
+ if (p_over.x < 0 || p_over.x >= get_size().width * win_scale || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP) * win_scale) {
return -1;
}
- Point2 ofs;
+ Point2 ofs = Point2(0, theme_cache.v_separation * 0.5) * win_scale;
for (int i = 0; i < items.size(); i++) {
- ofs.y += theme_cache.v_separation;
-
- ofs.y += _get_item_height(i);
-
- if (p_over.y - control->get_position().y < ofs.y) {
+ ofs.y += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5;
+ ofs.y += _get_item_height(i) * win_scale;
+ if (p_over.y - control->get_position().y * win_scale < ofs.y) {
return i;
}
}
@@ -341,15 +341,17 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
Rect2 this_rect(this_pos, get_size());
float scroll_offset = control->get_position().y;
+ float scaled_ofs_cache = items[p_over]._ofs_cache * get_content_scale_factor();
+ float scaled_height_cache = items[p_over]._height_cache * get_content_scale_factor();
submenu_popup->reset_size(); // Shrink the popup size to its contents.
Size2 submenu_size = submenu_popup->get_size();
Point2 submenu_pos;
if (control->is_layout_rtl()) {
- submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2);
+ submenu_pos = this_pos + Point2(-submenu_size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2);
} else {
- submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2);
+ submenu_pos = this_pos + Point2(this_rect.size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2);
}
// Fix pos if going outside parent rect.
@@ -386,8 +388,8 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
// Set autohide areas.
Rect2 safe_area = this_rect;
- safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2;
- safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation;
+ safe_area.position.y += scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2;
+ safe_area.size.y = scaled_height_cache + theme_cache.v_separation;
Viewport *vp = submenu_popup->get_embedder();
if (vp) {
vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area);
@@ -400,11 +402,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
// Autohide area above the submenu item.
submenu_pum->clear_autohide_areas();
- submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2));
+ submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2));
// If there is an area below the submenu item, add an autohide area there.
- if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
- int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height;
+ if (scaled_ofs_cache + scaled_height_cache + scroll_offset <= control->get_size().height) {
+ int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height;
submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
}
}
@@ -576,6 +578,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
}
item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width;
}
+ item_clickable_area.size = item_clickable_area.size * get_content_scale_factor();
Ref<InputEventMouseButton> b = p_event;
@@ -640,11 +643,17 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) {
for (const Rect2 &E : autohide_areas) {
if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) {
+ // The mouse left the safe area, prepare to close.
_close_pressed();
return;
}
}
+ if (!minimum_lifetime_timer->is_stopped()) {
+ // The mouse left the safe area, but came back again, so cancel the auto-closing.
+ minimum_lifetime_timer->stop();
+ }
+
if (!item_clickable_area.has_point(m->get_position())) {
return;
}
@@ -1005,9 +1014,6 @@ void PopupMenu::_notification(int p_what) {
float pm_delay = pm->get_submenu_popup_delay();
set_submenu_popup_delay(pm_delay);
}
- if (!is_embedded()) {
- set_flag(FLAG_NO_FOCUS, true);
- }
if (system_menu_id != NativeMenu::INVALID_MENU_ID) {
bind_global_menu();
}
@@ -2799,6 +2805,7 @@ void PopupMenu::_bind_methods() {
Item defaults(true);
base_property_helper.set_prefix("item_");
+ base_property_helper.set_array_length_getter(&PopupMenu::get_item_count);
base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &PopupMenu::set_item_text, &PopupMenu::get_item_text);
base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &PopupMenu::set_item_icon, &PopupMenu::get_item_icon);
base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"), defaults.checkable_type, &PopupMenu::_set_item_checkable_type, &PopupMenu::_get_item_checkable_type);
@@ -2819,8 +2826,18 @@ void PopupMenu::popup(const Rect2i &p_bounds) {
if (native) {
NativeMenu::get_singleton()->popup(global_menu, (p_bounds != Rect2i()) ? p_bounds.position : get_position());
} else {
+ set_flag(FLAG_NO_FOCUS, !is_embedded());
+
moved = Vector2();
popup_time_msec = OS::get_singleton()->get_ticks_msec();
+ if (!is_embedded()) {
+ float win_scale = get_parent_visible_window()->get_content_scale_factor();
+ set_content_scale_factor(win_scale);
+ Size2 minsize = get_contents_minimum_size() * win_scale;
+ minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing.
+ set_min_size(minsize); // `height` is truncated here by the cast to Size2i for Window.min_size.
+ set_size(Vector2(0, 0)); // Shrinkwraps to min size.
+ }
Popup::popup(p_bounds);
}
}
@@ -2838,6 +2855,8 @@ void PopupMenu::set_visible(bool p_visible) {
NativeMenu::get_singleton()->popup(global_menu, get_position());
}
} else {
+ set_flag(FLAG_NO_FOCUS, !is_embedded());
+
Popup::set_visible(p_visible);
}
}
@@ -2856,7 +2875,7 @@ PopupMenu::PopupMenu() {
control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
scroll_container->add_child(control, false, INTERNAL_MODE_FRONT);
- control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
+ control->connect(SceneStringName(draw), callable_mp(this, &PopupMenu::_draw_items));
submenu_timer = memnew(Timer);
submenu_timer->set_wait_time(0.3);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index f7097fffaf..832c1bcc8b 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -97,6 +97,10 @@ class PopupMenu : public Popup {
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
+ // To make Item available.
+ friend class OptionButton;
+ friend class MenuButton;
+
RID global_menu;
RID system_menu;
NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID;
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
index b2617e6fc7..90ce01e383 100644
--- a/scene/gui/progress_bar.cpp
+++ b/scene/gui/progress_bar.cpp
@@ -41,7 +41,7 @@ Size2 ProgressBar::get_minimum_size() const {
TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size);
minimum_size.height = MAX(minimum_size.height, theme_cache.background_style->get_minimum_size().height + tl.get_size().y);
} else { // this is needed, else the progressbar will collapse
- minimum_size = minimum_size.max(Size2(1, 1));
+ minimum_size = minimum_size.maxf(1);
}
return minimum_size;
}
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 236dfcc864..00f4a1089a 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -60,7 +60,7 @@ void Range::Shared::emit_value_changed() {
}
void Range::_changed_notify(const char *p_what) {
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
queue_redraw();
}
diff --git a/scene/gui/rich_text_label.compat.inc b/scene/gui/rich_text_label.compat.inc
index 626278a405..97739c4b79 100644
--- a/scene/gui/rich_text_label.compat.inc
+++ b/scene/gui/rich_text_label.compat.inc
@@ -38,9 +38,14 @@ void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image,
add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false);
}
+bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) {
+ return remove_paragraph(p_paragraph, false);
+}
+
void RichTextLabel::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024);
ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()));
+ ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098);
}
#endif // DISABLE_DEPRECATED
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index f8bbedde09..f6942ca206 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -39,7 +39,6 @@
#include "scene/gui/label.h"
#include "scene/gui/rich_text_effect.h"
#include "scene/resources/atlas_texture.h"
-#include "scene/scene_string_names.h"
#include "scene/theme/theme_db.h"
#include "servers/display_server.h"
@@ -931,9 +930,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
RID rid = l.text_buf->get_line_rid(line);
- //draw_rect(Rect2(p_ofs + off, TS->shaped_text_get_size(rid)), Color(1,0,0), false, 2); //DEBUG_RECTS
+ double l_ascent = TS->shaped_text_get_ascent(rid);
+ Size2 l_size = TS->shaped_text_get_size(rid);
+ double upos = TS->shaped_text_get_underline_position(rid);
+ double uth = TS->shaped_text_get_underline_thickness(rid);
- off.y += TS->shaped_text_get_ascent(rid);
+ off.y += l_ascent;
// Draw inlined objects.
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {
@@ -950,7 +952,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
}
Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
- //draw_rect(rect, Color(1,0,0), false, 2); //DEBUG_RECTS
switch (it->type) {
case ITEM_IMAGE: {
ItemImage *img = static_cast<ItemImage *>(it);
@@ -1015,514 +1016,416 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
const Glyph *glyphs = TS->shaped_text_get_glyphs(rid);
int gl_size = TS->shaped_text_get_glyph_count(rid);
+ Vector2i chr_range = TS->shaped_text_get_range(rid);
- Vector2 gloff = off;
- // Draw outlines and shadow.
- int processed_glyphs_ol = r_processed_glyphs;
- for (int i = 0; i < gl_size; i++) {
- Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
- int size = _find_outline_size(it, p_outline_size);
- Color font_color = _find_color(it, p_base_color);
- Color font_outline_color = _find_outline_color(it, p_outline_color);
- Color font_shadow_color = p_font_shadow_color;
- if ((size <= 0 || font_outline_color.a == 0) && (font_shadow_color.a == 0)) {
- gloff.x += glyphs[i].advance;
- continue;
- }
-
- // Get FX.
- ItemFade *fade = nullptr;
- Item *fade_item = it;
- while (fade_item) {
- if (fade_item->type == ITEM_FADE) {
- fade = static_cast<ItemFade *>(fade_item);
- break;
- }
- fade_item = fade_item->parent;
- }
-
- Vector<ItemFX *> fx_stack;
- _fetch_item_fx_stack(it, fx_stack);
- bool custom_fx_ok = true;
-
- Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
- RID frid = glyphs[i].font_rid;
- uint32_t gl = glyphs[i].index;
- uint16_t gl_fl = glyphs[i].flags;
- uint8_t gl_cn = glyphs[i].count;
- bool cprev_cluster = false;
- bool cprev_conn = false;
- if (gl_cn == 0) { // Parts of the same cluster, always connected.
- cprev_cluster = true;
- }
- if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
- if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
- cprev_conn = true;
- }
- } else {
- if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
- cprev_conn = true;
- }
- }
+ int sel_start = -1;
+ int sel_end = -1;
- //Apply fx.
- if (fade) {
- float faded_visibility = 1.0f;
- if (glyphs[i].start >= fade->starting_index) {
- faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
- faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
- }
- font_outline_color.a = faded_visibility;
- font_shadow_color.a = faded_visibility;
- }
-
- bool txt_visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0);
- Transform2D char_xform;
- char_xform.set_origin(gloff + p_ofs);
-
- for (int j = 0; j < fx_stack.size(); j++) {
- ItemFX *item_fx = fx_stack[j];
- bool cn = cprev_cluster || (cprev_conn && item_fx->connected);
-
- if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) {
- ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx);
-
- Ref<CharFXTransform> charfx = item_custom->char_fx_transform;
- Ref<RichTextEffect> custom_effect = item_custom->custom_effect;
-
- if (!custom_effect.is_null()) {
- charfx->elapsed_time = item_custom->elapsed_time;
- charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end);
- charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs;
- charfx->visibility = txt_visible;
- charfx->outline = true;
- charfx->font = frid;
- charfx->glyph_index = gl;
- charfx->glyph_flags = gl_fl;
- charfx->glyph_count = gl_cn;
- charfx->offset = fx_offset;
- charfx->color = font_color;
- charfx->transform = char_xform;
-
- bool effect_status = custom_effect->_process_effect_impl(charfx);
- custom_fx_ok = effect_status;
-
- char_xform = charfx->transform;
- fx_offset += charfx->offset;
- font_color = charfx->color;
- frid = charfx->font;
- gl = charfx->glyph_index;
- txt_visible &= charfx->visibility;
+ if (selection.active && (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) <= (l.char_offset + chr_range.y) && (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) >= (l.char_offset + chr_range.x)) {
+ sel_start = MAX(chr_range.x, (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) - l.char_offset);
+ sel_end = MIN(chr_range.y, (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) - l.char_offset);
+ }
+
+ int processed_glyphs_step = 0;
+ for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) {
+ Vector2 off_step = off;
+ processed_glyphs_step = r_processed_glyphs;
+
+ Vector2 ul_start;
+ bool ul_started = false;
+ Color ul_color_prev;
+ Color ul_color;
+
+ Vector2 dot_ul_start;
+ bool dot_ul_started = false;
+ Color dot_ul_color_prev;
+ Color dot_ul_color;
+
+ Vector2 st_start;
+ bool st_started = false;
+ Color st_color_prev;
+ Color st_color;
+
+ float box_start = 0.0;
+ Color last_color = Color(0, 0, 0, 0);
+
+ for (int i = 0; i < gl_size; i++) {
+ bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end);
+ Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
+
+ Color font_color = (step == DRAW_STEP_SHADOW || step == DRAW_STEP_OUTLINE || step == DRAW_STEP_TEXT) ? _find_color(it, p_base_color) : Color();
+ int outline_size = (step == DRAW_STEP_OUTLINE) ? _find_outline_size(it, p_outline_size) : 0;
+ Color font_outline_color = (step == DRAW_STEP_OUTLINE) ? _find_outline_color(it, p_outline_color) : Color();
+ Color font_shadow_color = p_font_shadow_color;
+ bool txt_visible = false;
+ if (step == DRAW_STEP_OUTLINE) {
+ txt_visible = (font_outline_color.a != 0 && outline_size > 0);
+ } else if (step == DRAW_STEP_SHADOW) {
+ txt_visible = (font_shadow_color.a != 0);
+ } else if (step == DRAW_STEP_TEXT) {
+ txt_visible = (font_color.a != 0);
+ bool has_ul = _find_underline(it);
+ if (!has_ul && underline_meta) {
+ ItemMeta *meta = nullptr;
+ if (_find_meta(it, nullptr, &meta) && meta) {
+ switch (meta->underline) {
+ case META_UNDERLINE_ALWAYS: {
+ has_ul = true;
+ } break;
+ case META_UNDERLINE_NEVER: {
+ has_ul = false;
+ } break;
+ case META_UNDERLINE_ON_HOVER: {
+ has_ul = (meta == meta_hovering);
+ } break;
+ }
+ }
}
- } else if (item_fx->type == ITEM_SHAKE) {
- ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
-
- if (!cn) {
- uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
- uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
- uint64_t max_rand = 2147483647;
- double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
- double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
- double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
- n_time = (n_time > 1.0) ? 1.0 : n_time;
- item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
+ if (has_ul) {
+ if (ul_started && font_color != ul_color_prev) {
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width);
+ ul_start = p_ofs + Vector2(off_step.x, off_step.y);
+ ul_color_prev = font_color;
+ ul_color = font_color;
+ ul_color.a *= 0.5;
+ } else if (!ul_started) {
+ ul_started = true;
+ ul_start = p_ofs + Vector2(off_step.x, off_step.y);
+ ul_color_prev = font_color;
+ ul_color = font_color;
+ ul_color.a *= 0.5;
+ }
+ } else if (ul_started) {
+ ul_started = false;
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width);
}
- fx_offset += item_shake->prev_off;
- } else if (item_fx->type == ITEM_WAVE) {
- ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
-
- if (!cn) {
- double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f);
- item_wave->prev_off = Point2(0, 1) * value;
+ if (_find_hint(it, nullptr) && underline_hint) {
+ if (dot_ul_started && font_color != dot_ul_color_prev) {
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
+ dot_ul_start = p_ofs + Vector2(off_step.x, off_step.y);
+ dot_ul_color_prev = font_color;
+ dot_ul_color = font_color;
+ dot_ul_color.a *= 0.5;
+ } else if (!dot_ul_started) {
+ dot_ul_started = true;
+ dot_ul_start = p_ofs + Vector2(off_step.x, off_step.y);
+ dot_ul_color_prev = font_color;
+ dot_ul_color = font_color;
+ dot_ul_color.a *= 0.5;
+ }
+ } else if (dot_ul_started) {
+ dot_ul_started = false;
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
}
- fx_offset += item_wave->prev_off;
- } else if (item_fx->type == ITEM_TORNADO) {
- ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
-
- if (!cn) {
- double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
- double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
- item_tornado->prev_off = Point2(torn_x, torn_y);
+ if (_find_strikethrough(it)) {
+ if (st_started && font_color != st_color_prev) {
+ float y_off = -l_ascent + l_size.y / 2;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width);
+ st_start = p_ofs + Vector2(off_step.x, off_step.y);
+ st_color_prev = font_color;
+ st_color = font_color;
+ st_color.a *= 0.5;
+ } else if (!st_started) {
+ st_started = true;
+ st_start = p_ofs + Vector2(off_step.x, off_step.y);
+ st_color_prev = font_color;
+ st_color = font_color;
+ st_color.a *= 0.5;
+ }
+ } else if (st_started) {
+ st_started = false;
+ float y_off = -l_ascent + l_size.y / 2;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width);
}
- fx_offset += item_tornado->prev_off;
- } else if (item_fx->type == ITEM_RAINBOW) {
- ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
-
- font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + gloff.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a);
- } else if (item_fx->type == ITEM_PULSE) {
- ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx);
-
- const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease));
- font_color = font_color.lerp(font_color * item_pulse->color, sined_time);
}
- }
+ if (step == DRAW_STEP_SHADOW || step == DRAW_STEP_OUTLINE || step == DRAW_STEP_TEXT) {
+ ItemFade *fade = nullptr;
+ Item *fade_item = it;
+ while (fade_item) {
+ if (fade_item->type == ITEM_FADE) {
+ fade = static_cast<ItemFade *>(fade_item);
+ break;
+ }
+ fade_item = fade_item->parent;
+ }
- if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
- fx_offset = fx_offset.round();
- }
- Vector2 char_off = char_xform.get_origin();
-
- // Draw glyph outlines.
- const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a);
- const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a);
- for (int j = 0; j < glyphs[i].repeat; j++) {
- if (txt_visible) {
- bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
- if (!skip && frid != RID()) {
- if (modulated_shadow_color.a > 0) {
- Transform2D char_reverse_xform;
- char_reverse_xform.set_origin(-char_off - p_shadow_ofs);
- Transform2D char_final_xform = char_xform * char_reverse_xform;
- char_final_xform.columns[2] += p_shadow_ofs;
- draw_set_transform_matrix(char_final_xform);
-
- TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off + p_shadow_ofs, gl, modulated_shadow_color);
- if (p_shadow_outline_size > 0) {
- TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, fx_offset + char_off + p_shadow_ofs, gl, modulated_shadow_color);
- }
+ Vector<ItemFX *> fx_stack;
+ _fetch_item_fx_stack(it, fx_stack);
+ bool custom_fx_ok = true;
+
+ Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
+ RID frid = glyphs[i].font_rid;
+ uint32_t gl = glyphs[i].index;
+ uint16_t gl_fl = glyphs[i].flags;
+ uint8_t gl_cn = glyphs[i].count;
+ bool cprev_cluster = false;
+ bool cprev_conn = false;
+ if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected.
+ cprev_cluster = true;
+ }
+ if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
+ if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
+ cprev_conn = true;
}
- if (modulated_outline_color.a != 0.0 && size > 0) {
- Transform2D char_reverse_xform;
- char_reverse_xform.set_origin(-char_off);
- Transform2D char_final_xform = char_xform * char_reverse_xform;
- draw_set_transform_matrix(char_final_xform);
+ } else {
+ if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
+ cprev_conn = true;
+ }
+ }
- TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, fx_offset + char_off, gl, modulated_outline_color);
+ //Apply fx.
+ if (fade) {
+ float faded_visibility = 1.0f;
+ if (glyphs[i].start >= fade->starting_index) {
+ faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
+ faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
}
+ font_color.a = faded_visibility;
}
- processed_glyphs_ol++;
- }
- gloff.x += glyphs[i].advance;
- }
- }
- draw_set_transform_matrix(Transform2D());
- Vector2 fbg_line_off = off + p_ofs;
- // Draw background color box
- Vector2i chr_range = TS->shaped_text_get_range(rid);
- _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0);
+ Transform2D char_xform;
+ char_xform.set_origin(p_ofs + off_step);
+
+ for (int j = 0; j < fx_stack.size(); j++) {
+ ItemFX *item_fx = fx_stack[j];
+ bool cn = cprev_cluster || (cprev_conn && item_fx->connected);
+
+ if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) {
+ ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx);
+
+ Ref<CharFXTransform> charfx = item_custom->char_fx_transform;
+ Ref<RichTextEffect> custom_effect = item_custom->custom_effect;
+
+ if (!custom_effect.is_null()) {
+ charfx->elapsed_time = item_custom->elapsed_time;
+ charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end);
+ charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs;
+ charfx->visibility = txt_visible;
+ charfx->outline = (step == DRAW_STEP_SHADOW) || (step == DRAW_STEP_OUTLINE);
+ charfx->font = frid;
+ charfx->glyph_index = gl;
+ charfx->glyph_flags = gl_fl;
+ charfx->glyph_count = gl_cn;
+ charfx->offset = fx_offset;
+ charfx->color = font_color;
+ charfx->transform = char_xform;
+
+ bool effect_status = custom_effect->_process_effect_impl(charfx);
+ custom_fx_ok = effect_status;
+
+ char_xform = charfx->transform;
+ fx_offset += charfx->offset;
+ font_color = charfx->color;
+ frid = charfx->font;
+ gl = charfx->glyph_index;
+ txt_visible &= charfx->visibility;
+ }
+ } else if (item_fx->type == ITEM_SHAKE) {
+ ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
+
+ if (!cn) {
+ uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
+ uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
+ uint64_t max_rand = 2147483647;
+ double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+ double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+ double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
+ n_time = (n_time > 1.0) ? 1.0 : n_time;
+ item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
+ }
+ fx_offset += item_shake->prev_off;
+ } else if (item_fx->type == ITEM_WAVE) {
+ ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
- // Draw main text.
- Color selection_bg = theme_cache.selection_color;
+ if (!cn) {
+ double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off_step.x) / 50)) * (item_wave->amplitude / 10.0f);
+ item_wave->prev_off = Point2(0, 1) * value;
+ }
+ fx_offset += item_wave->prev_off;
+ } else if (item_fx->type == ITEM_TORNADO) {
+ ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
+
+ if (!cn) {
+ double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off_step.x) / 50)) * (item_tornado->radius);
+ double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off_step.x) / 50)) * (item_tornado->radius);
+ item_tornado->prev_off = Point2(torn_x, torn_y);
+ }
+ fx_offset += item_tornado->prev_off;
+ } else if (item_fx->type == ITEM_RAINBOW) {
+ ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
- int sel_start = -1;
- int sel_end = -1;
+ font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + off_step.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a);
+ } else if (item_fx->type == ITEM_PULSE) {
+ ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx);
- if (selection.active && (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) <= (l.char_offset + TS->shaped_text_get_range(rid).y) && (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) >= (l.char_offset + TS->shaped_text_get_range(rid).x)) {
- sel_start = MAX(TS->shaped_text_get_range(rid).x, (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) - l.char_offset);
- sel_end = MIN(TS->shaped_text_get_range(rid).y, (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) - l.char_offset);
-
- Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_start, sel_end);
- for (int i = 0; i < sel.size(); i++) {
- Rect2 rect = Rect2(sel[i].x + p_ofs.x + off.x, p_ofs.y + off.y - TS->shaped_text_get_ascent(rid), sel[i].y - sel[i].x, TS->shaped_text_get_size(rid).y);
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_bg);
- }
- }
-
- Vector2 ul_start;
- bool ul_started = false;
- Color ul_color_prev;
- Color ul_color;
-
- Vector2 dot_ul_start;
- bool dot_ul_started = false;
- Color dot_ul_color_prev;
- Color dot_ul_color;
-
- Vector2 st_start;
- bool st_started = false;
- Color st_color_prev;
- Color st_color;
-
- for (int i = 0; i < gl_size; i++) {
- bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end);
- Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
- bool has_ul = _find_underline(it);
- if (!has_ul && underline_meta) {
- ItemMeta *meta = nullptr;
- if (_find_meta(it, nullptr, &meta) && meta) {
- switch (meta->underline) {
- case META_UNDERLINE_ALWAYS: {
- has_ul = true;
- } break;
- case META_UNDERLINE_NEVER: {
- has_ul = false;
- } break;
- case META_UNDERLINE_ON_HOVER: {
- has_ul = (meta == meta_hovering);
- } break;
+ const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease));
+ font_color = font_color.lerp(font_color * item_pulse->color, sined_time);
+ }
}
- }
- }
- Color font_color = _find_color(it, p_base_color);
- if (has_ul) {
- if (ul_started && font_color != ul_color_prev) {
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
- ul_start = p_ofs + Vector2(off.x, off.y);
- ul_color_prev = font_color;
- ul_color = font_color;
- ul_color.a *= 0.5;
- } else if (!ul_started) {
- ul_started = true;
- ul_start = p_ofs + Vector2(off.x, off.y);
- ul_color_prev = font_color;
- ul_color = font_color;
- ul_color.a *= 0.5;
- }
- } else if (ul_started) {
- ul_started = false;
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
- }
- if (_find_hint(it, nullptr) && underline_hint) {
- if (dot_ul_started && font_color != dot_ul_color_prev) {
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
- dot_ul_start = p_ofs + Vector2(off.x, off.y);
- dot_ul_color_prev = font_color;
- dot_ul_color = font_color;
- dot_ul_color.a *= 0.5;
- } else if (!dot_ul_started) {
- dot_ul_started = true;
- dot_ul_start = p_ofs + Vector2(off.x, off.y);
- dot_ul_color_prev = font_color;
- dot_ul_color = font_color;
- dot_ul_color.a *= 0.5;
- }
- } else if (dot_ul_started) {
- dot_ul_started = false;
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
- }
- if (_find_strikethrough(it)) {
- if (st_started && font_color != st_color_prev) {
- float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
- st_start = p_ofs + Vector2(off.x, off.y);
- st_color_prev = font_color;
- st_color = font_color;
- st_color.a *= 0.5;
- } else if (!st_started) {
- st_started = true;
- st_start = p_ofs + Vector2(off.x, off.y);
- st_color_prev = font_color;
- st_color = font_color;
- st_color.a *= 0.5;
- }
- } else if (st_started) {
- st_started = false;
- float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
- }
-
- // Get FX.
- ItemFade *fade = nullptr;
- Item *fade_item = it;
- while (fade_item) {
- if (fade_item->type == ITEM_FADE) {
- fade = static_cast<ItemFade *>(fade_item);
- break;
- }
- fade_item = fade_item->parent;
- }
-
- Vector<ItemFX *> fx_stack;
- _fetch_item_fx_stack(it, fx_stack);
- bool custom_fx_ok = true;
-
- Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
- RID frid = glyphs[i].font_rid;
- uint32_t gl = glyphs[i].index;
- uint16_t gl_fl = glyphs[i].flags;
- uint8_t gl_cn = glyphs[i].count;
- bool cprev_cluster = false;
- bool cprev_conn = false;
- if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected.
- cprev_cluster = true;
- }
- if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
- if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
- cprev_conn = true;
- }
- } else {
- if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
- cprev_conn = true;
- }
- }
- //Apply fx.
- if (fade) {
- float faded_visibility = 1.0f;
- if (glyphs[i].start >= fade->starting_index) {
- faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
- faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
- }
- font_color.a = faded_visibility;
- }
-
- bool txt_visible = (font_color.a != 0);
-
- Transform2D char_xform;
- char_xform.set_origin(p_ofs + off);
-
- for (int j = 0; j < fx_stack.size(); j++) {
- ItemFX *item_fx = fx_stack[j];
- bool cn = cprev_cluster || (cprev_conn && item_fx->connected);
-
- if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) {
- ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx);
-
- Ref<CharFXTransform> charfx = item_custom->char_fx_transform;
- Ref<RichTextEffect> custom_effect = item_custom->custom_effect;
-
- if (!custom_effect.is_null()) {
- charfx->elapsed_time = item_custom->elapsed_time;
- charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end);
- charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs;
- charfx->visibility = txt_visible;
- charfx->outline = false;
- charfx->font = frid;
- charfx->glyph_index = gl;
- charfx->glyph_flags = gl_fl;
- charfx->glyph_count = gl_cn;
- charfx->offset = fx_offset;
- charfx->color = font_color;
- charfx->transform = char_xform;
-
- bool effect_status = custom_effect->_process_effect_impl(charfx);
- custom_fx_ok = effect_status;
-
- char_xform = charfx->transform;
- fx_offset += charfx->offset;
- font_color = charfx->color;
- frid = charfx->font;
- gl = charfx->glyph_index;
- txt_visible &= charfx->visibility;
+ if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
+ fx_offset = fx_offset.round();
}
- } else if (item_fx->type == ITEM_SHAKE) {
- ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
-
- if (!cn) {
- uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
- uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
- uint64_t max_rand = 2147483647;
- double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
- double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
- double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
- n_time = (n_time > 1.0) ? 1.0 : n_time;
- item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
+
+ Vector2 char_off = char_xform.get_origin();
+ Transform2D char_reverse_xform;
+ if (step == DRAW_STEP_TEXT) {
+ if (selected && use_selected_font_color) {
+ font_color = theme_cache.font_selected_color;
+ }
+
+ char_reverse_xform.set_origin(-char_off);
+ Transform2D char_final_xform = char_xform * char_reverse_xform;
+ draw_set_transform_matrix(char_final_xform);
+ } else if (step == DRAW_STEP_SHADOW) {
+ font_color = font_shadow_color * Color(1, 1, 1, font_color.a);
+
+ char_reverse_xform.set_origin(-char_off - p_shadow_ofs);
+ Transform2D char_final_xform = char_xform * char_reverse_xform;
+ char_final_xform.columns[2] += p_shadow_ofs;
+ draw_set_transform_matrix(char_final_xform);
+ } else if (step == DRAW_STEP_OUTLINE) {
+ font_color = font_outline_color * Color(1, 1, 1, font_color.a);
+
+ char_reverse_xform.set_origin(-char_off);
+ Transform2D char_final_xform = char_xform * char_reverse_xform;
+ draw_set_transform_matrix(char_final_xform);
}
- fx_offset += item_shake->prev_off;
- } else if (item_fx->type == ITEM_WAVE) {
- ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
- if (!cn) {
- double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
- item_wave->prev_off = Point2(0, 1) * value;
+ // Draw glyphs.
+ for (int j = 0; j < glyphs[i].repeat; j++) {
+ bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
+ if (!skip) {
+ if (txt_visible) {
+ if (step == DRAW_STEP_TEXT) {
+ if (frid != RID()) {
+ TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color);
+ } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
+ TS->draw_hex_code_box(ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color);
+ }
+ } else if (step == DRAW_STEP_SHADOW && frid != RID()) {
+ TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off + p_shadow_ofs, gl, font_color);
+ if (p_shadow_outline_size > 0) {
+ TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, fx_offset + char_off + p_shadow_ofs, gl, font_color);
+ }
+ } else if (step == DRAW_STEP_OUTLINE && frid != RID() && outline_size > 0) {
+ TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, outline_size, fx_offset + char_off, gl, font_color);
+ }
+ }
+ processed_glyphs_step++;
+ }
+ if (step == DRAW_STEP_TEXT && skip) {
+ // Finish underline/overline/strikethrough is previous glyph is skipped.
+ if (ul_started) {
+ ul_started = false;
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width);
+ }
+ if (dot_ul_started) {
+ dot_ul_started = false;
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
+ }
+ if (st_started) {
+ st_started = false;
+ float y_off = -l_ascent + l_size.y / 2;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width);
+ }
+ }
+ off_step.x += glyphs[i].advance;
}
- fx_offset += item_wave->prev_off;
- } else if (item_fx->type == ITEM_TORNADO) {
- ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
-
- if (!cn) {
- double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
- double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
- item_tornado->prev_off = Point2(torn_x, torn_y);
+ draw_set_transform_matrix(Transform2D());
+ }
+ // Draw boxes.
+ if (step == DRAW_STEP_BACKGROUND || step == DRAW_STEP_FOREGROUND) {
+ for (int j = 0; j < glyphs[i].repeat; j++) {
+ bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
+ if (!skip) {
+ Color color;
+ if (step == DRAW_STEP_BACKGROUND) {
+ color = _find_bgcolor(it);
+ } else if (step == DRAW_STEP_FOREGROUND) {
+ color = _find_fgcolor(it);
+ }
+ if (color != last_color) {
+ if (last_color.a > 0.0) {
+ Vector2 rect_off = p_ofs + Vector2(box_start - theme_cache.text_highlight_h_padding, off_step.y - l_ascent - theme_cache.text_highlight_v_padding);
+ Vector2 rect_size = Vector2(off_step.x - box_start + 2 * theme_cache.text_highlight_h_padding, l_size.y + 2 * theme_cache.text_highlight_v_padding);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(rect_off, rect_size), last_color);
+ }
+ if (color.a > 0.0) {
+ box_start = off_step.x;
+ }
+ }
+ last_color = color;
+ processed_glyphs_step++;
+ } else {
+ // Finish box is previous glyph is skipped.
+ if (last_color.a > 0.0) {
+ Vector2 rect_off = p_ofs + Vector2(box_start - theme_cache.text_highlight_h_padding, off_step.y - l_ascent - theme_cache.text_highlight_v_padding);
+ Vector2 rect_size = Vector2(off_step.x - box_start + 2 * theme_cache.text_highlight_h_padding, l_size.y + 2 * theme_cache.text_highlight_v_padding);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(rect_off, rect_size), last_color);
+ }
+ last_color = Color(0, 0, 0, 0);
+ }
+ off_step.x += glyphs[i].advance;
}
- fx_offset += item_tornado->prev_off;
- } else if (item_fx->type == ITEM_RAINBOW) {
- ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
-
- font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + off.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a);
- } else if (item_fx->type == ITEM_PULSE) {
- ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx);
-
- const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease));
- font_color = font_color.lerp(font_color * item_pulse->color, sined_time);
}
}
-
- if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
- fx_offset = fx_offset.round();
+ // Finish lines and boxes.
+ if (step == DRAW_STEP_BACKGROUND) {
+ if (sel_start != -1) {
+ Color selection_bg = theme_cache.selection_color;
+ Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_start, sel_end);
+ for (int i = 0; i < sel.size(); i++) {
+ Rect2 rect = Rect2(sel[i].x + p_ofs.x + off.x, p_ofs.y + off.y - l_ascent, sel[i].y - sel[i].x, l_size.y); // Note: use "off" not "off_step", selection is relative to the line start.
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_bg);
+ }
+ }
}
- Vector2 char_off = char_xform.get_origin();
-
- Transform2D char_reverse_xform;
- char_reverse_xform.set_origin(-char_off);
- char_xform = char_xform * char_reverse_xform;
- draw_set_transform_matrix(char_xform);
-
- if (selected && use_selected_font_color) {
- font_color = theme_cache.font_selected_color;
+ if (step == DRAW_STEP_BACKGROUND || step == DRAW_STEP_FOREGROUND) {
+ if (last_color.a > 0.0) {
+ Vector2 rect_off = p_ofs + Vector2(box_start - theme_cache.text_highlight_h_padding, off_step.y - l_ascent - theme_cache.text_highlight_v_padding);
+ Vector2 rect_size = Vector2(off_step.x - box_start + 2 * theme_cache.text_highlight_h_padding, l_size.y + 2 * theme_cache.text_highlight_v_padding);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(rect_off, rect_size), last_color);
+ }
}
-
- // Draw glyphs.
- for (int j = 0; j < glyphs[i].repeat; j++) {
- bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs));
- if (txt_visible) {
- if (!skip) {
- if (frid != RID()) {
- TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color);
- } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
- TS->draw_hex_code_box(ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color);
- }
- }
- r_processed_glyphs++;
+ if (step == DRAW_STEP_TEXT) {
+ if (ul_started) {
+ ul_started = false;
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), ul_color, underline_width);
}
- if (skip) {
- // End underline/overline/strikethrough is previous glyph is skipped.
- if (ul_started) {
- ul_started = false;
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
- }
- if (dot_ul_started) {
- dot_ul_started = false;
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
- }
- if (st_started) {
- st_started = false;
- float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
- }
+ if (dot_ul_started) {
+ dot_ul_started = false;
+ float y_off = upos;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
+ }
+ if (st_started) {
+ st_started = false;
+ float y_off = -l_ascent + l_size.y / 2;
+ float underline_width = MAX(1.0, uth * theme_cache.base_scale);
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off_step.x, off_step.y + y_off), st_color, underline_width);
}
- off.x += glyphs[i].advance;
}
-
- draw_set_transform_matrix(Transform2D());
- }
- if (ul_started) {
- ul_started = false;
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
- }
- if (dot_ul_started) {
- dot_ul_started = false;
- float y_off = TS->shaped_text_get_underline_position(rid);
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
}
- if (st_started) {
- st_started = false;
- float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
- float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
- draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
- }
- // Draw foreground color box
- _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1);
+ r_processed_glyphs = processed_glyphs_step;
off.y += TS->shaped_text_get_descent(rid);
}
@@ -1704,8 +1607,34 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
if (p_meta) {
int64_t glyph_idx = TS->shaped_text_hit_test_grapheme(rid, p_click.x - rect.position.x);
if (glyph_idx >= 0) {
+ float baseline_y = rect.position.y + TS->shaped_text_get_ascent(rid);
const Glyph *glyphs = TS->shaped_text_get_glyphs(rid);
- char_pos = glyphs[glyph_idx].start;
+ if (glyphs[glyph_idx].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) {
+ // Emebedded object.
+ for (int i = 0; i < objects.size(); i++) {
+ if (TS->shaped_text_get_object_glyph(rid, objects[i]) == glyph_idx) {
+ Rect2 obj_rect = TS->shaped_text_get_object_rect(rid, objects[i]);
+ obj_rect.position.y += baseline_y;
+ if (p_click.y >= obj_rect.position.y && p_click.y <= obj_rect.position.y + obj_rect.size.y) {
+ char_pos = glyphs[glyph_idx].start;
+ }
+ break;
+ }
+ }
+ } else if (glyphs[glyph_idx].font_rid != RID()) {
+ // Normal glyph.
+ float fa = TS->font_get_ascent(glyphs[glyph_idx].font_rid, glyphs[glyph_idx].font_size);
+ float fd = TS->font_get_descent(glyphs[glyph_idx].font_rid, glyphs[glyph_idx].font_size);
+ if (p_click.y >= baseline_y - fa && p_click.y <= baseline_y + fd) {
+ char_pos = glyphs[glyph_idx].start;
+ }
+ } else if (!(glyphs[glyph_idx].flags & TextServer::GRAPHEME_IS_VIRTUAL)) {
+ // Hex code box.
+ Vector2 gl_size = TS->get_hex_code_box_size(glyphs[glyph_idx].font_size, glyphs[glyph_idx].index);
+ if (p_click.y >= baseline_y - gl_size.y * 0.9 && p_click.y <= baseline_y + gl_size.y * 0.2) {
+ char_pos = glyphs[glyph_idx].start;
+ }
+ }
}
} else {
char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x);
@@ -1803,7 +1732,7 @@ void RichTextLabel::_scroll_changed(double) {
return;
}
- if (scroll_follow && vscroll->get_value() >= (vscroll->get_max() - Math::round(vscroll->get_page()))) {
+ if (scroll_follow && vscroll->get_value() > (vscroll->get_max() - vscroll->get_page() - 1)) {
scroll_following = true;
} else {
scroll_following = false;
@@ -2149,12 +2078,12 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (b->get_button_index() == MouseButton::WHEEL_UP) {
if (scroll_active) {
- vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8);
+ vscroll->scroll(-vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
}
if (b->get_button_index() == MouseButton::WHEEL_DOWN) {
if (scroll_active) {
- vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8);
+ vscroll->scroll(vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
}
if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
@@ -2169,7 +2098,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
if (scroll_active) {
- vscroll->set_value(vscroll->get_value() + vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8);
+ vscroll->scroll(vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8);
}
return;
@@ -2182,27 +2111,27 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
bool handled = false;
if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) {
- vscroll->set_value(vscroll->get_value() - vscroll->get_page());
+ vscroll->scroll(-vscroll->get_page());
handled = true;
}
if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) {
- vscroll->set_value(vscroll->get_value() + vscroll->get_page());
+ vscroll->scroll(vscroll->get_page());
handled = true;
}
if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) {
- vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size));
+ vscroll->scroll(-theme_cache.normal_font->get_height(theme_cache.normal_font_size));
handled = true;
}
if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) {
- vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size));
+ vscroll->scroll(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size));
handled = true;
}
if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) {
- vscroll->set_value(0);
+ vscroll->scroll_to(0);
handled = true;
}
if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) {
- vscroll->set_value(vscroll->get_max());
+ vscroll->scroll_to(vscroll->get_max());
handled = true;
}
if (is_shortcut_keys_enabled()) {
@@ -3089,7 +3018,7 @@ void RichTextLabel::_process_line_caches() {
if (fit_content) {
update_minimum_size();
}
- emit_signal(SNAME("finished"));
+ emit_signal(SceneStringName(finished));
}
void RichTextLabel::_invalidate_current_line(ItemFrame *p_frame) {
@@ -3212,33 +3141,6 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
queue_redraw();
}
-void RichTextLabel::_remove_item(Item *p_item, const int p_line) {
- int size = p_item->subitems.size();
- if (size == 0) {
- p_item->parent->subitems.erase(p_item);
- // If a newline was erased, all lines AFTER the newline need to be decremented.
- if (p_item->type == ITEM_NEWLINE) {
- current_frame->lines.remove_at(p_line);
- if (p_line < (int)current_frame->lines.size() && current_frame->lines[p_line].from) {
- for (List<Item *>::Element *E = current_frame->lines[p_line].from->E; E; E = E->next()) {
- if (E->get()->line > p_line) {
- E->get()->line--;
- }
- }
- }
- }
- } else {
- // First, remove all child items for the provided item.
- while (p_item->subitems.size()) {
- _remove_item(p_item->subitems.front()->get(), p_line);
- }
- // Then remove the provided item itself.
- p_item->parent->subitems.erase(p_item);
- }
- items.free(p_item->rid);
- memdelete(p_item);
-}
-
Size2 RichTextLabel::_get_image_size(const Ref<Texture2D> &p_image, int p_width, int p_height, const Rect2 &p_region) {
Size2 ret;
if (p_width > 0) {
@@ -3424,49 +3326,134 @@ void RichTextLabel::add_newline() {
queue_redraw();
}
-bool RichTextLabel::remove_paragraph(const int p_paragraph) {
+void RichTextLabel::_remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset) {
+ Line &l = p_frame->lines[p_line];
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ if (!p_erase) {
+ l.char_offset -= p_char_offset;
+ }
+
+ for (Item *it = l.from; it && it != it_to;) {
+ Item *next_it = _get_next_item(it);
+ it->line -= p_line_offset;
+ if (!p_erase) {
+ while (r_erase_list.has(it->parent)) {
+ it->E->erase();
+ it->parent = it->parent->parent;
+ it->E = it->parent->subitems.push_back(it);
+ }
+ }
+ if (it->type == ITEM_TABLE) {
+ ItemTable *table = static_cast<ItemTable *>(it);
+ for (List<Item *>::Element *sub_it = table->subitems.front(); sub_it; sub_it = sub_it->next()) {
+ ERR_CONTINUE(sub_it->get()->type != ITEM_FRAME); // Children should all be frames.
+ ItemFrame *frame = static_cast<ItemFrame *>(sub_it->get());
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ _remove_frame(r_erase_list, frame, i, p_erase, p_char_offset, 0);
+ }
+ if (p_erase) {
+ r_erase_list.insert(frame);
+ } else {
+ frame->char_ofs -= p_char_offset;
+ }
+ }
+ }
+ if (p_erase) {
+ r_erase_list.insert(it);
+ } else {
+ it->char_ofs -= p_char_offset;
+ }
+ it = next_it;
+ }
+}
+
+bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) {
_stop_thread();
MutexLock data_lock(data_mutex);
- if (p_paragraph >= (int)current_frame->lines.size() || p_paragraph < 0) {
+ if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) {
return false;
}
- // Remove all subitems with the same line as that provided.
- Vector<List<Item *>::Element *> subitem_to_remove;
- if (current_frame->lines[p_paragraph].from) {
- for (List<Item *>::Element *E = current_frame->lines[p_paragraph].from->E; E; E = E->next()) {
- if (E->get()->line == p_paragraph) {
- subitem_to_remove.push_back(E);
+ if (main->lines.size() == 1) {
+ // Clear all.
+ main->_clear_children();
+ current = main;
+ current_frame = main;
+ main->lines.clear();
+ main->lines.resize(1);
+
+ current_char_ofs = 0;
+ } else {
+ HashSet<Item *> erase_list;
+ Line &l = main->lines[p_paragraph];
+ int off = l.char_count;
+ for (int i = p_paragraph; i < (int)main->lines.size(); i++) {
+ if (i == p_paragraph) {
+ _remove_frame(erase_list, main, i, true, off, 0);
} else {
- break;
+ _remove_frame(erase_list, main, i, false, off, 1);
}
}
- }
-
- bool had_newline = false;
- // Reverse for loop to remove items from the end first.
- for (int i = subitem_to_remove.size() - 1; i >= 0; i--) {
- List<Item *>::Element *subitem = subitem_to_remove[i];
- had_newline = had_newline || subitem->get()->type == ITEM_NEWLINE;
- if (subitem->get() == current) {
- pop();
+ for (HashSet<Item *>::Iterator E = erase_list.begin(); E; ++E) {
+ Item *it = *E;
+ if (current_frame == it) {
+ current_frame = main;
+ }
+ if (current == it) {
+ current = main;
+ }
+ if (!erase_list.has(it->parent)) {
+ it->E->erase();
+ }
+ items.free(it->rid);
+ it->subitems.clear();
+ memdelete(it);
}
- _remove_item(subitem->get(), p_paragraph);
+ main->lines.remove_at(p_paragraph);
+ current_char_ofs -= off;
}
- if (!had_newline) {
- current_frame->lines.remove_at(p_paragraph);
- }
+ selection.click_frame = nullptr;
+ selection.click_item = nullptr;
+ selection.active = false;
- if (current_frame->lines.is_empty()) {
- current_frame->lines.resize(1);
+ if (p_no_invalidate) {
+ // Do not invalidate cache, only update vertical offsets of the paragraphs after deleted one and scrollbar.
+ int to_line = main->first_invalid_line.load() - 1;
+ float total_height = (p_paragraph == 0) ? 0 : _calculate_line_vertical_offset(main->lines[p_paragraph - 1]);
+ for (int i = p_paragraph; i < to_line; i++) {
+ MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex());
+ main->lines[i].offset.y = total_height;
+ total_height = _calculate_line_vertical_offset(main->lines[i]);
+ }
+ updating_scroll = true;
+ vscroll->set_max(total_height);
+ updating_scroll = false;
+
+ main->first_invalid_line.store(MAX(main->first_invalid_line.load() - 1, 0));
+ main->first_resized_line.store(MAX(main->first_resized_line.load() - 1, 0));
+ main->first_invalid_font_line.store(MAX(main->first_invalid_font_line.load() - 1, 0));
+ } else {
+ // Invalidate cache after the deleted paragraph.
+ main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph));
+ main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph));
+ main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph));
}
+ queue_redraw();
+
+ return true;
+}
+
+bool RichTextLabel::invalidate_paragraph(int p_paragraph) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
- if (p_paragraph == 0 && current->subitems.size() > 0) {
- main->lines[0].from = main;
+ if (p_paragraph >= (int)main->lines.size() || p_paragraph < 0) {
+ return false;
}
+ // Invalidate cache.
main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph));
main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph));
main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph));
@@ -4099,7 +4086,7 @@ bool RichTextLabel::is_scroll_active() const {
void RichTextLabel::set_scroll_follow(bool p_follow) {
scroll_follow = p_follow;
- if (!vscroll->is_visible_in_tree() || vscroll->get_value() >= (vscroll->get_max() - vscroll->get_page())) {
+ if (!vscroll->is_visible_in_tree() || vscroll->get_value() > (vscroll->get_max() - vscroll->get_page() - 1)) {
scroll_following = true;
}
}
@@ -4410,6 +4397,10 @@ void RichTextLabel::append_text(const String &p_bbcode) {
push_strikethrough();
pos = brk_end + 1;
tag_stack.push_front(tag);
+ } else if (tag.begins_with("char=")) {
+ int32_t char_code = tag.substr(5, tag.length()).hex_to_int();
+ add_text(String::chr(char_code));
+ pos = brk_end + 1;
} else if (tag == "lb") {
add_text("[");
pos = brk_end + 1;
@@ -5193,7 +5184,7 @@ void RichTextLabel::scroll_to_paragraph(int p_paragraph) {
}
int RichTextLabel::get_paragraph_count() const {
- return current_frame->lines.size();
+ return main->lines.size();
}
int RichTextLabel::get_visible_paragraph_count() const {
@@ -5921,7 +5912,8 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(Variant()), DEFVAL(false), DEFVAL(String()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("update_image", "key", "mask", "image", "width", "height", "color", "inline_align", "region", "pad", "tooltip", "size_in_percent"), &RichTextLabel::update_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(false), DEFVAL(String()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline);
- ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::remove_paragraph);
+ ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph", "no_invalidate"), &RichTextLabel::remove_paragraph, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("invalidate_paragraph", "paragraph"), &RichTextLabel::invalidate_paragraph);
ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font, DEFVAL(0));
ClassDB::bind_method(D_METHOD("push_font_size", "font_size"), &RichTextLabel::push_font_size);
ClassDB::bind_method(D_METHOD("push_normal"), &RichTextLabel::push_normal);
@@ -6378,65 +6370,6 @@ void RichTextLabel::menu_option(int p_option) {
}
}
-void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag) {
- Vector2i fbg_index = Vector2i(end, start);
- Color last_color = Color(0, 0, 0, 0);
- bool draw_box = false;
- // Draw a box based on color tags associated with glyphs
- for (int i = start; i < end; i++) {
- Item *it = _get_item_at_pos(it_from, it_to, i);
- Color color;
-
- if (fbg_flag == 0) {
- color = _find_bgcolor(it);
- } else {
- color = _find_fgcolor(it);
- }
-
- bool change_to_color = ((color.a > 0) && ((last_color.a - 0.0) < 0.01));
- bool change_from_color = (((color.a - 0.0) < 0.01) && (last_color.a > 0.0));
- bool change_color = (((color.a > 0) == (last_color.a > 0)) && (color != last_color));
-
- if (change_to_color) {
- fbg_index.x = MIN(i, fbg_index.x);
- fbg_index.y = MAX(i, fbg_index.y);
- }
-
- if (change_from_color || change_color) {
- fbg_index.x = MIN(i, fbg_index.x);
- fbg_index.y = MAX(i, fbg_index.y);
- draw_box = true;
- }
-
- if (draw_box) {
- Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y);
- for (int j = 0; j < sel.size(); j++) {
- Vector2 rect_off = line_off + Vector2(sel[j].x - theme_cache.text_highlight_h_padding, -TS->shaped_text_get_ascent(p_rid) - theme_cache.text_highlight_v_padding);
- Vector2 rect_size = Vector2(sel[j].y - sel[j].x + 2 * theme_cache.text_highlight_h_padding, TS->shaped_text_get_size(p_rid).y + 2 * theme_cache.text_highlight_v_padding);
- RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color);
- }
- fbg_index = Vector2i(end, start);
- draw_box = false;
- }
-
- if (change_color) {
- fbg_index.x = MIN(i, fbg_index.x);
- fbg_index.y = MAX(i, fbg_index.y);
- }
-
- last_color = color;
- }
-
- if (last_color.a > 0) {
- Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end);
- for (int i = 0; i < sel.size(); i++) {
- Vector2 rect_off = line_off + Vector2(sel[i].x - theme_cache.text_highlight_h_padding, -TS->shaped_text_get_ascent(p_rid) - theme_cache.text_highlight_v_padding);
- Vector2 rect_size = Vector2(sel[i].y - sel[i].x + 2 * theme_cache.text_highlight_h_padding, TS->shaped_text_get_size(p_rid).y + 2 * theme_cache.text_highlight_v_padding);
- RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color);
- }
- }
-}
-
Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) {
for (int i = 0; i < custom_effects.size(); i++) {
Ref<RichTextEffect> effect = custom_effects[i];
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index f8dd9e663c..189ee1da6e 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -43,6 +43,15 @@ class RichTextEffect;
class RichTextLabel : public Control {
GDCLASS(RichTextLabel, Control);
+ enum RTLDrawStep {
+ DRAW_STEP_BACKGROUND,
+ DRAW_STEP_SHADOW,
+ DRAW_STEP_OUTLINE,
+ DRAW_STEP_TEXT,
+ DRAW_STEP_FOREGROUND,
+ DRAW_STEP_MAX,
+ };
+
public:
enum ListType {
LIST_NUMBERS,
@@ -125,6 +134,7 @@ protected:
#ifndef DISABLE_DEPRECATED
void _push_meta_bind_compat_89024(const Variant &p_meta);
void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region);
+ bool _remove_paragraph_bind_compat_91098(int p_paragraph);
static void _bind_compatibility_methods();
#endif
@@ -485,7 +495,7 @@ private:
_FORCE_INLINE_ float _update_scroll_exceeds(float p_total_height, float p_ctrl_height, float p_width, int p_idx, float p_old_scroll, float p_text_rect_height);
void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false);
- void _remove_item(Item *p_item, const int p_line);
+ void _remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset);
void _texture_changed(RID p_item);
@@ -596,7 +606,6 @@ private:
Size2 _get_image_size(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2());
- void _draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag);
#ifndef DISABLE_DEPRECATED
// Kept for compatibility from 3.x to 4.0.
bool _set(const StringName &p_name, const Variant &p_value);
@@ -656,7 +665,8 @@ public:
void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void add_newline();
- bool remove_paragraph(const int p_paragraph);
+ bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
+ bool invalidate_paragraph(int p_paragraph);
void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0));
void _push_def_font(DefaultFont p_def_font);
void _push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size = -1);
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 1310cac2c7..af9f08e389 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -46,9 +46,6 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventMouseMotion> m = p_event;
- if (!m.is_valid() || drag.active) {
- emit_signal(SNAME("scrolling"));
- }
Ref<InputEventMouseButton> b = p_event;
@@ -57,13 +54,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) {
double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0;
- set_value(get_value() + MAX(change, get_step()));
+ scroll(MAX(change, get_step()));
accept_event();
}
if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) {
double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0;
- set_value(get_value() - MAX(change, get_step()));
+ scroll(-MAX(change, get_step()));
accept_event();
}
@@ -84,14 +81,14 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
if (ofs < decr_size) {
decr_active = true;
- set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ scroll(-(custom_step >= 0 ? custom_step : get_step()));
queue_redraw();
return;
}
if (ofs > total - incr_size) {
incr_active = true;
- set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
+ scroll(custom_step >= 0 ? custom_step : get_step());
queue_redraw();
return;
}
@@ -110,7 +107,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
scrolling = true;
set_physics_process_internal(true);
} else {
- set_value(target_scroll);
+ scroll_to(target_scroll);
}
return;
}
@@ -134,7 +131,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
scrolling = true;
set_physics_process_internal(true);
} else {
- set_value(target_scroll);
+ scroll_to(target_scroll);
}
}
@@ -158,7 +155,13 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
double diff = (ofs - drag.pos_at_click) / get_area_size();
+ double prev_scroll = get_value();
+
set_as_ratio(drag.value_at_click + diff);
+
+ if (!Math::is_equal_approx(prev_scroll, get_value())) {
+ emit_signal(SNAME("scrolling"));
+ }
} else {
double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x;
Ref<Texture2D> decr = theme_cache.decrement_icon;
@@ -192,32 +195,32 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
if (orientation != HORIZONTAL) {
return;
}
- set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ scroll(-(custom_step >= 0 ? custom_step : get_step()));
} else if (p_event->is_action("ui_right", true)) {
if (orientation != HORIZONTAL) {
return;
}
- set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
+ scroll(custom_step >= 0 ? custom_step : get_step());
} else if (p_event->is_action("ui_up", true)) {
if (orientation != VERTICAL) {
return;
}
- set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ scroll(-(custom_step >= 0 ? custom_step : get_step()));
} else if (p_event->is_action("ui_down", true)) {
if (orientation != VERTICAL) {
return;
}
- set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
+ scroll(custom_step >= 0 ? custom_step : get_step());
} else if (p_event->is_action("ui_home", true)) {
- set_value(get_min());
+ scroll_to(get_min());
} else if (p_event->is_action("ui_end", true)) {
- set_value(get_max());
+ scroll_to(get_max());
}
}
}
@@ -307,15 +310,15 @@ void ScrollBar::_notification(int p_what) {
}
if (drag_node) {
- drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input));
- drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
+ drag_node->connect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
+ drag_node->connect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (drag_node) {
- drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input));
- drag_node->disconnect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit));
+ drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
+ drag_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit));
}
drag_node = nullptr;
@@ -329,11 +332,11 @@ void ScrollBar::_notification(int p_what) {
double vel = ((target / dist) * 500) * get_physics_process_delta_time();
if (Math::abs(vel) >= dist) {
- set_value(target_scroll);
+ scroll_to(target_scroll);
scrolling = false;
set_physics_process_internal(false);
} else {
- set_value(get_value() + vel);
+ scroll(vel);
}
} else {
scrolling = false;
@@ -358,7 +361,7 @@ void ScrollBar::_notification(int p_what) {
turnoff = true;
}
- set_value(pos.x);
+ scroll_to(pos.x);
float sgn_x = drag_node_speed.x < 0 ? -1 : 1;
float val_x = Math::abs(drag_node_speed.x);
@@ -381,7 +384,7 @@ void ScrollBar::_notification(int p_what) {
turnoff = true;
}
- set_value(pos.y);
+ scroll_to(pos.y);
float sgn_y = drag_node_speed.y < 0 ? -1 : 1;
float val_y = Math::abs(drag_node_speed.y);
@@ -497,6 +500,18 @@ Size2 ScrollBar::get_minimum_size() const {
return minsize;
}
+void ScrollBar::scroll(double p_amount) {
+ scroll_to(get_value() + p_amount);
+}
+
+void ScrollBar::scroll_to(double p_position) {
+ double prev_scroll = get_value();
+ set_value(p_position);
+ if (!Math::is_equal_approx(prev_scroll, get_value())) {
+ emit_signal(SNAME("scrolling"));
+ }
+}
+
void ScrollBar::set_custom_step(float p_custom_step) {
custom_step = p_custom_step;
}
@@ -507,7 +522,7 @@ float ScrollBar::get_custom_step() const {
void ScrollBar::_drag_node_exit() {
if (drag_node) {
- drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input));
+ drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
}
drag_node = nullptr;
}
@@ -561,11 +576,11 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
Vector2 diff = drag_node_from + drag_node_accum;
if (orientation == HORIZONTAL) {
- set_value(diff.x);
+ scroll_to(diff.x);
}
if (orientation == VERTICAL) {
- set_value(diff.y);
+ scroll_to(diff.y);
}
time_since_motion = 0;
@@ -576,8 +591,8 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
void ScrollBar::set_drag_node(const NodePath &p_path) {
if (is_inside_tree()) {
if (drag_node) {
- drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input));
- drag_node->disconnect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit));
+ drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
+ drag_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit));
}
}
@@ -591,8 +606,8 @@ void ScrollBar::set_drag_node(const NodePath &p_path) {
}
if (drag_node) {
- drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input));
- drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
+ drag_node->connect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
+ drag_node->connect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
}
}
}
diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h
index deadbb53d6..ad88d826a2 100644
--- a/scene/gui/scroll_bar.h
+++ b/scene/gui/scroll_bar.h
@@ -111,6 +111,9 @@ protected:
static void _bind_methods();
public:
+ void scroll(double p_amount);
+ void scroll_to(double p_position);
+
void set_custom_step(float p_custom_step);
float get_custom_step() const;
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 1447d2f002..6f5d0cdcfb 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -43,11 +43,8 @@ Size2 ScrollContainer::get_minimum_size() const {
largest_child_min_size = Size2();
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
if (c == h_scroll || c == v_scroll) {
@@ -114,19 +111,19 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MouseButton::WHEEL_UP) {
// By default, the vertical orientation takes precedence. This is an exception.
if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) {
- h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor());
+ h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (v_scroll_enabled) {
- v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor());
+ v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
if (mb->get_button_index() == MouseButton::WHEEL_DOWN) {
if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) {
- h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor());
+ h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (v_scroll_enabled) {
- v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor());
+ v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
@@ -135,19 +132,19 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
// By default, the horizontal orientation takes precedence. This is an exception.
if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) {
- v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor());
+ v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (h_scroll_enabled) {
- h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor());
+ h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) {
- v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor());
+ v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (h_scroll_enabled) {
- h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor());
+ h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
@@ -213,12 +210,12 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
}
Vector2 diff = drag_from + drag_accum;
if (h_scroll_enabled) {
- h_scroll->set_value(diff.x);
+ h_scroll->scroll_to(diff.x);
} else {
drag_accum.x = 0;
}
if (v_scroll_enabled) {
- v_scroll->set_value(diff.y);
+ v_scroll->scroll_to(diff.y);
} else {
drag_accum.y = 0;
}
@@ -235,10 +232,10 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventPanGesture> pan_gesture = p_gui_input;
if (pan_gesture.is_valid()) {
if (h_scroll_enabled) {
- h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
+ h_scroll->scroll(h_scroll->get_page() * pan_gesture->get_delta().x / 8);
}
if (v_scroll_enabled) {
- v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
+ v_scroll->scroll(v_scroll->get_page() * pan_gesture->get_delta().y / 8);
}
if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
@@ -308,11 +305,8 @@ void ScrollContainer::_reposition_children() {
}
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i));
+ if (!c) {
continue;
}
if (c == h_scroll || c == v_scroll) {
@@ -391,10 +385,10 @@ void ScrollContainer::_notification(int p_what) {
}
if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) {
- h_scroll->set_value(pos.x);
+ h_scroll->scroll_to(pos.x);
}
if (vertical_scroll_mode != SCROLL_MODE_DISABLED) {
- v_scroll->set_value(pos.y);
+ v_scroll->scroll_to(pos.y);
}
float sgn_x = drag_speed.x < 0 ? -1 : 1;
@@ -542,13 +536,10 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const {
int found = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = Object::cast_to<Control>(get_child(i));
+ Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
- if (c->is_set_as_top_level()) {
- continue;
- }
if (c == h_scroll || c == v_scroll) {
continue;
}
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 70acaf7adf..bfea7b0fbe 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -409,9 +409,9 @@ SpinBox::SpinBox() {
line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED);
- line_edit->connect("focus_entered", callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED);
- line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED);
- line_edit->connect("gui_input", callable_mp(this, &SpinBox::_line_edit_input));
+ line_edit->connect(SceneStringName(focus_entered), callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED);
+ line_edit->connect(SceneStringName(focus_exited), callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED);
+ line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input));
range_click_timer = memnew(Timer);
range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout));
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
index 5f4586a6d5..925600756a 100644
--- a/scene/gui/split_container.cpp
+++ b/scene/gui/split_container.cpp
@@ -39,7 +39,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
- if (sc->collapsed || !sc->get_containable_child(0) || !sc->get_containable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) {
+ if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) {
return;
}
@@ -122,15 +122,12 @@ void SplitContainerDragger::_notification(int p_what) {
}
}
-Control *SplitContainer::get_containable_child(int p_idx) const {
+Control *SplitContainer::_get_sortable_child(int p_idx) const {
int idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
- Control *c = Object::cast_to<Control>(get_child(i, false));
- if (!c || !c->is_visible()) {
- continue;
- }
- if (c->is_set_as_top_level()) {
+ Control *c = as_sortable_control(get_child(i, false));
+ if (!c) {
continue;
}
@@ -157,8 +154,8 @@ Ref<Texture2D> SplitContainer::_get_grabber_icon() const {
}
void SplitContainer::_compute_middle_sep(bool p_clamp) {
- Control *first = get_containable_child(0);
- Control *second = get_containable_child(1);
+ Control *first = _get_sortable_child(0);
+ Control *second = _get_sortable_child(1);
// Determine expanded children.
bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND;
@@ -199,8 +196,8 @@ void SplitContainer::_compute_middle_sep(bool p_clamp) {
}
void SplitContainer::_resort() {
- Control *first = get_containable_child(0);
- Control *second = get_containable_child(1);
+ Control *first = _get_sortable_child(0);
+ Control *second = _get_sortable_child(1);
// If we have only one element.
if (!first || !second) {
@@ -261,7 +258,7 @@ Size2 SplitContainer::get_minimum_size() const {
int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
for (int i = 0; i < 2; i++) {
- if (!get_containable_child(i)) {
+ if (!_get_sortable_child(i)) {
break;
}
@@ -273,7 +270,7 @@ Size2 SplitContainer::get_minimum_size() const {
}
}
- Size2 ms = get_containable_child(i)->get_combined_minimum_size();
+ Size2 ms = _get_sortable_child(i)->get_combined_minimum_size();
if (vertical) {
minimum.height += ms.height;
@@ -325,7 +322,7 @@ int SplitContainer::get_split_offset() const {
}
void SplitContainer::clamp_split_offset() {
- if (!get_containable_child(0) || !get_containable_child(1)) {
+ if (!_get_sortable_child(0) || !_get_sortable_child(1)) {
return;
}
diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h
index 0f45ef166d..95f26f5e0b 100644
--- a/scene/gui/split_container.h
+++ b/scene/gui/split_container.h
@@ -82,12 +82,11 @@ private:
Ref<Texture2D> _get_grabber_icon() const;
void _compute_middle_sep(bool p_clamp);
void _resort();
+ Control *_get_sortable_child(int p_idx) const;
protected:
bool is_fixed = false;
- Control *get_containable_child(int p_idx) const;
-
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index d20fef8164..ddc757c452 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -323,6 +323,19 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
}
+String TabBar::get_tooltip(const Point2 &p_pos) const {
+ int tab_idx = get_tab_idx_at_point(p_pos);
+ if (tab_idx < 0) {
+ return Control::get_tooltip(p_pos);
+ }
+
+ if (tabs[tab_idx].tooltip.is_empty() && tabs[tab_idx].truncated) {
+ return tabs[tab_idx].text;
+ }
+
+ return tabs[tab_idx].tooltip;
+}
+
void TabBar::_shape(int p_tab) {
tabs.write[p_tab].text_buf->clear();
tabs.write[p_tab].text_buf->set_width(-1);
@@ -757,6 +770,16 @@ String TabBar::get_tab_title(int p_tab) const {
return tabs[p_tab].text;
}
+void TabBar::set_tab_tooltip(int p_tab, const String &p_tooltip) {
+ ERR_FAIL_INDEX(p_tab, tabs.size());
+ tabs.write[p_tab].tooltip = p_tooltip;
+}
+
+String TabBar::get_tab_tooltip(int p_tab) const {
+ ERR_FAIL_INDEX_V(p_tab, tabs.size(), "");
+ return tabs[p_tab].tooltip;
+}
+
void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) {
ERR_FAIL_INDEX(p_tab, tabs.size());
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
@@ -998,7 +1021,8 @@ void TabBar::_update_cache(bool p_update_hover) {
tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x);
tabs.write[i].size_cache = get_tab_width(i);
- if (max_width > 0 && tabs[i].size_cache > max_width) {
+ tabs.write[i].truncated = max_width > 0 && tabs[i].size_cache > max_width;
+ if (tabs[i].truncated) {
int size_textless = tabs[i].size_cache - tabs[i].size_text;
int mw = MAX(size_textless, max_width);
@@ -1720,58 +1744,6 @@ bool TabBar::get_deselect_enabled() const {
return deselect_enabled;
}
-bool TabBar::_set(const StringName &p_name, const Variant &p_value) {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) {
- int tab_index = components[0].trim_prefix("tab_").to_int();
- const String &property = components[1];
- if (property == "title") {
- set_tab_title(tab_index, p_value);
- return true;
- } else if (property == "icon") {
- set_tab_icon(tab_index, p_value);
- return true;
- } else if (property == "disabled") {
- set_tab_disabled(tab_index, p_value);
- return true;
- }
- }
- return false;
-}
-
-bool TabBar::_get(const StringName &p_name, Variant &r_ret) const {
- Vector<String> components = String(p_name).split("/", true, 2);
- if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) {
- int tab_index = components[0].trim_prefix("tab_").to_int();
- const String &property = components[1];
- if (property == "title") {
- r_ret = get_tab_title(tab_index);
- return true;
- } else if (property == "icon") {
- r_ret = get_tab_icon(tab_index);
- return true;
- } else if (property == "disabled") {
- r_ret = is_tab_disabled(tab_index);
- return true;
- }
- }
- return false;
-}
-
-void TabBar::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < tabs.size(); i++) {
- p_list->push_back(PropertyInfo(Variant::STRING, vformat("tab_%d/title", i)));
-
- PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("tab_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
- pi.usage &= ~(get_tab_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
-
- pi = PropertyInfo(Variant::BOOL, vformat("tab_%d/disabled", i));
- pi.usage &= ~(!is_tab_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
- p_list->push_back(pi);
- }
-}
-
void TabBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tab_count", "count"), &TabBar::set_tab_count);
ClassDB::bind_method(D_METHOD("get_tab_count"), &TabBar::get_tab_count);
@@ -1782,6 +1754,8 @@ void TabBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("select_next_available"), &TabBar::select_next_available);
ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabBar::set_tab_title);
ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title);
+ ClassDB::bind_method(D_METHOD("set_tab_tooltip", "tab_idx", "tooltip"), &TabBar::set_tab_tooltip);
+ ClassDB::bind_method(D_METHOD("get_tab_tooltip", "tab_idx"), &TabBar::get_tab_tooltip);
ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction);
ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &TabBar::get_tab_text_direction);
ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &TabBar::set_tab_language);
@@ -1890,10 +1864,21 @@ void TabBar::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, close_icon, "close");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_pressed_style, "button_pressed");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_hl_style, "button_highlight");
+
+ Tab defaults(true);
+
+ base_property_helper.set_prefix("tab_");
+ base_property_helper.set_array_length_getter(&TabBar::get_tab_count);
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "title"), defaults.text, &TabBar::set_tab_title, &TabBar::get_tab_title);
+ base_property_helper.register_property(PropertyInfo(Variant::STRING, "tooltip"), defaults.tooltip, &TabBar::set_tab_tooltip, &TabBar::get_tab_tooltip);
+ base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon);
+ base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &TabBar::set_tab_disabled, &TabBar::is_tab_disabled);
}
TabBar::TabBar() {
set_size(Size2(get_size().width, get_minimum_size().height));
set_focus_mode(FOCUS_ALL);
- connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited));
+ connect(SceneStringName(mouse_exited), callable_mp(this, &TabBar::_on_mouse_exited));
+
+ property_helper.setup_for_instance(base_property_helper, this);
}
diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h
index 65a1d5bd4f..52f1da5ec8 100644
--- a/scene/gui/tab_bar.h
+++ b/scene/gui/tab_bar.h
@@ -32,6 +32,7 @@
#define TAB_BAR_H
#include "scene/gui/control.h"
+#include "scene/property_list_helper.h"
#include "scene/resources/text_line.h"
class TabBar : public Control {
@@ -55,6 +56,7 @@ public:
private:
struct Tab {
String text;
+ String tooltip;
String language;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
@@ -65,6 +67,8 @@ private:
bool disabled = false;
bool hidden = false;
+ bool truncated = false;
+
Variant metadata;
int ofs_cache = 0;
int size_cache = 0;
@@ -77,8 +81,13 @@ private:
Tab() {
text_buf.instantiate();
}
+
+ Tab(bool p_dummy) {}
};
+ static inline PropertyListHelper base_property_helper;
+ PropertyListHelper property_helper;
+
int offset = 0;
int max_drawn_tab = 0;
int highlight_arrow = -1;
@@ -162,10 +171,13 @@ private:
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ virtual String get_tooltip(const Point2 &p_pos) const override;
- bool _set(const StringName &p_name, const Variant &p_value);
- bool _get(const StringName &p_name, Variant &r_ret) const;
- void _get_property_list(List<PropertyInfo> *p_list) const;
+ bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); }
+ bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
+ void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, tabs.size()); }
+ bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
+ bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
void _notification(int p_what);
static void _bind_methods();
@@ -184,6 +196,9 @@ public:
void set_tab_title(int p_tab, const String &p_title);
String get_tab_title(int p_tab) const;
+ void set_tab_tooltip(int p_tab, const String &p_tooltip);
+ String get_tab_tooltip(int p_tab) const;
+
void set_tab_text_direction(int p_tab, TextDirection p_text_direction);
TextDirection get_tab_text_direction(int p_tab) const;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 105f4484b2..d0c3f3d65e 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -30,9 +30,6 @@
#include "tab_container.h"
-#include "scene/gui/box_container.h"
-#include "scene/gui/label.h"
-#include "scene/gui/texture_rect.h"
#include "scene/theme/theme_db.h"
int TabContainer::_get_tab_height() const {
@@ -150,9 +147,7 @@ void TabContainer::_notification(int p_what) {
if (get_tab_count() > 0) {
_refresh_tab_names();
}
- } break;
- case NOTIFICATION_POST_ENTER_TREE: {
if (setup_current_tab >= -1) {
set_current_tab(setup_current_tab);
setup_current_tab = -2;
@@ -194,6 +189,25 @@ void TabContainer::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (!is_visible() || setup_current_tab > -2) {
+ return;
+ }
+
+ updating_visibility = true;
+
+ // As the visibility change notification will be triggered for all children soon after,
+ // beat it to the punch and make sure that the correct node is the only one visible first.
+ // Otherwise, it can prevent a tab change done right before this container was made visible.
+ Vector<Control *> controls = _get_tab_controls();
+ int current = get_current_tab();
+ for (int i = 0; i < controls.size(); i++) {
+ controls[i]->set_visible(i == current);
+ }
+
+ updating_visibility = false;
+ } break;
+
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
@@ -403,6 +417,7 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_
// Get the tab properties before they get erased by the child removal.
String tab_title = p_from->get_tab_title(p_from_index);
+ String tab_tooltip = p_from->get_tab_tooltip(p_from_index);
Ref<Texture2D> tab_icon = p_from->get_tab_icon(p_from_index);
Ref<Texture2D> tab_button_icon = p_from->get_tab_button_icon(p_from_index);
bool tab_disabled = p_from->is_tab_disabled(p_from_index);
@@ -420,6 +435,7 @@ void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_
move_child(moving_tabc, get_tab_control(p_to_index)->get_index(false));
set_tab_title(p_to_index, tab_title);
+ set_tab_tooltip(p_to_index, tab_tooltip);
set_tab_icon(p_to_index, tab_icon);
set_tab_button_icon(p_to_index, tab_button_icon);
set_tab_disabled(p_to_index, tab_disabled);
@@ -538,7 +554,7 @@ void TabContainer::add_child_notify(Node *p_child) {
}
p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names));
- p_child->connect(SNAME("visibility_changed"), callable_mp(this, &TabContainer::_on_tab_visibility_changed).bind(c));
+ p_child->connect(SceneStringName(visibility_changed), callable_mp(this, &TabContainer::_on_tab_visibility_changed).bind(c));
// TabBar won't emit the "tab_changed" signal when not inside the tree.
if (!is_inside_tree()) {
@@ -591,7 +607,7 @@ void TabContainer::remove_child_notify(Node *p_child) {
p_child->remove_meta("_tab_index");
p_child->remove_meta("_tab_name");
p_child->disconnect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names));
- p_child->disconnect(SNAME("visibility_changed"), callable_mp(this, &TabContainer::_on_tab_visibility_changed));
+ p_child->disconnect(SceneStringName(visibility_changed), callable_mp(this, &TabContainer::_on_tab_visibility_changed));
// TabBar won't emit the "tab_changed" signal when not inside the tree.
if (!is_inside_tree()) {
@@ -612,6 +628,7 @@ void TabContainer::set_current_tab(int p_current) {
setup_current_tab = p_current;
return;
}
+
tab_bar->set_current_tab(p_current);
}
@@ -763,16 +780,22 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) {
child->set_meta("_tab_name", p_title);
}
- _update_margins();
- if (!get_clip_tabs()) {
- update_minimum_size();
- }
+ _repaint();
+ queue_redraw();
}
String TabContainer::get_tab_title(int p_tab) const {
return tab_bar->get_tab_title(p_tab);
}
+void TabContainer::set_tab_tooltip(int p_tab, const String &p_tooltip) {
+ tab_bar->set_tab_tooltip(p_tab, p_tooltip);
+}
+
+String TabContainer::get_tab_tooltip(int p_tab) const {
+ return tab_bar->get_tab_tooltip(p_tab);
+}
+
void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) {
if (tab_bar->get_tab_icon(p_tab) == p_icon) {
return;
@@ -782,12 +805,29 @@ void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) {
_update_margins();
_repaint();
+ queue_redraw();
}
Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const {
return tab_bar->get_tab_icon(p_tab);
}
+void TabContainer::set_tab_icon_max_width(int p_tab, int p_width) {
+ if (tab_bar->get_tab_icon_max_width(p_tab) == p_width) {
+ return;
+ }
+
+ tab_bar->set_tab_icon_max_width(p_tab, p_width);
+
+ _update_margins();
+ _repaint();
+ queue_redraw();
+}
+
+int TabContainer::get_tab_icon_max_width(int p_tab) const {
+ return tab_bar->get_tab_icon_max_width(p_tab);
+}
+
void TabContainer::set_tab_disabled(int p_tab, bool p_disabled) {
if (tab_bar->is_tab_disabled(p_tab) == p_disabled) {
return;
@@ -980,8 +1020,12 @@ void TabContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_all_tabs_in_front"), &TabContainer::is_all_tabs_in_front);
ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &TabContainer::set_tab_title);
ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabContainer::get_tab_title);
+ ClassDB::bind_method(D_METHOD("set_tab_tooltip", "tab_idx", "tooltip"), &TabContainer::set_tab_tooltip);
+ ClassDB::bind_method(D_METHOD("get_tab_tooltip", "tab_idx"), &TabContainer::get_tab_tooltip);
ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabContainer::set_tab_icon);
ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon);
+ ClassDB::bind_method(D_METHOD("set_tab_icon_max_width", "tab_idx", "width"), &TabContainer::set_tab_icon_max_width);
+ ClassDB::bind_method(D_METHOD("get_tab_icon_max_width", "tab_idx"), &TabContainer::get_tab_icon_max_width);
ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled);
ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabContainer::is_tab_disabled);
ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabContainer::set_tab_hidden);
@@ -1077,5 +1121,5 @@ TabContainer::TabContainer() {
tab_bar->connect("tab_button_pressed", callable_mp(this, &TabContainer::_on_tab_button_pressed));
tab_bar->connect("active_tab_rearranged", callable_mp(this, &TabContainer::_on_active_tab_rearranged));
- connect("mouse_exited", callable_mp(this, &TabContainer::_on_mouse_exited));
+ connect(SceneStringName(mouse_exited), callable_mp(this, &TabContainer::_on_mouse_exited));
}
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index 8645a6d14e..e00bc780d4 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -155,9 +155,15 @@ public:
void set_tab_title(int p_tab, const String &p_title);
String get_tab_title(int p_tab) const;
+ void set_tab_tooltip(int p_tab, const String &p_tooltip);
+ String get_tab_tooltip(int p_tab) const;
+
void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_tab_icon(int p_tab) const;
+ void set_tab_icon_max_width(int p_tab, int p_width);
+ int get_tab_icon_max_width(int p_tab) const;
+
void set_tab_disabled(int p_tab, bool p_disabled);
bool is_tab_disabled(int p_tab) const;
diff --git a/scene/gui/text_edit.compat.inc b/scene/gui/text_edit.compat.inc
new file mode 100644
index 0000000000..bf73229868
--- /dev/null
+++ b/scene/gui/text_edit.compat.inc
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* text_edit.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+void TextEdit::_set_selection_mode_compat_86978(SelectionMode p_mode, int p_line, int p_column, int p_caret) {
+ set_selection_mode(p_mode);
+}
+
+void TextEdit::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::_set_selection_mode_compat_86978, DEFVAL(-1), DEFVAL(-1), DEFVAL(0));
+}
+
+#endif
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 6bb745ac57..a36eb0652e 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "text_edit.h"
+#include "text_edit.compat.inc"
#include "core/config/project_settings.h"
#include "core/input/input.h"
@@ -451,7 +452,7 @@ void TextEdit::_notification(int p_what) {
callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
}
if (text_changed_dirty) {
- callable_mp(this, &TextEdit::_text_changed_emit).call_deferred();
+ callable_mp(this, &TextEdit::_emit_text_changed).call_deferred();
}
_update_wrap_at_column(true);
} break;
@@ -557,17 +558,15 @@ void TextEdit::_notification(int p_what) {
int visible_rows = get_visible_line_count() + 1;
- Color color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color;
-
if (theme_cache.background_color.a > 0.01) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), theme_cache.background_color);
}
Vector<BraceMatchingData> brace_matching;
if (highlight_matching_braces_enabled) {
- brace_matching.resize(carets.size());
+ brace_matching.resize(get_caret_count());
- for (int caret = 0; caret < carets.size(); caret++) {
+ for (int caret = 0; caret < get_caret_count(); caret++) {
if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) {
continue;
}
@@ -733,7 +732,7 @@ void TextEdit::_notification(int p_what) {
if (draw_minimap) {
int minimap_visible_lines = get_minimap_visible_lines();
int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
- int minimap_tab_size = minimap_char_size.x * text.get_tab_size();
+ int tab_size = text.get_tab_size();
// Calculate viewport size and y offset.
int viewport_height = (draw_amount - 1) * minimap_line_height;
@@ -752,16 +751,15 @@ void TextEdit::_notification(int p_what) {
// Draw the minimap.
// Add visual feedback when dragging or hovering the visible area rectangle.
- float viewport_alpha;
+ Color viewport_color = theme_cache.caret_color;
if (dragging_minimap) {
- viewport_alpha = 0.25;
+ viewport_color.a = 0.25;
} else if (hovering_minimap) {
- viewport_alpha = 0.175;
+ viewport_color.a = 0.175;
} else {
- viewport_alpha = 0.1;
+ viewport_color.a = 0.1;
}
- const Color viewport_color = (theme_cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha);
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color);
} else {
@@ -839,68 +837,74 @@ void TextEdit::_notification(int p_what) {
}
}
- Color previous_color;
+ Color next_color = current_color;
int characters = 0;
- int tabs = 0;
+ int tab_alignment = 0;
+ int xpos = xmargin_end + 2 + indent_px;
for (int j = 0; j < str.length(); j++) {
- const Variant *color_data = color_map.getptr(last_wrap_column + j);
- if (color_data != nullptr) {
- current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
- if (!editable) {
- current_color.a = theme_cache.font_readonly_color.a;
+ bool next_is_whitespace = false;
+ bool next_is_tab = false;
+ // Get the number of characters to draw together.
+ for (characters = 0; j + characters < str.length(); characters++) {
+ int next_char_index = j + characters;
+ const Variant *color_data = color_map.getptr(last_wrap_column + next_char_index);
+ if (color_data != nullptr) {
+ next_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
+ if (!editable) {
+ next_color.a = theme_cache.font_readonly_color.a;
+ }
+ next_color.a *= 0.6;
}
- }
- color = current_color;
-
- if (j == 0) {
- previous_color = color;
- }
-
- int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs;
- bool out_of_bounds = (xpos >= xmargin_end + minimap_width);
-
- bool whitespace = is_whitespace(str[j]);
- if (!whitespace) {
- characters++;
-
- if (j < str.length() - 1 && color == previous_color && !out_of_bounds) {
- continue;
+ if (characters == 0) {
+ current_color = next_color;
}
-
- // If we've changed color we are at the start of a new section, therefore we need to go back to the end
- // of the previous section to draw it, we'll also add the character back on.
- if (color != previous_color) {
- characters--;
- j--;
-
- if (str[j] == '\t') {
- tabs -= minimap_tab_size;
+ if (next_color != current_color) {
+ break;
+ }
+ next_is_whitespace = is_whitespace(str[next_char_index]);
+ if (next_is_whitespace) {
+ if (str[next_char_index] == '\t') {
+ next_is_tab = true;
}
+ break;
+ }
+ bool out_of_bounds = xpos + minimap_char_size.x * characters >= xmargin_end + minimap_width;
+ if (out_of_bounds) {
+ break;
}
}
+ if (!next_is_whitespace && characters == 0) {
+ break;
+ }
if (characters > 0) {
- previous_color.a *= 0.6;
- // Take one for zero indexing, and if we hit whitespace / the end of a word.
- int chars = MAX(0, (j - (characters - 1)) - (whitespace ? 1 : 0)) + 1;
- int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs;
if (rtl) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - char_x_ofs - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - xpos - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), current_color);
} else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(xpos, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), current_color);
}
}
- if (out_of_bounds) {
- break;
- }
+ j += characters - 1;
+ xpos += minimap_char_size.x * characters;
+ tab_alignment += characters;
- if (str[j] == '\t') {
- tabs += minimap_tab_size;
+ if (next_is_whitespace) {
+ if (next_is_tab) {
+ tab_alignment %= tab_size;
+ xpos += minimap_char_size.x * (tab_size - tab_alignment);
+ tab_alignment = 0;
+ } else {
+ xpos += minimap_char_size.x;
+ tab_alignment += 1;
+ }
+ j += 1;
}
- previous_color = color;
- characters = 0;
+ if (xpos >= xmargin_end + minimap_width) {
+ // Out of bounds.
+ break;
+ }
}
}
}
@@ -999,23 +1003,12 @@ void TextEdit::_notification(int p_what) {
}
}
- if (str.length() == 0) {
- // Draw line background if empty as we won't loop at all.
- if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
- if (rtl) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
- }
- }
- } else {
- // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later.
- if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
- if (rtl) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
- } else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
- }
+ // Draw current line highlight.
+ if (highlight_current_line && caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index)) {
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
}
}
@@ -1105,7 +1098,7 @@ void TextEdit::_notification(int p_what) {
// Draw selections.
float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
- for (int c = 0; c < carets.size(); c++) {
+ for (int c = 0; c < get_caret_count(); c++) {
if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) {
int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c);
int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c);
@@ -1187,7 +1180,8 @@ void TextEdit::_notification(int p_what) {
}
if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word
- if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') {
+ if (is_ascii_alphabet_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') {
+ Color highlight_underline_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color;
int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
int lookup_symbol_word_len = lookup_symbol_word.length();
while (lookup_symbol_word_col != -1) {
@@ -1205,7 +1199,7 @@ void TextEdit::_notification(int p_what) {
}
rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size));
rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size));
- draw_rect(rect, color);
+ draw_rect(rect, highlight_underline_color);
}
lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len);
@@ -1258,7 +1252,7 @@ void TextEdit::_notification(int p_what) {
}
Color gl_color = current_color;
- for (int c = 0; c < carets.size(); c++) {
+ for (int c = 0; c < get_caret_count(); c++) {
if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection
int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c);
int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c);
@@ -1272,7 +1266,7 @@ void TextEdit::_notification(int p_what) {
float char_pos = char_ofs + char_margin + ofs_x;
if (char_pos >= xmargin_beg) {
if (highlight_matching_braces_enabled) {
- for (int c = 0; c < carets.size(); c++) {
+ for (int c = 0; c < get_caret_count(); c++) {
if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) ||
(get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) {
if (brace_matching[c].open_mismatch) {
@@ -1563,10 +1557,15 @@ void TextEdit::_notification(int p_what) {
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (has_focus()) {
+ bool had_ime_text = has_ime_text();
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
- if (!ime_text.is_empty() && has_selection()) {
+ if (!had_ime_text && has_ime_text()) {
+ _cancel_drag_and_drop_text();
+ }
+
+ if (has_ime_text() && has_selection()) {
delete_selection();
}
@@ -1577,7 +1576,7 @@ void TextEdit::_notification(int p_what) {
} break;
case NOTIFICATION_DRAG_BEGIN: {
- selecting_mode = SelectionMode::SELECTION_MODE_NONE;
+ set_selection_mode(SelectionMode::SELECTION_MODE_NONE);
drag_action = true;
dragging_minimap = false;
dragging_selection = false;
@@ -1588,19 +1587,31 @@ void TextEdit::_notification(int p_what) {
case NOTIFICATION_DRAG_END: {
if (is_drag_successful()) {
if (selection_drag_attempt) {
- selection_drag_attempt = false;
+ // Dropped elsewhere.
if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
delete_selection();
} else if (deselect_on_focus_loss_enabled) {
deselect();
}
}
- } else {
- selection_drag_attempt = false;
}
+ if (drag_caret_index >= 0) {
+ if (drag_caret_index < carets.size()) {
+ remove_caret(drag_caret_index);
+ }
+ drag_caret_index = -1;
+ }
+ selection_drag_attempt = false;
drag_action = false;
drag_caret_force_displayed = false;
} break;
+
+ case NOTIFICATION_MOUSE_EXIT_SELF: {
+ if (drag_caret_force_displayed) {
+ drag_caret_force_displayed = false;
+ queue_redraw();
+ }
+ } break;
}
}
@@ -1703,15 +1714,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
}
+
if (mb->get_button_index() == MouseButton::LEFT) {
_reset_caret_blink_timer();
apply_ime();
Point2i pos = get_line_column_at_pos(mpos);
- int row = pos.y;
+ int line = pos.y;
int col = pos.x;
+ // Gutters.
int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
for (int i = 0; i < gutters.size(); i++) {
if (!gutters[i].draw || gutters[i].width <= 0) {
@@ -1719,14 +1732,14 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) {
- emit_signal(SNAME("gutter_clicked"), row, i);
+ emit_signal(SNAME("gutter_clicked"), line, i);
return;
}
left_margin += gutters[i].width;
}
- // Minimap
+ // Minimap.
if (draw_minimap) {
_update_minimap_click();
if (dragging_minimap) {
@@ -1734,121 +1747,86 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
+ // Update caret.
+
int caret = carets.size() - 1;
int prev_col = get_caret_column(caret);
int prev_line = get_caret_line(caret);
+ int mouse_over_selection_caret = get_selection_at_line_column(line, col, true);
+
const int triple_click_timeout = 600;
const int triple_click_tolerance = 5;
bool is_triple_click = (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance);
if (!mb->is_double_click() && !is_triple_click) {
if (mb->is_alt_pressed()) {
- prev_line = row;
+ prev_line = line;
prev_col = col;
// Remove caret at clicked location.
- if (carets.size() > 1) {
- for (int i = 0; i < carets.size(); i++) {
- // Deselect if clicked on caret or its selection.
- if ((get_caret_column(i) == col && get_caret_line(i) == row) || is_mouse_over_selection(true, i)) {
- remove_caret(i);
- last_dblclk = 0;
- return;
- }
+ if (get_caret_count() > 1) {
+ // Deselect if clicked on caret or its selection.
+ int clicked_caret = get_selection_at_line_column(line, col, true, false);
+ if (clicked_caret != -1) {
+ remove_caret(clicked_caret);
+ last_dblclk = 0;
+ return;
}
}
- if (is_mouse_over_selection()) {
+ if (mouse_over_selection_caret >= 0) {
+ // Did not remove selection under mouse, don't add a new caret.
return;
}
- caret = add_caret(row, col);
+ // Create new caret at clicked location.
+ caret = add_caret(line, col);
if (caret == -1) {
return;
}
- carets.write[caret].selection.selecting_line = row;
- carets.write[caret].selection.selecting_column = col;
-
last_dblclk = 0;
- } else if (!mb->is_shift_pressed() && !is_mouse_over_selection()) {
- caret = 0;
- remove_secondary_carets();
- }
- }
-
- _push_current_op();
- set_caret_line(row, false, true, 0, caret);
- set_caret_column(col, false, caret);
- selection_drag_attempt = false;
-
- if (selecting_enabled && mb->is_shift_pressed() && (get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line)) {
- if (!has_selection(caret)) {
- carets.write[caret].selection.active = true;
- selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
- carets.write[caret].selection.from_column = prev_col;
- carets.write[caret].selection.from_line = prev_line;
- carets.write[caret].selection.to_column = carets[caret].column;
- carets.write[caret].selection.to_line = carets[caret].line;
-
- if (get_selection_from_line(caret) > get_selection_to_line(caret) || (get_selection_from_line(caret) == get_selection_to_line(caret) && get_selection_from_column(caret) > get_selection_to_column(caret))) {
- SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column);
- SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line);
- carets.write[caret].selection.shiftclick_left = false;
- } else {
- carets.write[caret].selection.shiftclick_left = true;
- }
- carets.write[caret].selection.selecting_line = prev_line;
- carets.write[caret].selection.selecting_column = prev_col;
- caret_index_edit_dirty = true;
- merge_overlapping_carets();
- queue_redraw();
- } else {
- if (carets[caret].line < get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column < get_selection_column(caret))) {
- if (carets[caret].selection.shiftclick_left) {
- carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left;
- }
- carets.write[caret].selection.from_column = carets[caret].column;
- carets.write[caret].selection.from_line = carets[caret].line;
-
- } else if (carets[caret].line > get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column > get_selection_column(caret))) {
- if (!carets[caret].selection.shiftclick_left) {
- SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column);
- SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line);
- carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left;
- }
- carets.write[caret].selection.to_column = carets[caret].column;
- carets.write[caret].selection.to_line = carets[caret].line;
-
+ } else if (!mb->is_shift_pressed()) {
+ if (drag_and_drop_selection_enabled && mouse_over_selection_caret >= 0) {
+ // Try to drag and drop.
+ set_selection_mode(SelectionMode::SELECTION_MODE_NONE);
+ selection_drag_attempt = true;
+ drag_and_drop_origin_caret_index = mouse_over_selection_caret;
+ last_dblclk = 0;
+ // Don't update caret until we know if it is not drag and drop.
+ return;
} else {
- deselect(caret);
+ // A regular click clears all other carets.
+ caret = 0;
+ remove_secondary_carets();
+ deselect();
}
- caret_index_edit_dirty = true;
- merge_overlapping_carets();
- queue_redraw();
}
- } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) {
- set_selection_mode(SelectionMode::SELECTION_MODE_NONE, get_selection_line(caret), get_selection_column(caret), caret);
- // We use the main caret for dragging, so reset this one.
- set_caret_line(prev_line, false, true, 0, caret);
- set_caret_column(prev_col, false, caret);
- selection_drag_attempt = true;
- } else if (caret == 0) {
- deselect();
- set_selection_mode(SelectionMode::SELECTION_MODE_POINTER, row, col);
- }
- if (is_triple_click) {
- // Triple-click select line.
- selecting_mode = SelectionMode::SELECTION_MODE_LINE;
+ _push_current_op();
+ set_caret_line(line, false, true, -1, caret);
+ set_caret_column(col, false, caret);
selection_drag_attempt = false;
- _update_selection_mode_line();
+ bool caret_moved = get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line;
+
+ if (selecting_enabled && mb->is_shift_pressed() && !has_selection(caret) && caret_moved) {
+ // Select from the previous caret position.
+ select(prev_line, prev_col, line, col, caret);
+ }
+
+ // Start regular select mode.
+ set_selection_mode(SelectionMode::SELECTION_MODE_POINTER);
+ _update_selection_mode_pointer(true);
+ } else if (is_triple_click) {
+ // Start triple-click select line mode.
+ set_selection_mode(SelectionMode::SELECTION_MODE_LINE);
+ _update_selection_mode_line(true);
last_dblclk = 0;
- } else if (mb->is_double_click() && text[get_caret_line(caret)].length()) {
- // Double-click select word.
- selecting_mode = SelectionMode::SELECTION_MODE_WORD;
- _update_selection_mode_word();
+ } else if (mb->is_double_click()) {
+ // Start double-click select word mode.
+ set_selection_mode(SelectionMode::SELECTION_MODE_WORD);
+ _update_selection_mode_word(true);
last_dblclk = OS::get_singleton()->get_ticks_msec();
last_dblclk_pos = mb->get_position();
}
@@ -1864,34 +1842,20 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
_push_current_op();
_reset_caret_blink_timer();
apply_ime();
+ _cancel_drag_and_drop_text();
Point2i pos = get_line_column_at_pos(mpos);
- int row = pos.y;
- int col = pos.x;
+ int mouse_line = pos.y;
+ int mouse_column = pos.x;
- bool selection_clicked = false;
if (is_move_caret_on_right_click_enabled()) {
- if (has_selection()) {
- for (int i = 0; i < get_caret_count(); i++) {
- int from_line = get_selection_from_line(i);
- int to_line = get_selection_to_line(i);
- int from_column = get_selection_from_column(i);
- int to_column = get_selection_to_column(i);
-
- if (row >= from_line && row <= to_line && (row != from_line || col >= from_column) && (row != to_line || col <= to_column)) {
- // Right click in one of the selected text
- selection_clicked = true;
- break;
- }
- }
- }
+ bool selection_clicked = get_selection_at_line_column(mouse_line, mouse_column, true) >= 0;
if (!selection_clicked) {
deselect();
remove_secondary_carets();
- set_caret_line(row, false, false);
- set_caret_column(col);
+ set_caret_line(mouse_line, false, false, -1);
+ set_caret_column(mouse_column);
}
- merge_overlapping_carets();
}
if (context_menu_enabled) {
@@ -1909,22 +1873,20 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (mb->get_button_index() == MouseButton::LEFT) {
- if (selection_drag_attempt && is_mouse_over_selection()) {
+ if (!drag_action && selection_drag_attempt && is_mouse_over_selection()) {
+ // This is not a drag and drop attempt, update the caret.
+ selection_drag_attempt = false;
remove_secondary_carets();
+ deselect();
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- set_caret_line(pos.y, false, true, 0, 0);
+ set_caret_line(pos.y, false, true, -1, 0);
set_caret_column(pos.x, true, 0);
-
- deselect();
}
dragging_minimap = false;
dragging_selection = false;
can_drag_minimap = false;
click_select_held->stop();
- if (!drag_action) {
- selection_drag_attempt = false;
- }
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
@@ -1959,7 +1921,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
mpos.x = get_size().x - mpos.x;
}
- if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging.
+ if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && get_viewport()->gui_get_drag_data() == Variant()) {
+ // Update if not in drag and drop.
_reset_caret_blink_timer();
if (draw_minimap && !dragging_selection) {
@@ -2012,10 +1975,19 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) {
apply_ime();
+ // Update drag and drop caret.
drag_caret_force_displayed = true;
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- set_caret_line(pos.y, false, true, 0, 0);
- set_caret_column(pos.x, true, 0);
+
+ if (drag_caret_index == -1) {
+ // Force create a new caret for drag and drop.
+ carets.push_back(Caret());
+ drag_caret_index = carets.size() - 1;
+ }
+
+ drag_caret_force_displayed = true;
+ set_caret_line(pos.y, false, true, -1, drag_caret_index);
+ set_caret_column(pos.x, true, drag_caret_index);
dragging_selection = true;
}
}
@@ -2044,6 +2016,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
+ _cancel_drag_and_drop_text();
+
_reset_caret_blink_timer();
// Allow unicode handling if:
@@ -2322,42 +2296,36 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
- bool first_line = false;
- if (!p_split_current_line) {
- deselect(i);
- if (p_above) {
- if (get_caret_line(i) > 0) {
- set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
- set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
- } else {
- set_caret_column(0, i == 0, i);
- first_line = true;
- }
- } else {
- set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
- }
- }
+ begin_multicaret_edit();
- insert_text_at_caret("\n", i);
-
- if (first_line) {
- set_caret_line(0, i == 0, true, 0, i);
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
+ if (p_split_current_line) {
+ insert_text_at_caret("\n", i);
+ } else {
+ int line = get_caret_line(i);
+ insert_text("\n", line, p_above ? 0 : text[line].length(), p_above, p_above);
+ deselect(i);
+ set_caret_line(p_above ? line : line + 1, false, true, -1, i);
+ set_caret_column(0, i == 0, i);
}
}
+
+ end_multicaret_edit();
end_complex_operation();
}
void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
// Handle selection.
if (p_select) {
_pre_shift_selection(i);
} else if (has_selection(i) && !p_move_by_word) {
// If a selection is active, move caret to start of selection.
- set_caret_line(get_selection_from_line(i), false, true, 0, i);
+ set_caret_line(get_selection_from_line(i), false, true, -1, i);
set_caret_column(get_selection_from_column(i), i == 0, i);
deselect(i);
continue;
@@ -2369,7 +2337,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
int cc = get_caret_column(i);
// If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
if (cc == 0 && get_caret_line(i) > 0) {
- set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
+ set_caret_line(get_caret_line(i) - 1, false, true, -1, i);
set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
@@ -2390,7 +2358,8 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
// If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
if (get_caret_column(i) == 0) {
if (get_caret_line(i) > 0) {
- set_caret_line(get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1), false, true, 0, i);
+ int new_caret_line = get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1);
+ set_caret_line(new_caret_line, false, true, -1, i);
set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
}
} else {
@@ -2401,23 +2370,19 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
}
}
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
// Handle selection.
if (p_select) {
_pre_shift_selection(i);
} else if (has_selection(i) && !p_move_by_word) {
// If a selection is active, move caret to end of selection.
- set_caret_line(get_selection_to_line(i), false, true, 0, i);
+ set_caret_line(get_selection_to_line(i), false, true, -1, i);
set_caret_column(get_selection_to_column(i), i == 0, i);
deselect(i);
continue;
@@ -2429,7 +2394,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
int cc = get_caret_column(i);
// If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line.
if (cc == text[get_caret_line(i)].length() && get_caret_line(i) < text.size() - 1) {
- set_caret_line(get_caret_line(i) + 1, false, true, 0, i);
+ set_caret_line(get_caret_line(i) + 1, false, true, -1, i);
set_caret_column(0, i == 0, i);
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
@@ -2450,7 +2415,8 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
// If we are at the end of the line, move the caret to the next line down.
if (get_caret_column(i) == text[get_caret_line(i)].length()) {
if (get_caret_line(i) < text.size() - 1) {
- set_caret_line(get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1), false, false, 0, i);
+ int new_caret_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1);
+ set_caret_line(new_caret_line, false, false, -1, i);
set_caret_column(0, i == 0, i);
}
} else {
@@ -2461,17 +2427,13 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
}
}
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_up(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2491,17 +2453,13 @@ void TextEdit::_move_caret_up(bool p_select) {
set_caret_line(new_line, i == 0, false, 0, i);
}
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_down(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2517,17 +2475,13 @@ void TextEdit::_move_caret_down(bool p_select) {
int new_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1);
set_caret_line(new_line, i == 0, false, 0, i);
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_to_line_start(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2552,17 +2506,13 @@ void TextEdit::_move_caret_to_line_start(bool p_select) {
} else {
set_caret_column(row_start_col, i == 0, i);
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_to_line_end(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2581,17 +2531,13 @@ void TextEdit::_move_caret_to_line_end(bool p_select) {
} else {
set_caret_column(row_end_col, i == 0, i);
}
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_page_up(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2601,17 +2547,13 @@ void TextEdit::_move_caret_page_up(bool p_select) {
Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), -get_visible_line_count());
int n_line = get_caret_line(i) - next_line.x + 1;
set_caret_line(n_line, i == 0, false, next_line.y, i);
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
void TextEdit::_move_caret_page_down(bool p_select) {
_push_current_op();
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_select) {
_pre_shift_selection(i);
} else {
@@ -2621,10 +2563,6 @@ void TextEdit::_move_caret_page_down(bool p_select) {
Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), get_visible_line_count());
int n_line = get_caret_line(i) + next_line.x - 1;
set_caret_line(n_line, i == 0, false, next_line.y, i);
-
- if (p_select) {
- _post_shift_selection(i);
- }
}
merge_overlapping_carets();
}
@@ -2635,58 +2573,47 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
}
start_action(EditAction::ACTION_BACKSPACE);
- Vector<int> carets_to_remove;
+ begin_multicaret_edit();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = 0; i < caret_edit_order.size(); i++) {
- int caret_idx = caret_edit_order[i];
- if (get_caret_column(caret_idx) == 0 && get_caret_line(caret_idx) == 0 && !has_selection(caret_idx)) {
+ Vector<int> sorted_carets = get_sorted_carets();
+ sorted_carets.reverse();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (multicaret_edit_ignore_caret(caret_index)) {
continue;
}
- if (has_selection(caret_idx) || (!p_all_to_left && !p_word) || get_caret_column(caret_idx) == 0) {
- backspace(caret_idx);
+ if (get_caret_column(caret_index) == 0 && get_caret_line(caret_index) == 0 && !has_selection(caret_index)) {
continue;
}
- if (p_all_to_left) {
- int caret_current_column = get_caret_column(caret_idx);
- set_caret_column(0, caret_idx == 0, caret_idx);
- _remove_text(get_caret_line(caret_idx), 0, get_caret_line(caret_idx), caret_current_column);
- adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), caret_current_column, get_caret_line(caret_idx), get_caret_column(caret_idx));
-
- // Check for any overlapping carets since we removed the entire line.
- for (int j = i + 1; j < caret_edit_order.size(); j++) {
- // Selection only end on this line, only the one as carets cannot overlap.
- if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx) && get_selection_to_line(caret_edit_order[j]) == get_caret_line(caret_idx)) {
- carets.write[caret_edit_order[j]].selection.to_column = 0;
- break;
- }
-
- // Check for caret.
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) || (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx))) {
- break;
- }
+ if (has_selection(caret_index) || (!p_all_to_left && !p_word) || get_caret_column(caret_index) == 0) {
+ backspace(caret_index);
+ continue;
+ }
- deselect(caret_edit_order[j]);
- carets_to_remove.push_back(caret_edit_order[j]);
- set_caret_column(0, caret_idx == 0, caret_idx);
- i = j;
- }
+ if (p_all_to_left) {
+ // Remove everything to left of caret to the start of the line.
+ int caret_current_column = get_caret_column(caret_index);
+ _remove_text(get_caret_line(caret_index), 0, get_caret_line(caret_index), caret_current_column);
+ collapse_carets(get_caret_line(caret_index), 0, get_caret_line(caret_index), caret_current_column);
+ set_caret_column(0, caret_index == 0, caret_index);
+ _offset_carets_after(get_caret_line(caret_index), caret_current_column, get_caret_line(caret_index), 0);
continue;
}
if (p_word) {
- // Save here as the caret may change when resolving overlaps.
- int from_column = get_caret_column(caret_idx);
- int column = get_caret_column(caret_idx);
+ // Remove text to the start of the word left of the caret.
+ int from_column = get_caret_column(caret_index);
+ int column = get_caret_column(caret_index);
// Check for the case "<word><space><caret>" and ignore the space.
// No need to check for column being 0 since it is checked above.
- if (is_whitespace(text[get_caret_line(caret_idx)][get_caret_column(caret_idx) - 1])) {
+ if (is_whitespace(text[get_caret_line(caret_index)][get_caret_column(caret_index) - 1])) {
column -= 1;
}
+
// Get a list with the indices of the word bounds of the given text line.
- const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_idx))->get_rid());
+ const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_index))->get_rid());
if (words.is_empty() || column <= words[0]) {
// If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line.
column = 0;
@@ -2700,57 +2627,14 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
}
}
- // Check for any other carets in this range.
- int overlapping_caret_index = -1;
- for (int j = i + 1; j < caret_edit_order.size(); j++) {
- // Check caret and selection in on the right line.
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) && (!has_selection(caret_edit_order[j]) || get_selection_to_line(caret_edit_order[j]) != get_caret_line(caret_idx))) {
- break;
- }
-
- // If it has a selection, check it ends with in the range.
- if ((has_selection(caret_edit_order[j]) && get_selection_to_column(caret_edit_order[j]) < column)) {
- break;
- }
-
- // If it has a selection and it starts outside our word, we need to adjust the selection, and handle it later to prevent overlap.
- if ((has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) < column)) {
- carets.write[caret_edit_order[j]].selection.to_column = column;
- overlapping_caret_index = caret_edit_order[j];
- break;
- }
-
- // Otherwise we can remove it.
- if (get_caret_column(caret_edit_order[j]) > column || (has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) > column)) {
- deselect(caret_edit_order[j]);
- carets_to_remove.push_back(caret_edit_order[j]);
- set_caret_column(0, caret_idx == 0, caret_idx);
- i = j;
- }
- }
-
- _remove_text(get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column);
-
- set_caret_line(get_caret_line(caret_idx), false, true, 0, caret_idx);
- set_caret_column(column, caret_idx == 0, caret_idx);
- adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column);
-
- // Now we can clean up the overlapping caret.
- if (overlapping_caret_index != -1) {
- backspace(overlapping_caret_index);
- i++;
- carets_to_remove.push_back(overlapping_caret_index);
- set_caret_column(get_caret_column(overlapping_caret_index), caret_idx == 0, caret_idx);
- }
- continue;
+ _remove_text(get_caret_line(caret_index), column, get_caret_line(caret_index), from_column);
+ collapse_carets(get_caret_line(caret_index), column, get_caret_line(caret_index), from_column);
+ set_caret_column(column, caret_index == 0, caret_index);
+ _offset_carets_after(get_caret_line(caret_index), from_column, get_caret_line(caret_index), column);
}
}
- // Sort and remove backwards to preserve indexes.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
- }
+ end_multicaret_edit();
end_action();
}
@@ -2760,61 +2644,40 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
}
start_action(EditAction::ACTION_DELETE);
- Vector<int> carets_to_remove;
+ begin_multicaret_edit();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = 0; i < caret_edit_order.size(); i++) {
- int caret_idx = caret_edit_order[i];
- if (has_selection(caret_idx)) {
- delete_selection(caret_idx);
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (multicaret_edit_ignore_caret(caret_index)) {
continue;
}
- int curline_len = text[get_caret_line(caret_idx)].length();
- if (get_caret_line(caret_idx) == text.size() - 1 && get_caret_column(caret_idx) == curline_len) {
+ if (has_selection(caret_index)) {
+ delete_selection(caret_index);
+ continue;
+ }
+
+ int curline_len = text[get_caret_line(caret_index)].length();
+ if (get_caret_line(caret_index) == text.size() - 1 && get_caret_column(caret_index) == curline_len) {
continue; // Last line, last column: Nothing to do.
}
- int next_line = get_caret_column(caret_idx) < curline_len ? get_caret_line(caret_idx) : get_caret_line(caret_idx) + 1;
+ int next_line = get_caret_column(caret_index) < curline_len ? get_caret_line(caret_index) : get_caret_line(caret_index) + 1;
int next_column;
if (p_all_to_right) {
- // Get caret furthest to the left.
- for (int j = i + 1; j < caret_edit_order.size(); j++) {
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
-
- if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
-
- if (!has_selection(caret_edit_order[j])) {
- i = j;
- caret_idx = caret_edit_order[i];
- }
- }
-
- if (get_caret_column(caret_idx) == curline_len) {
+ if (get_caret_column(caret_index) == curline_len) {
continue;
}
// Delete everything to right of caret.
next_column = curline_len;
- next_line = get_caret_line(caret_idx);
-
- // Remove overlapping carets.
- for (int j = i - 1; j >= 0; j--) {
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
- carets_to_remove.push_back(caret_edit_order[j]);
- }
-
- } else if (p_word && get_caret_column(caret_idx) < curline_len - 1) {
+ next_line = get_caret_line(caret_index);
+ } else if (p_word && get_caret_column(caret_index) < curline_len - 1) {
// Delete next word to right of caret.
- int line = get_caret_line(caret_idx);
- int column = get_caret_column(caret_idx);
+ int line = get_caret_line(caret_index);
+ int column = get_caret_column(caret_index);
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
for (int j = 1; j < words.size(); j = j + 2) {
@@ -2826,49 +2689,22 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
next_line = line;
next_column = column;
-
- // Remove overlapping carets.
- for (int j = i - 1; j >= 0; j--) {
- if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
- break;
- }
-
- if (get_caret_column(caret_edit_order[j]) > column) {
- break;
- }
- carets_to_remove.push_back(caret_edit_order[j]);
- }
} else {
// Delete one character.
if (caret_mid_grapheme_enabled) {
- next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0;
+ next_column = get_caret_column(caret_index) < curline_len ? (get_caret_column(caret_index) + 1) : 0;
} else {
- next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0;
- }
-
- // Remove overlapping carets.
- if (i > 0) {
- int prev_caret_idx = caret_edit_order[i - 1];
- if (get_caret_line(prev_caret_idx) == next_line && get_caret_column(prev_caret_idx) == next_column) {
- carets_to_remove.push_back(prev_caret_idx);
- }
+ next_column = get_caret_column(caret_index) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_index))->get_rid(), (get_caret_column(caret_index))) : 0;
}
}
- _remove_text(get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column);
- adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column);
- }
-
- // Sort and remove backwards to preserve indexes.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
+ _remove_text(get_caret_line(caret_index), get_caret_column(caret_index), next_line, next_column);
+ collapse_carets(get_caret_line(caret_index), get_caret_column(caret_index), next_line, next_column);
+ _offset_carets_after(next_line, next_column, get_caret_line(caret_index), get_caret_column(caret_index));
}
- // If we are deleting from the end of a line, due to column preservation we could still overlap with another caret.
- merge_overlapping_carets();
+ end_multicaret_edit();
end_action();
- queue_redraw();
}
void TextEdit::_move_caret_document_start(bool p_select) {
@@ -2879,12 +2715,8 @@ void TextEdit::_move_caret_document_start(bool p_select) {
deselect();
}
- set_caret_line(0, false);
+ set_caret_line(0, false, true, -1);
set_caret_column(0);
-
- if (p_select) {
- _post_shift_selection(0);
- }
}
void TextEdit::_move_caret_document_end(bool p_select) {
@@ -2895,12 +2727,8 @@ void TextEdit::_move_caret_document_end(bool p_select) {
deselect();
}
- set_caret_line(get_last_unhidden_line(), true, false, 9999);
+ set_caret_line(get_last_unhidden_line(), true, false, -1);
set_caret_column(text[get_caret_line()].length());
-
- if (p_select) {
- _post_shift_selection(0);
- }
}
bool TextEdit::_clear_carets_and_selection() {
@@ -2918,51 +2746,6 @@ bool TextEdit::_clear_carets_and_selection() {
return false;
}
-void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const {
- if (p_last_fit_x == -1) {
- p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column);
- }
-
- // Calculate the new line and wrap index.
- p_new_line = p_old_line;
- int caret_wrap_index = p_old_wrap_index;
- if (p_below) {
- if (caret_wrap_index < get_line_wrap_count(p_new_line)) {
- caret_wrap_index++;
- } else {
- p_new_line++;
- caret_wrap_index = 0;
- }
- } else {
- if (caret_wrap_index == 0) {
- p_new_line--;
- caret_wrap_index = get_line_wrap_count(p_new_line);
- } else {
- caret_wrap_index--;
- }
- }
-
- // Boundary checks.
- if (p_new_line < 0) {
- p_new_line = 0;
- }
- if (p_new_line >= text.size()) {
- p_new_line = text.size() - 1;
- }
-
- p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index);
- if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) {
- Vector<String> rows = get_line_wrapped_text(p_new_line);
- int row_end_col = 0;
- for (int i = 0; i < caret_wrap_index + 1; i++) {
- row_end_col += rows[i].length();
- }
- if (p_new_column >= row_end_col) {
- p_new_column -= 1;
- }
- }
-}
-
void TextEdit::_update_placeholder() {
if (theme_cache.font.is_null() || theme_cache.font_size <= 0) {
return; // Not in tree?
@@ -3128,53 +2911,48 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
if (p_data.get_type() == Variant::STRING && is_editable()) {
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- int caret_row_tmp = pos.y;
- int caret_column_tmp = pos.x;
+ int drop_at_line = pos.y;
+ int drop_at_column = pos.x;
+ int selection_index = get_selection_at_line_column(drop_at_line, drop_at_column, !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL));
+
+ // Remove drag caret before the complex operation starts so it won't appear in undo.
+ remove_caret(drag_caret_index);
+
+ if (selection_drag_attempt && selection_index >= 0 && selection_index == drag_and_drop_origin_caret_index) {
+ // Dropped onto original selection, do nothing.
+ selection_drag_attempt = false;
+ return;
+ }
+
+ begin_complex_operation();
+ begin_multicaret_edit();
if (selection_drag_attempt) {
+ // Drop from self.
selection_drag_attempt = false;
- if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) {
- // Set caret back at selection for undo / redo.
- set_caret_line(get_selection_to_line(), false, false);
- set_caret_column(get_selection_to_column());
-
- begin_complex_operation();
- if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
- if (caret_row_tmp > get_selection_to_line()) {
- caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line());
- } else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) {
- caret_column_tmp = caret_column_tmp - (get_selection_to_column() - get_selection_from_column());
- }
- delete_selection();
- } else {
- deselect();
- }
+ if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
+ // Delete all selections.
+ int temp_caret = add_caret(drop_at_line, drop_at_column);
- remove_secondary_carets();
- set_caret_line(caret_row_tmp, true, false);
- set_caret_column(caret_column_tmp);
- insert_text_at_caret(p_data);
- end_complex_operation();
- }
- } else if (is_mouse_over_selection()) {
- remove_secondary_carets();
- caret_row_tmp = get_selection_from_line();
- caret_column_tmp = get_selection_from_column();
- set_caret_line(caret_row_tmp, true, false);
- set_caret_column(caret_column_tmp);
- insert_text_at_caret(p_data);
- grab_focus();
- } else {
- remove_secondary_carets();
- deselect();
- set_caret_line(caret_row_tmp, true, false);
- set_caret_column(caret_column_tmp);
- insert_text_at_caret(p_data);
- grab_focus();
- }
+ delete_selection();
- if (caret_row_tmp != get_caret_line() || caret_column_tmp != get_caret_column()) {
- select(caret_row_tmp, caret_column_tmp, get_caret_line(), get_caret_column());
+ // Use a temporary caret to update the drop at position.
+ drop_at_line = get_caret_line(temp_caret);
+ drop_at_column = get_caret_column(temp_caret);
+ }
}
+ remove_secondary_carets();
+ deselect();
+
+ // Insert the dragged text.
+ set_caret_line(drop_at_line, true, false, -1);
+ set_caret_column(drop_at_column);
+ insert_text_at_caret(p_data);
+
+ select(drop_at_line, drop_at_column, get_caret_line(), get_caret_column());
+ grab_focus();
+ adjust_viewport_to_caret();
+ end_multicaret_edit();
+ end_complex_operation();
}
}
@@ -3460,7 +3238,7 @@ void TextEdit::_clear() {
clear_undo_history();
text.clear();
remove_secondary_carets();
- set_caret_line(0, false);
+ set_caret_line(0, false, true, -1);
set_caret_column(0);
first_visible_col = 0;
first_visible_line = 0;
@@ -3533,17 +3311,36 @@ void TextEdit::set_line(int p_line, const String &p_new_text) {
return;
}
begin_complex_operation();
- _remove_text(p_line, 0, p_line, text[p_line].length());
- _insert_text(p_line, 0, p_new_text);
- for (int i = 0; i < carets.size(); i++) {
- if (get_caret_line(i) == p_line && get_caret_column(i) > p_new_text.length()) {
- set_caret_column(p_new_text.length(), false, i);
+
+ int old_column = text[p_line].length();
+
+ // Set the affected carets column to update their last offset x.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_line, 0, p_line, old_column)) {
+ set_caret_column(get_caret_column(i), false, i);
}
+ if (has_selection(i) && _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_line, 0, p_line, old_column)) {
+ set_selection_origin_column(get_selection_origin_column(i), i);
+ }
+ }
+
+ _remove_text(p_line, 0, p_line, old_column);
+ int new_line, new_column;
+ _insert_text(p_line, 0, p_new_text, &new_line, &new_column);
- if (has_selection(i) && p_line == get_selection_to_line(i) && get_selection_to_column(i) > text[p_line].length()) {
- carets.write[i].selection.to_column = text[p_line].length();
+ // Don't offset carets that were on the old line.
+ _offset_carets_after(p_line, old_column, new_line, new_column, false, false);
+
+ // Set the caret lines to update the column to match visually.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_line, 0, p_line, old_column)) {
+ set_caret_line(get_caret_line(i), false, true, 0, i);
+ }
+ if (has_selection(i) && _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_line, 0, p_line, old_column)) {
+ set_selection_origin_line(get_selection_origin_line(i), true, 0, i);
}
}
+ merge_overlapping_carets();
end_complex_operation();
}
@@ -3597,71 +3394,163 @@ void TextEdit::swap_lines(int p_from_line, int p_to_line) {
ERR_FAIL_INDEX(p_from_line, text.size());
ERR_FAIL_INDEX(p_to_line, text.size());
- String tmp = get_line(p_from_line);
- String tmp2 = get_line(p_to_line);
+ if (p_from_line == p_to_line) {
+ return;
+ }
+
+ String from_line_text = get_line(p_from_line);
+ String to_line_text = get_line(p_to_line);
begin_complex_operation();
- set_line(p_to_line, tmp);
- set_line(p_from_line, tmp2);
+ begin_multicaret_edit();
+ // Don't use set_line to avoid clamping and updating carets.
+ _remove_text(p_to_line, 0, p_to_line, text[p_to_line].length());
+ _insert_text(p_to_line, 0, from_line_text);
+ _remove_text(p_from_line, 0, p_from_line, text[p_from_line].length());
+ _insert_text(p_from_line, 0, to_line_text);
+
+ // Swap carets.
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool selected = has_selection(i);
+ if (get_caret_line(i) == p_from_line || get_caret_line(i) == p_to_line) {
+ int caret_new_line = get_caret_line(i) == p_from_line ? p_to_line : p_from_line;
+ int caret_column = get_caret_column(i);
+ set_caret_line(caret_new_line, false, true, -1, i);
+ set_caret_column(caret_column, false, i);
+ }
+ if (selected && (get_selection_origin_line(i) == p_from_line || get_selection_origin_line(i) == p_to_line)) {
+ int origin_new_line = get_selection_origin_line(i) == p_from_line ? p_to_line : p_from_line;
+ int origin_column = get_selection_origin_column(i);
+ select(origin_new_line, origin_column, get_caret_line(i), get_caret_column(i), i);
+ }
+ }
+ // If only part of a selection was changed, it may now overlap.
+ merge_overlapping_carets();
+
+ end_multicaret_edit();
end_complex_operation();
}
-void TextEdit::insert_line_at(int p_at, const String &p_text) {
- ERR_FAIL_INDEX(p_at, text.size());
+void TextEdit::insert_line_at(int p_line, const String &p_text) {
+ ERR_FAIL_INDEX(p_line, text.size());
- _insert_text(p_at, 0, p_text + "\n");
+ // Use a complex operation so subsequent calls aren't merged together.
+ begin_complex_operation();
- for (int i = 0; i < carets.size(); i++) {
- if (get_caret_line(i) >= p_at) {
- // Offset caret when located after inserted line.
- set_caret_line(get_caret_line(i) + 1, false, true, 0, i);
- }
- if (has_selection(i)) {
- if (get_selection_from_line(i) >= p_at) {
- // Offset selection when located after inserted line.
- select(get_selection_from_line(i) + 1, get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i);
- } else if (get_selection_to_line(i) >= p_at) {
- // Extend selection that includes inserted line.
- select(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i);
+ int new_line, new_column;
+ _insert_text(p_line, 0, p_text + "\n", &new_line, &new_column);
+ _offset_carets_after(p_line, 0, new_line, new_column);
+
+ end_complex_operation();
+}
+
+void TextEdit::remove_line_at(int p_line, bool p_move_carets_down) {
+ ERR_FAIL_INDEX(p_line, text.size());
+
+ if (get_line_count() == 1) {
+ // Only one line, just remove contents.
+ begin_complex_operation();
+ int line_length = get_line(p_line).length();
+ _remove_text(p_line, 0, p_line, line_length);
+ collapse_carets(p_line, 0, p_line, line_length, true);
+ end_complex_operation();
+ return;
+ }
+
+ begin_complex_operation();
+
+ bool is_last_line = p_line == get_line_count() - 1;
+ int from_line = is_last_line ? p_line - 1 : p_line;
+ int next_line = is_last_line ? p_line : p_line + 1;
+ int from_column = is_last_line ? get_line(from_line).length() : 0;
+ int next_column = is_last_line ? get_line(next_line).length() : 0;
+
+ if ((!is_last_line && p_move_carets_down) || (p_line != 0 && !p_move_carets_down)) {
+ // Set the carets column to update their last offset x.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (get_caret_line(i) == p_line) {
+ set_caret_column(get_caret_column(i), false, i);
+ }
+ if (has_selection(i) && get_selection_origin_line(i) == p_line) {
+ set_selection_origin_column(get_selection_origin_column(i), i);
}
}
}
- // Need to apply the above adjustments to the undo / redo carets.
- current_op.end_carets = carets;
- queue_redraw();
+ // Remove line.
+ _remove_text(from_line, from_column, next_line, next_column);
+
+ begin_multicaret_edit();
+ if ((is_last_line && p_move_carets_down) || (p_line == 0 && !p_move_carets_down)) {
+ // Collapse carets.
+ collapse_carets(from_line, from_column, next_line, next_column, true);
+ } else {
+ // Move carets to visually line up.
+ int target_line = p_move_carets_down ? p_line : p_line - 1;
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool selected = has_selection(i);
+ if (get_caret_line(i) == p_line) {
+ set_caret_line(target_line, i == 0, true, 0, i);
+ }
+ if (selected && get_selection_origin_line(i) == p_line) {
+ set_selection_origin_line(target_line, true, 0, i);
+ select(get_selection_origin_line(i), get_selection_origin_column(i), get_caret_line(i), get_caret_column(i), i);
+ }
+ }
+
+ merge_overlapping_carets();
+ }
+ _offset_carets_after(next_line, next_column, from_line, from_column);
+ end_multicaret_edit();
+ end_complex_operation();
}
void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
delete_selection(i);
int from_line = get_caret_line(i);
int from_col = get_caret_column(i);
- int new_column, new_line;
+ int new_line, new_column;
_insert_text(from_line, from_col, p_text, &new_line, &new_column);
_update_scrollbars();
+ _offset_carets_after(from_line, from_col, new_line, new_column);
- set_caret_line(new_line, false, true, 0, i);
+ set_caret_line(new_line, false, true, -1, i);
set_caret_column(new_column, i == 0, i);
-
- adjust_carets_after_edit(i, new_line, new_column, from_line, from_col);
}
if (has_ime_text()) {
_update_ime_text();
}
+ end_multicaret_edit();
+ end_complex_operation();
+}
+
+void TextEdit::insert_text(const String &p_text, int p_line, int p_column, bool p_before_selection_begin, bool p_before_selection_end) {
+ ERR_FAIL_INDEX(p_line, text.size());
+ ERR_FAIL_INDEX(p_column, text[p_line].length() + 1);
+
+ begin_complex_operation();
+
+ int new_line, new_column;
+ _insert_text(p_line, p_column, p_text, &new_line, &new_column);
+
+ _offset_carets_after(p_line, p_column, new_line, new_column, p_before_selection_begin, p_before_selection_end);
+
end_complex_operation();
- queue_redraw();
}
void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
@@ -3672,7 +3561,13 @@ void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, in
ERR_FAIL_COND(p_to_line < p_from_line);
ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column);
+ begin_complex_operation();
+
_remove_text(p_from_line, p_from_column, p_to_line, p_to_column);
+ collapse_carets(p_from_line, p_from_column, p_to_line, p_to_column);
+ _offset_carets_after(p_to_line, p_to_column, p_from_line, p_from_column);
+
+ end_complex_operation();
}
int TextEdit::get_last_unhidden_line() const {
@@ -4041,7 +3936,7 @@ void TextEdit::undo() {
_push_current_op();
if (undo_stack_pos == nullptr) {
- if (!undo_stack.size()) {
+ if (undo_stack.is_empty()) {
return; // Nothing to undo.
}
@@ -4060,6 +3955,7 @@ void TextEdit::undo() {
current_op.version = op.prev_version;
if (undo_stack_pos->get().chain_backward) {
+ // This was part of a complex operation, undo until the chain forward at the start of the complex operation.
while (true) {
ERR_BREAK(!undo_stack_pos->prev());
undo_stack_pos = undo_stack_pos->prev();
@@ -4073,9 +3969,9 @@ void TextEdit::undo() {
}
_update_scrollbars();
- bool dirty_carets = carets.size() != undo_stack_pos->get().start_carets.size();
+ bool dirty_carets = get_caret_count() != undo_stack_pos->get().start_carets.size();
if (!dirty_carets) {
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (carets[i].line != undo_stack_pos->get().start_carets[i].line || carets[i].column != undo_stack_pos->get().start_carets[i].column) {
dirty_carets = true;
break;
@@ -4085,11 +3981,11 @@ void TextEdit::undo() {
carets = undo_stack_pos->get().start_carets;
- if (dirty_carets && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ _unhide_carets();
+
+ if (dirty_carets) {
+ _caret_changed();
+ _selection_changed();
}
adjust_viewport_to_caret();
}
@@ -4114,6 +4010,7 @@ void TextEdit::redo() {
_do_text_op(op, false);
current_op.version = op.version;
if (undo_stack_pos->get().chain_forward) {
+ // This was part of a complex operation, redo until the chain backward at the end of the complex operation.
while (true) {
ERR_BREAK(!undo_stack_pos->next());
undo_stack_pos = undo_stack_pos->next();
@@ -4127,9 +4024,9 @@ void TextEdit::redo() {
}
_update_scrollbars();
- bool dirty_carets = carets.size() != undo_stack_pos->get().end_carets.size();
+ bool dirty_carets = get_caret_count() != undo_stack_pos->get().end_carets.size();
if (!dirty_carets) {
- for (int i = 0; i < carets.size(); i++) {
+ for (int i = 0; i < get_caret_count(); i++) {
if (carets[i].line != undo_stack_pos->get().end_carets[i].line || carets[i].column != undo_stack_pos->get().end_carets[i].column) {
dirty_carets = true;
break;
@@ -4140,11 +4037,11 @@ void TextEdit::redo() {
carets = undo_stack_pos->get().end_carets;
undo_stack_pos = undo_stack_pos->next();
- if (dirty_carets && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ _unhide_carets();
+
+ if (dirty_carets) {
+ _caret_changed();
+ _selection_changed();
}
adjust_viewport_to_caret();
}
@@ -4359,13 +4256,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
}
}
- if (row < 0) {
- row = 0;
- }
-
- if (row >= text.size()) {
- row = text.size() - 1;
- }
+ row = CLAMP(row, 0, text.size() - 1);
int visible_lines = get_visible_line_count_in_range(first_vis_line, row);
if (rows > visible_lines) {
@@ -4511,29 +4402,13 @@ bool TextEdit::is_dragging_cursor() const {
}
bool TextEdit::is_mouse_over_selection(bool p_edges, int p_caret) const {
- for (int i = 0; i < carets.size(); i++) {
- if (p_caret != -1 && p_caret != i) {
- continue;
- }
-
- if (!has_selection(i)) {
- continue;
- }
-
- Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
- int row = pos.y;
- int col = pos.x;
- if (p_edges) {
- if ((row == get_selection_from_line(i) && col == get_selection_from_column(i)) || (row == get_selection_to_line(i) && col == get_selection_to_column(i))) {
- return true;
- }
- }
+ Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
+ int line = pos.y;
+ int column = pos.x;
- if (row >= get_selection_from_line(i) && row <= get_selection_to_line(i) && (row > get_selection_from_line(i) || col > get_selection_from_column(i)) && (row < get_selection_to_line(i) || col < get_selection_to_column(i))) {
- return true;
- }
+ if ((p_caret == -1 && get_selection_at_line_column(line, column, p_edges) != -1) || (p_caret != -1 && _selection_contains(p_caret, line, column, p_edges))) {
+ return true;
}
-
return false;
}
@@ -4613,6 +4488,9 @@ void TextEdit::set_multiple_carets_enabled(bool p_enabled) {
multi_carets_enabled = p_enabled;
if (!multi_carets_enabled) {
remove_secondary_carets();
+ multicaret_edit_count = 0;
+ multicaret_edit_ignore_carets.clear();
+ multicaret_edit_merge_queued = false;
}
}
@@ -4620,270 +4498,411 @@ bool TextEdit::is_multiple_carets_enabled() const {
return multi_carets_enabled;
}
-int TextEdit::add_caret(int p_line, int p_col) {
+int TextEdit::add_caret(int p_line, int p_column) {
if (!multi_carets_enabled) {
return -1;
}
+ _cancel_drag_and_drop_text();
p_line = CLAMP(p_line, 0, text.size() - 1);
- p_col = CLAMP(p_col, 0, get_line(p_line).length());
+ p_column = CLAMP(p_column, 0, get_line(p_line).length());
- for (int i = 0; i < carets.size(); i++) {
- if (get_caret_line(i) == p_line && get_caret_column(i) == p_col) {
+ if (!is_in_mulitcaret_edit()) {
+ // Carets cannot overlap.
+ if (get_selection_at_line_column(p_line, p_column, true, false) != -1) {
return -1;
}
-
- if (has_selection(i)) {
- if (p_line >= get_selection_from_line(i) && p_line <= get_selection_to_line(i) && (p_line > get_selection_from_line(i) || p_col >= get_selection_from_column(i)) && (p_line < get_selection_to_line(i) || p_col <= get_selection_to_column(i))) {
- return -1;
- }
- }
}
carets.push_back(Caret());
- set_caret_line(p_line, false, false, 0, carets.size() - 1);
- set_caret_column(p_col, false, carets.size() - 1);
- caret_index_edit_dirty = true;
- return carets.size() - 1;
+ int new_index = carets.size() - 1;
+ set_caret_line(p_line, false, false, -1, new_index);
+ set_caret_column(p_column, false, new_index);
+ _caret_changed(new_index);
+
+ if (is_in_mulitcaret_edit()) {
+ multicaret_edit_ignore_carets.insert(new_index);
+ merge_overlapping_carets();
+ }
+ return new_index;
}
void TextEdit::remove_caret(int p_caret) {
ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed.");
ERR_FAIL_INDEX(p_caret, carets.size());
+
+ _caret_changed(p_caret);
carets.remove_at(p_caret);
- caret_index_edit_dirty = true;
+
+ if (drag_caret_index >= 0) {
+ if (p_caret == drag_caret_index) {
+ drag_caret_index = -1;
+ } else if (p_caret < drag_caret_index) {
+ drag_caret_index -= 1;
+ }
+ }
}
void TextEdit::remove_secondary_carets() {
+ if (carets.size() == 1) {
+ return;
+ }
+
+ _caret_changed();
carets.resize(1);
- caret_index_edit_dirty = true;
- queue_redraw();
+
+ if (drag_caret_index >= 0) {
+ drag_caret_index = -1;
+ }
}
-void TextEdit::merge_overlapping_carets() {
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = 0; i < caret_edit_order.size() - 1; i++) {
- int first_caret = caret_edit_order[i];
- int second_caret = caret_edit_order[i + 1];
+int TextEdit::get_caret_count() const {
+ // Don't include drag caret.
+ if (drag_caret_index >= 0) {
+ return carets.size() - 1;
+ }
+ return carets.size();
+}
- // Both have selection.
- if (has_selection(first_caret) && has_selection(second_caret)) {
- bool should_merge = false;
- if (get_selection_from_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_from_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_from_line(first_caret) > get_selection_from_line(second_caret) || get_selection_from_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_from_line(first_caret) < get_selection_to_line(second_caret) || get_selection_from_column(first_caret) <= get_selection_to_column(second_caret))) {
- should_merge = true;
- }
+void TextEdit::add_caret_at_carets(bool p_below) {
+ if (!multi_carets_enabled) {
+ return;
+ }
+ const int last_line_max_wrap = get_line_wrap_count(text.size() - 1);
+
+ begin_multicaret_edit();
+ int view_target_caret = -1;
+ int view_line = p_below ? -1 : INT_MAX;
+ int num_carets = get_caret_count();
+ for (int i = 0; i < num_carets; i++) {
+ const int caret_line = get_caret_line(i);
+ const int caret_column = get_caret_column(i);
+ const bool is_selected = has_selection(i) || carets[i].last_fit_x != carets[i].selection.origin_last_fit_x;
+ const int selection_origin_line = get_selection_origin_line(i);
+ const int selection_origin_column = get_selection_origin_column(i);
+ const int caret_wrap_index = get_caret_wrap_index(i);
+ const int selection_origin_wrap_index = !is_selected ? -1 : get_line_wrap_index_at_column(selection_origin_line, selection_origin_column);
+
+ if (caret_line == 0 && !p_below && (caret_wrap_index == 0 || selection_origin_wrap_index == 0)) {
+ // Can't add above the first line.
+ continue;
+ }
+ if (caret_line == text.size() - 1 && p_below && (caret_wrap_index == last_line_max_wrap || selection_origin_wrap_index == last_line_max_wrap)) {
+ // Can't add below the last line.
+ continue;
+ }
- if (get_selection_to_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_to_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_to_line(first_caret) > get_selection_from_line(second_caret) || get_selection_to_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_to_line(first_caret) < get_selection_to_line(second_caret) || get_selection_to_column(first_caret) <= get_selection_to_column(second_caret))) {
- should_merge = true;
- }
+ // Add a new caret.
+ int new_caret_index = add_caret(caret_line, caret_column);
+ ERR_FAIL_COND_MSG(new_caret_index < 0, "Failed to add a caret.");
- if (!should_merge) {
- continue;
- }
+ // Copy the selection origin and last fit.
+ set_selection_origin_line(selection_origin_line, true, -1, new_caret_index);
+ set_selection_origin_column(selection_origin_column, new_caret_index);
+ carets.write[new_caret_index].last_fit_x = carets[i].last_fit_x;
+ carets.write[new_caret_index].selection.origin_last_fit_x = carets[i].selection.origin_last_fit_x;
- // Save the newest one for Click + Drag.
- int caret_to_save = first_caret;
- int caret_to_remove = second_caret;
- if (first_caret < second_caret) {
- caret_to_save = second_caret;
- caret_to_remove = first_caret;
+ // Move the caret up or down one visible line.
+ if (!p_below) {
+ // Move caret up.
+ if (caret_wrap_index > 0) {
+ set_caret_line(caret_line, false, false, caret_wrap_index - 1, new_caret_index);
+ } else {
+ int new_line = caret_line - get_next_visible_line_offset_from(caret_line - 1, -1);
+ if (is_line_wrapped(new_line)) {
+ set_caret_line(new_line, false, false, get_line_wrap_count(new_line), new_caret_index);
+ } else {
+ set_caret_line(new_line, false, false, 0, new_caret_index);
+ }
}
-
- int from_line = MIN(get_selection_from_line(caret_to_save), get_selection_from_line(caret_to_remove));
- int to_line = MAX(get_selection_to_line(caret_to_save), get_selection_to_line(caret_to_remove));
- int from_col = get_selection_from_column(caret_to_save);
- int to_col = get_selection_to_column(caret_to_save);
- int selection_line = get_selection_line(caret_to_save);
- int selection_col = get_selection_column(caret_to_save);
-
- bool at_from = (get_caret_line(caret_to_save) == get_selection_from_line(caret_to_save) && get_caret_column(caret_to_save) == get_selection_from_column(caret_to_save));
-
- if (at_from) {
- if (get_selection_line(caret_to_remove) > get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) >= get_selection_column(caret_to_save))) {
- selection_line = get_selection_line(caret_to_remove);
- selection_col = get_selection_column(caret_to_remove);
+ // Move selection origin up.
+ if (is_selected) {
+ if (selection_origin_wrap_index > 0) {
+ set_selection_origin_line(caret_line, false, selection_origin_wrap_index - 1, new_caret_index);
+ } else {
+ int new_line = selection_origin_line - get_next_visible_line_offset_from(selection_origin_line - 1, -1);
+ if (is_line_wrapped(new_line)) {
+ set_selection_origin_line(new_line, false, get_line_wrap_count(new_line), new_caret_index);
+ } else {
+ set_selection_origin_line(new_line, false, 0, new_caret_index);
+ }
}
- } else if (get_selection_line(caret_to_remove) < get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) <= get_selection_column(caret_to_save))) {
- selection_line = get_selection_line(caret_to_remove);
- selection_col = get_selection_column(caret_to_remove);
}
-
- if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save) || (get_selection_from_line(caret_to_remove) == get_selection_from_line(caret_to_save) && get_selection_from_column(caret_to_remove) <= get_selection_from_column(caret_to_save))) {
- from_col = get_selection_from_column(caret_to_remove);
+ if (get_caret_line(new_caret_index) < view_line) {
+ view_line = get_caret_line(new_caret_index);
+ view_target_caret = new_caret_index;
+ }
+ } else {
+ // Move caret down.
+ if (caret_wrap_index < get_line_wrap_count(caret_line)) {
+ set_caret_line(caret_line, false, false, caret_wrap_index + 1, new_caret_index);
} else {
- to_col = get_selection_to_column(caret_to_remove);
+ int new_line = caret_line + get_next_visible_line_offset_from(CLAMP(caret_line + 1, 0, text.size() - 1), 1);
+ set_caret_line(new_line, false, false, 0, new_caret_index);
+ }
+ // Move selection origin down.
+ if (is_selected) {
+ if (selection_origin_wrap_index < get_line_wrap_count(selection_origin_line)) {
+ set_selection_origin_line(selection_origin_line, false, selection_origin_wrap_index + 1, new_caret_index);
+ } else {
+ int new_line = selection_origin_line + get_next_visible_line_offset_from(CLAMP(selection_origin_line + 1, 0, text.size() - 1), 1);
+ set_selection_origin_line(new_line, false, 0, new_caret_index);
+ }
}
+ if (get_caret_line(new_caret_index) > view_line) {
+ view_line = get_caret_line(new_caret_index);
+ view_target_caret = new_caret_index;
+ }
+ }
+ if (is_selected) {
+ // Make sure selection is active.
+ select(get_selection_origin_line(new_caret_index), get_selection_origin_column(new_caret_index), get_caret_line(new_caret_index), get_caret_column(new_caret_index), new_caret_index);
+ carets.write[new_caret_index].last_fit_x = carets[i].last_fit_x;
+ carets.write[new_caret_index].selection.origin_last_fit_x = carets[i].selection.origin_last_fit_x;
+ }
- select(from_line, from_col, to_line, to_col, caret_to_save);
- set_selection_mode(selecting_mode, selection_line, selection_col, caret_to_save);
- set_caret_line((at_from ? from_line : to_line), caret_to_save == 0, true, 0, caret_to_save);
- set_caret_column((at_from ? from_col : to_col), caret_to_save == 0, caret_to_save);
- remove_caret(caret_to_remove);
- i--;
- caret_edit_order = get_caret_index_edit_order();
- continue;
+ bool check_edges = !has_selection(0) || !has_selection(new_caret_index);
+ bool will_merge_with_main_caret = _selection_contains(0, get_caret_line(new_caret_index), get_caret_column(new_caret_index), check_edges, false) || _selection_contains(new_caret_index, get_caret_line(0), get_caret_column(0), check_edges, false);
+ if (will_merge_with_main_caret) {
+ // Move next to the main caret so it stays the main caret after merging.
+ Caret new_caret = carets[new_caret_index];
+ carets.remove_at(new_caret_index);
+ carets.insert(0, new_caret);
+ i++;
}
+ }
- // Only first has selection.
- if (has_selection(first_caret)) {
- if (get_caret_line(second_caret) >= get_selection_from_line(first_caret) && get_caret_line(second_caret) <= get_selection_to_line(first_caret) && (get_caret_line(second_caret) > get_selection_from_line(first_caret) || get_caret_column(second_caret) >= get_selection_from_column(first_caret)) && (get_caret_line(second_caret) < get_selection_to_line(first_caret) || get_caret_column(second_caret) <= get_selection_to_column(first_caret))) {
- remove_caret(second_caret);
- caret_edit_order = get_caret_index_edit_order();
- i--;
- }
- continue;
+ // Show the topmost caret if added above or bottommost caret if added below.
+ if (view_target_caret >= 0 && view_target_caret < get_caret_count()) {
+ adjust_viewport_to_caret(view_target_caret);
+ }
+
+ merge_overlapping_carets();
+ end_multicaret_edit();
+}
+
+struct _CaretSortComparator {
+ _FORCE_INLINE_ bool operator()(const Vector3i &a, const Vector3i &b) const {
+ // x is column, y is line, z is caret index.
+ if (a.y == b.y) {
+ return a.x < b.x;
}
+ return a.y < b.y;
+ }
+};
- // Only second has selection.
- if (has_selection(second_caret)) {
- if (get_caret_line(first_caret) >= get_selection_from_line(second_caret) && get_caret_line(first_caret) <= get_selection_to_line(second_caret) && (get_caret_line(first_caret) > get_selection_from_line(second_caret) || get_caret_column(first_caret) >= get_selection_from_column(second_caret)) && (get_caret_line(first_caret) < get_selection_to_line(second_caret) || get_caret_column(first_caret) <= get_selection_to_column(second_caret))) {
- remove_caret(first_caret);
- caret_edit_order = get_caret_index_edit_order();
- i--;
- }
+Vector<int> TextEdit::get_sorted_carets(bool p_include_ignored_carets) const {
+ // Returns caret indexes sorted by selection start or caret position from top to bottom of text.
+ Vector<Vector3i> caret_line_col_indexes;
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (!p_include_ignored_carets && multicaret_edit_ignore_caret(i)) {
continue;
}
+ caret_line_col_indexes.push_back(Vector3i(get_selection_from_column(i), get_selection_from_line(i), i));
+ }
+ caret_line_col_indexes.sort_custom<_CaretSortComparator>();
+ Vector<int> sorted;
+ sorted.resize(caret_line_col_indexes.size());
+ for (int i = 0; i < caret_line_col_indexes.size(); i++) {
+ sorted.set(i, caret_line_col_indexes[i].z);
+ }
+ return sorted;
+}
- // Both have no selection.
- if (get_caret_line(first_caret) == get_caret_line(second_caret) && get_caret_column(first_caret) == get_caret_column(second_caret)) {
- // Save the newest one for Click + Drag.
- if (first_caret < second_caret) {
- remove_caret(first_caret);
- } else {
- remove_caret(second_caret);
+void TextEdit::collapse_carets(int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_inclusive) {
+ // Collapse carets in the selected range to the from position.
+
+ // Clamp the collapse target position.
+ int collapse_line = CLAMP(p_from_line, 0, text.size() - 1);
+ int collapse_column = CLAMP(p_from_column, 0, text[collapse_line].length());
+
+ // Swap the lines if they are in the wrong order.
+ if (p_from_line > p_to_line) {
+ SWAP(p_from_line, p_to_line);
+ SWAP(p_from_column, p_to_column);
+ }
+ if (p_from_line == p_to_line && p_from_column > p_to_column) {
+ SWAP(p_from_column, p_to_column);
+ }
+ bool any_collapsed = false;
+
+ // Intentionally includes carets in the multicaret_edit_ignore list so that they are moved together.
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool is_caret_in = _is_line_col_in_range(get_caret_line(i), get_caret_column(i), p_from_line, p_from_column, p_to_line, p_to_column, p_inclusive);
+ if (!has_selection(i)) {
+ if (is_caret_in) {
+ // Caret was in the collapsed area.
+ set_caret_line(collapse_line, false, true, -1, i);
+ set_caret_column(collapse_column, false, i);
+ if (is_in_mulitcaret_edit() && get_caret_count() > 1) {
+ multicaret_edit_ignore_carets.insert(i);
+ }
+ any_collapsed = true;
+ }
+ } else {
+ bool is_origin_in = _is_line_col_in_range(get_selection_origin_line(i), get_selection_origin_column(i), p_from_line, p_from_column, p_to_line, p_to_column, p_inclusive);
+
+ if (is_caret_in && is_origin_in) {
+ // Selection was completely encapsulated.
+ deselect(i);
+ set_caret_line(collapse_line, false, true, -1, i);
+ set_caret_column(collapse_column, false, i);
+ if (is_in_mulitcaret_edit() && get_caret_count() > 1) {
+ multicaret_edit_ignore_carets.insert(i);
+ }
+ any_collapsed = true;
+ } else if (is_caret_in) {
+ // Only caret was inside.
+ set_caret_line(collapse_line, false, true, -1, i);
+ set_caret_column(collapse_column, false, i);
+ any_collapsed = true;
+ } else if (is_origin_in) {
+ // Only selection origin was inside.
+ set_selection_origin_line(collapse_line, true, -1, i);
+ set_selection_origin_column(collapse_column, i);
+ any_collapsed = true;
}
- i--;
- caret_edit_order = get_caret_index_edit_order();
- continue;
}
+ if (!p_inclusive && !any_collapsed) {
+ if ((get_caret_line(i) == collapse_line && get_caret_column(i) == collapse_column) || (get_selection_origin_line(i) == collapse_line && get_selection_origin_column(i) == collapse_column)) {
+ // Make sure to queue a merge, even if we didn't include it.
+ any_collapsed = true;
+ }
+ }
+ }
+ if (any_collapsed) {
+ merge_overlapping_carets();
}
}
-int TextEdit::get_caret_count() const {
- return carets.size();
-}
+void TextEdit::merge_overlapping_carets() {
+ if (is_in_mulitcaret_edit()) {
+ // Queue merge to be performed the end of the multicaret edit.
+ multicaret_edit_merge_queued = true;
+ return;
+ }
-void TextEdit::add_caret_at_carets(bool p_below) {
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &caret_index : caret_edit_order) {
- const int caret_line = get_caret_line(caret_index);
- const int caret_column = get_caret_column(caret_index);
-
- // The last fit x will be cleared if the caret has a selection,
- // but if it does not have a selection the last fit x will be
- // transferred to the new caret.
- int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x;
- if (has_selection(caret_index)) {
- // If the selection goes over multiple lines, deselect it.
- if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) {
- deselect(caret_index);
+ multicaret_edit_merge_queued = false;
+ multicaret_edit_ignore_carets.clear();
+
+ if (get_caret_count() == 1) {
+ return;
+ }
+
+ Vector<int> sorted_carets = get_sorted_carets(true);
+ for (int i = 0; i < sorted_carets.size() - 1; i++) {
+ int first_caret = sorted_carets[i];
+ int second_caret = sorted_carets[i + 1];
+
+ bool merge_carets;
+ if (!has_selection(first_caret) || !has_selection(second_caret)) {
+ // Merge if touching.
+ merge_carets = get_selection_from_line(second_caret) < get_selection_to_line(first_caret) || (get_selection_from_line(second_caret) == get_selection_to_line(first_caret) && get_selection_from_column(second_caret) <= get_selection_to_column(first_caret));
+ } else {
+ // Merge two selections if overlapping.
+ merge_carets = get_selection_from_line(second_caret) < get_selection_to_line(first_caret) || (get_selection_from_line(second_caret) == get_selection_to_line(first_caret) && get_selection_from_column(second_caret) < get_selection_to_column(first_caret));
+ }
+
+ if (!merge_carets) {
+ continue;
+ }
+
+ // Save the newest one for Click + Drag.
+ int caret_to_save = first_caret;
+ int caret_to_remove = second_caret;
+ if (first_caret < second_caret) {
+ caret_to_save = second_caret;
+ caret_to_remove = first_caret;
+ }
+
+ if (get_selection_from_line(caret_to_save) != get_selection_from_line(caret_to_remove) || get_selection_to_line(caret_to_save) != get_selection_to_line(caret_to_remove) || get_selection_from_column(caret_to_save) != get_selection_from_column(caret_to_remove) || get_selection_to_column(caret_to_save) != get_selection_to_column(caret_to_remove)) {
+ // Selections are not the same, merge them into one bigger selection.
+ int new_from_line = MIN(get_selection_from_line(caret_to_remove), get_selection_from_line(caret_to_save));
+ int new_to_line = MAX(get_selection_to_line(caret_to_remove), get_selection_to_line(caret_to_save));
+ int new_from_col;
+ int new_to_col;
+ if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save)) {
+ new_from_col = get_selection_from_column(caret_to_remove);
+ } else if (get_selection_from_line(caret_to_remove) > get_selection_from_line(caret_to_save)) {
+ new_from_col = get_selection_from_column(caret_to_save);
} else {
- caret_from_column = get_selection_from_column(caret_index);
- caret_to_column = get_selection_to_column(caret_index);
- caret_last_fit_x = -1;
- carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column);
+ new_from_col = MIN(get_selection_from_column(caret_to_remove), get_selection_from_column(caret_to_save));
+ }
+ if (get_selection_to_line(caret_to_remove) < get_selection_to_line(caret_to_save)) {
+ new_to_col = get_selection_to_column(caret_to_save);
+ } else if (get_selection_to_line(caret_to_remove) > get_selection_to_line(caret_to_save)) {
+ new_to_col = get_selection_to_column(caret_to_remove);
+ } else {
+ new_to_col = MAX(get_selection_to_column(caret_to_remove), get_selection_to_column(caret_to_save));
}
- }
- // Get the line and column of the new caret as if you would move the caret by pressing the arrow keys.
- int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0;
- _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x);
+ // Use the direction from the last caret or the saved one.
+ int caret_dir_to_copy;
+ if (has_selection(caret_to_remove) && has_selection(caret_to_save)) {
+ caret_dir_to_copy = caret_to_remove == get_caret_count() - 1 ? caret_to_remove : caret_to_save;
+ } else {
+ caret_dir_to_copy = !has_selection(caret_to_remove) ? caret_to_save : caret_to_remove;
+ }
- // If the caret does have a selection calculate the new from and to columns.
- if (caret_from_column != caret_to_column) {
- // We only need to calculate the selection columns if the column of the caret changed.
- if (caret_column != new_caret_column) {
- int _; // Unused placeholder for p_new_line.
- _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column);
- _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column);
+ if (is_caret_after_selection_origin(caret_dir_to_copy)) {
+ select(new_from_line, new_from_col, new_to_line, new_to_col, caret_to_save);
} else {
- new_caret_from_column = caret_from_column;
- new_caret_to_column = caret_to_column;
+ select(new_to_line, new_to_col, new_from_line, new_from_col, caret_to_save);
}
}
- // Add the new caret.
- const int new_caret_index = add_caret(new_caret_line, new_caret_column);
-
- if (new_caret_index == -1) {
- continue;
+ if (caret_to_save == 0) {
+ adjust_viewport_to_caret(caret_to_save);
}
- // Also add the selection if there should be one.
- if (new_caret_from_column != new_caret_to_column) {
- select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index);
- // Necessary to properly modify the selection after adding the new caret.
- carets.write[new_caret_index].selection.selecting_line = new_caret_line;
- carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column;
- continue;
+ remove_caret(caret_to_remove);
+
+ // Update the rest of the sorted list.
+ for (int j = i; j < sorted_carets.size(); j++) {
+ if (sorted_carets[j] > caret_to_remove) {
+ // Shift the index since a caret before it was removed.
+ sorted_carets.write[j] -= 1;
+ }
}
+ // Remove the caret from the sorted array.
+ sorted_carets.remove_at(caret_to_remove == first_caret ? i : i + 1);
- // Copy the last fit x over.
- carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x;
+ // Process the caret again, since it and the next caret might also overlap.
+ i--;
}
-
- merge_overlapping_carets();
- queue_redraw();
}
-Vector<int> TextEdit::get_caret_index_edit_order() {
- if (!caret_index_edit_dirty) {
- return caret_index_edit_order;
+// Starts a multicaret edit operation. Call this before iterating over the carets and call [end_multicaret_edit] afterwards.
+void TextEdit::begin_multicaret_edit() {
+ if (!multi_carets_enabled) {
+ return;
}
+ multicaret_edit_count++;
+}
- caret_index_edit_order.clear();
- caret_index_edit_order.push_back(0);
- for (int i = 1; i < carets.size(); i++) {
- int j = 0;
-
- int line = has_selection(i) ? get_selection_to_line(i) : carets[i].line;
- int col = has_selection(i) ? get_selection_to_column(i) : carets[i].column;
+void TextEdit::end_multicaret_edit() {
+ if (!multi_carets_enabled) {
+ return;
+ }
+ if (multicaret_edit_count > 0) {
+ multicaret_edit_count--;
+ }
+ if (multicaret_edit_count != 0) {
+ return;
+ }
- for (; j < caret_index_edit_order.size(); j++) {
- int idx = caret_index_edit_order[j];
- int other_line = has_selection(idx) ? get_selection_to_line(idx) : carets[idx].line;
- int other_col = has_selection(idx) ? get_selection_to_column(idx) : carets[idx].column;
- if (line > other_line || (line == other_line && col > other_col)) {
- break;
- }
- }
- caret_index_edit_order.insert(j, i);
+ // This was the last multicaret edit operation.
+ if (multicaret_edit_merge_queued) {
+ merge_overlapping_carets();
}
- caret_index_edit_dirty = false;
- return caret_index_edit_order;
+ multicaret_edit_ignore_carets.clear();
}
-void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) {
- int edit_height = p_from_line - p_to_line;
- int edit_size = ((edit_height == 0) ? p_from_col : 0) - p_to_col;
-
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int j = 0; j < caret_edit_order.size(); j++) {
- if (caret_edit_order[j] == p_caret) {
- return;
- }
-
- // Adjust caret.
- // set_caret_line could adjust the column, so save here.
- int cc = get_caret_column(caret_edit_order[j]);
- if (edit_height != 0) {
- set_caret_line(get_caret_line(caret_edit_order[j]) + edit_height, false, true, 0, caret_edit_order[j]);
- }
- if (get_caret_line(p_caret) == get_caret_line(caret_edit_order[j])) {
- set_caret_column(cc + edit_size, false, caret_edit_order[j]);
- }
+bool TextEdit::is_in_mulitcaret_edit() const {
+ return multicaret_edit_count > 0;
+}
- // Adjust selection.
- if (!has_selection(caret_edit_order[j])) {
- continue;
- }
- if (edit_height != 0) {
- carets.write[caret_edit_order[j]].selection.from_line += edit_height;
- carets.write[caret_edit_order[j]].selection.to_line += edit_height;
- }
- if (get_caret_line(p_caret) == get_selection_from_line(caret_edit_order[j])) {
- carets.write[caret_edit_order[j]].selection.from_column += edit_size;
- }
- }
+bool TextEdit::multicaret_edit_ignore_caret(int p_caret) const {
+ return multicaret_edit_ignore_carets.has(p_caret);
}
bool TextEdit::is_caret_visible(int p_caret) const {
@@ -4903,16 +4922,10 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
}
setting_caret_line = true;
- if (p_line < 0) {
- p_line = 0;
- }
-
- if (p_line >= text.size()) {
- p_line = text.size() - 1;
- }
+ p_line = CLAMP(p_line, 0, text.size() - 1);
if (!p_can_be_hidden) {
- if (_is_line_hidden(CLAMP(p_line, 0, text.size() - 1))) {
+ if (_is_line_hidden(p_line)) {
int move_down = get_next_visible_line_offset_from(p_line, 1) - 1;
if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) {
p_line += move_down;
@@ -4921,7 +4934,7 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) {
p_line -= move_up;
} else {
- WARN_PRINT(("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines."));
+ WARN_PRINT("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines.");
}
}
}
@@ -4929,31 +4942,36 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_
bool caret_moved = get_caret_line(p_caret) != p_line;
carets.write[p_caret].line = p_line;
- int n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index);
- if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) {
- Vector<String> rows = get_line_wrapped_text(p_line);
- int row_end_col = 0;
- for (int i = 0; i < p_wrap_index + 1; i++) {
- row_end_col += rows[i].length();
- }
- if (n_col >= row_end_col) {
- n_col -= 1;
+ int n_col;
+ if (p_wrap_index >= 0) {
+ // Keep caret in same visual x position it was at previously.
+ n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index);
+ if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) {
+ // Offset by one to not go past the end of the wrapped line.
+ if (n_col >= text.get_line_wrap_ranges(p_line)[p_wrap_index].y) {
+ n_col -= 1;
+ }
}
+ } else {
+ // Clamp the column.
+ n_col = MIN(get_caret_column(p_caret), get_line(p_line).length());
}
caret_moved = (caret_moved || get_caret_column(p_caret) != n_col);
carets.write[p_caret].column = n_col;
+ // Unselect if the caret moved to the selection origin.
+ if (p_wrap_index >= 0 && has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
if (is_inside_tree() && p_adjust_viewport) {
adjust_viewport_to_caret(p_caret);
}
setting_caret_line = false;
- if (caret_moved && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ if (caret_moved) {
+ _caret_changed(p_caret);
}
}
@@ -4962,29 +4980,32 @@ int TextEdit::get_caret_line(int p_caret) const {
return carets[p_caret].line;
}
-void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport, int p_caret) {
+void TextEdit::set_caret_column(int p_column, bool p_adjust_viewport, int p_caret) {
ERR_FAIL_INDEX(p_caret, carets.size());
- if (p_col < 0) {
- p_col = 0;
- }
- if (p_col > get_line(get_caret_line(p_caret)).length()) {
- p_col = get_line(get_caret_line(p_caret)).length();
- }
- bool caret_moved = get_caret_column(p_caret) != p_col;
- carets.write[p_caret].column = p_col;
+ p_column = CLAMP(p_column, 0, get_line(get_caret_line(p_caret)).length());
+
+ bool caret_moved = get_caret_column(p_caret) != p_column;
+ carets.write[p_caret].column = p_column;
carets.write[p_caret].last_fit_x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
+ if (!has_selection(p_caret)) {
+ // Set the selection origin last fit x to be the same, so we can tell if there was a selection.
+ carets.write[p_caret].selection.origin_last_fit_x = carets[p_caret].last_fit_x;
+ }
+
+ // Unselect if the caret moved to the selection origin.
+ if (has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
if (is_inside_tree() && p_adjust_viewport) {
adjust_viewport_to_caret(p_caret);
}
- if (caret_moved && !caret_pos_dirty) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
- }
- caret_pos_dirty = true;
+ if (caret_moved) {
+ _caret_changed(p_caret);
}
}
@@ -4999,7 +5020,7 @@ int TextEdit::get_caret_wrap_index(int p_caret) const {
}
String TextEdit::get_word_under_caret(int p_caret) const {
- ERR_FAIL_COND_V(p_caret > carets.size(), "");
+ ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, "");
StringBuilder selected_text;
for (int c = 0; c < carets.size(); c++) {
@@ -5060,20 +5081,8 @@ bool TextEdit::is_drag_and_drop_selection_enabled() const {
return drag_and_drop_selection_enabled;
}
-void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) {
- ERR_FAIL_INDEX(p_caret, carets.size());
-
+void TextEdit::set_selection_mode(SelectionMode p_mode) {
selecting_mode = p_mode;
- if (p_line >= 0) {
- ERR_FAIL_INDEX(p_line, text.size());
- carets.write[p_caret].selection.selecting_line = p_line;
- carets.write[p_caret].selection.selecting_column = CLAMP(carets[p_caret].selection.selecting_column, 0, text[carets[p_caret].selection.selecting_line].length());
- }
- if (p_column >= 0) {
- ERR_FAIL_INDEX(carets[p_caret].selection.selecting_line, text.size());
- ERR_FAIL_INDEX(p_column, text[carets[p_caret].selection.selecting_line].length() + 1);
- carets.write[p_caret].selection.selecting_column = p_column;
- }
}
TextEdit::SelectionMode TextEdit::get_selection_mode() const {
@@ -5091,16 +5100,12 @@ void TextEdit::select_all() {
}
remove_secondary_carets();
+ set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT);
select(0, 0, text.size() - 1, text[text.size() - 1].length());
- set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, 0, 0);
- carets.write[0].selection.shiftclick_left = true;
- set_caret_line(get_selection_to_line(), false);
- set_caret_column(get_selection_to_column(), false);
- queue_redraw();
}
void TextEdit::select_word_under_caret(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= carets.size() || p_caret < -1);
_push_current_op();
if (!selecting_enabled) {
@@ -5141,8 +5146,6 @@ void TextEdit::select_word_under_caret(int p_caret) {
}
select(get_caret_line(c), begin, get_caret_line(c), end, c);
- // Move the caret to the end of the word for easier editing.
- set_caret_column(end, false, c);
}
merge_overlapping_carets();
}
@@ -5235,53 +5238,37 @@ void TextEdit::skip_selection_for_next_occurrence() {
}
}
-void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) {
- ERR_FAIL_INDEX(p_caret, carets.size());
+void TextEdit::select(int p_origin_line, int p_origin_column, int p_caret_line, int p_caret_column, int p_caret) {
+ ERR_FAIL_INDEX(p_caret, get_caret_count());
+
+ p_caret_line = CLAMP(p_caret_line, 0, text.size() - 1);
+ p_caret_column = CLAMP(p_caret_column, 0, text[p_caret_line].length());
+ set_caret_line(p_caret_line, false, true, -1, p_caret);
+ set_caret_column(p_caret_column, false, p_caret);
+
if (!selecting_enabled) {
return;
}
- p_from_line = CLAMP(p_from_line, 0, text.size() - 1);
- p_from_column = CLAMP(p_from_column, 0, text[p_from_line].length());
- p_to_line = CLAMP(p_to_line, 0, text.size() - 1);
- p_to_column = CLAMP(p_to_column, 0, text[p_to_line].length());
-
- carets.write[p_caret].selection.from_line = p_from_line;
- carets.write[p_caret].selection.from_column = p_from_column;
- carets.write[p_caret].selection.to_line = p_to_line;
- carets.write[p_caret].selection.to_column = p_to_column;
-
- carets.write[p_caret].selection.active = true;
+ p_origin_line = CLAMP(p_origin_line, 0, text.size() - 1);
+ p_origin_column = CLAMP(p_origin_column, 0, text[p_origin_line].length());
+ set_selection_origin_line(p_origin_line, true, -1, p_caret);
+ set_selection_origin_column(p_origin_column, p_caret);
- if (get_selection_from_line(p_caret) == get_selection_to_line(p_caret)) {
- if (get_selection_from_column(p_caret) == get_selection_to_column(p_caret)) {
- carets.write[p_caret].selection.active = false;
-
- } else if (get_selection_from_column(p_caret) > get_selection_to_column(p_caret)) {
- carets.write[p_caret].selection.shiftclick_left = false;
- SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column);
- } else {
- carets.write[p_caret].selection.shiftclick_left = true;
- }
- } else if (get_selection_from_line(p_caret) > get_selection_to_line(p_caret)) {
- carets.write[p_caret].selection.shiftclick_left = false;
- SWAP(carets.write[p_caret].selection.from_line, carets.write[p_caret].selection.to_line);
- SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column);
- } else {
- carets.write[p_caret].selection.shiftclick_left = true;
+ bool had_selection = has_selection(p_caret);
+ bool activate = p_origin_line != p_caret_line || p_origin_column != p_caret_column;
+ carets.write[p_caret].selection.active = activate;
+ if (had_selection != activate) {
+ _selection_changed(p_caret);
}
-
- caret_index_edit_dirty = true;
- queue_redraw();
}
bool TextEdit::has_selection(int p_caret) const {
- ERR_FAIL_COND_V(p_caret > carets.size(), false);
+ ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, false);
+ if (p_caret >= 0) {
+ return carets[p_caret].selection.active;
+ }
for (int i = 0; i < carets.size(); i++) {
- if (p_caret != -1 && p_caret != i) {
- continue;
- }
-
if (carets[i].selection.active) {
return true;
}
@@ -5290,100 +5277,268 @@ bool TextEdit::has_selection(int p_caret) const {
}
String TextEdit::get_selected_text(int p_caret) {
- ERR_FAIL_COND_V(p_caret > carets.size(), "");
+ ERR_FAIL_COND_V(p_caret >= carets.size() || p_caret < -1, "");
- StringBuilder selected_text;
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
- int caret_idx = caret_edit_order[i];
- if (p_caret != -1 && p_caret != caret_idx) {
- continue;
+ if (p_caret >= 0) {
+ if (!has_selection(p_caret)) {
+ return "";
}
+ return _base_get_text(get_selection_from_line(p_caret), get_selection_from_column(p_caret), get_selection_to_line(p_caret), get_selection_to_column(p_caret));
+ }
+
+ StringBuilder selected_text;
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
- if (!has_selection(caret_idx)) {
+ if (!has_selection(caret_index)) {
continue;
}
- selected_text += _base_get_text(get_selection_from_line(caret_idx), get_selection_from_column(caret_idx), get_selection_to_line(caret_idx), get_selection_to_column(caret_idx));
- if (p_caret == -1 && i != 0) {
+ if (selected_text.get_string_length() != 0) {
selected_text += "\n";
}
+ selected_text += _base_get_text(get_selection_from_line(caret_index), get_selection_from_column(caret_index), get_selection_to_line(caret_index), get_selection_to_column(caret_index));
}
return selected_text.as_string();
}
-int TextEdit::get_selection_line(int p_caret) const {
+int TextEdit::get_selection_at_line_column(int p_line, int p_column, bool p_include_edges, bool p_only_selections) const {
+ // Return the caret index of the found selection, or -1.
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (_selection_contains(i, p_line, p_column, p_include_edges, p_only_selections)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+Vector<Point2i> TextEdit::get_line_ranges_from_carets(bool p_only_selections, bool p_merge_adjacent) const {
+ // Get a series of line ranges that cover all lines that have a caret or selection.
+ // For each Point2i range, x is the first line and y is the last line.
+ Vector<Point2i> ret;
+ int last_to_line = INT_MIN;
+
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (p_only_selections && !has_selection(caret_index)) {
+ continue;
+ }
+ Point2i range = Point2i(get_selection_from_line(caret_index), get_selection_to_line(caret_index));
+ if (has_selection(caret_index) && get_selection_to_column(caret_index) == 0) {
+ // Dont include selection end line if it ends at column 0.
+ range.y--;
+ }
+ if (range.x == last_to_line || (p_merge_adjacent && range.x - 1 == last_to_line)) {
+ // Merge if starts on the same line or adjacent line.
+ ret.write[ret.size() - 1].y = range.y;
+ } else {
+ ret.append(range);
+ }
+ last_to_line = range.y;
+ }
+ return ret;
+}
+
+TypedArray<Vector2i> TextEdit::get_line_ranges_from_carets_typed_array(bool p_only_selections, bool p_merge_adjacent) const {
+ // Wrapper for `get_line_ranges_from_carets` to return a datatype that can be exposed.
+ TypedArray<Vector2i> ret;
+ Vector<Point2i> ranges = get_line_ranges_from_carets(p_only_selections, p_merge_adjacent);
+ for (const Point2i &range : ranges) {
+ ret.push_back(range);
+ }
+ return ret;
+}
+
+void TextEdit::set_selection_origin_line(int p_line, bool p_can_be_hidden, int p_wrap_index, int p_caret) {
+ if (!selecting_enabled) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_caret, carets.size());
+ p_line = CLAMP(p_line, 0, text.size() - 1);
+
+ if (!p_can_be_hidden) {
+ if (_is_line_hidden(p_line)) {
+ int move_down = get_next_visible_line_offset_from(p_line, 1) - 1;
+ if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) {
+ p_line += move_down;
+ } else {
+ int move_up = get_next_visible_line_offset_from(p_line, -1) - 1;
+ if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) {
+ p_line -= move_up;
+ } else {
+ WARN_PRINT("Selection origin set to hidden line " + itos(p_line) + " and there are no nonhidden lines.");
+ }
+ }
+ }
+ }
+
+ bool selection_moved = get_selection_origin_line(p_caret) != p_line;
+ carets.write[p_caret].selection.origin_line = p_line;
+
+ int n_col;
+ if (p_wrap_index >= 0) {
+ // Keep selection origin in same visual x position it was at previously.
+ n_col = _get_char_pos_for_line(carets[p_caret].selection.origin_last_fit_x, p_line, p_wrap_index);
+ if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) {
+ // Offset by one to not go past the end of the wrapped line.
+ if (n_col >= text.get_line_wrap_ranges(p_line)[p_wrap_index].y) {
+ n_col -= 1;
+ }
+ }
+ } else {
+ // Clamp the column.
+ n_col = MIN(get_selection_origin_column(p_caret), get_line(p_line).length());
+ }
+ selection_moved = (selection_moved || get_selection_origin_column(p_caret) != n_col);
+ carets.write[p_caret].selection.origin_column = n_col;
+
+ // Unselect if the selection origin moved to the caret.
+ if (p_wrap_index >= 0 && has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
+ if (selection_moved && has_selection(p_caret)) {
+ _selection_changed(p_caret);
+ }
+}
+
+void TextEdit::set_selection_origin_column(int p_column, int p_caret) {
+ if (!selecting_enabled) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_caret, carets.size());
+
+ p_column = CLAMP(p_column, 0, get_line(get_selection_origin_line(p_caret)).length());
+
+ bool selection_moved = get_selection_origin_column(p_caret) != p_column;
+
+ carets.write[p_caret].selection.origin_column = p_column;
+
+ carets.write[p_caret].selection.origin_last_fit_x = _get_column_x_offset_for_line(get_selection_origin_column(p_caret), get_selection_origin_line(p_caret), get_selection_origin_column(p_caret));
+
+ // Unselect if the selection origin moved to the caret.
+ if (has_selection(p_caret) && get_caret_line(p_caret) == get_selection_origin_line(p_caret) && get_caret_column(p_caret) == get_selection_origin_column(p_caret)) {
+ deselect(p_caret);
+ }
+
+ if (selection_moved && has_selection(p_caret)) {
+ _selection_changed(p_caret);
+ }
+}
+
+int TextEdit::get_selection_origin_line(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.selecting_line;
+ return carets[p_caret].selection.origin_line;
}
-int TextEdit::get_selection_column(int p_caret) const {
+int TextEdit::get_selection_origin_column(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.selecting_column;
+ return carets[p_caret].selection.origin_column;
}
int TextEdit::get_selection_from_line(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.from_line;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].line;
+ }
+ return MIN(carets[p_caret].selection.origin_line, carets[p_caret].line);
}
int TextEdit::get_selection_from_column(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.from_column;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].column;
+ }
+ if (carets[p_caret].selection.origin_line < carets[p_caret].line) {
+ return carets[p_caret].selection.origin_column;
+ } else if (carets[p_caret].selection.origin_line > carets[p_caret].line) {
+ return carets[p_caret].column;
+ } else {
+ return MIN(carets[p_caret].selection.origin_column, carets[p_caret].column);
+ }
}
int TextEdit::get_selection_to_line(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.to_line;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].line;
+ }
+ return MAX(carets[p_caret].selection.origin_line, carets[p_caret].line);
}
int TextEdit::get_selection_to_column(int p_caret) const {
ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
- ERR_FAIL_COND_V(!has_selection(p_caret), -1);
- return carets[p_caret].selection.to_column;
+ if (!has_selection(p_caret)) {
+ return carets[p_caret].column;
+ }
+ if (carets[p_caret].selection.origin_line < carets[p_caret].line) {
+ return carets[p_caret].column;
+ } else if (carets[p_caret].selection.origin_line > carets[p_caret].line) {
+ return carets[p_caret].selection.origin_column;
+ } else {
+ return MAX(carets[p_caret].selection.origin_column, carets[p_caret].column);
+ }
+}
+
+bool TextEdit::is_caret_after_selection_origin(int p_caret) const {
+ ERR_FAIL_INDEX_V(p_caret, carets.size(), false);
+ if (!has_selection(p_caret)) {
+ return true;
+ }
+ return carets[p_caret].line > carets[p_caret].selection.origin_line || (carets[p_caret].line == carets[p_caret].selection.origin_line && carets[p_caret].column >= carets[p_caret].selection.origin_column);
}
void TextEdit::deselect(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
- for (int i = 0; i < carets.size(); i++) {
- if (p_caret != -1 && p_caret != i) {
- continue;
+ ERR_FAIL_COND(p_caret >= carets.size() || p_caret < -1);
+ bool selection_changed = false;
+ if (p_caret >= 0) {
+ selection_changed = carets.write[p_caret].selection.active;
+ carets.write[p_caret].selection.active = false;
+ } else {
+ for (int i = 0; i < carets.size(); i++) {
+ selection_changed |= carets.write[i].selection.active;
+ carets.write[i].selection.active = false;
}
- carets.write[i].selection.active = false;
}
- caret_index_edit_dirty = true;
- queue_redraw();
+ if (selection_changed) {
+ _selection_changed(p_caret);
+ }
}
void TextEdit::delete_selection(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
if (p_caret != -1 && p_caret != i) {
continue;
}
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
if (!has_selection(i)) {
continue;
}
- selecting_mode = SelectionMode::SELECTION_MODE_NONE;
- _remove_text(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i), get_selection_to_column(i));
- set_caret_line(get_selection_from_line(i), false, false, 0, i);
- set_caret_column(get_selection_from_column(i), i == 0, i);
- carets.write[i].selection.active = false;
+ int selection_from_line = get_selection_from_line(i);
+ int selection_from_column = get_selection_from_column(i);
+ int selection_to_line = get_selection_to_line(i);
+ int selection_to_column = get_selection_to_column(i);
- adjust_carets_after_edit(i, carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column);
+ _remove_text(selection_from_line, selection_from_column, selection_to_line, selection_to_column);
+ _offset_carets_after(selection_to_line, selection_to_column, selection_from_line, selection_from_column);
+ merge_overlapping_carets();
+
+ deselect(i);
+ set_caret_line(selection_from_line, false, false, -1, i);
+ set_caret_column(selection_from_column, i == 0, i);
}
+ end_multicaret_edit();
end_complex_operation();
- queue_redraw();
}
/* Line wrapping. */
@@ -6225,8 +6380,10 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("swap_lines", "from_line", "to_line"), &TextEdit::swap_lines);
ClassDB::bind_method(D_METHOD("insert_line_at", "line", "text"), &TextEdit::insert_line_at);
+ ClassDB::bind_method(D_METHOD("remove_line_at", "line", "move_carets_down"), &TextEdit::remove_line_at, DEFVAL(true));
ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text", "caret_index"), &TextEdit::insert_text_at_caret, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("insert_text", "text", "line", "column", "before_selection_begin", "before_selection_end"), &TextEdit::insert_text, DEFVAL(true), DEFVAL(false));
ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text);
ClassDB::bind_method(D_METHOD("get_last_unhidden_line"), &TextEdit::get_last_unhidden_line);
@@ -6312,7 +6469,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_search_text", "search_text"), &TextEdit::set_search_text);
ClassDB::bind_method(D_METHOD("set_search_flags", "flags"), &TextEdit::set_search_flags);
- ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_colum"), &TextEdit::search);
+ ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_column"), &TextEdit::search);
/* Tooltip */
ClassDB::bind_method(D_METHOD("set_tooltip_request_func", "callback"), &TextEdit::set_tooltip_request_func);
@@ -6356,15 +6513,20 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multiple_carets_enabled", "enabled"), &TextEdit::set_multiple_carets_enabled);
ClassDB::bind_method(D_METHOD("is_multiple_carets_enabled"), &TextEdit::is_multiple_carets_enabled);
- ClassDB::bind_method(D_METHOD("add_caret", "line", "col"), &TextEdit::add_caret);
+ ClassDB::bind_method(D_METHOD("add_caret", "line", "column"), &TextEdit::add_caret);
ClassDB::bind_method(D_METHOD("remove_caret", "caret"), &TextEdit::remove_caret);
ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets);
- ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets);
ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count);
ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets);
- ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order);
- ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit);
+ ClassDB::bind_method(D_METHOD("get_sorted_carets", "include_ignored_carets"), &TextEdit::get_sorted_carets, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("collapse_carets", "from_line", "from_column", "to_line", "to_column", "inclusive"), &TextEdit::collapse_carets, DEFVAL(false));
+
+ ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets);
+ ClassDB::bind_method(D_METHOD("begin_multicaret_edit"), &TextEdit::begin_multicaret_edit);
+ ClassDB::bind_method(D_METHOD("end_multicaret_edit"), &TextEdit::end_multicaret_edit);
+ ClassDB::bind_method(D_METHOD("is_in_mulitcaret_edit"), &TextEdit::is_in_mulitcaret_edit);
+ ClassDB::bind_method(D_METHOD("multicaret_edit_ignore_caret", "caret_index"), &TextEdit::multicaret_edit_ignore_caret);
ClassDB::bind_method(D_METHOD("is_caret_visible", "caret_index"), &TextEdit::is_caret_visible, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_caret_draw_pos", "caret_index"), &TextEdit::get_caret_draw_pos, DEFVAL(0));
@@ -6395,27 +6557,33 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled);
ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled);
- ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_selection_mode", "mode"), &TextEdit::set_selection_mode);
ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode);
ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all);
ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence);
ClassDB::bind_method(D_METHOD("skip_selection_for_next_occurrence"), &TextEdit::skip_selection_for_next_occurrence);
- ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("select", "origin_line", "origin_column", "caret_line", "caret_column", "caret_index"), &TextEdit::select, DEFVAL(0));
ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_selected_text", "caret_index"), &TextEdit::get_selected_text, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_selection_at_line_column", "line", "column", "include_edges", "only_selections"), &TextEdit::get_selection_at_line_column, DEFVAL(true), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("get_line_ranges_from_carets", "only_selections", "merge_adjacent"), &TextEdit::get_line_ranges_from_carets_typed_array, DEFVAL(false), DEFVAL(true));
- ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0));
- ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_selection_origin_line", "caret_index"), &TextEdit::get_selection_origin_line, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_selection_origin_column", "caret_index"), &TextEdit::get_selection_origin_column, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_selection_origin_line", "line", "can_be_hidden", "wrap_index", "caret_index"), &TextEdit::set_selection_origin_line, DEFVAL(true), DEFVAL(-1), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_selection_origin_column", "column", "caret_index"), &TextEdit::set_selection_origin_column, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_from_line", "caret_index"), &TextEdit::get_selection_from_line, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_from_column", "caret_index"), &TextEdit::get_selection_from_column, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_to_line", "caret_index"), &TextEdit::get_selection_to_line, DEFVAL(0));
ClassDB::bind_method(D_METHOD("get_selection_to_column", "caret_index"), &TextEdit::get_selection_to_column, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("is_caret_after_selection_origin", "caret_index"), &TextEdit::is_caret_after_selection_origin, DEFVAL(0));
+
ClassDB::bind_method(D_METHOD("deselect", "caret_index"), &TextEdit::deselect, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("delete_selection", "caret_index"), &TextEdit::delete_selection, DEFVAL(-1));
@@ -6551,6 +6719,14 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_menu_visible"), &TextEdit::is_menu_visible);
ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option);
+ /* Deprecated */
+#ifndef DISABLE_DEPRECATED
+ ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit);
+ ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order);
+ ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0));
+#endif
+
/* Inspector */
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder");
@@ -6618,7 +6794,7 @@ void TextEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("gutter_added"));
ADD_SIGNAL(MethodInfo("gutter_removed"));
- /* Theme items */
+ // Theme items
/* Search */
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_border_color);
@@ -6691,6 +6867,10 @@ void TextEdit::_unhide_all_lines() {
queue_redraw();
}
+void TextEdit::_unhide_carets() {
+ // Override for functionality.
+}
+
void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) {
ERR_FAIL_INDEX(p_line, text.size());
@@ -6718,14 +6898,17 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) {
// Overridable actions
void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!editable) {
return;
}
start_action(EditAction::ACTION_TYPING);
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
if (p_caret != -1 && p_caret != i) {
continue;
}
@@ -6743,11 +6926,12 @@ void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_ca
const char32_t chr[2] = { (char32_t)p_unicode, 0 };
insert_text_at_caret(chr, i);
}
+ end_multicaret_edit();
end_action();
}
void TextEdit::_backspace_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!editable) {
return;
}
@@ -6758,194 +6942,163 @@ void TextEdit::_backspace_internal(int p_caret) {
}
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (const int &i : caret_edit_order) {
+ begin_multicaret_edit();
+ for (int i = 0; i < get_caret_count(); i++) {
+ if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
+ continue;
+ }
if (p_caret != -1 && p_caret != i) {
continue;
}
- int cc = get_caret_column(i);
- int cl = get_caret_line(i);
+ int to_line = get_caret_line(i);
+ int to_column = get_caret_column(i);
- if (cc == 0 && cl == 0) {
+ if (to_column == 0 && to_line == 0) {
continue;
}
- int prev_line = cc ? cl : cl - 1;
- int prev_column = cc ? (cc - 1) : (text[cl - 1].length());
+ int from_line = to_column > 0 ? to_line : to_line - 1;
+ int from_column = to_column > 0 ? (to_column - 1) : (text[to_line - 1].length());
- merge_gutters(prev_line, cl);
+ merge_gutters(from_line, to_line);
- if (_is_line_hidden(cl)) {
- _set_line_as_hidden(prev_line, true);
- }
- _remove_text(prev_line, prev_column, cl, cc);
-
- set_caret_line(prev_line, false, true, 0, i);
- set_caret_column(prev_column, i == 0, i);
+ _remove_text(from_line, from_column, to_line, to_column);
+ collapse_carets(from_line, from_column, to_line, to_column);
+ _offset_carets_after(to_line, to_column, from_line, from_column);
- adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
+ set_caret_line(from_line, false, true, -1, i);
+ set_caret_column(from_column, i == 0, i);
}
- merge_overlapping_carets();
+ end_multicaret_edit();
end_complex_operation();
}
void TextEdit::_cut_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
+
+ _copy_internal(p_caret);
+
if (!editable) {
return;
}
if (has_selection(p_caret)) {
- DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
delete_selection(p_caret);
- cut_copy_line = "";
return;
}
+ // Remove full lines.
begin_complex_operation();
- Vector<int> carets_to_remove;
-
- StringBuilder clipboard;
- // This is the exception and has to edit in reverse order else the string copied to the clipboard will be backwards.
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
- int caret_idx = caret_edit_order[i];
- if (p_caret != -1 && p_caret != caret_idx) {
- continue;
- }
-
- int cl = get_caret_line(caret_idx);
- int cc = get_caret_column(caret_idx);
- int indent_level = get_indent_level(cl);
- double hscroll = get_h_scroll();
-
- // Check for overlapping carets.
- // We don't need to worry about selections as that is caught before this entire section.
- for (int j = i - 1; j >= 0; j--) {
- if (get_caret_line(caret_edit_order[j]) == cl) {
- carets_to_remove.push_back(caret_edit_order[j]);
- i = j;
- }
- }
-
- clipboard += text[cl];
- if (p_caret == -1 && caret_idx != 0) {
- clipboard += "\n";
- }
-
- if (cl == 0 && get_line_count() > 1) {
- _remove_text(cl, 0, cl + 1, 0);
- adjust_carets_after_edit(caret_idx, cl, 0, cl + 1, text[cl].length());
- } else {
- _remove_text(cl, 0, cl, text[cl].length());
- set_caret_column(0, false, caret_idx);
- backspace(caret_idx);
- set_caret_line(get_caret_line(caret_idx) + 1, caret_idx == 0, 0, 0, caret_idx);
- }
-
- // Correct the visually perceived caret column taking care of indentation level of the lines.
- int diff_indent = indent_level - get_indent_level(get_caret_line(caret_idx));
- cc += diff_indent;
- if (diff_indent != 0) {
- cc += diff_indent > 0 ? -1 : 1;
- }
-
- // Restore horizontal scroll and caret column modified by the backspace() call.
- set_h_scroll(hscroll);
- set_caret_column(cc, caret_idx == 0, caret_idx);
+ begin_multicaret_edit();
+ Vector<Point2i> line_ranges;
+ if (p_caret == -1) {
+ line_ranges = get_line_ranges_from_carets();
+ } else {
+ line_ranges.push_back(Point2i(get_caret_line(p_caret), get_caret_line(p_caret)));
}
-
- // Sort and remove backwards to preserve indexes.
- carets_to_remove.sort();
- for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
- remove_caret(carets_to_remove[i]);
+ int line_offset = 0;
+ for (Point2i line_range : line_ranges) {
+ // Preserve carets on the last line.
+ remove_line_at(line_range.y + line_offset);
+ if (line_range.x != line_range.y) {
+ remove_text(line_range.x + line_offset, 0, line_range.y + line_offset, 0);
+ }
+ line_offset += line_range.x - line_range.y - 1;
}
+ end_multicaret_edit();
end_complex_operation();
-
- String clipboard_string = clipboard.as_string();
- DisplayServer::get_singleton()->clipboard_set(clipboard_string);
- cut_copy_line = clipboard_string;
}
void TextEdit::_copy_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (has_selection(p_caret)) {
DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
cut_copy_line = "";
return;
}
+ // Copy full lines.
StringBuilder clipboard;
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
- int caret_idx = caret_edit_order[i];
- if (p_caret != -1 && p_caret != caret_idx) {
- continue;
- }
-
- int cl = get_caret_line(caret_idx);
- if (text[cl].length() != 0) {
- clipboard += _base_get_text(cl, 0, cl, text[cl].length());
- if (p_caret == -1 && i != 0) {
- clipboard += "\n";
+ Vector<Point2i> line_ranges;
+ if (p_caret == -1) {
+ // When there are multiple carets on a line, only copy it once.
+ line_ranges = get_line_ranges_from_carets(false, true);
+ } else {
+ line_ranges.push_back(Point2i(get_caret_line(p_caret), get_caret_line(p_caret)));
+ }
+ for (Point2i line_range : line_ranges) {
+ for (int i = line_range.x; i <= line_range.y; i++) {
+ if (text[i].length() != 0) {
+ clipboard += _base_get_text(i, 0, i, text[i].length());
}
+ clipboard += "\n";
}
}
String clipboard_string = clipboard.as_string();
DisplayServer::get_singleton()->clipboard_set(clipboard_string);
- cut_copy_line = clipboard_string;
+ // Set the cut copy line so we know to paste as a line.
+ if (get_caret_count() == 1) {
+ cut_copy_line = clipboard_string;
+ } else {
+ cut_copy_line = "";
+ }
}
void TextEdit::_paste_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!editable) {
return;
}
String clipboard = DisplayServer::get_singleton()->clipboard_get();
+
+ // Paste a full line. Ignore '\r' characters that may have been added to the clipboard by the OS.
+ if (get_caret_count() == 1 && !has_selection(0) && !cut_copy_line.is_empty() && cut_copy_line == clipboard.replace("\r", "")) {
+ insert_text(clipboard, get_caret_line(), 0);
+ return;
+ }
+
+ // Paste text at each caret or one line per caret.
Vector<String> clipboad_lines = clipboard.split("\n");
- bool insert_line_per_caret = p_caret == -1 && carets.size() > 1 && clipboad_lines.size() == carets.size();
+ bool insert_line_per_caret = p_caret == -1 && get_caret_count() > 1 && clipboad_lines.size() == get_caret_count();
begin_complex_operation();
- Vector<int> caret_edit_order = get_caret_index_edit_order();
- int clipboad_line = clipboad_lines.size() - 1;
- for (const int &i : caret_edit_order) {
- if (p_caret != -1 && p_caret != i) {
+ begin_multicaret_edit();
+ Vector<int> sorted_carets = get_sorted_carets();
+ for (int i = 0; i < sorted_carets.size(); i++) {
+ int caret_index = sorted_carets[i];
+ if (p_caret != -1 && p_caret != caret_index) {
continue;
}
- if (has_selection(i)) {
- delete_selection(i);
- } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) {
- set_caret_column(0, i == 0, i);
- String ins = "\n";
- clipboard += ins;
+ if (has_selection(caret_index)) {
+ delete_selection(caret_index);
}
if (insert_line_per_caret) {
- clipboard = clipboad_lines[clipboad_line];
+ clipboard = clipboad_lines[i];
}
- insert_text_at_caret(clipboard, i);
- clipboad_line--;
+ insert_text_at_caret(clipboard, caret_index);
}
+ end_multicaret_edit();
end_complex_operation();
}
void TextEdit::_paste_primary_clipboard_internal(int p_caret) {
- ERR_FAIL_COND(p_caret > carets.size());
+ ERR_FAIL_COND(p_caret >= get_caret_count() || p_caret < -1);
if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
return;
}
String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary();
- if (carets.size() == 1) {
+ if (get_caret_count() == 1) {
Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
deselect();
- set_caret_line(pos.y, true, false);
+ set_caret_line(pos.y, true, false, -1);
set_caret_column(pos.x);
}
@@ -7204,10 +7357,26 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con
}
/* Caret */
+void TextEdit::_caret_changed(int p_caret) {
+ queue_redraw();
+
+ if (has_selection(p_caret)) {
+ _selection_changed(p_caret);
+ }
+
+ if (caret_pos_dirty) {
+ return;
+ }
+
+ if (is_inside_tree()) {
+ callable_mp(this, &TextEdit::_emit_caret_changed).call_deferred();
+ }
+ caret_pos_dirty = true;
+}
+
void TextEdit::_emit_caret_changed() {
emit_signal(SNAME("caret_changed"));
caret_pos_dirty = false;
- caret_index_edit_dirty = true;
}
void TextEdit::_reset_caret_blink_timer() {
@@ -7252,60 +7421,152 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column
}
}
-/* Selection */
-void TextEdit::_click_selection_held() {
- // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD
- // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem.
- // I'm unsure if there's an actual fix that doesn't have a ton of side effects.
- if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && get_selection_mode() != SelectionMode::SELECTION_MODE_NONE) {
- switch (get_selection_mode()) {
- case SelectionMode::SELECTION_MODE_POINTER: {
- _update_selection_mode_pointer();
- } break;
- case SelectionMode::SELECTION_MODE_WORD: {
- _update_selection_mode_word();
- } break;
- case SelectionMode::SELECTION_MODE_LINE: {
- _update_selection_mode_line();
- } break;
- default: {
- break;
+bool TextEdit::_is_line_col_in_range(int p_line, int p_column, int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_include_edges) const {
+ if (p_line >= p_from_line && p_line <= p_to_line && (p_line > p_from_line || p_column > p_from_column) && (p_line < p_to_line || p_column < p_to_column)) {
+ return true;
+ }
+ if (p_include_edges) {
+ if ((p_line == p_from_line && p_column == p_from_column) || (p_line == p_to_line && p_column == p_to_column)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void TextEdit::_offset_carets_after(int p_old_line, int p_old_column, int p_new_line, int p_new_column, bool p_include_selection_begin, bool p_include_selection_end) {
+ // Moves all carets at or after old_line and old_column.
+ // Called after deleting or inserting text so that the carets stay with the text they are at.
+
+ int edit_height = p_new_line - p_old_line;
+ int edit_size = p_new_column - p_old_column;
+ if (edit_height == 0 && edit_size == 0) {
+ return;
+ }
+
+ // Intentionally includes carets in the multicaret_edit_ignore list so that they are moved together.
+ for (int i = 0; i < get_caret_count(); i++) {
+ bool selected = has_selection(i);
+ bool caret_at_end = selected && is_caret_after_selection_origin(i);
+ bool include_caret_at = caret_at_end ? p_include_selection_end : p_include_selection_begin;
+
+ // Move caret.
+ int caret_line = get_caret_line(i);
+ int caret_column = get_caret_column(i);
+ bool caret_after = caret_line > p_old_line || (caret_line == p_old_line && caret_column > p_old_column);
+ bool caret_at = caret_line == p_old_line && caret_column == p_old_column;
+ if (caret_after || (caret_at && include_caret_at)) {
+ caret_line += edit_height;
+ if (caret_line == p_new_line) {
+ caret_column += edit_size;
+ }
+
+ if (edit_height != 0) {
+ set_caret_line(caret_line, false, true, -1, i);
}
+ set_caret_column(caret_column, false, i);
}
- } else {
+
+ // Move selection origin.
+ if (!selected) {
+ continue;
+ }
+ bool include_selection_origin_at = !caret_at_end ? p_include_selection_end : p_include_selection_begin;
+
+ int selection_origin_line = get_selection_origin_line(i);
+ int selection_origin_column = get_selection_origin_column(i);
+ bool selection_origin_after = selection_origin_line > p_old_line || (selection_origin_line == p_old_line && selection_origin_column > p_old_column);
+ bool selection_origin_at = selection_origin_line == p_old_line && selection_origin_column == p_old_column;
+ if (selection_origin_after || (selection_origin_at && include_selection_origin_at)) {
+ selection_origin_line += edit_height;
+ if (selection_origin_line == p_new_line) {
+ selection_origin_column += edit_size;
+ }
+ select(selection_origin_line, selection_origin_column, caret_line, caret_column, i);
+ }
+ }
+ if (!p_include_selection_begin && p_include_selection_end && has_selection()) {
+ // It is possible that two adjacent selections now overlap.
+ merge_overlapping_carets();
+ }
+}
+
+void TextEdit::_cancel_drag_and_drop_text() {
+ // Cancel the drag operation if drag originated from here.
+ if (selection_drag_attempt && get_viewport()) {
+ get_viewport()->gui_cancel_drag();
+ }
+}
+
+/* Selection */
+void TextEdit::_selection_changed(int p_caret) {
+ if (!selecting_enabled) {
+ return;
+ }
+
+ _cancel_drag_and_drop_text();
+ queue_redraw();
+}
+
+void TextEdit::_click_selection_held() {
+ // Update the selection mode on a timer so it is updated when the view scrolls even if the mouse isn't moving.
+ if (!Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) {
click_select_held->stop();
+ return;
+ }
+ switch (get_selection_mode()) {
+ case SelectionMode::SELECTION_MODE_POINTER: {
+ _update_selection_mode_pointer();
+ } break;
+ case SelectionMode::SELECTION_MODE_WORD: {
+ _update_selection_mode_word();
+ } break;
+ case SelectionMode::SELECTION_MODE_LINE: {
+ _update_selection_mode_line();
+ } break;
+ default: {
+ break;
+ }
}
}
-void TextEdit::_update_selection_mode_pointer() {
- dragging_selection = true;
+void TextEdit::_update_selection_mode_pointer(bool p_initial) {
Point2 mp = get_local_mouse_pos();
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
- int col = pos.x;
- int caret_idx = carets.size() - 1;
-
- select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx);
+ int column = pos.x;
+ int caret_index = get_caret_count() - 1;
+
+ if (p_initial && !has_selection(caret_index)) {
+ set_selection_origin_line(line, true, -1, caret_index);
+ set_selection_origin_column(column, caret_index);
+ // Set the word begin and end to the column in case the mode changes later.
+ carets.write[caret_index].selection.word_begin_column = column;
+ carets.write[caret_index].selection.word_end_column = column;
+ } else {
+ select(get_selection_origin_line(caret_index), get_selection_origin_column(caret_index), line, column, caret_index);
+ }
+ adjust_viewport_to_caret(caret_index);
- set_caret_line(line, false, true, 0, caret_idx);
- set_caret_column(col, true, caret_idx);
- queue_redraw();
+ if (has_selection(caret_index)) {
+ // Only set to true if any selection has been made.
+ dragging_selection = true;
+ }
click_select_held->start();
merge_overlapping_carets();
}
-void TextEdit::_update_selection_mode_word() {
+void TextEdit::_update_selection_mode_word(bool p_initial) {
dragging_selection = true;
Point2 mp = get_local_mouse_pos();
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
- int col = pos.x;
- int caret_idx = carets.size() - 1;
+ int column = pos.x;
+ int caret_index = get_caret_count() - 1;
- int caret_pos = CLAMP(col, 0, text[line].length());
+ int caret_pos = CLAMP(column, 0, text[line].length());
int beg = caret_pos;
int end = beg;
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
@@ -7317,70 +7578,57 @@ void TextEdit::_update_selection_mode_word() {
}
}
- /* Initial selection. */
- if (!has_selection(caret_idx)) {
- select(line, beg, line, end, caret_idx);
- carets.write[caret_idx].selection.selecting_column = beg;
- carets.write[caret_idx].selection.selected_word_beg = beg;
- carets.write[caret_idx].selection.selected_word_end = end;
- carets.write[caret_idx].selection.selected_word_origin = beg;
- set_caret_line(line, false, true, 0, caret_idx);
- set_caret_column(end, true, caret_idx);
+ if (p_initial && !has_selection(caret_index)) {
+ // Set the selection origin if there is no existing selection.
+ select(line, beg, line, end, caret_index);
+ carets.write[caret_index].selection.word_begin_column = beg;
+ carets.write[caret_index].selection.word_end_column = end;
} else {
- if ((col <= carets[caret_idx].selection.selected_word_origin && line == get_selection_line(caret_idx)) || line < get_selection_line(caret_idx)) {
- carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_end;
- select(line, beg, get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_end, caret_idx);
- set_caret_line(line, false, true, 0, caret_idx);
- set_caret_column(beg, true, caret_idx);
- } else {
- carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_beg;
- select(get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_beg, line, end, caret_idx);
- set_caret_line(get_selection_to_line(caret_idx), false, true, 0, caret_idx);
- set_caret_column(get_selection_to_column(caret_idx), true, caret_idx);
- }
+ // Expand the word selection to the mouse.
+ int origin_line = get_selection_origin_line(caret_index);
+ bool is_new_selection_dir_right = line > origin_line || (line == origin_line && column >= carets[caret_index].selection.word_begin_column);
+ int origin_col = is_new_selection_dir_right ? carets[caret_index].selection.word_begin_column : carets[caret_index].selection.word_end_column;
+ int caret_col = is_new_selection_dir_right ? end : beg;
+
+ select(origin_line, origin_col, line, caret_col, caret_index);
}
+ adjust_viewport_to_caret(caret_index);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
- queue_redraw();
-
click_select_held->start();
merge_overlapping_carets();
}
-void TextEdit::_update_selection_mode_line() {
+void TextEdit::_update_selection_mode_line(bool p_initial) {
dragging_selection = true;
Point2 mp = get_local_mouse_pos();
Point2i pos = get_line_column_at_pos(mp);
int line = pos.y;
- int col = pos.x;
- int caret_idx = carets.size() - 1;
-
- col = 0;
- if (line < carets[caret_idx].selection.selecting_line) {
- // Caret is above us.
- set_caret_line(line - 1, false, true, 0, caret_idx);
- carets.write[caret_idx].selection.selecting_column = has_selection(caret_idx)
- ? text[get_selection_line(caret_idx)].length()
- : 0;
- } else {
- // Caret is below us.
- set_caret_line(line + 1, false, true, 0, caret_idx);
- carets.write[caret_idx].selection.selecting_column = 0;
- col = text[line].length();
+ int caret_index = get_caret_count() - 1;
+
+ int origin_line = p_initial && !has_selection(caret_index) ? line : get_selection_origin_line();
+ bool line_below = line >= origin_line;
+ int origin_col = line_below ? 0 : get_line(origin_line).length();
+ int caret_line = line_below ? line + 1 : line;
+ int caret_col = caret_line < text.size() ? 0 : get_line(text.size() - 1).length();
+
+ select(origin_line, origin_col, caret_line, caret_col, caret_index);
+ adjust_viewport_to_caret(caret_index);
+
+ if (p_initial) {
+ // Set the word begin and end to the start and end of the origin line in case the mode changes later.
+ carets.write[caret_index].selection.word_begin_column = 0;
+ carets.write[caret_index].selection.word_end_column = get_line(origin_line).length();
}
- set_caret_column(0, false, caret_idx);
- select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
- queue_redraw();
-
click_select_held->start();
merge_overlapping_carets();
}
@@ -7390,23 +7638,23 @@ void TextEdit::_pre_shift_selection(int p_caret) {
return;
}
- if (!has_selection(p_caret) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) {
- carets.write[p_caret].selection.active = true;
- set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_caret_line(p_caret), get_caret_column(p_caret), p_caret);
+ set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT);
+ if (has_selection(p_caret)) {
return;
}
-
- set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_selection_line(p_caret), get_selection_column(p_caret), p_caret);
+ // Prepare selection to start at current caret position.
+ set_selection_origin_line(get_caret_line(p_caret), true, -1, p_caret);
+ set_selection_origin_column(get_caret_column(p_caret), p_caret);
+ carets.write[p_caret].selection.active = true;
+ carets.write[p_caret].selection.word_begin_column = get_caret_column(p_caret);
+ carets.write[p_caret].selection.word_end_column = get_caret_column(p_caret);
}
-void TextEdit::_post_shift_selection(int p_caret) {
- if (!selecting_enabled) {
- return;
- }
-
- if (has_selection(p_caret) && get_selection_mode() == SelectionMode::SELECTION_MODE_SHIFT) {
- select(get_selection_line(p_caret), get_selection_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret), p_caret);
+bool TextEdit::_selection_contains(int p_caret, int p_line, int p_column, bool p_include_edges, bool p_only_selections) const {
+ if (!has_selection(p_caret)) {
+ return !p_only_selections && p_line == get_caret_line(p_caret) && p_column == get_caret_column(p_caret);
}
+ return _is_line_col_in_range(p_line, p_column, get_selection_from_line(p_caret), get_selection_from_column(p_caret), get_selection_to_line(p_caret), get_selection_to_column(p_caret), p_include_edges);
}
/* Line Wrapping */
@@ -7492,7 +7740,7 @@ void TextEdit::_update_scrollbars() {
updating_scrolls = true;
- if (total_rows > visible_rows) {
+ if (!fit_content_height && total_rows > visible_rows) {
v_scroll->show();
v_scroll->set_max(total_rows + _get_visible_lines_offset());
v_scroll->set_page(visible_rows + _get_visible_lines_offset());
@@ -7719,7 +7967,7 @@ void TextEdit::_update_minimap_click() {
Point2 mp = get_local_mouse_pos();
int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
- if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) {
+ if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.x > xmargin_end)) {
minimap_clicked = false;
return;
}
@@ -7781,9 +8029,43 @@ Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) {
return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line);
}
+/* Deprecated. */
+#ifndef DISABLE_DEPRECATED
+Vector<int> TextEdit::get_caret_index_edit_order() {
+ Vector<int> carets_order = get_sorted_carets();
+ carets_order.reverse();
+ return carets_order;
+}
+
+void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) {
+}
+
+int TextEdit::get_selection_line(int p_caret) const {
+ return get_selection_origin_line(p_caret);
+}
+
+int TextEdit::get_selection_column(int p_caret) const {
+ return get_selection_origin_column(p_caret);
+}
+#endif
+
/*** Super internal Core API. Everything builds on it. ***/
-void TextEdit::_text_changed_emit() {
+void TextEdit::_text_changed() {
+ _cancel_drag_and_drop_text();
+ queue_redraw();
+
+ if (text_changed_dirty || setting_text) {
+ return;
+ }
+
+ if (is_inside_tree()) {
+ callable_mp(this, &TextEdit::_emit_text_changed).call_deferred();
+ }
+ text_changed_dirty = true;
+}
+
+void TextEdit::_emit_text_changed() {
emit_signal(SNAME("text_changed"));
text_changed_dirty = false;
}
@@ -7919,12 +8201,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
input_direction = (TextDirection)dir;
}
- if (!text_changed_dirty && !setting_text) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_text_changed_emit).call_deferred();
- }
- text_changed_dirty = true;
- }
+ _text_changed();
emit_signal(SNAME("lines_edited_from"), p_line, r_end_line);
}
@@ -7965,12 +8242,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
text.remove_range(p_from_line, p_to_line);
text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text));
- if (!text_changed_dirty && !setting_text) {
- if (is_inside_tree()) {
- callable_mp(this, &TextEdit::_text_changed_emit).call_deferred();
- }
- text_changed_dirty = true;
- }
+ _text_changed();
emit_signal(SNAME("lines_edited_from"), p_to_line, p_from_line);
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 1099295d3b..efade39876 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -389,18 +389,12 @@ private:
/* Caret. */
struct Selection {
bool active = false;
- bool shiftclick_left = false;
- int selecting_line = 0;
- int selecting_column = 0;
- int selected_word_beg = 0;
- int selected_word_end = 0;
- int selected_word_origin = 0;
-
- int from_line = 0;
- int from_column = 0;
- int to_line = 0;
- int to_column = 0;
+ int origin_line = 0;
+ int origin_column = 0;
+ int origin_last_fit_x = 0;
+ int word_begin_column = 0;
+ int word_end_column = 0;
};
struct Caret {
@@ -415,11 +409,13 @@ private:
// Vector containing all the carets, index '0' is the "main caret" and should never be removed.
Vector<Caret> carets;
- Vector<int> caret_index_edit_order;
bool setting_caret_line = false;
bool caret_pos_dirty = false;
- bool caret_index_edit_dirty = true;
+
+ int multicaret_edit_count = 0;
+ bool multicaret_edit_merge_queued = false;
+ HashSet<int> multicaret_edit_ignore_carets;
CaretType caret_type = CaretType::CARET_TYPE_LINE;
@@ -438,12 +434,18 @@ private:
bool drag_action = false;
bool drag_caret_force_displayed = false;
+ void _caret_changed(int p_caret = -1);
void _emit_caret_changed();
void _reset_caret_blink_timer();
void _toggle_draw_caret();
int _get_column_x_offset_for_line(int p_char, int p_line, int p_column) const;
+ bool _is_line_col_in_range(int p_line, int p_column, int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_include_edges = true) const;
+
+ void _offset_carets_after(int p_old_line, int p_old_column, int p_new_line, int p_new_column, bool p_include_selection_begin = true, bool p_include_selection_end = true);
+
+ void _cancel_drag_and_drop_text();
/* Selection. */
SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE;
@@ -456,18 +458,23 @@ private:
bool selection_drag_attempt = false;
bool dragging_selection = false;
+ int drag_and_drop_origin_caret_index = -1;
+ int drag_caret_index = -1;
Timer *click_select_held = nullptr;
uint64_t last_dblclk = 0;
Vector2 last_dblclk_pos;
+
+ void _selection_changed(int p_caret = -1);
void _click_selection_held();
- void _update_selection_mode_pointer();
- void _update_selection_mode_word();
- void _update_selection_mode_line();
+ void _update_selection_mode_pointer(bool p_initial = false);
+ void _update_selection_mode_word(bool p_initial = false);
+ void _update_selection_mode_line(bool p_initial = false);
void _pre_shift_selection(int p_caret);
- void _post_shift_selection(int p_caret);
+
+ bool _selection_contains(int p_caret, int p_line, int p_column, bool p_include_edges = true, bool p_only_selections = true) const;
/* Line wrapping. */
LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE;
@@ -599,7 +606,8 @@ private:
/*** Super internal Core API. Everything builds on it. ***/
bool text_changed_dirty = false;
- void _text_changed_emit();
+ void _text_changed();
+ void _emit_text_changed();
void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr);
void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
@@ -625,13 +633,15 @@ private:
void _move_caret_document_end(bool p_select);
bool _clear_carets_and_selection();
- // Used in add_caret_at_carets
- void _get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x = -1) const;
-
protected:
void _notification(int p_what);
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _set_selection_mode_compat_86978(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0);
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
virtual void _update_theme_item_cache() override;
/* Internal API for CodeEdit, pending public API. */
@@ -659,6 +669,7 @@ protected:
bool _is_line_hidden(int p_line) const;
void _unhide_all_lines();
+ virtual void _unhide_carets();
// Symbol lookup.
String lookup_symbol_word;
@@ -765,9 +776,11 @@ public:
void swap_lines(int p_from_line, int p_to_line);
- void insert_line_at(int p_at, const String &p_text);
- void insert_text_at_caret(const String &p_text, int p_caret = -1);
+ void insert_line_at(int p_line, const String &p_text);
+ void remove_line_at(int p_line, bool p_move_carets_down = true);
+ void insert_text_at_caret(const String &p_text, int p_caret = -1);
+ void insert_text(const String &p_text, int p_line, int p_column, bool p_before_selection_begin = true, bool p_before_selection_end = false);
void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
int get_last_unhidden_line() const;
@@ -851,15 +864,20 @@ public:
void set_multiple_carets_enabled(bool p_enabled);
bool is_multiple_carets_enabled() const;
- int add_caret(int p_line, int p_col);
+ int add_caret(int p_line, int p_column);
void remove_caret(int p_caret);
void remove_secondary_carets();
- void merge_overlapping_carets();
int get_caret_count() const;
void add_caret_at_carets(bool p_below);
- Vector<int> get_caret_index_edit_order();
- void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col);
+ Vector<int> get_sorted_carets(bool p_include_ignored_carets = false) const;
+ void collapse_carets(int p_from_line, int p_from_column, int p_to_line, int p_to_column, bool p_inclusive = false);
+
+ void merge_overlapping_carets();
+ void begin_multicaret_edit();
+ void end_multicaret_edit();
+ bool is_in_mulitcaret_edit() const;
+ bool multicaret_edit_ignore_caret(int p_caret) const;
bool is_caret_visible(int p_caret = 0) const;
Point2 get_caret_draw_pos(int p_caret = 0) const;
@@ -867,7 +885,7 @@ public:
void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0, int p_caret = 0);
int get_caret_line(int p_caret = 0) const;
- void set_caret_column(int p_col, bool p_adjust_viewport = true, int p_caret = 0);
+ void set_caret_column(int p_column, bool p_adjust_viewport = true, int p_caret = 0);
int get_caret_column(int p_caret = 0) const;
int get_caret_wrap_index(int p_caret = 0) const;
@@ -884,27 +902,34 @@ public:
void set_drag_and_drop_selection_enabled(const bool p_enabled);
bool is_drag_and_drop_selection_enabled() const;
- void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0);
+ void set_selection_mode(SelectionMode p_mode);
SelectionMode get_selection_mode() const;
void select_all();
void select_word_under_caret(int p_caret = -1);
void add_selection_for_next_occurrence();
void skip_selection_for_next_occurrence();
- void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0);
+ void select(int p_origin_line, int p_origin_column, int p_caret_line, int p_caret_column, int p_caret = 0);
bool has_selection(int p_caret = -1) const;
String get_selected_text(int p_caret = -1);
+ int get_selection_at_line_column(int p_line, int p_column, bool p_include_edges = true, bool p_only_selections = true) const;
+ Vector<Point2i> get_line_ranges_from_carets(bool p_only_selections = false, bool p_merge_adjacent = true) const;
+ TypedArray<Vector2i> get_line_ranges_from_carets_typed_array(bool p_only_selections = false, bool p_merge_adjacent = true) const;
- int get_selection_line(int p_caret = 0) const;
- int get_selection_column(int p_caret = 0) const;
+ void set_selection_origin_line(int p_line, bool p_can_be_hidden = true, int p_wrap_index = -1, int p_caret = 0);
+ void set_selection_origin_column(int p_column, int p_caret = 0);
+ int get_selection_origin_line(int p_caret = 0) const;
+ int get_selection_origin_column(int p_caret = 0) const;
int get_selection_from_line(int p_caret = 0) const;
int get_selection_from_column(int p_caret = 0) const;
int get_selection_to_line(int p_caret = 0) const;
int get_selection_to_column(int p_caret = 0) const;
+ bool is_caret_after_selection_origin(int p_caret = 0) const;
+
void deselect(int p_caret = -1);
void delete_selection(int p_caret = -1);
@@ -1043,6 +1068,15 @@ public:
Color get_font_color() const;
+ /* Deprecated. */
+#ifndef DISABLE_DEPRECATED
+ Vector<int> get_caret_index_edit_order();
+ void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col);
+
+ int get_selection_line(int p_caret = 0) const;
+ int get_selection_column(int p_caret = 0) const;
+#endif
+
TextEdit(const String &p_placeholder = String());
};
diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp
index 0b197c8c02..c267ff93c6 100644
--- a/scene/gui/texture_button.cpp
+++ b/scene/gui/texture_button.cpp
@@ -103,7 +103,7 @@ bool TextureButton::has_point(const Point2 &p_point) const {
point *= scale;
// finally, we need to check if the point is inside a rectangle with a position >= 0,0 and a size <= mask_size
- rect.position = Point2().max(_texture_region.position);
+ rect.position = _texture_region.position.maxf(0);
rect.size = mask_size.min(_texture_region.size);
}
@@ -178,13 +178,14 @@ void TextureButton::_notification(int p_what) {
texdraw = focused;
}
- if (texdraw.is_valid()) {
- size = texdraw->get_size();
- _texture_region = Rect2(Point2(), texdraw->get_size());
+ if (texdraw.is_valid() || click_mask.is_valid()) {
+ const Size2 texdraw_size = texdraw.is_valid() ? texdraw->get_size() : Size2(click_mask->get_size());
+
+ size = texdraw_size;
+ _texture_region = Rect2(Point2(), texdraw_size);
_tile = false;
switch (stretch_mode) {
case STRETCH_KEEP:
- size = texdraw->get_size();
break;
case STRETCH_SCALE:
size = get_size();
@@ -194,18 +195,17 @@ void TextureButton::_notification(int p_what) {
_tile = true;
break;
case STRETCH_KEEP_CENTERED:
- ofs = (get_size() - texdraw->get_size()) / 2;
- size = texdraw->get_size();
+ ofs = (get_size() - texdraw_size) / 2;
break;
case STRETCH_KEEP_ASPECT_CENTERED:
case STRETCH_KEEP_ASPECT: {
Size2 _size = get_size();
- float tex_width = texdraw->get_width() * _size.height / texdraw->get_height();
+ float tex_width = texdraw_size.width * _size.height / texdraw_size.height;
float tex_height = _size.height;
if (tex_width > _size.width) {
tex_width = _size.width;
- tex_height = texdraw->get_height() * tex_width / texdraw->get_width();
+ tex_height = texdraw_size.height * tex_width / texdraw_size.width;
}
if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) {
@@ -217,10 +217,9 @@ void TextureButton::_notification(int p_what) {
} break;
case STRETCH_KEEP_ASPECT_COVERED: {
size = get_size();
- Size2 tex_size = texdraw->get_size();
- Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height);
+ Size2 scale_size = size / texdraw_size;
float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height;
- Size2 scaled_tex_size = tex_size * scale;
+ Size2 scaled_tex_size = texdraw_size * scale;
Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f;
_texture_region = Rect2(ofs2, size / scale);
} break;
@@ -233,10 +232,12 @@ void TextureButton::_notification(int p_what) {
if (draw_focus_only) {
// Do nothing, we only needed to calculate the rectangle.
- } else if (_tile) {
- draw_texture_rect(texdraw, Rect2(ofs, size), _tile);
- } else {
- draw_texture_rect_region(texdraw, Rect2(ofs, size), _texture_region);
+ } else if (texdraw.is_valid()) {
+ if (_tile) {
+ draw_texture_rect(texdraw, Rect2(ofs, size), _tile);
+ } else {
+ draw_texture_rect_region(texdraw, Rect2(ofs, size), _texture_region);
+ }
}
} else {
_position_rect = Rect2();
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index 5261cbe3eb..dc48945d9b 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -249,7 +249,7 @@ Point2 TextureProgressBar::get_relative_center() {
p += rad_center_off;
p.x /= progress->get_width();
p.y /= progress->get_height();
- p = p.clamp(Point2(), Point2(1, 1));
+ p = p.clampf(0, 1);
return p;
}
@@ -494,7 +494,7 @@ void TextureProgressBar::_notification(int p_what) {
Rect2 source = Rect2(Point2(), progress->get_size());
draw_texture_rect_region(progress, region, source, tint_progress);
} else if (val != 0) {
- Array pts;
+ LocalVector<float> pts;
float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1;
float start;
@@ -507,11 +507,11 @@ void TextureProgressBar::_notification(int p_what) {
float end = start + direction * val;
float from = MIN(start, end);
float to = MAX(start, end);
- pts.append(from);
+ pts.push_back(from);
for (float corner = Math::floor(from * 4 + 0.5) * 0.25 + 0.125; corner < to; corner += 0.25) {
- pts.append(corner);
+ pts.push_back(corner);
}
- pts.append(to);
+ pts.push_back(to);
Ref<AtlasTexture> atlas_progress = progress;
bool valid_atlas_progress = atlas_progress.is_valid() && atlas_progress->get_atlas().is_valid();
@@ -524,9 +524,9 @@ void TextureProgressBar::_notification(int p_what) {
Vector<Point2> uvs;
Vector<Point2> points;
- for (int i = 0; i < pts.size(); i++) {
- Point2 uv = unit_val_to_uv(pts[i]);
- if (uvs.find(uv) >= 0) {
+ for (const float &f : pts) {
+ Point2 uv = unit_val_to_uv(f);
+ if (uvs.has(uv)) {
continue;
}
points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y));
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 1daf86fe0f..fc5b942918 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -35,8 +35,6 @@
#include "core/math/math_funcs.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
-#include "core/string/print_string.h"
-#include "core/string/translation.h"
#include "scene/gui/box_container.h"
#include "scene/gui/text_edit.h"
#include "scene/main/window.h"
@@ -381,8 +379,8 @@ void TreeItem::set_text_overrun_behavior(int p_column, TextServer::OverrunBehavi
cells.write[p_column].text_buf->set_text_overrun_behavior(p_behavior);
cells.write[p_column].dirty = true;
- _changed_notify(p_column);
cells.write[p_column].cached_minimum_size_dirty = true;
+ _changed_notify(p_column);
}
TextServer::OverrunBehavior TreeItem::get_text_overrun_behavior(int p_column) const {
@@ -1514,7 +1512,9 @@ Size2 TreeItem::get_minimum_size(int p_column) {
const TreeItem::Cell &cell = cells[p_column];
if (cell.cached_minimum_size_dirty) {
- Size2 size;
+ Size2 size = Size2(
+ parent_tree->theme_cache.inner_item_margin_left + parent_tree->theme_cache.inner_item_margin_right,
+ parent_tree->theme_cache.inner_item_margin_top + parent_tree->theme_cache.inner_item_margin_bottom);
// Text.
if (!cell.text.is_empty()) {
@@ -1522,7 +1522,9 @@ Size2 TreeItem::get_minimum_size(int p_column) {
parent_tree->update_item_cell(this, p_column);
}
Size2 text_size = cell.text_buf->get_size();
- size.width += text_size.width;
+ if (get_text_overrun_behavior(p_column) == TextServer::OVERRUN_NO_TRIMMING) {
+ size.width += text_size.width;
+ }
size.height = MAX(size.height, text_size.height);
}
@@ -1541,13 +1543,10 @@ Size2 TreeItem::get_minimum_size(int p_column) {
Ref<Texture2D> texture = cell.buttons[i].texture;
if (texture.is_valid()) {
Size2 button_size = texture->get_size() + parent_tree->theme_cache.button_pressed->get_minimum_size();
- size.width += button_size.width;
+ size.width += button_size.width + parent_tree->theme_cache.button_margin;
size.height = MAX(size.height, button_size.height);
}
}
- if (cell.buttons.size() >= 2) {
- size.width += (cell.buttons.size() - 1) * parent_tree->theme_cache.button_margin;
- }
cells.write[p_column].cached_minimum_size = size;
cells.write[p_column].cached_minimum_size_dirty = false;
@@ -2459,7 +2458,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (rtl) {
button_ofs.x = get_size().width - button_ofs.x - button_texture->get_width();
}
- p_item->cells.write[i].buttons.write[j].rect = Rect2i(button_ofs, button_size);
button_texture->draw(ci, button_ofs, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color);
item_width_with_buttons -= button_size.width + theme_cache.button_margin;
}
@@ -2830,7 +2828,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
return -1;
}
- if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + theme_cache.item_margin))) {
+ if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x < (x_ofs + theme_cache.item_margin))) {
if (enable_recursive_folding && p_mod->is_shift_pressed()) {
p_item->set_collapsed_recursive(!p_item->is_collapsed());
} else {
@@ -3154,10 +3152,12 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
void Tree::_text_editor_popup_modal_close() {
- if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) ||
- Input::get_singleton()->is_key_pressed(Key::KP_ENTER) ||
- Input::get_singleton()->is_key_pressed(Key::ENTER)) {
- return;
+ if (popup_edit_commited) {
+ return; // Already processed by LineEdit/TextEdit commit.
+ }
+
+ if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) {
+ return; // ESC pressed, app focus lost, or forced close from code.
}
if (value_editor->has_point(value_editor->get_local_mouse_position())) {
@@ -3176,9 +3176,18 @@ void Tree::_text_editor_popup_modal_close() {
}
void Tree::_text_editor_gui_input(const Ref<InputEvent> &p_event) {
+ if (popup_edit_commited) {
+ return; // Already processed by _text_editor_popup_modal_close
+ }
+
+ if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) {
+ return; // ESC pressed, app focus lost, or forced close from code.
+ }
+
if (p_event->is_action_pressed("ui_text_newline_blank", true)) {
accept_event();
} else if (p_event->is_action_pressed("ui_text_newline")) {
+ popup_edit_commited = true; // End edit popup processing.
popup_editor->hide();
_apply_multiline_edit();
accept_event();
@@ -3209,6 +3218,15 @@ void Tree::_apply_multiline_edit() {
}
void Tree::_line_editor_submit(String p_text) {
+ if (popup_edit_commited) {
+ return; // Already processed by _text_editor_popup_modal_close
+ }
+
+ if (popup_editor->get_hide_reason() == Popup::HIDE_REASON_CANCELED) {
+ return; // ESC pressed, app focus lost, or forced close from code.
+ }
+
+ popup_edit_commited = true; // End edit popup processing.
popup_editor->hide();
if (!popup_edited_item) {
@@ -3431,7 +3449,7 @@ Rect2 Tree::_get_content_rect() const {
const real_t v_size = v_scroll->is_visible() ? (v_scroll->get_combined_minimum_size().x + theme_cache.scrollbar_h_separation) : 0;
const real_t h_size = h_scroll->is_visible() ? (h_scroll->get_combined_minimum_size().y + theme_cache.scrollbar_v_separation) : 0;
const Point2 scroll_begin = _get_scrollbar_layout_rect().get_end() - Vector2(v_size, h_size);
- const Size2 offset = (content_rect.get_end() - scroll_begin).max(Vector2(0, 0));
+ const Size2 offset = (content_rect.get_end() - scroll_begin).maxf(0);
return content_rect.grow_individual(0, 0, -offset.x, -offset.y);
}
@@ -3995,12 +4013,14 @@ bool Tree::edit_selected(bool p_force_edit) {
return false;
}
+ float popup_scale = popup_editor->is_embedded() ? 1.0 : popup_editor->get_parent_visible_window()->get_content_scale_factor();
Rect2 rect;
if (select_mode == SELECT_ROW) {
rect = s->get_meta("__focus_col_" + itos(selected_col));
} else {
rect = s->get_meta("__focus_rect");
}
+ rect.position *= popup_scale;
popup_edited_item = s;
popup_edited_item_col = col;
@@ -4043,7 +4063,7 @@ bool Tree::edit_selected(bool p_force_edit) {
popup_rect.size = rect.size;
// Account for icon.
- Size2 icon_size = _get_cell_icon_size(c);
+ Size2 icon_size = _get_cell_icon_size(c) * popup_scale;
popup_rect.position.x += icon_size.x;
popup_rect.size.x -= icon_size.x;
@@ -4070,7 +4090,11 @@ bool Tree::edit_selected(bool p_force_edit) {
}
popup_editor->set_position(popup_rect.position);
- popup_editor->set_size(popup_rect.size);
+ popup_editor->set_size(popup_rect.size * popup_scale);
+ if (!popup_editor->is_embedded()) {
+ popup_editor->set_content_scale_factor(popup_scale);
+ }
+ popup_edit_commited = false; // Start edit popup processing.
popup_editor->popup();
popup_editor->child_controls_changed();
@@ -4086,7 +4110,11 @@ bool Tree::edit_selected(bool p_force_edit) {
text_editor->show();
popup_editor->set_position(get_screen_position() + rect.position);
- popup_editor->set_size(rect.size);
+ popup_editor->set_size(rect.size * popup_scale);
+ if (!popup_editor->is_embedded()) {
+ popup_editor->set_content_scale_factor(popup_scale);
+ }
+ popup_edit_commited = false; // Start edit popup processing.
popup_editor->popup();
popup_editor->child_controls_changed();
@@ -4339,7 +4367,7 @@ void Tree::_notification(int p_what) {
}
default: {
- text_pos.x += sb->get_offset().x + (tbrect.size.width - columns[i].text_buf->get_size().x) / 2;
+ text_pos.x += (tbrect.size.width - columns[i].text_buf->get_size().x) / 2;
break;
}
}
@@ -4389,17 +4417,23 @@ void Tree::_update_all() {
}
Size2 Tree::get_minimum_size() const {
- if (h_scroll_enabled && v_scroll_enabled) {
- return Size2();
- } else {
- Vector2 min_size = get_internal_min_size();
- Ref<StyleBox> bg = theme_cache.panel_style;
- if (bg.is_valid()) {
- min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
- min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM);
- }
- return Vector2(h_scroll_enabled ? 0 : min_size.x, v_scroll_enabled ? 0 : min_size.y);
+ Vector2 min_size = Vector2(0, _get_title_button_height());
+
+ if (theme_cache.panel_style.is_valid()) {
+ min_size += theme_cache.panel_style->get_minimum_size();
+ }
+
+ Vector2 content_min_size = get_internal_min_size();
+ if (h_scroll_enabled) {
+ content_min_size.x = 0;
+ min_size.y += h_scroll->get_combined_minimum_size().height;
}
+ if (v_scroll_enabled) {
+ min_size.x += v_scroll->get_combined_minimum_size().width;
+ content_min_size.y = 0;
+ }
+
+ return min_size + content_min_size;
}
TreeItem *Tree::create_item(TreeItem *p_parent, int p_index) {
@@ -4577,6 +4611,7 @@ void Tree::set_hide_root(bool p_enabled) {
hide_root = p_enabled;
queue_redraw();
+ update_minimum_size();
}
bool Tree::is_root_hidden() const {
@@ -4721,34 +4756,37 @@ int Tree::get_column_minimum_width(int p_column) const {
// Check if the visible title of the column is wider.
if (show_column_titles) {
- min_width = MAX(theme_cache.font->get_string_size(columns[p_column].xl_title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT), min_width);
- }
-
- if (!columns[p_column].clip_content) {
- int depth = 0;
- TreeItem *next;
- for (TreeItem *item = get_root(); item; item = next) {
- next = item->get_next_visible();
- // Compute the depth in tree.
- if (next && p_column == 0) {
- if (next->get_parent() == item) {
- depth += 1;
- } else {
- TreeItem *common_parent = item->get_parent();
- while (common_parent != next->get_parent() && common_parent) {
- common_parent = common_parent->get_parent();
- depth -= 1;
+ const float padding = theme_cache.title_button->get_margin(SIDE_LEFT) + theme_cache.title_button->get_margin(SIDE_RIGHT);
+ min_width = MAX(theme_cache.font->get_string_size(columns[p_column].xl_title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + padding, min_width);
+ }
+
+ if (root && !columns[p_column].clip_content) {
+ int depth = 1;
+
+ TreeItem *last = nullptr;
+ TreeItem *first = hide_root ? root->get_next_visible() : root;
+ for (TreeItem *item = first; item; last = item, item = item->get_next_visible()) {
+ // Get column indentation.
+ int indent;
+ if (p_column == 0) {
+ if (last) {
+ if (item->parent == last) {
+ depth += 1;
+ } else if (item->parent != last->parent) {
+ depth = hide_root ? 0 : 1;
+ for (TreeItem *iter = item->parent; iter; iter = iter->parent) {
+ depth += 1;
+ }
}
}
+ indent = theme_cache.item_margin * depth;
+ } else {
+ indent = theme_cache.h_separation;
}
// Get the item minimum size.
Size2 item_size = item->get_minimum_size(p_column);
- if (p_column == 0) {
- item_size.width += theme_cache.item_margin * depth;
- } else {
- item_size.width += theme_cache.h_separation;
- }
+ item_size.width += indent;
// Check if the item is wider.
min_width = MAX(min_width, item_size.width);
@@ -4962,6 +5000,7 @@ void Tree::set_column_titles_visible(bool p_show) {
show_column_titles = p_show;
queue_redraw();
+ update_minimum_size();
}
bool Tree::are_column_titles_visible() const {
@@ -5260,6 +5299,86 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
return nullptr;
}
+// When on a button, r_index is valid.
+// When on an item, both r_item and r_column are valid.
+// Otherwise, all output arguments are invalid.
+void Tree::_find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const {
+ r_item = nullptr;
+ r_column = -1;
+ r_index = -1;
+
+ if (!root) {
+ return;
+ }
+
+ Point2 pos = p_pos - theme_cache.panel_style->get_offset();
+ pos.y -= _get_title_button_height();
+ if (pos.y < 0) {
+ return;
+ }
+
+ if (cache.rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos += theme_cache.offset; // Scrolling.
+
+ int col, h, section;
+ TreeItem *it = _find_item_at_pos(root, pos, col, h, section);
+ if (!it) {
+ return;
+ }
+
+ r_item = it;
+ r_column = col;
+
+ const TreeItem::Cell &c = it->cells[col];
+ if (c.buttons.is_empty()) {
+ return;
+ }
+
+ int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width + theme_cache.offset.x;
+ if (v_scroll->is_visible_in_tree()) {
+ x_limit -= v_scroll->get_minimum_size().width;
+ }
+
+ for (int i = 0; i < col; i++) {
+ const int col_w = get_column_width(i) + theme_cache.h_separation;
+ pos.x -= col_w;
+ x_limit -= col_w;
+ }
+
+ int x_check;
+ if (cache.rtl) {
+ x_check = get_column_width(col);
+ } else {
+ // Right edge of the buttons area, relative to the start of the column.
+ int buttons_area_min = 0;
+ if (col == 0) {
+ // Content of column 0 should take indentation into account.
+ for (TreeItem *current = it; current && (current != root || !hide_root); current = current->parent) {
+ buttons_area_min += theme_cache.item_margin;
+ }
+ }
+ for (int i = c.buttons.size() - 1; i >= 0; i--) {
+ Ref<Texture2D> b = c.buttons[i].texture;
+ buttons_area_min += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin;
+ }
+
+ x_check = MAX(buttons_area_min, MIN(get_column_width(col), x_limit));
+ }
+
+ for (int i = c.buttons.size() - 1; i >= 0; i--) {
+ Ref<Texture2D> b = c.buttons[i].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ if (pos.x > x_check - size.width) {
+ x_limit -= theme_cache.item_margin;
+ r_index = i;
+ return;
+ }
+ x_check -= size.width + theme_cache.button_margin;
+ }
+}
+
int Tree::get_column_at_position(const Point2 &p_pos) const {
if (root) {
Point2 pos = p_pos;
@@ -5351,84 +5470,37 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const {
}
int Tree::get_button_id_at_position(const Point2 &p_pos) const {
- if (root) {
- Point2 pos = p_pos;
- pos -= theme_cache.panel_style->get_offset();
- pos.y -= _get_title_button_height();
- if (pos.y < 0) {
- return -1;
- }
-
- if (h_scroll->is_visible_in_tree()) {
- pos.x += h_scroll->get_value();
- }
- if (v_scroll->is_visible_in_tree()) {
- pos.y += v_scroll->get_value();
- }
-
- int col, h, section;
- TreeItem *it = _find_item_at_pos(root, pos, col, h, section);
-
- if (it) {
- const TreeItem::Cell &c = it->cells[col];
- int col_width = get_column_width(col);
-
- for (int i = 0; i < col; i++) {
- pos.x -= get_column_width(i);
- }
+ TreeItem *it;
+ int col, index;
+ _find_button_at_pos(p_pos, it, col, index);
- for (int j = c.buttons.size() - 1; j >= 0; j--) {
- Ref<Texture2D> b = c.buttons[j].texture;
- Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
- if (pos.x > col_width - size.width) {
- return c.buttons[j].id;
- }
- col_width -= size.width;
- }
- }
+ if (index == -1) {
+ return -1;
}
-
- return -1;
+ return it->cells[col].buttons[index].id;
}
String Tree::get_tooltip(const Point2 &p_pos) const {
- if (root) {
- Point2 pos = p_pos;
- pos -= theme_cache.panel_style->get_offset();
- pos.y -= _get_title_button_height();
- if (pos.y < 0) {
- return Control::get_tooltip(p_pos);
- }
+ Point2 pos = p_pos - theme_cache.panel_style->get_offset();
+ pos.y -= _get_title_button_height();
+ if (pos.y < 0) {
+ return Control::get_tooltip(p_pos);
+ }
- Point2 button_pos = pos;
- if (h_scroll->is_visible_in_tree()) {
- pos.x += h_scroll->get_value();
- }
- if (v_scroll->is_visible_in_tree()) {
- pos.y += v_scroll->get_value();
- }
+ TreeItem *it;
+ int col, index;
+ _find_button_at_pos(p_pos, it, col, index);
- int col, h, section;
- TreeItem *it = _find_item_at_pos(root, pos, col, h, section);
+ if (index != -1) {
+ return it->cells[col].buttons[index].tooltip;
+ }
- if (it) {
- const TreeItem::Cell &c = it->cells[col];
- for (int j = c.buttons.size() - 1; j >= 0; j--) {
- if (c.buttons[j].rect.has_point(button_pos)) {
- String tooltip = c.buttons[j].tooltip;
- if (!tooltip.is_empty()) {
- return tooltip;
- }
- }
- }
- String ret;
- if (it->get_tooltip_text(col) == "") {
- ret = it->get_text(col);
- } else {
- ret = it->get_tooltip_text(col);
- }
- return ret;
+ if (it) {
+ const String item_tooltip = it->get_tooltip_text(col);
+ if (item_tooltip.is_empty()) {
+ return it->get_text(col);
}
+ return item_tooltip;
}
return Control::get_tooltip(p_pos);
@@ -5747,7 +5819,7 @@ Tree::Tree() {
h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
line_editor->connect("text_submitted", callable_mp(this, &Tree::_line_editor_submit));
- text_editor->connect("gui_input", callable_mp(this, &Tree::_text_editor_gui_input));
+ text_editor->connect(SceneStringName(gui_input), callable_mp(this, &Tree::_text_editor_gui_input));
popup_editor->connect("popup_hide", callable_mp(this, &Tree::_text_editor_popup_modal_close));
popup_menu->connect("id_pressed", callable_mp(this, &Tree::popup_select));
value_editor->connect("value_changed", callable_mp(this, &Tree::value_editor_changed));
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 21696d8216..e9c93c6e03 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -108,7 +108,6 @@ private:
Ref<Texture2D> texture;
Color color = Color(1, 1, 1, 1);
String tooltip;
- Rect2i rect;
};
Vector<Button> buttons;
@@ -480,6 +479,7 @@ private:
VBoxContainer *popup_editor_vb = nullptr;
+ bool popup_edit_commited = true;
Popup *popup_editor = nullptr;
LineEdit *line_editor = nullptr;
TextEdit *text_editor = nullptr;
@@ -646,6 +646,8 @@ private:
TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int &section) const;
+ void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const;
+
/* float drag_speed;
float drag_accum;
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index 687a9e46a0..0b521f926d 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -31,7 +31,6 @@
#include "video_stream_player.h"
#include "core/os/os.h"
-#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
int VideoStreamPlayer::sp_get_channel_count() const {
@@ -163,7 +162,7 @@ void VideoStreamPlayer::_notification(int p_notification) {
play();
return;
}
- emit_signal(SceneStringNames::get_singleton()->finished);
+ emit_signal(SceneStringName(finished));
}
} break;
@@ -460,7 +459,7 @@ StringName VideoStreamPlayer::get_bus() const {
return bus;
}
}
- return SceneStringNames::get_singleton()->Master;
+ return SceneStringName(Master);
}
void VideoStreamPlayer::_validate_property(PropertyInfo &p_property) const {
diff --git a/scene/main/canvas_item.compat.inc b/scene/main/canvas_item.compat.inc
new file mode 100644
index 0000000000..9bc3d01a69
--- /dev/null
+++ b/scene/main/canvas_item.compat.inc
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* canvas_item.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+void CanvasItem::_draw_circle_bind_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color) {
+ draw_circle(p_pos, p_radius, p_color, true, -1.0, false);
+}
+
+void CanvasItem::_draw_rect_bind_compat_84523(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width) {
+ draw_rect(p_rect, p_color, p_filled, p_width, false);
+}
+
+void CanvasItem::_draw_dashed_line_bind_compat_84523(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned) {
+ draw_dashed_line(p_from, p_to, p_color, p_width, p_dash, p_aligned, false);
+}
+
+void CanvasItem::_draw_multiline_bind_compat_84523(const Vector<Point2> &p_points, const Color &p_color, real_t p_width) {
+ draw_multiline(p_points, p_color, p_width, false);
+}
+
+void CanvasItem::_draw_multiline_colors_bind_compat_84523(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width) {
+ draw_multiline_colors(p_points, p_colors, p_width, false);
+}
+
+void CanvasItem::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::_draw_circle_bind_compat_84472);
+ ClassDB::bind_compatibility_method(D_METHOD("draw_rect", "rect", "color", "filled", "width"), &CanvasItem::_draw_rect_bind_compat_84523, DEFVAL(true), DEFVAL(-1.0));
+ ClassDB::bind_compatibility_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash", "aligned"), &CanvasItem::_draw_dashed_line_bind_compat_84523, DEFVAL(-1.0), DEFVAL(2.0), DEFVAL(true));
+ ClassDB::bind_compatibility_method(D_METHOD("draw_multiline", "points", "color", "width"), &CanvasItem::_draw_multiline_bind_compat_84523, DEFVAL(-1.0));
+ ClassDB::bind_compatibility_method(D_METHOD("draw_multiline_colors", "points", "colors", "width"), &CanvasItem::_draw_multiline_colors_bind_compat_84523, DEFVAL(-1.0));
+}
+
+#endif
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 56aa453407..7ed1f130c8 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "canvas_item.h"
+#include "canvas_item.compat.inc"
#include "scene/2d/canvas_group.h"
#include "scene/main/canvas_layer.h"
@@ -38,7 +39,6 @@
#include "scene/resources/multimesh.h"
#include "scene/resources/style_box.h"
#include "scene/resources/world_2d.h"
-#include "scene/scene_string_names.h"
#define ERR_DRAW_GUARD \
ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside this node's `_draw()`, functions connected to its `draw` signal, or when it receives NOTIFICATION_DRAW.")
@@ -94,7 +94,7 @@ void CanvasItem::_handle_visibility_change(bool p_visible) {
if (p_visible) {
queue_redraw();
} else {
- emit_signal(SceneStringNames::get_singleton()->hidden);
+ emit_signal(SceneStringName(hidden));
}
_block();
@@ -140,7 +140,7 @@ void CanvasItem::_redraw_callback() {
drawing = true;
current_item_drawn = this;
notification(NOTIFICATION_DRAW);
- emit_signal(SceneStringNames::get_singleton()->draw);
+ emit_signal(SceneStringName(draw));
GDVIRTUAL_CALL(_draw);
current_item_drawn = nullptr;
drawing = false;
@@ -308,7 +308,7 @@ void CanvasItem::_notification(int p_what) {
window = Object::cast_to<Window>(viewport);
if (window) {
- window->connect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed));
+ window->connect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItem::_window_visibility_changed));
parent_visible_in_tree = window->is_visible();
} else {
parent_visible_in_tree = true;
@@ -362,7 +362,7 @@ void CanvasItem::_notification(int p_what) {
C = nullptr;
}
if (window) {
- window->disconnect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &CanvasItem::_window_visibility_changed));
+ window->disconnect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItem::_window_visibility_changed));
window = nullptr;
}
_set_global_invalid(true);
@@ -382,7 +382,7 @@ void CanvasItem::_notification(int p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
ERR_MAIN_THREAD_GUARD;
- emit_signal(SceneStringNames::get_singleton()->visibility_changed);
+ emit_signal(SceneStringName(visibility_changed));
} break;
case NOTIFICATION_WORLD_2D_CHANGED: {
ERR_MAIN_THREAD_GUARD;
@@ -554,7 +554,7 @@ void CanvasItem::item_rect_changed(bool p_size_changed) {
if (p_size_changed) {
queue_redraw();
}
- emit_signal(SceneStringNames::get_singleton()->item_rect_changed);
+ emit_signal(SceneStringName(item_rect_changed));
}
void CanvasItem::set_z_index(int p_z) {
@@ -608,7 +608,7 @@ bool CanvasItem::is_y_sort_enabled() const {
return y_sort_enabled;
}
-void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned) {
+void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned, bool p_antialiased) {
ERR_THREAD_GUARD;
ERR_DRAW_GUARD;
ERR_FAIL_COND(p_dash <= 0.0);
@@ -617,7 +617,7 @@ void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, cons
Vector2 step = p_dash * (p_to - p_from).normalized();
if (length < p_dash || step == Vector2()) {
- RenderingServer::get_singleton()->canvas_item_add_line(canvas_item, p_from, p_to, p_color, p_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(canvas_item, p_from, p_to, p_color, p_width, p_antialiased);
return;
}
@@ -641,7 +641,7 @@ void CanvasItem::draw_dashed_line(const Point2 &p_from, const Point2 &p_to, cons
Vector<Color> colors = { p_color };
- RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, points, colors, p_width);
+ RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, points, colors, p_width, p_antialiased);
}
void CanvasItem::draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, bool p_antialiased) {
@@ -682,22 +682,22 @@ void CanvasItem::draw_arc(const Vector2 &p_center, real_t p_radius, real_t p_sta
draw_polyline(points, p_color, p_width, p_antialiased);
}
-void CanvasItem::draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width) {
+void CanvasItem::draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width, bool p_antialiased) {
ERR_THREAD_GUARD;
ERR_DRAW_GUARD;
Vector<Color> colors = { p_color };
- RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, colors, p_width);
+ RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, colors, p_width, p_antialiased);
}
-void CanvasItem::draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width) {
+void CanvasItem::draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width, bool p_antialiased) {
ERR_THREAD_GUARD;
ERR_DRAW_GUARD;
- RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, p_colors, p_width);
+ RenderingServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, p_colors, p_width, p_antialiased);
}
-void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width) {
+void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width, bool p_antialiased) {
ERR_THREAD_GUARD;
ERR_DRAW_GUARD;
@@ -708,9 +708,9 @@ void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_fil
WARN_PRINT("The draw_rect() \"width\" argument has no effect when \"filled\" is \"true\".");
}
- RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect, p_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect, p_color, p_antialiased);
} else if (p_width >= rect.size.width || p_width >= rect.size.height) {
- RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect.grow(0.5f * p_width), p_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(canvas_item, rect.grow(0.5f * p_width), p_color, p_antialiased);
} else {
Vector<Vector2> points;
points.resize(5);
@@ -722,15 +722,44 @@ void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_fil
Vector<Color> colors = { p_color };
- RenderingServer::get_singleton()->canvas_item_add_polyline(canvas_item, points, colors, p_width);
+ RenderingServer::get_singleton()->canvas_item_add_polyline(canvas_item, points, colors, p_width, p_antialiased);
}
}
-void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color) {
+void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color, bool p_filled, real_t p_width, bool p_antialiased) {
ERR_THREAD_GUARD;
ERR_DRAW_GUARD;
- RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color);
+ if (p_filled) {
+ if (p_width != -1.0) {
+ WARN_PRINT("The draw_circle() \"width\" argument has no effect when \"filled\" is \"true\".");
+ }
+
+ RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color, p_antialiased);
+ } else if (p_width >= 2.0 * p_radius) {
+ RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius + 0.5 * p_width, p_color, p_antialiased);
+ } else {
+ // Tessellation count is hardcoded. Keep in sync with the same variable in `RendererCanvasCull::canvas_item_add_circle()`.
+ const int circle_segments = 64;
+
+ Vector<Vector2> points;
+ points.resize(circle_segments + 1);
+
+ Vector2 *points_ptr = points.ptrw();
+ const real_t circle_point_step = Math_TAU / circle_segments;
+
+ for (int i = 0; i < circle_segments; i++) {
+ float angle = i * circle_point_step;
+ points_ptr[i].x = Math::cos(angle) * p_radius;
+ points_ptr[i].y = Math::sin(angle) * p_radius;
+ points_ptr[i] += p_pos;
+ }
+ points_ptr[circle_segments] = points_ptr[0];
+
+ Vector<Color> colors = { p_color };
+
+ RenderingServer::get_singleton()->canvas_item_add_polyline(canvas_item, points, colors, p_width, p_antialiased);
+ }
}
void CanvasItem::draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate) {
@@ -1156,14 +1185,14 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_draw_behind_parent_enabled"), &CanvasItem::is_draw_behind_parent_enabled);
ClassDB::bind_method(D_METHOD("draw_line", "from", "to", "color", "width", "antialiased"), &CanvasItem::draw_line, DEFVAL(-1.0), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash", "aligned"), &CanvasItem::draw_dashed_line, DEFVAL(-1.0), DEFVAL(2.0), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("draw_dashed_line", "from", "to", "color", "width", "dash", "aligned", "antialiased"), &CanvasItem::draw_dashed_line, DEFVAL(-1.0), DEFVAL(2.0), DEFVAL(true), DEFVAL(false));
ClassDB::bind_method(D_METHOD("draw_polyline", "points", "color", "width", "antialiased"), &CanvasItem::draw_polyline, DEFVAL(-1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("draw_polyline_colors", "points", "colors", "width", "antialiased"), &CanvasItem::draw_polyline_colors, DEFVAL(-1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("draw_arc", "center", "radius", "start_angle", "end_angle", "point_count", "color", "width", "antialiased"), &CanvasItem::draw_arc, DEFVAL(-1.0), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("draw_multiline", "points", "color", "width"), &CanvasItem::draw_multiline, DEFVAL(-1.0));
- ClassDB::bind_method(D_METHOD("draw_multiline_colors", "points", "colors", "width"), &CanvasItem::draw_multiline_colors, DEFVAL(-1.0));
- ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled", "width"), &CanvasItem::draw_rect, DEFVAL(true), DEFVAL(-1.0));
- ClassDB::bind_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::draw_circle);
+ ClassDB::bind_method(D_METHOD("draw_multiline", "points", "color", "width", "antialiased"), &CanvasItem::draw_multiline, DEFVAL(-1.0), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("draw_multiline_colors", "points", "colors", "width", "antialiased"), &CanvasItem::draw_multiline_colors, DEFVAL(-1.0), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled", "width", "antialiased"), &CanvasItem::draw_rect, DEFVAL(true), DEFVAL(-1.0), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("draw_circle", "position", "radius", "color", "filled", "width", "antialiased"), &CanvasItem::draw_circle, DEFVAL(true), DEFVAL(-1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("draw_texture", "texture", "position", "modulate"), &CanvasItem::draw_texture, DEFVAL(Color(1, 1, 1, 1)));
ClassDB::bind_method(D_METHOD("draw_texture_rect", "texture", "rect", "tile", "modulate", "transpose"), &CanvasItem::draw_texture_rect, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false));
ClassDB::bind_method(D_METHOD("draw_texture_rect_region", "texture", "rect", "src_rect", "modulate", "transpose", "clip_uv"), &CanvasItem::draw_texture_rect_region, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(false), DEFVAL(true));
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 8cec086ca6..028c2cb2cf 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -166,6 +166,16 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+
+#ifndef DISABLE_DEPRECATED
+ void _draw_circle_bind_compat_84472(const Point2 &p_pos, real_t p_radius, const Color &p_color);
+ void _draw_rect_bind_compat_84523(const Rect2 &p_rect, const Color &p_color, bool p_filled, real_t p_width);
+ void _draw_dashed_line_bind_compat_84523(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width, real_t p_dash, bool p_aligned);
+ void _draw_multiline_bind_compat_84523(const Vector<Point2> &p_points, const Color &p_color, real_t p_width);
+ void _draw_multiline_colors_bind_compat_84523(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width);
+ static void _bind_compatibility_methods();
+#endif
+
void _validate_property(PropertyInfo &p_property) const;
_FORCE_INLINE_ void set_hide_clip_children(bool p_value) { hide_clip_children = p_value; }
@@ -265,15 +275,15 @@ public:
/* DRAWING API */
- void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, real_t p_dash = 2.0, bool p_aligned = true);
+ void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, real_t p_dash = 2.0, bool p_aligned = true, bool p_antialiased = false);
void draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_polyline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
void draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false);
void draw_arc(const Vector2 &p_center, real_t p_radius, real_t p_start_angle, real_t p_end_angle, int p_point_count, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
- void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0);
- void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0);
- void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0);
- void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color);
+ void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
+ void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false);
+ void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0, bool p_antialiased = false);
+ void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color, bool p_filled = true, real_t p_width = -1.0, bool p_antialiased = false);
void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1));
void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false);
void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false);
diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp
index c9cca76dea..11d074f014 100644
--- a/scene/main/canvas_layer.cpp
+++ b/scene/main/canvas_layer.cpp
@@ -52,7 +52,7 @@ void CanvasLayer::set_visible(bool p_visible) {
}
visible = p_visible;
- emit_signal(SNAME("visibility_changed"));
+ emit_signal(SceneStringName(visibility_changed));
for (int i = 0; i < get_child_count(); i++) {
CanvasItem *c = Object::cast_to<CanvasItem>(get_child(i));
diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp
index 0f89784369..3469b806a6 100644
--- a/scene/main/http_request.cpp
+++ b/scene/main/http_request.cpp
@@ -239,7 +239,7 @@ bool HTTPRequest::_handle_response(bool *ret_value) {
String new_request;
for (const String &E : rheaders) {
- if (E.findn("Location: ") != -1) {
+ if (E.containsn("Location: ")) {
new_request = E.substr(9, E.length()).strip_edges();
}
}
diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp
index fe23ca1800..f36bbe9395 100644
--- a/scene/main/instance_placeholder.cpp
+++ b/scene/main/instance_placeholder.cpp
@@ -88,16 +88,16 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene
if (!ps.is_valid()) {
return nullptr;
}
- Node *scene = ps->instantiate();
- if (!scene) {
+ Node *instance = ps->instantiate();
+ if (!instance) {
return nullptr;
}
- scene->set_name(get_name());
- scene->set_multiplayer_authority(get_multiplayer_authority());
+ instance->set_name(get_name());
+ instance->set_multiplayer_authority(get_multiplayer_authority());
int pos = get_index();
for (const PropSet &E : stored_values) {
- scene->set(E.name, E.value);
+ set_value_on_instance(this, instance, E);
}
if (p_replace) {
@@ -105,10 +105,125 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene
base->remove_child(this);
}
- base->add_child(scene);
- base->move_child(scene, pos);
+ base->add_child(instance);
+ base->move_child(instance, pos);
- return scene;
+ return instance;
+}
+
+// This method will attempt to set the correct values on the placeholder instance
+// for regular types this is trivial and unnecessary.
+// For nodes however this becomes a bit tricky because they might now have existed until the instantiation,
+// so this method will try to find the correct nodes and resolve them.
+void InstancePlaceholder::set_value_on_instance(InstancePlaceholder *p_placeholder, Node *p_instance, const PropSet &p_set) {
+ bool is_valid;
+
+ // If we don't have any info, we can't do anything,
+ // so try setting the value directly.
+ Variant current = p_instance->get(p_set.name, &is_valid);
+ if (!is_valid) {
+ p_instance->set(p_set.name, p_set.value, &is_valid);
+ return;
+ }
+
+ Variant::Type current_type = current.get_type();
+ Variant::Type placeholder_type = p_set.value.get_type();
+
+ // Arrays are a special case, because their containing type might be different.
+ if (current_type != Variant::Type::ARRAY) {
+ // Check if the variant types match.
+ if (Variant::evaluate(Variant::OP_EQUAL, current_type, placeholder_type)) {
+ p_instance->set(p_set.name, p_set.value, &is_valid);
+ if (is_valid) {
+ return;
+ }
+ // Types match but setting failed? This is strange, so let's print a warning!
+ WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name()));
+ return;
+ }
+ } else {
+ // We are dealing with an Array.
+ // Let's check if the subtype of the array matches first.
+ // This is needed because the set method of ScriptInstance checks for type,
+ // but the ClassDB set method doesn't! So we cannot reliably know what actually happens.
+ Array current_array = current;
+ Array placeholder_array = p_set.value;
+ if (current_array.is_same_typed(placeholder_array)) {
+ p_instance->set(p_set.name, p_set.value, &is_valid);
+ if (is_valid) {
+ return;
+ }
+ // Internal array types match but setting failed? This is strange, so let's print a warning!
+ WARN_PRINT(vformat("Array Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(Variant::Type(current_array.get_typed_builtin())), p_placeholder->get_name()));
+ }
+ // Arrays are not the same internal type. This should be happening because we have a NodePath Array,
+ // but the instance wants a Node Array.
+ }
+
+ switch (current_type) {
+ case Variant::Type::NIL:
+ if (placeholder_type != Variant::Type::NODE_PATH) {
+ break;
+ }
+ // If it's nil but we have a NodePath, we guess what works.
+
+ p_instance->set(p_set.name, p_set.value, &is_valid);
+ if (is_valid) {
+ break;
+ }
+
+ p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value), &is_valid);
+ break;
+ case Variant::Type::OBJECT:
+ if (placeholder_type != Variant::Type::NODE_PATH) {
+ break;
+ }
+ // Easiest case, we want a node, but we have a deferred NodePath.
+ p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value));
+ break;
+ case Variant::Type::ARRAY: {
+ // If we have reached here it means our array types don't match,
+ // so we will convert the placeholder array into the correct type
+ // and resolve nodes if necessary.
+ Array current_array = current;
+ Array converted_array;
+ Array placeholder_array = p_set.value;
+ converted_array = current_array.duplicate();
+ converted_array.resize(placeholder_array.size());
+
+ if (Variant::evaluate(Variant::OP_EQUAL, current_array.get_typed_builtin(), Variant::Type::NODE_PATH)) {
+ // We want a typed NodePath array.
+ for (int i = 0; i < placeholder_array.size(); i++) {
+ converted_array.set(i, placeholder_array[i]);
+ }
+ } else {
+ // We want Nodes, convert NodePaths.
+ for (int i = 0; i < placeholder_array.size(); i++) {
+ converted_array.set(i, try_get_node(p_placeholder, p_instance, placeholder_array[i]));
+ }
+ }
+
+ p_instance->set(p_set.name, converted_array, &is_valid);
+ if (!is_valid) {
+ WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name()));
+ }
+ break;
+ }
+ default:
+ WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name()));
+ break;
+ }
+}
+
+Node *InstancePlaceholder::try_get_node(InstancePlaceholder *p_placeholder, Node *p_instance, const NodePath &p_path) {
+ // First try to resolve internally,
+ // if that fails try resolving externally.
+ Node *node = p_instance->get_node_or_null(p_path);
+ if (node == nullptr) {
+ node = p_placeholder->get_node_or_null(p_path);
+ }
+
+ return node;
}
Dictionary InstancePlaceholder::get_stored_values(bool p_with_order) {
diff --git a/scene/main/instance_placeholder.h b/scene/main/instance_placeholder.h
index 480474d0bd..ccf1e63a16 100644
--- a/scene/main/instance_placeholder.h
+++ b/scene/main/instance_placeholder.h
@@ -46,6 +46,10 @@ class InstancePlaceholder : public Node {
List<PropSet> stored_values;
+private:
+ void set_value_on_instance(InstancePlaceholder *p_placeholder, Node *p_instance, const PropSet &p_set);
+ Node *try_get_node(InstancePlaceholder *p_placeholder, Node *p_instance, const NodePath &p_path);
+
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 5c5049759f..884fc6de07 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -31,7 +31,6 @@
#include "node.h"
#include "core/config/project_settings.h"
-#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
#include "core/object/message_queue.h"
#include "core/object/script_language.h"
@@ -42,7 +41,6 @@
#include "scene/main/multiplayer_api.h"
#include "scene/main/window.h"
#include "scene/resources/packed_scene.h"
-#include "scene/scene_string_names.h"
#include "viewport.h"
#include <stdint.h>
@@ -266,7 +264,7 @@ void Node::_propagate_ready() {
if (data.ready_first) {
data.ready_first = false;
notification(NOTIFICATION_READY);
- emit_signal(SceneStringNames::get_singleton()->ready);
+ emit_signal(SceneStringName(ready));
}
}
@@ -295,7 +293,7 @@ void Node::_propagate_enter_tree() {
GDVIRTUAL_CALL(_enter_tree);
- emit_signal(SceneStringNames::get_singleton()->tree_entered);
+ emit_signal(SceneStringName(tree_entered));
data.tree->node_added(this);
@@ -350,7 +348,7 @@ void Node::_propagate_after_exit_tree() {
data.blocked--;
- emit_signal(SceneStringNames::get_singleton()->tree_exited);
+ emit_signal(SceneStringName(tree_exited));
}
void Node::_propagate_exit_tree() {
@@ -372,7 +370,7 @@ void Node::_propagate_exit_tree() {
GDVIRTUAL_CALL(_exit_tree);
- emit_signal(SceneStringNames::get_singleton()->tree_exiting);
+ emit_signal(SceneStringName(tree_exiting));
notification(NOTIFICATION_EXIT_TREE, true);
if (data.tree) {
@@ -1738,12 +1736,10 @@ Node *Node::get_node_or_null(const NodePath &p_path) const {
StringName name = p_path.get_name(i);
Node *next = nullptr;
- if (name == SceneStringNames::get_singleton()->dot) { // .
-
+ if (name == SNAME(".")) {
next = current;
- } else if (name == SceneStringNames::get_singleton()->doubledot) { // ..
-
+ } else if (name == SNAME("..")) {
if (current == nullptr || !current->data.parent) {
return nullptr;
}
@@ -2667,8 +2663,6 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c
node->data.editable_instance = data.editable_instance;
}
- StringName script_property_name = CoreStringNames::get_singleton()->_script;
-
List<const Node *> hidden_roots;
List<const Node *> node_tree;
node_tree.push_front(this);
@@ -2770,45 +2764,6 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c
parent->move_child(dup, pos);
}
}
-
- for (List<const Node *>::Element *N = node_tree.front(); N; N = N->next()) {
- Node *current_node = node->get_node(get_path_to(N->get()));
- ERR_CONTINUE(!current_node);
-
- if (p_flags & DUPLICATE_SCRIPTS) {
- bool is_valid = false;
- Variant scr = N->get()->get(script_property_name, &is_valid);
- if (is_valid) {
- current_node->set(script_property_name, scr);
- }
- }
-
- List<PropertyInfo> plist;
- N->get()->get_property_list(&plist);
-
- for (const PropertyInfo &E : plist) {
- if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
- continue;
- }
- String name = E.name;
- if (name == script_property_name) {
- continue;
- }
-
- Variant value = N->get()->get(name).duplicate(true);
-
- if (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE) {
- Resource *res = Object::cast_to<Resource>(value);
- if (res) { // Duplicate only if it's a resource
- current_node->set(name, res->duplicate());
- }
-
- } else {
- current_node->set(name, value);
- }
- }
- }
-
return node;
}
@@ -2820,7 +2775,7 @@ Node *Node::duplicate(int p_flags) const {
_duplicate_signals(this, dupe);
}
- _duplicate_properties_node(this, this, dupe);
+ _duplicate_properties(this, this, dupe, p_flags);
return dupe;
}
@@ -2831,7 +2786,8 @@ Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) con
}
Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const {
- Node *dupe = _duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANTIATION | DUPLICATE_FROM_EDITOR, &r_duplimap);
+ int flags = DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANTIATION | DUPLICATE_FROM_EDITOR;
+ Node *dupe = _duplicate(flags, &r_duplimap);
// This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated.
if (!p_resource_remap.is_empty()) {
@@ -2843,7 +2799,7 @@ Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, con
// if the emitter node comes later in tree order than the receiver
_duplicate_signals(this, dupe);
- _duplicate_properties_node(this, this, dupe);
+ _duplicate_properties(this, this, dupe, flags);
return dupe;
}
@@ -2897,41 +2853,67 @@ void Node::remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Re
}
#endif
-// Duplicates properties that store a Node.
-// This has to be called after nodes have been duplicated since
-// only then do we get a full picture of how the duplicated node tree looks like.
-void Node::_duplicate_properties_node(const Node *p_root, const Node *p_original, Node *p_copy) const {
+// Duplicate node's properties.
+// This has to be called after nodes have been duplicated since there might be properties
+// of type Node that can be updated properly only if duplicated node tree is complete.
+void Node::_duplicate_properties(const Node *p_root, const Node *p_original, Node *p_copy, int p_flags) const {
List<PropertyInfo> props;
- p_copy->get_property_list(&props);
+ p_original->get_property_list(&props);
+ const StringName &script_property_name = CoreStringName(script);
+ if (p_flags & DUPLICATE_SCRIPTS) {
+ bool is_valid = false;
+ Variant scr = p_original->get(script_property_name, &is_valid);
+ if (is_valid) {
+ p_copy->set(script_property_name, scr);
+ }
+ }
for (const PropertyInfo &E : props) {
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
- String name = E.name;
+ const StringName name = E.name;
+
+ if (name == script_property_name) {
+ continue;
+ }
+
Variant value = p_original->get(name).duplicate(true);
- if (value.get_type() == Variant::OBJECT) {
- Node *property_node = Object::cast_to<Node>(value);
- if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) {
- value = p_copy->get_node_or_null(p_original->get_path_to(property_node));
- p_copy->set(name, value);
+
+ if (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE) {
+ Resource *res = Object::cast_to<Resource>(value);
+ if (res) { // Duplicate only if it's a resource
+ p_copy->set(name, res->duplicate());
}
- } else if (value.get_type() == Variant::ARRAY) {
- Array arr = value;
- if (arr.get_typed_builtin() == Variant::OBJECT) {
- for (int i = 0; i < arr.size(); i++) {
- Node *property_node = Object::cast_to<Node>(arr[i]);
- if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) {
- arr[i] = p_copy->get_node_or_null(p_original->get_path_to(property_node));
+ } else {
+ if (value.get_type() == Variant::OBJECT) {
+ Node *property_node = Object::cast_to<Node>(value);
+ Variant out_value = value;
+ if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) {
+ out_value = p_copy->get_node_or_null(p_original->get_path_to(property_node));
+ }
+ p_copy->set(name, out_value);
+ } else if (value.get_type() == Variant::ARRAY) {
+ Array arr = value;
+ if (arr.get_typed_builtin() == Variant::OBJECT) {
+ for (int i = 0; i < arr.size(); i++) {
+ Node *property_node = Object::cast_to<Node>(arr[i]);
+ if (property_node && (p_root == property_node || p_root->is_ancestor_of(property_node))) {
+ arr[i] = p_copy->get_node_or_null(p_original->get_path_to(property_node));
+ }
}
+ value = arr;
+ p_copy->set(name, value);
}
- value = arr;
+ } else {
p_copy->set(name, value);
}
}
}
- for (int i = 0; i < p_copy->get_child_count(); i++) {
- _duplicate_properties_node(p_root, p_original->get_child(i), p_copy->get_child(i));
+ for (int i = 0; i < p_original->get_child_count(); i++) {
+ Node *copy_child = p_copy->get_child(i);
+ ERR_FAIL_NULL_MSG(copy_child, "Child node disappeared while duplicating.");
+ _duplicate_properties(p_root, p_original->get_child(i), copy_child, p_flags);
}
}
@@ -3231,7 +3213,7 @@ void Node::print_orphan_nodes() {
ObjectDB::debug_objects(_print_orphan_nodes_routine);
for (const KeyValue<ObjectID, List<String>> &E : _print_orphan_nodes_map) {
- print_line(itos(E.key) + " - Stray Node: " + E.value[0] + " (Type: " + E.value[1] + ")");
+ print_line(itos(E.key) + " - Stray Node: " + E.value.get(0) + " (Type: " + E.value.get(1) + ")");
}
// Flush it after use.
@@ -3321,7 +3303,7 @@ void Node::update_configuration_warnings() {
return;
}
if (get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root() == this || get_tree()->get_edited_scene_root()->is_ancestor_of(this))) {
- get_tree()->emit_signal(SceneStringNames::get_singleton()->node_configuration_warning_changed, this);
+ get_tree()->emit_signal(SceneStringName(node_configuration_warning_changed), this);
}
#endif
}
@@ -3520,6 +3502,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource);
ClassDB::bind_method(D_METHOD("is_inside_tree"), &Node::is_inside_tree);
+ ClassDB::bind_method(D_METHOD("is_part_of_edited_scene"), &Node::is_part_of_edited_scene);
ClassDB::bind_method(D_METHOD("is_ancestor_of", "node"), &Node::is_ancestor_of);
ClassDB::bind_method(D_METHOD("is_greater_than", "node"), &Node::is_greater_than);
ClassDB::bind_method(D_METHOD("get_path"), &Node::get_path);
diff --git a/scene/main/node.h b/scene/main/node.h
index f49eeec9cd..6b93724478 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -35,6 +35,7 @@
#include "core/templates/rb_map.h"
#include "core/variant/typed_array.h"
#include "scene/main/scene_tree.h"
+#include "scene/scene_string_names.h"
class Viewport;
class Window;
@@ -266,7 +267,7 @@ private:
void _propagate_groups_dirty();
Array _get_node_and_resource(const NodePath &p_path);
- void _duplicate_properties_node(const Node *p_root, const Node *p_original, Node *p_copy) const;
+ void _duplicate_properties(const Node *p_root, const Node *p_original, Node *p_copy, int p_flags) const;
void _duplicate_signals(const Node *p_original, Node *p_copy) const;
Node *_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap = nullptr) const;
@@ -525,6 +526,8 @@ public:
bool is_property_pinned(const StringName &p_property) const;
virtual StringName get_property_store_alias(const StringName &p_property) const;
bool is_part_of_edited_scene() const;
+#else
+ bool is_part_of_edited_scene() const { return false; }
#endif
void get_storable_properties(HashSet<StringName> &r_storable_properties) const;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 04dd38fb5d..ced6d9aaa6 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -55,7 +55,6 @@
#include "scene/resources/mesh.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/world_2d.h"
-#include "scene/scene_string_names.h"
#include "servers/display_server.h"
#include "servers/navigation_server_3d.h"
#include "servers/physics_server_2d.h"
@@ -729,13 +728,6 @@ void SceneTree::set_quit_on_go_back(bool p_enable) {
quit_on_go_back = p_enable;
}
-#ifdef TOOLS_ENABLED
-
-bool SceneTree::is_node_being_edited(const Node *p_node) const {
- return Engine::get_singleton()->is_editor_hint() && edited_scene_root && (edited_scene_root->is_ancestor_of(p_node) || edited_scene_root == p_node);
-}
-#endif
-
#ifdef DEBUG_ENABLED
void SceneTree::set_debug_collisions_hint(bool p_enabled) {
debug_collisions_hint = p_enabled;
@@ -1752,7 +1744,7 @@ SceneTree::SceneTree() {
root = memnew(Window);
root->set_min_size(Size2i(64, 64)); // Define a very small minimum window size to prevent bugs such as GH-37242.
root->set_process_mode(Node::PROCESS_MODE_PAUSABLE);
- root->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_ALWAYS);
+ root->set_auto_translate_mode(GLOBAL_GET("internationalization/rendering/root_node_auto_translate") ? Node::AUTO_TRANSLATE_MODE_ALWAYS : Node::AUTO_TRANSLATE_MODE_DISABLED);
root->set_name("root");
root->set_title(GLOBAL_GET("application/config/name"));
@@ -1883,7 +1875,7 @@ SceneTree::SceneTree() {
root->connect("close_requested", callable_mp(this, &SceneTree::_main_window_close));
root->connect("go_back_requested", callable_mp(this, &SceneTree::_main_window_go_back));
- root->connect("focus_entered", callable_mp(this, &SceneTree::_main_window_focus_in));
+ root->connect(SceneStringName(focus_entered), callable_mp(this, &SceneTree::_main_window_focus_in));
#ifdef TOOLS_ENABLED
edited_scene_root = nullptr;
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index c9f3a4de1f..6f0a61ec51 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -330,12 +330,6 @@ public:
_FORCE_INLINE_ double get_physics_process_time() const { return physics_process_time; }
_FORCE_INLINE_ double get_process_time() const { return process_time; }
-#ifdef TOOLS_ENABLED
- bool is_node_being_edited(const Node *p_node) const;
-#else
- bool is_node_being_edited(const Node *p_node) const { return false; }
-#endif
-
void set_pause(bool p_enabled);
bool is_paused() const;
diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp
index 091e6249bf..41e0aa739e 100644
--- a/scene/main/shader_globals_override.cpp
+++ b/scene/main/shader_globals_override.cpp
@@ -31,7 +31,6 @@
#include "shader_globals_override.h"
#include "scene/main/node.h"
-#include "scene/scene_string_names.h"
StringName *ShaderGlobalsOverride::_remap(const StringName &p_name) const {
StringName *r = param_remaps.getptr(p_name);
@@ -223,11 +222,11 @@ void ShaderGlobalsOverride::_get_property_list(List<PropertyInfo> *p_list) const
void ShaderGlobalsOverride::_activate() {
ERR_FAIL_NULL(get_tree());
List<Node *> nodes;
- get_tree()->get_nodes_in_group(SceneStringNames::get_singleton()->shader_overrides_group_active, &nodes);
+ get_tree()->get_nodes_in_group(SceneStringName(shader_overrides_group_active), &nodes);
if (nodes.size() == 0) {
//good we are the only override, enable all
active = true;
- add_to_group(SceneStringNames::get_singleton()->shader_overrides_group_active);
+ add_to_group(SceneStringName(shader_overrides_group_active));
for (const KeyValue<StringName, Override> &E : overrides) {
const Override *o = &E.value;
@@ -248,7 +247,7 @@ void ShaderGlobalsOverride::_activate() {
void ShaderGlobalsOverride::_notification(int p_what) {
switch (p_what) {
case Node::NOTIFICATION_ENTER_TREE: {
- add_to_group(SceneStringNames::get_singleton()->shader_overrides_group);
+ add_to_group(SceneStringName(shader_overrides_group));
_activate();
} break;
@@ -263,9 +262,9 @@ void ShaderGlobalsOverride::_notification(int p_what) {
}
}
- remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group_active);
- remove_from_group(SceneStringNames::get_singleton()->shader_overrides_group);
- get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->shader_overrides_group, "_activate"); //another may want to activate when this is removed
+ remove_from_group(SceneStringName(shader_overrides_group_active));
+ remove_from_group(SceneStringName(shader_overrides_group));
+ get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringName(shader_overrides_group), "_activate"); //another may want to activate when this is removed
active = false;
} break;
}
diff --git a/scene/main/status_indicator.cpp b/scene/main/status_indicator.cpp
index 54b2ff75ca..f0ba5db1a0 100644
--- a/scene/main/status_indicator.cpp
+++ b/scene/main/status_indicator.cpp
@@ -30,6 +30,8 @@
#include "status_indicator.h"
+#include "scene/gui/popup_menu.h"
+
void StatusIndicator::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
#ifdef TOOLS_ENABLED
@@ -43,12 +45,22 @@ void StatusIndicator::_notification(int p_what) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
+ if (pm) {
+ RID menu_rid = pm->bind_global_menu();
+ DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid);
+ }
}
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
+ if (pm) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID());
+ }
DisplayServer::get_singleton()->delete_status_indicator(iid);
iid = DisplayServer::INVALID_INDICATOR_ID;
}
@@ -66,19 +78,23 @@ void StatusIndicator::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_icon"), &StatusIndicator::get_icon);
ClassDB::bind_method(D_METHOD("set_visible", "visible"), &StatusIndicator::set_visible);
ClassDB::bind_method(D_METHOD("is_visible"), &StatusIndicator::is_visible);
+ ClassDB::bind_method(D_METHOD("set_menu", "menu"), &StatusIndicator::set_menu);
+ ClassDB::bind_method(D_METHOD("get_menu"), &StatusIndicator::get_menu);
+ ClassDB::bind_method(D_METHOD("get_rect"), &StatusIndicator::get_rect);
ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "mouse_position")));
ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "get_tooltip");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_icon", "get_icon");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_icon", "get_icon");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "menu", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PopupMenu"), "set_menu", "get_menu");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
}
void StatusIndicator::_callback(MouseButton p_index, const Point2i &p_pos) {
- emit_signal(SNAME("pressed"), p_index, p_pos);
+ emit_signal(SceneStringName(pressed), p_index, p_pos);
}
-void StatusIndicator::set_icon(const Ref<Image> &p_icon) {
+void StatusIndicator::set_icon(const Ref<Texture2D> &p_icon) {
ERR_MAIN_THREAD_GUARD;
icon = p_icon;
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
@@ -86,7 +102,7 @@ void StatusIndicator::set_icon(const Ref<Image> &p_icon) {
}
}
-Ref<Image> StatusIndicator::get_icon() const {
+Ref<Texture2D> StatusIndicator::get_icon() const {
return icon;
}
@@ -102,6 +118,30 @@ String StatusIndicator::get_tooltip() const {
return tooltip;
}
+void StatusIndicator::set_menu(const NodePath &p_menu) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
+ if (pm) {
+ pm->unbind_global_menu();
+ if (iid != DisplayServer::INVALID_INDICATOR_ID) {
+ DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID());
+ }
+ }
+
+ menu = p_menu;
+
+ pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
+ if (pm) {
+ if (iid != DisplayServer::INVALID_INDICATOR_ID) {
+ RID menu_rid = pm->bind_global_menu();
+ DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid);
+ }
+ }
+}
+
+NodePath StatusIndicator::get_menu() const {
+ return menu;
+}
+
void StatusIndicator::set_visible(bool p_visible) {
ERR_MAIN_THREAD_GUARD;
if (visible == p_visible) {
@@ -122,8 +162,18 @@ void StatusIndicator::set_visible(bool p_visible) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
+ if (pm) {
+ RID menu_rid = pm->bind_global_menu();
+ DisplayServer::get_singleton()->status_indicator_set_menu(iid, menu_rid);
+ }
}
if (!visible && iid != DisplayServer::INVALID_INDICATOR_ID) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(menu));
+ if (pm) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->status_indicator_set_menu(iid, RID());
+ }
DisplayServer::get_singleton()->delete_status_indicator(iid);
iid = DisplayServer::INVALID_INDICATOR_ID;
}
@@ -133,3 +183,10 @@ void StatusIndicator::set_visible(bool p_visible) {
bool StatusIndicator::is_visible() const {
return visible;
}
+
+Rect2 StatusIndicator::get_rect() const {
+ if (iid == DisplayServer::INVALID_INDICATOR_ID) {
+ return Rect2();
+ }
+ return DisplayServer::get_singleton()->status_indicator_get_rect(iid);
+}
diff --git a/scene/main/status_indicator.h b/scene/main/status_indicator.h
index aa3aa68d78..cd38da6e6c 100644
--- a/scene/main/status_indicator.h
+++ b/scene/main/status_indicator.h
@@ -37,10 +37,11 @@
class StatusIndicator : public Node {
GDCLASS(StatusIndicator, Node);
- Ref<Image> icon;
+ Ref<Texture2D> icon;
String tooltip;
bool visible = true;
DisplayServer::IndicatorID iid = DisplayServer::INVALID_INDICATOR_ID;
+ NodePath menu;
protected:
void _notification(int p_what);
@@ -49,14 +50,19 @@ protected:
void _callback(MouseButton p_index, const Point2i &p_pos);
public:
- void set_icon(const Ref<Image> &p_icon);
- Ref<Image> get_icon() const;
+ void set_icon(const Ref<Texture2D> &p_icon);
+ Ref<Texture2D> get_icon() const;
void set_tooltip(const String &p_tooltip);
String get_tooltip() const;
+ void set_menu(const NodePath &p_menu);
+ NodePath get_menu() const;
+
void set_visible(bool p_visible);
bool is_visible() const;
+
+ Rect2 get_rect() const;
};
#endif // STATUS_INDICATOR_H
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 17d7fec230..26128a08ab 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -54,7 +54,6 @@
#include "scene/resources/mesh.h"
#include "scene/resources/text_line.h"
#include "scene/resources/world_2d.h"
-#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
#include "servers/rendering/rendering_server_globals.h"
@@ -81,7 +80,7 @@ void ViewportTexture::setup_local_to_scene() {
if (loc_scene->is_ready()) {
_setup_local_to_scene(loc_scene);
} else {
- loc_scene->connect(SNAME("ready"), callable_mp(this, &ViewportTexture::_setup_local_to_scene).bind(loc_scene), CONNECT_ONE_SHOT);
+ loc_scene->connect(SceneStringName(ready), callable_mp(this, &ViewportTexture::_setup_local_to_scene).bind(loc_scene), CONNECT_ONE_SHOT);
vp_pending = true;
}
}
@@ -688,6 +687,18 @@ void Viewport::_process_picking() {
physics_picking_events.clear();
return;
}
+#ifndef _3D_DISABLED
+ if (use_xr) {
+ if (XRServer::get_singleton() != nullptr) {
+ Ref<XRInterface> xr_interface = XRServer::get_singleton()->get_primary_interface();
+ if (xr_interface.is_valid() && xr_interface->is_initialized() && xr_interface->get_view_count() > 1) {
+ WARN_PRINT_ONCE("Object picking can't be used when stereo rendering, this will be turned off!");
+ physics_object_picking = false; // don't try again.
+ return;
+ }
+ }
+ }
+#endif
_drop_physics_mouseover(true);
@@ -856,9 +867,10 @@ void Viewport::_process_picking() {
if (send_event) {
co->_input_event_call(this, ev, res[i].shape);
- if (physics_object_picking_first_only) {
- break;
- }
+ }
+
+ if (physics_object_picking_first_only) {
+ break;
}
}
}
@@ -970,7 +982,7 @@ void Viewport::_set_size(const Size2i &p_size, const Size2i &p_size_2d_override,
stretch_transform_new.scale(scale);
}
- Size2i new_size = p_size.max(Size2i(2, 2));
+ Size2i new_size = p_size.maxi(2);
if (size == new_size && size_allocated == p_allocated && stretch_transform == stretch_transform_new && p_size_2d_override == size_2d_override) {
return;
}
@@ -1452,7 +1464,7 @@ void Viewport::_gui_show_tooltip() {
gui.tooltip_label->set_theme_type_variation(SNAME("TooltipLabel"));
gui.tooltip_label->set_text(gui.tooltip_text);
base_tooltip = gui.tooltip_label;
- panel->connect("mouse_entered", callable_mp(this, &Viewport::_gui_cancel_tooltip));
+ panel->connect(SceneStringName(mouse_entered), callable_mp(this, &Viewport::_gui_cancel_tooltip));
}
base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
@@ -1461,6 +1473,8 @@ void Viewport::_gui_show_tooltip() {
panel->set_flag(Window::FLAG_NO_FOCUS, true);
panel->set_flag(Window::FLAG_POPUP, false);
panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true);
+ // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport.
+ panel->set_flag(Window::FLAG_TRANSPARENT, true);
panel->set_wrap_controls(true);
panel->add_child(base_tooltip);
panel->gui_parent = this;
@@ -1469,17 +1483,25 @@ void Viewport::_gui_show_tooltip() {
tooltip_owner->add_child(gui.tooltip_popup);
+ Window *window = Object::cast_to<Window>(gui.tooltip_popup->get_embedder());
+ if (!window) { // Not embedded.
+ window = gui.tooltip_popup->get_parent_visible_window();
+ }
+ float win_scale = window->content_scale_factor;
Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset");
+ if (!gui.tooltip_popup->is_embedded()) {
+ tooltip_offset *= win_scale;
+ }
Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size());
- r.size = r.size.min(panel->get_max_size());
-
- Window *window = gui.tooltip_popup->get_parent_visible_window();
Rect2i vr;
if (gui.tooltip_popup->is_embedded()) {
vr = gui.tooltip_popup->get_embedder()->get_visible_rect();
} else {
+ panel->content_scale_factor = win_scale;
+ r.size *= win_scale;
vr = window->get_usable_parent_rect();
}
+ r.size = r.size.min(panel->get_max_size());
if (r.size.x + r.position.x > vr.size.x + vr.position.x) {
// Place it in the opposite direction. If it fails, just hug the border.
@@ -1711,7 +1733,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
gui.mouse_focus_mask.set_flag(button_mask);
} else {
gui.mouse_focus = gui_find_control(mpos);
- gui.last_mouse_focus = gui.mouse_focus;
if (!gui.mouse_focus) {
return;
@@ -2296,6 +2317,7 @@ void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control *
gui.dragging = true;
gui.drag_data = p_data;
gui.mouse_focus = nullptr;
+ gui.mouse_focus_mask.clear();
if (p_control) {
_gui_set_drag_preview(p_base, p_control);
@@ -2351,7 +2373,7 @@ void Viewport::_gui_hide_control(Control *p_control) {
if (gui.key_focus == p_control) {
gui_release_focus();
}
- if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) {
+ if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) {
_drop_mouse_over(p_control->get_parent_control());
}
if (gui.drag_mouse_over == p_control) {
@@ -2368,13 +2390,10 @@ void Viewport::_gui_remove_control(Control *p_control) {
gui.forced_mouse_focus = false;
gui.mouse_focus_mask.clear();
}
- if (gui.last_mouse_focus == p_control) {
- gui.last_mouse_focus = nullptr;
- }
if (gui.key_focus == p_control) {
gui.key_focus = nullptr;
}
- if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) {
+ if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) {
_drop_mouse_over(p_control->get_parent_control());
}
if (gui.drag_mouse_over == p_control) {
@@ -2748,7 +2767,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
Size2i min_size = gui.currently_dragged_subwindow->get_min_size();
Size2i min_size_clamped = gui.currently_dragged_subwindow->get_clamped_minimum_size();
- min_size_clamped = min_size_clamped.max(Size2i(1, 1));
+ min_size_clamped = min_size_clamped.maxi(1);
Rect2i r = gui.subwindow_resize_from_rect;
@@ -2809,7 +2828,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
Size2i max_size = gui.currently_dragged_subwindow->get_max_size();
if ((max_size.x > 0 || max_size.y > 0) && (max_size.x >= min_size.x && max_size.y >= min_size.y)) {
- max_size = max_size.max(Size2i(1, 1));
+ max_size = max_size.maxi(1);
if (r.size.x > max_size.x) {
r.size.x = max_size.x;
@@ -3568,6 +3587,13 @@ bool Viewport::gui_is_drag_successful() const {
return gui.drag_successful;
}
+void Viewport::gui_cancel_drag() {
+ ERR_MAIN_THREAD_GUARD;
+ if (gui_is_dragging()) {
+ _perform_drop();
+ }
+}
+
void Viewport::set_input_as_handled() {
ERR_MAIN_THREAD_GUARD;
if (!handle_input_locally) {
@@ -3708,6 +3734,28 @@ Viewport::VRSMode Viewport::get_vrs_mode() const {
return vrs_mode;
}
+void Viewport::set_vrs_update_mode(VRSUpdateMode p_vrs_update_mode) {
+ ERR_MAIN_THREAD_GUARD;
+
+ vrs_update_mode = p_vrs_update_mode;
+ switch (p_vrs_update_mode) {
+ case VRS_UPDATE_ONCE: {
+ RS::get_singleton()->viewport_set_vrs_update_mode(viewport, RS::VIEWPORT_VRS_UPDATE_ONCE);
+ } break;
+ case VRS_UPDATE_ALWAYS: {
+ RS::get_singleton()->viewport_set_vrs_update_mode(viewport, RS::VIEWPORT_VRS_UPDATE_ALWAYS);
+ } break;
+ default: {
+ RS::get_singleton()->viewport_set_vrs_update_mode(viewport, RS::VIEWPORT_VRS_UPDATE_DISABLED);
+ } break;
+ }
+}
+
+Viewport::VRSUpdateMode Viewport::get_vrs_update_mode() const {
+ ERR_READ_THREAD_GUARD_V(VRS_UPDATE_DISABLED);
+ return vrs_update_mode;
+}
+
void Viewport::set_vrs_texture(Ref<Texture2D> p_texture) {
ERR_MAIN_THREAD_GUARD;
vrs_texture = p_texture;
@@ -4748,6 +4796,9 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_vrs_mode", "mode"), &Viewport::set_vrs_mode);
ClassDB::bind_method(D_METHOD("get_vrs_mode"), &Viewport::get_vrs_mode);
+ ClassDB::bind_method(D_METHOD("set_vrs_update_mode", "mode"), &Viewport::set_vrs_update_mode);
+ ClassDB::bind_method(D_METHOD("get_vrs_update_mode"), &Viewport::get_vrs_update_mode);
+
ClassDB::bind_method(D_METHOD("set_vrs_texture", "texture"), &Viewport::set_vrs_texture);
ClassDB::bind_method(D_METHOD("get_vrs_texture"), &Viewport::get_vrs_texture);
@@ -4780,6 +4831,7 @@ void Viewport::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fsr_sharpness", PROPERTY_HINT_RANGE, "0,2,0.1"), "set_fsr_sharpness", "get_fsr_sharpness");
ADD_GROUP("Variable Rate Shading", "vrs_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vrs_mode", PROPERTY_HINT_ENUM, "Disabled,Texture,Depth buffer,XR"), "set_vrs_mode", "get_vrs_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "vrs_update_mode", PROPERTY_HINT_ENUM, "Disabled,Once,Always"), "set_vrs_update_mode", "get_vrs_update_mode");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "vrs_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_vrs_texture", "get_vrs_texture");
#endif
ADD_GROUP("Canvas Items", "canvas_item_");
@@ -4903,12 +4955,21 @@ void Viewport::_bind_methods() {
BIND_ENUM_CONSTANT(VRS_TEXTURE);
BIND_ENUM_CONSTANT(VRS_XR);
BIND_ENUM_CONSTANT(VRS_MAX);
+
+ BIND_ENUM_CONSTANT(VRS_UPDATE_DISABLED);
+ BIND_ENUM_CONSTANT(VRS_UPDATE_ONCE);
+ BIND_ENUM_CONSTANT(VRS_UPDATE_ALWAYS);
+ BIND_ENUM_CONSTANT(VRS_UPDATE_MAX);
}
void Viewport::_validate_property(PropertyInfo &p_property) const {
if (vrs_mode != VRS_TEXTURE && (p_property.name == "vrs_texture")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
+
+ if (vrs_mode == VRS_DISABLED && (p_property.name == "vrs_update_mode")) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
}
Viewport::Viewport() {
@@ -4944,7 +5005,7 @@ Viewport::Viewport() {
unhandled_key_input_group = "_vp_unhandled_key_input" + id;
// Window tooltip.
- gui.tooltip_delay = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
+ gui.tooltip_delay = GLOBAL_GET("gui/timers/tooltip_delay_sec");
#ifndef _3D_DISABLED
set_scaling_3d_mode((Viewport::Scaling3DMode)(int)GLOBAL_GET("rendering/scaling_3d/mode"));
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 21832a454c..2474b890b0 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -213,6 +213,13 @@ public:
VRS_MAX
};
+ enum VRSUpdateMode {
+ VRS_UPDATE_DISABLED,
+ VRS_UPDATE_ONCE,
+ VRS_UPDATE_ALWAYS,
+ VRS_UPDATE_MAX
+ };
+
private:
friend class ViewportTexture;
@@ -337,6 +344,7 @@ private:
// VRS
VRSMode vrs_mode = VRS_DISABLED;
+ VRSUpdateMode vrs_update_mode = VRS_UPDATE_ONCE;
Ref<Texture2D> vrs_texture;
struct GUI {
@@ -345,7 +353,6 @@ private:
bool key_event_accepted = false;
HashMap<int, ObjectID> touch_focus;
Control *mouse_focus = nullptr;
- Control *last_mouse_focus = nullptr;
Control *mouse_click_grabber = nullptr;
BitField<MouseButtonMask> mouse_focus_mask;
Control *key_focus = nullptr;
@@ -616,6 +623,7 @@ public:
bool gui_is_dragging() const;
bool gui_is_drag_successful() const;
+ void gui_cancel_drag();
Control *gui_find_control(const Point2 &p_global);
@@ -636,6 +644,9 @@ public:
void set_vrs_mode(VRSMode p_vrs_mode);
VRSMode get_vrs_mode() const;
+ void set_vrs_update_mode(VRSUpdateMode p_vrs_update_mode);
+ VRSUpdateMode get_vrs_update_mode() const;
+
void set_vrs_texture(Ref<Texture2D> p_texture);
Ref<Texture2D> get_vrs_texture() const;
@@ -844,6 +855,7 @@ VARIANT_ENUM_CAST(Viewport::DebugDraw);
VARIANT_ENUM_CAST(Viewport::SDFScale);
VARIANT_ENUM_CAST(Viewport::SDFOversize);
VARIANT_ENUM_CAST(Viewport::VRSMode);
+VARIANT_ENUM_CAST(Viewport::VRSUpdateMode);
VARIANT_ENUM_CAST(SubViewport::ClearMode);
VARIANT_ENUM_CAST(Viewport::RenderInfo);
VARIANT_ENUM_CAST(Viewport::RenderInfoType);
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 65f1365e67..e9a7123da0 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -37,7 +37,6 @@
#include "core/string/translation.h"
#include "core/variant/variant_parser.h"
#include "scene/gui/control.h"
-#include "scene/scene_string_names.h"
#include "scene/theme/theme_db.h"
#include "scene/theme/theme_owner.h"
@@ -415,7 +414,7 @@ Size2i Window::_clamp_limit_size(const Size2i &p_limit_size) {
if (max_window_size != Size2i()) {
return p_limit_size.clamp(Vector2i(), max_window_size);
} else {
- return p_limit_size.max(Vector2i());
+ return p_limit_size.maxi(0);
}
}
@@ -742,13 +741,13 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) {
case DisplayServer::WINDOW_EVENT_FOCUS_IN: {
focused = true;
_propagate_window_notification(this, NOTIFICATION_WM_WINDOW_FOCUS_IN);
- emit_signal(SNAME("focus_entered"));
+ emit_signal(SceneStringName(focus_entered));
} break;
case DisplayServer::WINDOW_EVENT_FOCUS_OUT: {
focused = false;
_propagate_window_notification(this, NOTIFICATION_WM_WINDOW_FOCUS_OUT);
- emit_signal(SNAME("focus_exited"));
+ emit_signal(SceneStringName(focus_exited));
} break;
case DisplayServer::WINDOW_EVENT_CLOSE_REQUEST: {
if (exclusive_child != nullptr) {
@@ -846,7 +845,7 @@ void Window::set_visible(bool p_visible) {
focused = false;
}
notification(NOTIFICATION_VISIBILITY_CHANGED);
- emit_signal(SceneStringNames::get_singleton()->visibility_changed);
+ emit_signal(SceneStringName(visibility_changed));
RS::get_singleton()->viewport_set_active(get_viewport_rid(), visible);
@@ -1036,7 +1035,7 @@ void Window::_update_window_size() {
}
if (embedder) {
- size = size.max(Size2i(1, 1));
+ size = size.maxi(1);
embedder->_sub_window_update(this);
} else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
@@ -1215,10 +1214,10 @@ void Window::set_force_native(bool p_force_native) {
if (force_native == p_force_native) {
return;
}
- force_native = p_force_native;
if (is_visible() && !is_in_edited_scene_root()) {
- WARN_PRINT("Can't change \"force_native\" while a window is displayed. Consider hiding window before changing this value.");
+ ERR_FAIL_MSG("Can't change \"force_native\" while a window is displayed. Consider hiding window before changing this value.");
}
+ force_native = p_force_native;
}
bool Window::get_force_native() const {
@@ -1321,7 +1320,7 @@ void Window::_notification(int p_what) {
}
if (visible) {
notification(NOTIFICATION_VISIBILITY_CHANGED);
- emit_signal(SceneStringNames::get_singleton()->visibility_changed);
+ emit_signal(SceneStringName(visibility_changed));
RS::get_singleton()->viewport_set_active(get_viewport_rid(), true);
}
@@ -1337,7 +1336,7 @@ void Window::_notification(int p_what) {
} break;
case NOTIFICATION_THEME_CHANGED: {
- emit_signal(SceneStringNames::get_singleton()->theme_changed);
+ emit_signal(SceneStringName(theme_changed));
_invalidate_theme_cache();
_update_theme_item_cache();
} break;
@@ -1404,11 +1403,11 @@ void Window::_notification(int p_what) {
} break;
case NOTIFICATION_VP_MOUSE_ENTER: {
- emit_signal(SceneStringNames::get_singleton()->mouse_entered);
+ emit_signal(SceneStringName(mouse_entered));
} break;
case NOTIFICATION_VP_MOUSE_EXIT: {
- emit_signal(SceneStringNames::get_singleton()->mouse_exited);
+ emit_signal(SceneStringName(mouse_exited));
} break;
}
}
@@ -1579,6 +1578,7 @@ bool Window::_can_consume_input_events() const {
}
void Window::_window_input(const Ref<InputEvent> &p_ev) {
+ ERR_MAIN_THREAD_GUARD;
if (EngineDebugger::is_active()) {
// Quit from game window using the stop shortcut (F8 by default).
// The custom shortcut is provided via environment variable when running from the editor.
@@ -1621,7 +1621,7 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) {
_input_from_window(p_ev);
if (p_ev->get_device() != InputEvent::DEVICE_ID_INTERNAL && is_inside_tree()) {
- emit_signal(SceneStringNames::get_singleton()->window_input, p_ev);
+ emit_signal(SceneStringName(window_input), p_ev);
}
if (is_inside_tree()) {
diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp
index d9a80011b0..152ecaf89d 100644
--- a/scene/property_list_helper.cpp
+++ b/scene/property_list_helper.cpp
@@ -31,53 +31,104 @@
#include "property_list_helper.h"
const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const {
- const Vector<String> components = p_property.split("/", true, 2);
+ const Vector<String> components = p_property.rsplit("/", true, 1);
if (components.size() < 2 || !components[0].begins_with(prefix)) {
return nullptr;
}
- {
- const String index_string = components[0].trim_prefix(prefix);
- if (!index_string.is_valid_int()) {
- return nullptr;
- }
- *r_index = index_string.to_int();
+ const String index_string = components[0].trim_prefix(prefix);
+ if (!index_string.is_valid_int()) {
+ return nullptr;
}
+ int index = index_string.to_int();
+ if (index < 0 || index >= _call_array_length_getter()) {
+ return nullptr;
+ }
+
+ *r_index = index;
return property_list.getptr(components[1]);
}
void PropertyListHelper::_call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const {
+ DEV_ASSERT(p_setter);
Variant args[] = { p_index, p_value };
const Variant *argptrs[] = { &args[0], &args[1] };
Callable::CallError ce;
p_setter->call(object, argptrs, 2, ce);
}
-Variant PropertyListHelper::_call_getter(const MethodBind *p_getter, int p_index) const {
+Variant PropertyListHelper::_call_getter(const Property *p_property, int p_index) const {
+ if (!p_property->getter) {
+ return object->get(prefix + itos(p_index) + "/" + p_property->info.name);
+ }
+
Callable::CallError ce;
Variant args[] = { p_index };
const Variant *argptrs[] = { &args[0] };
- return p_getter->call(object, argptrs, 1, ce);
+ return p_property->getter->call(object, argptrs, 1, ce);
+}
+
+int PropertyListHelper::_call_array_length_getter() const {
+ Callable::CallError ce;
+ return array_length_getter->call(object, nullptr, 0, ce);
}
void PropertyListHelper::set_prefix(const String &p_prefix) {
prefix = p_prefix;
}
+void PropertyListHelper::register_property(const PropertyInfo &p_info, const Variant &p_default) {
+ Property property;
+ property.info = p_info;
+ property.default_value = p_default;
+
+ property_list[p_info.name] = property;
+}
+
+bool PropertyListHelper::is_initialized() const {
+ return !property_list.is_empty();
+}
+
void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) {
+ DEV_ASSERT(!p_base.prefix.is_empty());
+ DEV_ASSERT(p_base.array_length_getter != nullptr);
+ DEV_ASSERT(!p_base.property_list.is_empty());
+ DEV_ASSERT(p_object != nullptr);
+
prefix = p_base.prefix;
+ array_length_getter = p_base.array_length_getter;
property_list = p_base.property_list;
object = p_object;
}
+bool PropertyListHelper::is_property_valid(const String &p_property, int *r_index) const {
+ const Vector<String> components = p_property.rsplit("/", true, 1);
+ if (components.size() < 2 || !components[0].begins_with(prefix)) {
+ return false;
+ }
+
+ {
+ const String index_string = components[0].trim_prefix(prefix);
+ if (!index_string.is_valid_int()) {
+ return false;
+ }
+
+ if (r_index) {
+ *r_index = index_string.to_int();
+ }
+ }
+
+ return property_list.has(components[1]);
+}
+
void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_count) const {
for (int i = 0; i < p_count; i++) {
for (const KeyValue<String, Property> &E : property_list) {
const Property &property = E.value;
PropertyInfo info = property.info;
- if (_call_getter(property.getter, i) == property.default_value) {
+ if (_call_getter(&property, i) == property.default_value) {
info.usage &= (~PROPERTY_USAGE_STORAGE);
}
@@ -92,7 +143,7 @@ bool PropertyListHelper::property_get_value(const String &p_property, Variant &r
const Property *property = _get_property(p_property, &index);
if (property) {
- r_ret = _call_getter(property->getter, index);
+ r_ret = _call_getter(property, index);
return true;
}
return false;
@@ -110,8 +161,7 @@ bool PropertyListHelper::property_set_value(const String &p_property, const Vari
}
bool PropertyListHelper::property_can_revert(const String &p_property) const {
- int index;
- return _get_property(p_property, &index) != nullptr;
+ return is_property_valid(p_property);
}
bool PropertyListHelper::property_get_revert(const String &p_property, Variant &r_value) const {
@@ -129,8 +179,10 @@ PropertyListHelper::~PropertyListHelper() {
// No object = it's the main helper. Do a cleanup.
if (!object) {
for (const KeyValue<String, Property> &E : property_list) {
- memdelete(E.value.setter);
- memdelete(E.value.getter);
+ if (E.value.setter) {
+ memdelete(E.value.setter);
+ memdelete(E.value.getter);
+ }
}
}
}
diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h
index 6c1ad21a05..e19e7cd22e 100644
--- a/scene/property_list_helper.h
+++ b/scene/property_list_helper.h
@@ -43,15 +43,24 @@ class PropertyListHelper {
};
String prefix;
+ MethodBind *array_length_getter = nullptr;
HashMap<String, Property> property_list;
Object *object = nullptr;
const Property *_get_property(const String &p_property, int *r_index) const;
void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const;
- Variant _call_getter(const MethodBind *p_getter, int p_index) const;
+ Variant _call_getter(const Property *p_property, int p_index) const;
+ int _call_array_length_getter() const;
public:
void set_prefix(const String &p_prefix);
+ template <typename G>
+ void set_array_length_getter(G p_array_length_getter) {
+ array_length_getter = create_method_bind(p_array_length_getter);
+ }
+
+ // Register property without setter/getter. Only use when you don't need PropertyListHelper for _set/_get logic.
+ void register_property(const PropertyInfo &p_info, const Variant &p_default);
template <typename S, typename G>
void register_property(const PropertyInfo &p_info, const Variant &p_default, S p_setter, G p_getter) {
@@ -64,7 +73,9 @@ public:
property_list[p_info.name] = property;
}
+ bool is_initialized() const;
void setup_for_instance(const PropertyListHelper &p_base, Object *p_object);
+ bool is_property_valid(const String &p_property, int *r_index = nullptr) const;
void get_property_list(List<PropertyInfo> *p_list, int p_count) const;
bool property_get_value(const String &p_property, Variant &r_ret) const;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 0e3f309ca2..aa8ff75c6a 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -128,9 +128,6 @@
#include "scene/resources/mesh_texture.h"
#include "scene/resources/multimesh.h"
#include "scene/resources/navigation_mesh.h"
-#include "scene/resources/navigation_mesh_source_geometry_data_2d.h"
-#include "scene/resources/navigation_mesh_source_geometry_data_3d.h"
-#include "scene/resources/navigation_polygon.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/particle_process_material.h"
#include "scene/resources/physics_material.h"
@@ -158,7 +155,6 @@
#include "scene/resources/visual_shader_particle_nodes.h"
#include "scene/resources/visual_shader_sdf_nodes.h"
#include "scene/resources/world_2d.h"
-#include "scene/scene_string_names.h"
#include "scene/theme/theme_db.h"
// 2D
@@ -213,6 +209,8 @@
#include "scene/resources/2d/circle_shape_2d.h"
#include "scene/resources/2d/concave_polygon_shape_2d.h"
#include "scene/resources/2d/convex_polygon_shape_2d.h"
+#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h"
+#include "scene/resources/2d/navigation_polygon.h"
#include "scene/resources/2d/polygon_path_finder.h"
#include "scene/resources/2d/rectangle_shape_2d.h"
#include "scene/resources/2d/segment_shape_2d.h"
@@ -299,6 +297,7 @@
#include "scene/resources/3d/height_map_shape_3d.h"
#include "scene/resources/3d/importer_mesh.h"
#include "scene/resources/3d/mesh_library.h"
+#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "scene/resources/3d/separation_ray_shape_3d.h"
#include "scene/resources/3d/sky_material.h"
@@ -545,7 +544,7 @@ void register_scene_types() {
GDREGISTER_CLASS(Camera3D);
GDREGISTER_CLASS(AudioListener3D);
GDREGISTER_CLASS(XRCamera3D);
- GDREGISTER_ABSTRACT_CLASS(XRNode3D);
+ GDREGISTER_CLASS(XRNode3D);
GDREGISTER_CLASS(XRController3D);
GDREGISTER_CLASS(XRAnchor3D);
GDREGISTER_CLASS(XROrigin3D);
@@ -589,7 +588,7 @@ void register_scene_types() {
GDREGISTER_CLASS(CPUParticles3D);
GDREGISTER_CLASS(Marker3D);
GDREGISTER_CLASS(RootMotionView);
- GDREGISTER_ABSTRACT_CLASS(SkeletonModifier3D);
+ GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D);
OS::get_singleton()->yield(); // may take time to init
@@ -657,6 +656,9 @@ void register_scene_types() {
GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeConstant);
GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVectorBase);
GDREGISTER_CLASS(VisualShaderNodeFrame);
+#ifndef DISABLE_DEPRECATED
+ GDREGISTER_CLASS(VisualShaderNodeComment); // Deprecated, just for compatibility.
+#endif
GDREGISTER_CLASS(VisualShaderNodeFloatConstant);
GDREGISTER_CLASS(VisualShaderNodeIntConstant);
GDREGISTER_CLASS(VisualShaderNodeUIntConstant);
@@ -741,6 +743,7 @@ void register_scene_types() {
GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVarying);
GDREGISTER_CLASS(VisualShaderNodeVaryingSetter);
GDREGISTER_CLASS(VisualShaderNodeVaryingGetter);
+ GDREGISTER_CLASS(VisualShaderNodeReroute);
GDREGISTER_CLASS(VisualShaderNodeSDFToScreenUV);
GDREGISTER_CLASS(VisualShaderNodeScreenUVToSDF);
@@ -883,6 +886,7 @@ void register_scene_types() {
BaseMaterial3D::init_shaders();
GDREGISTER_CLASS(MeshLibrary);
+ GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D);
OS::get_singleton()->yield(); // may take time to init
@@ -1004,7 +1008,6 @@ void register_scene_types() {
GDREGISTER_CLASS(NavigationMesh);
GDREGISTER_CLASS(NavigationMeshSourceGeometryData2D);
- GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D);
GDREGISTER_CLASS(NavigationPolygon);
GDREGISTER_CLASS(NavigationRegion2D);
GDREGISTER_CLASS(NavigationAgent2D);
diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
index d613b498a1..aee743ccf2 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
@@ -97,6 +97,24 @@ TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_
return typed_array_obstruction_outlines;
}
+void NavigationMeshSourceGeometryData2D::append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines) {
+ RWLockWrite write_lock(geometry_rwlock);
+ int traversable_outlines_size = traversable_outlines.size();
+ traversable_outlines.resize(traversable_outlines_size + p_traversable_outlines.size());
+ for (int i = traversable_outlines_size; i < p_traversable_outlines.size(); i++) {
+ traversable_outlines.write[i] = p_traversable_outlines[i];
+ }
+}
+
+void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) {
+ RWLockWrite write_lock(geometry_rwlock);
+ int obstruction_outlines_size = obstruction_outlines.size();
+ obstruction_outlines.resize(obstruction_outlines_size + p_obstruction_outlines.size());
+ for (int i = obstruction_outlines_size; i < p_obstruction_outlines.size(); i++) {
+ obstruction_outlines.write[i] = p_obstruction_outlines[i];
+ }
+}
+
void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) {
if (p_shape_outline.size() > 1) {
Vector<Vector2> traversable_outline;
@@ -240,6 +258,9 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::set_obstruction_outlines);
ClassDB::bind_method(D_METHOD("get_obstruction_outlines"), &NavigationMeshSourceGeometryData2D::get_obstruction_outlines);
+ ClassDB::bind_method(D_METHOD("append_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::append_traversable_outlines);
+ ClassDB::bind_method(D_METHOD("append_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::append_obstruction_outlines);
+
ClassDB::bind_method(D_METHOD("add_traversable_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_traversable_outline);
ClassDB::bind_method(D_METHOD("add_obstruction_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_obstruction_outline);
diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.h b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
index 0e321fbeb9..aaa02ab40e 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_2d.h
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
@@ -33,7 +33,7 @@
#include "core/os/rw_lock.h"
#include "scene/2d/node_2d.h"
-#include "scene/resources/navigation_polygon.h"
+#include "scene/resources/2d/navigation_polygon.h"
class NavigationMeshSourceGeometryData2D : public Resource {
GDCLASS(NavigationMeshSourceGeometryData2D, Resource);
@@ -82,6 +82,9 @@ public:
void set_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines);
TypedArray<Vector<Vector2>> get_obstruction_outlines() const;
+ void append_traversable_outlines(const TypedArray<Vector<Vector2>> &p_traversable_outlines);
+ void append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines);
+
void add_traversable_outline(const PackedVector2Array &p_shape_outline);
void add_obstruction_outline(const PackedVector2Array &p_shape_outline);
diff --git a/scene/resources/navigation_polygon.cpp b/scene/resources/2d/navigation_polygon.cpp
index 274b13a487..274b13a487 100644
--- a/scene/resources/navigation_polygon.cpp
+++ b/scene/resources/2d/navigation_polygon.cpp
diff --git a/scene/resources/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h
index b9816f900c..b9816f900c 100644
--- a/scene/resources/navigation_polygon.h
+++ b/scene/resources/2d/navigation_polygon.h
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp
index 4e7563fdc3..5450f544c3 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp
@@ -233,6 +233,11 @@ void SkeletonModification2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode");
}
+void SkeletonModification2D::reset_state() {
+ stack = nullptr;
+ is_setup = false;
+}
+
SkeletonModification2D::SkeletonModification2D() {
stack = nullptr;
is_setup = false;
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.h b/scene/resources/2d/skeleton/skeleton_modification_2d.h
index 413b860a99..6a6f1bb39b 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d.h
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d.h
@@ -57,6 +57,8 @@ protected:
bool _print_execution_error(bool p_condition, String p_message);
+ virtual void reset_state() override;
+
GDVIRTUAL1(_execute, double)
GDVIRTUAL1(_setup_modification, Ref<SkeletonModificationStack2D>)
GDVIRTUAL0(_draw_editor_gizmo)
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp
index 1ad8d0eccc..051c4eabc0 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp
@@ -266,7 +266,9 @@ void SkeletonModification2DCCDIK::_draw_editor_gizmo() {
void SkeletonModification2DCCDIK::update_target_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ }
return;
}
@@ -287,7 +289,9 @@ void SkeletonModification2DCCDIK::update_target_cache() {
void SkeletonModification2DCCDIK::update_tip_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!");
+ }
return;
}
@@ -309,7 +313,9 @@ void SkeletonModification2DCCDIK::update_tip_cache() {
void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!");
+ }
return;
}
@@ -390,7 +396,6 @@ void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, in
ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
- WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification...");
ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp
index dd1c4a91d5..16a6166878 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp
@@ -289,13 +289,21 @@ void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack
if (stack != nullptr) {
is_setup = true;
+
+ if (stack->skeleton) {
+ for (int i = 0; i < fabrik_data_chain.size(); i++) {
+ fabrik_joint_update_bone2d_cache(i);
+ }
+ }
update_target_cache();
}
}
void SkeletonModification2DFABRIK::update_target_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ }
return;
}
@@ -317,7 +325,9 @@ void SkeletonModification2DFABRIK::update_target_cache() {
void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!");
+ }
return;
}
@@ -389,7 +399,6 @@ void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx,
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
- WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification...");
fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp
index 2ace9577e4..b7200b49c4 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp
@@ -254,6 +254,8 @@ void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack
Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx);
jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position();
}
+
+ jiggle_joint_update_bone2d_cache(i);
}
}
@@ -263,7 +265,9 @@ void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack
void SkeletonModification2DJiggle::update_target_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ }
return;
}
@@ -285,7 +289,9 @@ void SkeletonModification2DJiggle::update_target_cache() {
void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!");
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!");
+ }
return;
}
@@ -425,7 +431,6 @@ void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx,
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
} else {
- WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification...");
jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx;
}
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp
index 8f6f6bc4ae..cd4ca8e090 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp
@@ -200,7 +200,9 @@ void SkeletonModification2DLookAt::_draw_editor_gizmo() {
void SkeletonModification2DLookAt::update_bone2d_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!");
+ }
return;
}
@@ -256,7 +258,6 @@ void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) {
bone_idx = p_bone_idx;
}
} else {
- WARN_PRINT("Cannot verify the bone index for this modification...");
bone_idx = p_bone_idx;
}
@@ -265,7 +266,9 @@ void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) {
void SkeletonModification2DLookAt::update_target_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ }
return;
}
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
index 61e5aed150..001000fa17 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp
@@ -153,7 +153,7 @@ void SkeletonModification2DPhysicalBones::_setup_modification(SkeletonModificati
void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) {
ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!");
if (!is_setup || !stack) {
- if (!stack) {
+ if (is_setup) {
ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!");
}
return;
@@ -194,7 +194,7 @@ void SkeletonModification2DPhysicalBones::fetch_physical_bones() {
node_queue.push_back(stack->skeleton);
while (node_queue.size() > 0) {
- Node *node_to_process = node_queue[0];
+ Node *node_to_process = node_queue.front()->get();
node_queue.pop_front();
if (node_to_process != nullptr) {
diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp
index c3366d5c36..41e4ea828e 100644
--- a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp
+++ b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp
@@ -250,7 +250,9 @@ void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() {
void SkeletonModification2DTwoBoneIK::update_target_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
+ }
return;
}
@@ -271,7 +273,9 @@ void SkeletonModification2DTwoBoneIK::update_target_cache() {
void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!");
+ }
return;
}
@@ -299,7 +303,9 @@ void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() {
void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() {
if (!is_setup || !stack) {
- ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!");
+ if (is_setup) {
+ ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!");
+ }
return;
}
@@ -400,7 +406,6 @@ void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) {
joint_one_bone_idx = p_bone_idx;
}
} else {
- WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one...");
joint_one_bone_idx = p_bone_idx;
}
@@ -425,7 +430,6 @@ void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) {
joint_two_bone_idx = p_bone_idx;
}
} else {
- WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two...");
joint_two_bone_idx = p_bone_idx;
}
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index 04b00b6bdb..6c3356a205 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -74,7 +74,7 @@ void TileMapPattern::_set_tile_data(const Vector<int> &p_data) {
uint16_t alternative_tile = decode_uint16(&local[10]);
set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile);
}
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Vector<int> TileMapPattern::_get_tile_data() const {
@@ -4650,7 +4650,7 @@ Ref<Texture2D> TileSetAtlasSource::get_texture() const {
void TileSetAtlasSource::set_margins(Vector2i p_margins) {
if (p_margins.x < 0 || p_margins.y < 0) {
WARN_PRINT("Atlas source margins should be positive.");
- margins = p_margins.max(Vector2i());
+ margins = p_margins.maxi(0);
} else {
margins = p_margins;
}
@@ -4666,7 +4666,7 @@ Vector2i TileSetAtlasSource::get_margins() const {
void TileSetAtlasSource::set_separation(Vector2i p_separation) {
if (p_separation.x < 0 || p_separation.y < 0) {
WARN_PRINT("Atlas source separation should be positive.");
- separation = p_separation.max(Vector2i());
+ separation = p_separation.maxi(0);
} else {
separation = p_separation;
}
@@ -4682,7 +4682,7 @@ Vector2i TileSetAtlasSource::get_separation() const {
void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) {
if (p_tile_size.x <= 0 || p_tile_size.y <= 0) {
WARN_PRINT("Atlas source tile_size should be strictly positive.");
- texture_region_size = p_tile_size.max(Vector2i(1, 1));
+ texture_region_size = p_tile_size.maxi(1);
} else {
texture_region_size = p_tile_size;
}
@@ -4969,7 +4969,7 @@ void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector
tad.alternatives[0] = memnew(TileData);
tad.alternatives[0]->set_tile_set(tile_set);
tad.alternatives[0]->set_allow_transform(false);
- tad.alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed));
+ tad.alternatives[0]->connect(CoreStringName(changed), callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed));
tad.alternatives[0]->notify_property_list_changed();
tad.alternatives_ids.push_back(0);
@@ -5353,7 +5353,7 @@ int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, i
tiles[p_atlas_coords].alternatives[new_alternative_id] = memnew(TileData);
tiles[p_atlas_coords].alternatives[new_alternative_id]->set_tile_set(tile_set);
tiles[p_atlas_coords].alternatives[new_alternative_id]->set_allow_transform(true);
- tiles[p_atlas_coords].alternatives[new_alternative_id]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed));
+ tiles[p_atlas_coords].alternatives[new_alternative_id]->connect(CoreStringName(changed), callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed));
tiles[p_atlas_coords].alternatives[new_alternative_id]->notify_property_list_changed();
tiles[p_atlas_coords].alternatives_ids.push_back(new_alternative_id);
tiles[p_atlas_coords].alternatives_ids.sort();
@@ -5591,6 +5591,11 @@ Ref<ImageTexture> TileSetAtlasSource::_create_padded_image_texture(const Ref<Tex
ret.instantiate();
return ret;
}
+ if (src_image->is_compressed()) {
+ src_image = src_image->duplicate();
+ Error err = src_image->decompress();
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<ImageTexture>(), "Unable to decompress image.");
+ }
Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2));
Ref<Image> image = Image::create_empty(size.x, size.y, false, src_image->get_format());
@@ -5933,7 +5938,7 @@ void TileData::notify_tile_data_properties_should_change() {
}
notify_property_list_changed();
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
void TileData::add_occlusion_layer(int p_to_pos) {
@@ -6134,7 +6139,7 @@ TileData *TileData::duplicate() {
void TileData::set_flip_h(bool p_flip_h) {
ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)");
flip_h = p_flip_h;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
bool TileData::get_flip_h() const {
return flip_h;
@@ -6143,7 +6148,7 @@ bool TileData::get_flip_h() const {
void TileData::set_flip_v(bool p_flip_v) {
ERR_FAIL_COND_MSG(!allow_transform && p_flip_v, "Transform is only allowed for alternative tiles (with its alternative_id != 0)");
flip_v = p_flip_v;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
bool TileData::get_flip_v() const {
@@ -6153,7 +6158,7 @@ bool TileData::get_flip_v() const {
void TileData::set_transpose(bool p_transpose) {
ERR_FAIL_COND_MSG(!allow_transform && p_transpose, "Transform is only allowed for alternative tiles (with its alternative_id != 0)");
transpose = p_transpose;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
bool TileData::get_transpose() const {
return transpose;
@@ -6161,7 +6166,7 @@ bool TileData::get_transpose() const {
void TileData::set_texture_origin(Vector2i p_texture_origin) {
texture_origin = p_texture_origin;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Vector2i TileData::get_texture_origin() const {
@@ -6170,7 +6175,7 @@ Vector2i TileData::get_texture_origin() const {
void TileData::set_material(Ref<Material> p_material) {
material = p_material;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Ref<Material> TileData::get_material() const {
return material;
@@ -6178,7 +6183,7 @@ Ref<Material> TileData::get_material() const {
void TileData::set_modulate(Color p_modulate) {
modulate = p_modulate;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Color TileData::get_modulate() const {
return modulate;
@@ -6186,7 +6191,7 @@ Color TileData::get_modulate() const {
void TileData::set_z_index(int p_z_index) {
z_index = p_z_index;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
int TileData::get_z_index() const {
return z_index;
@@ -6194,7 +6199,7 @@ int TileData::get_z_index() const {
void TileData::set_y_sort_origin(int p_y_sort_origin) {
y_sort_origin = p_y_sort_origin;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
int TileData::get_y_sort_origin() const {
return y_sort_origin;
@@ -6204,7 +6209,7 @@ void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_po
ERR_FAIL_INDEX(p_layer_id, occluders.size());
occluders.write[p_layer_id].occluder = p_occluder_polygon;
occluders.write[p_layer_id].transformed_occluders.clear();
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
@@ -6237,7 +6242,7 @@ Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, boo
void TileData::set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity) {
ERR_FAIL_INDEX(p_layer_id, physics.size());
physics.write[p_layer_id].linear_velocity = p_velocity;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const {
@@ -6248,7 +6253,7 @@ Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const {
void TileData::set_constant_angular_velocity(int p_layer_id, real_t p_velocity) {
ERR_FAIL_INDEX(p_layer_id, physics.size());
physics.write[p_layer_id].angular_velocity = p_velocity;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
real_t TileData::get_constant_angular_velocity(int p_layer_id) const {
@@ -6264,7 +6269,7 @@ void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count
}
physics.write[p_layer_id].polygons.resize(p_polygons_count);
notify_property_list_changed();
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
int TileData::get_collision_polygons_count(int p_layer_id) const {
@@ -6275,14 +6280,14 @@ int TileData::get_collision_polygons_count(int p_layer_id) const {
void TileData::add_collision_polygon(int p_layer_id) {
ERR_FAIL_INDEX(p_layer_id, physics.size());
physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData());
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
void TileData::remove_collision_polygon(int p_layer_id, int p_polygon_index) {
ERR_FAIL_INDEX(p_layer_id, physics.size());
ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size());
physics.write[p_layer_id].polygons.remove_at(p_polygon_index);
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector<Vector2> p_polygon) {
@@ -6309,7 +6314,7 @@ void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index,
}
polygon_shape_tile_data.transformed_shapes.clear();
polygon_shape_tile_data.polygon = p_polygon;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Vector<Vector2> TileData::get_collision_polygon_points(int p_layer_id, int p_polygon_index) const {
@@ -6322,7 +6327,7 @@ void TileData::set_collision_polygon_one_way(int p_layer_id, int p_polygon_index
ERR_FAIL_INDEX(p_layer_id, physics.size());
ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size());
physics.write[p_layer_id].polygons.write[p_polygon_index].one_way = p_one_way;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
bool TileData::is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const {
@@ -6335,7 +6340,7 @@ void TileData::set_collision_polygon_one_way_margin(int p_layer_id, int p_polygo
ERR_FAIL_INDEX(p_layer_id, physics.size());
ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size());
physics.write[p_layer_id].polygons.write[p_polygon_index].one_way_margin = p_one_way_margin;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
float TileData::get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const {
@@ -6373,7 +6378,7 @@ Ref<ConvexPolygonShape2D> TileData::get_collision_polygon_shape(int p_layer_id,
for (int i = 0; i < size; i++) {
Ref<ConvexPolygonShape2D> transformed_polygon;
transformed_polygon.instantiate();
- transformed_polygon->set_points(get_transformed_vertices(shapes_data.shapes[shape_index]->get_points(), p_flip_h, p_flip_v, p_transpose));
+ transformed_polygon->set_points(get_transformed_vertices(shapes_data.shapes[i]->get_points(), p_flip_h, p_flip_v, p_transpose));
shapes_data.transformed_shapes[key][i] = transformed_polygon;
}
return shapes_data.transformed_shapes[key][shape_index];
@@ -6397,7 +6402,7 @@ void TileData::set_terrain_set(int p_terrain_set) {
}
terrain_set = p_terrain_set;
notify_property_list_changed();
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
int TileData::get_terrain_set() const {
@@ -6411,7 +6416,7 @@ void TileData::set_terrain(int p_terrain) {
ERR_FAIL_COND(p_terrain >= tile_set->get_terrains_count(terrain_set));
}
terrain = p_terrain;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
int TileData::get_terrain() const {
@@ -6427,7 +6432,7 @@ void TileData::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int
ERR_FAIL_COND(!is_valid_terrain_peering_bit(p_peering_bit));
}
terrain_peering_bits[p_peering_bit] = p_terrain_index;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
int TileData::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const {
@@ -6459,7 +6464,7 @@ void TileData::set_navigation_polygon(int p_layer_id, Ref<NavigationPolygon> p_n
ERR_FAIL_INDEX(p_layer_id, navigation.size());
navigation.write[p_layer_id].navigation_polygon = p_navigation_polygon;
navigation.write[p_layer_id].transformed_navigation_polygon.clear();
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
@@ -6507,7 +6512,7 @@ Ref<NavigationPolygon> TileData::get_navigation_polygon(int p_layer_id, bool p_f
void TileData::set_probability(float p_probability) {
ERR_FAIL_COND(p_probability < 0.0);
probability = p_probability;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
float TileData::get_probability() const {
return probability;
@@ -6531,7 +6536,7 @@ Variant TileData::get_custom_data(String p_layer_name) const {
void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) {
ERR_FAIL_INDEX(p_layer_id, custom_data.size());
custom_data.write[p_layer_id] = p_value;
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const {
diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h
index dceda1a791..e6d3f7e15d 100644
--- a/scene/resources/2d/tile_set.h
+++ b/scene/resources/2d/tile_set.h
@@ -38,8 +38,8 @@
#include "scene/2d/light_occluder_2d.h"
#include "scene/main/canvas_item.h"
#include "scene/resources/2d/convex_polygon_shape_2d.h"
+#include "scene/resources/2d/navigation_polygon.h"
#include "scene/resources/image_texture.h"
-#include "scene/resources/navigation_polygon.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/physics_material.h"
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index 952e99608d..375efcb227 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -1139,7 +1139,7 @@ Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform,
s.material = get_surface_material(i);
s.name = get_surface_name(i);
- SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format);
+ SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format);
PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];
int vc = rvertices.size();
diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
index 39a17946fa..3f3f8b44fd 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
@@ -35,9 +35,24 @@ void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_ver
}
void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indices) {
+ ERR_FAIL_COND(vertices.size() < p_indices.size());
indices = p_indices;
}
+void NavigationMeshSourceGeometryData3D::append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices) {
+ RWLockWrite write_lock(geometry_rwlock);
+
+ const int64_t number_of_vertices_before_merge = vertices.size();
+ const int64_t number_of_indices_before_merge = indices.size();
+
+ vertices.append_array(p_vertices);
+ indices.append_array(p_indices);
+
+ for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
+ indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
+ }
+}
+
void NavigationMeshSourceGeometryData3D::clear() {
vertices.clear();
indices.clear();
@@ -158,6 +173,15 @@ void NavigationMeshSourceGeometryData3D::_add_faces(const PackedVector3Array &p_
void NavigationMeshSourceGeometryData3D::add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform) {
ERR_FAIL_COND(!p_mesh.is_valid());
+
+#ifdef DEBUG_ENABLED
+ if (!Engine::get_singleton()->is_editor_hint()) {
+ WARN_PRINT_ONCE("Source geometry parsing for navigation mesh baking had to parse RenderingServer meshes at runtime.\n\
+ This poses a significant performance issues as visual meshes store geometry data on the GPU and transferring this data back to the CPU blocks the rendering.\n\
+ For runtime (re)baking navigation meshes use and parse collision shapes as source geometry or create geometry data procedurally in scripts.");
+ }
+#endif
+
_add_mesh(p_mesh, root_node_transform * p_xform);
}
@@ -174,14 +198,7 @@ void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_f
void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) {
ERR_FAIL_NULL(p_other_geometry);
- // No need to worry about `root_node_transform` here as the vertices are already xformed.
- const int64_t number_of_vertices_before_merge = vertices.size();
- const int64_t number_of_indices_before_merge = indices.size();
- vertices.append_array(p_other_geometry->vertices);
- indices.append_array(p_other_geometry->indices);
- for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
- indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
- }
+ append_arrays(p_other_geometry->vertices, p_other_geometry->indices);
if (p_other_geometry->_projected_obstructions.size() > 0) {
RWLockWrite write_lock(geometry_rwlock);
@@ -306,6 +323,8 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_indices", "indices"), &NavigationMeshSourceGeometryData3D::set_indices);
ClassDB::bind_method(D_METHOD("get_indices"), &NavigationMeshSourceGeometryData3D::get_indices);
+ ClassDB::bind_method(D_METHOD("append_arrays", "vertices", "indices"), &NavigationMeshSourceGeometryData3D::append_arrays);
+
ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData3D::clear);
ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData3D::has_data);
diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.h b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
index 79e2f3740d..6c1ca760ea 100644
--- a/scene/resources/navigation_mesh_source_geometry_data_3d.h
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
@@ -80,6 +80,8 @@ public:
void set_indices(const Vector<int> &p_indices);
const Vector<int> &get_indices() const { return indices; }
+ void append_arrays(const Vector<float> &p_vertices, const Vector<int> &p_indices);
+
bool has_data() { return vertices.size() && indices.size(); };
void clear();
void clear_projected_obstructions();
diff --git a/scene/resources/3d/world_3d.cpp b/scene/resources/3d/world_3d.cpp
index 7948a8bfd5..b743b24262 100644
--- a/scene/resources/3d/world_3d.cpp
+++ b/scene/resources/3d/world_3d.cpp
@@ -35,7 +35,6 @@
#include "scene/3d/visible_on_screen_notifier_3d.h"
#include "scene/resources/camera_attributes.h"
#include "scene/resources/environment.h"
-#include "scene/scene_string_names.h"
#include "servers/navigation_server_3d.h"
void World3D::_register_camera(Camera3D *p_camera) {
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index cd530f100e..a3bfa987c6 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -33,7 +33,6 @@
#include "core/io/marshalls.h"
#include "core/math/geometry_3d.h"
-#include "scene/scene_string_names.h"
bool Animation::_set(const StringName &p_name, const Variant &p_value) {
String prop_name = p_name;
@@ -247,6 +246,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
}
vt->update_mode = UpdateMode(um);
}
+ capture_included = capture_included || (vt->update_mode == UPDATE_CAPTURE);
Vector<real_t> times = d["times"];
Array values = d["values"];
@@ -966,6 +966,28 @@ void Animation::remove_track(int p_track) {
memdelete(t);
tracks.remove_at(p_track);
emit_changed();
+ _check_capture_included();
+}
+
+void Animation::set_capture_included(bool p_capture_included) {
+ capture_included = p_capture_included;
+}
+
+bool Animation::is_capture_included() const {
+ return capture_included;
+}
+
+void Animation::_check_capture_included() {
+ capture_included = false;
+ for (int i = 0; i < tracks.size(); i++) {
+ if (tracks[i]->type == TYPE_VALUE) {
+ ValueTrack *vt = static_cast<ValueTrack *>(tracks[i]);
+ if (vt->update_mode == UPDATE_CAPTURE) {
+ capture_included = true;
+ break;
+ }
+ }
+ }
}
int Animation::get_track_count() const {
@@ -2681,6 +2703,8 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) {
ValueTrack *vt = static_cast<ValueTrack *>(t);
vt->update_mode = p_mode;
+
+ _check_capture_included();
emit_changed();
}
@@ -3870,9 +3894,13 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("compress", "page_size", "fps", "split_tolerance"), &Animation::compress, DEFVAL(8192), DEFVAL(120), DEFVAL(4.0));
+ ClassDB::bind_method(D_METHOD("_set_capture_included", "capture_included"), &Animation::set_capture_included);
+ ClassDB::bind_method(D_METHOD("is_capture_included"), &Animation::is_capture_included);
+
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001,suffix:s"), "set_length", "get_length");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001,suffix:s"), "set_step", "get_step");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_included", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_NO_EDITOR), "_set_capture_included", "is_capture_included");
BIND_ENUM_CONSTANT(TYPE_VALUE);
BIND_ENUM_CONSTANT(TYPE_POSITION_3D);
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index c72327e464..cc7bbae8a3 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -266,8 +266,10 @@ private:
_FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const;
double length = 1.0;
- real_t step = 0.1;
+ real_t step = 1.0 / 30;
LoopMode loop_mode = LOOP_NONE;
+ bool capture_included = false;
+ void _check_capture_included();
void _track_update_hash(int p_track);
@@ -392,6 +394,9 @@ public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
+ void set_capture_included(bool p_capture_included);
+ bool is_capture_included() const;
+
int get_track_count() const;
TrackType track_get_type(int p_track) const;
diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp
index 79d06c4d66..22666876ae 100644
--- a/scene/resources/animation_library.cpp
+++ b/scene/resources/animation_library.cpp
@@ -30,6 +30,8 @@
#include "animation_library.h"
+#include "scene/scene_string_names.h"
+
bool AnimationLibrary::is_valid_animation_name(const String &p_name) {
return !(p_name.is_empty() || p_name.contains("/") || p_name.contains(":") || p_name.contains(",") || p_name.contains("["));
}
@@ -106,7 +108,7 @@ TypedArray<StringName> AnimationLibrary::_get_animation_list() const {
}
void AnimationLibrary::_animation_changed(const StringName &p_name) {
- emit_signal(SNAME("animation_changed"), p_name);
+ emit_signal(SceneStringName(animation_changed), p_name);
}
void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const {
diff --git a/scene/resources/atlas_texture.cpp b/scene/resources/atlas_texture.cpp
index 6aed68849b..ef2f1eb135 100644
--- a/scene/resources/atlas_texture.cpp
+++ b/scene/resources/atlas_texture.cpp
@@ -30,8 +30,6 @@
#include "atlas_texture.h"
-#include "core/core_string_names.h"
-
int AtlasTexture::get_width() const {
if (region.size.width == 0) {
if (atlas.is_valid()) {
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index 0185c6ef85..db2564af22 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -86,15 +86,15 @@ void AudioStreamPlaybackWAV::seek(double p_time) {
offset = uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS;
}
-template <typename Depth, bool is_stereo, bool is_ima_adpcm>
-void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm) {
+template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa>
+void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa) {
// this function will be compiled branchless by any decent compiler
- int32_t final, final_r, next, next_r;
+ int32_t final = 0, final_r = 0, next = 0, next_r = 0;
while (p_amount) {
p_amount--;
int64_t pos = p_offset >> MIX_FRAC_BITS;
- if (is_stereo && !is_ima_adpcm) {
+ if (is_stereo && !is_ima_adpcm && !is_qoa) {
pos <<= 1;
}
@@ -175,32 +175,77 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
}
} else {
- final = p_src[pos];
- if (is_stereo) {
- final_r = p_src[pos + 1];
- }
+ if (is_qoa) {
+ if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates.
+ for (int i = 0; i < 2; i++) {
+ // Sign operations prevent triple decoding on backward loops, maxing prevents pop.
+ uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1);
+ uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len;
+
+ if (p_qoa->data_ofs != new_data_ofs) {
+ p_qoa->data_ofs = new_data_ofs;
+ const uint8_t *src_ptr = (const uint8_t *)base->data;
+ src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD;
+ qoa_decode_frame(src_ptr, p_qoa->frame_len, p_qoa->desc, p_qoa->dec, &p_qoa->dec_len);
+ }
- if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */
- final <<= 8;
+ uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels;
+
+ if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) {
+ final = p_qoa->dec[dec_idx];
+ p_qoa->cache[0] = final;
+ if (is_stereo) {
+ final_r = p_qoa->dec[dec_idx + 1];
+ p_qoa->cache_r[0] = final_r;
+ }
+ } else {
+ next = p_qoa->dec[dec_idx];
+ p_qoa->cache[1] = next;
+ if (is_stereo) {
+ next_r = p_qoa->dec[dec_idx + 1];
+ p_qoa->cache_r[1] = next_r;
+ }
+ }
+ }
+ p_qoa->cache_pos = pos;
+ } else {
+ final = p_qoa->cache[0];
+ if (is_stereo) {
+ final_r = p_qoa->cache_r[0];
+ }
+
+ next = p_qoa->cache[1];
+ if (is_stereo) {
+ next_r = p_qoa->cache_r[1];
+ }
+ }
+ } else {
+ final = p_src[pos];
if (is_stereo) {
- final_r <<= 8;
+ final_r = p_src[pos + 1];
}
- }
- if (is_stereo) {
- next = p_src[pos + 2];
- next_r = p_src[pos + 3];
- } else {
- next = p_src[pos + 1];
- }
+ if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */
+ final <<= 8;
+ if (is_stereo) {
+ final_r <<= 8;
+ }
+ }
- if constexpr (sizeof(Depth) == 1) {
- next <<= 8;
if (is_stereo) {
- next_r <<= 8;
+ next = p_src[pos + 2];
+ next_r = p_src[pos + 3];
+ } else {
+ next = p_src[pos + 1];
}
- }
+ if constexpr (sizeof(Depth) == 1) {
+ next <<= 8;
+ if (is_stereo) {
+ next_r <<= 8;
+ }
+ }
+ }
int32_t frac = int64_t(p_offset & MIX_FRAC_MASK);
final = final + ((next - final) * frac >> MIX_FRAC_BITS);
@@ -240,6 +285,9 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
case AudioStreamWAV::FORMAT_IMA_ADPCM:
len *= 2;
break;
+ case AudioStreamWAV::FORMAT_QOA:
+ len = qoa.desc->samples * qoa.desc->channels;
+ break;
}
if (base->stereo) {
@@ -368,27 +416,34 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
switch (base->format) {
case AudioStreamWAV::FORMAT_8_BITS: {
if (is_stereo) {
- do_resample<int8_t, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
+ do_resample<int8_t, true, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
} else {
- do_resample<int8_t, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
+ do_resample<int8_t, false, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
}
} break;
case AudioStreamWAV::FORMAT_16_BITS: {
if (is_stereo) {
- do_resample<int16_t, true, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm);
+ do_resample<int16_t, true, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
} else {
- do_resample<int16_t, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm);
+ do_resample<int16_t, false, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
}
} break;
case AudioStreamWAV::FORMAT_IMA_ADPCM: {
if (is_stereo) {
- do_resample<int8_t, true, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
+ do_resample<int8_t, true, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
} else {
- do_resample<int8_t, false, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
+ do_resample<int8_t, false, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
}
} break;
+ case AudioStreamWAV::FORMAT_QOA: {
+ if (is_stereo) {
+ do_resample<uint8_t, true, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+ } else {
+ do_resample<uint8_t, false, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+ }
+ } break;
}
dst_buff += target;
@@ -412,6 +467,16 @@ void AudioStreamPlaybackWAV::tag_used_streams() {
AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {}
+AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {
+ if (qoa.desc) {
+ memfree(qoa.desc);
+ }
+
+ if (qoa.dec) {
+ memfree(qoa.dec);
+ }
+}
+
/////////////////////
void AudioStreamWAV::set_format(Format p_format) {
@@ -475,6 +540,10 @@ double AudioStreamWAV::get_length() const {
case AudioStreamWAV::FORMAT_IMA_ADPCM:
len *= 2;
break;
+ case AudioStreamWAV::FORMAT_QOA:
+ qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
+ qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc);
+ len = desc.samples * desc.channels;
}
if (stereo) {
@@ -526,8 +595,8 @@ Vector<uint8_t> AudioStreamWAV::get_data() const {
}
Error AudioStreamWAV::save_to_wav(const String &p_path) {
- if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) {
- WARN_PRINT("Saving IMA_ADPC samples are not supported yet");
+ if (format == AudioStreamWAV::FORMAT_IMA_ADPCM || format == AudioStreamWAV::FORMAT_QOA) {
+ WARN_PRINT("Saving IMA_ADPCM and QOA samples is not supported yet");
return ERR_UNAVAILABLE;
}
@@ -548,6 +617,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
byte_pr_sample = 1;
break;
case AudioStreamWAV::FORMAT_16_BITS:
+ case AudioStreamWAV::FORMAT_QOA:
byte_pr_sample = 2;
break;
case AudioStreamWAV::FORMAT_IMA_ADPCM:
@@ -590,6 +660,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
}
break;
case AudioStreamWAV::FORMAT_16_BITS:
+ case AudioStreamWAV::FORMAT_QOA:
for (unsigned int i = 0; i < data_bytes / 2; i++) {
uint16_t data_point = decode_uint16(&read_data[i * 2]);
file->store_16(data_point);
@@ -607,6 +678,17 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() {
Ref<AudioStreamPlaybackWAV> sample;
sample.instantiate();
sample->base = Ref<AudioStreamWAV>(this);
+
+ if (format == AudioStreamWAV::FORMAT_QOA) {
+ sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc));
+ uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc);
+ ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>());
+ sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc);
+ int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples);
+ int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t);
+ sample->qoa.dec = (int16_t *)memalloc(alloc_len);
+ }
+
return sample;
}
@@ -639,7 +721,7 @@ void AudioStreamWAV::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
@@ -649,6 +731,7 @@ void AudioStreamWAV::_bind_methods() {
BIND_ENUM_CONSTANT(FORMAT_8_BITS);
BIND_ENUM_CONSTANT(FORMAT_16_BITS);
BIND_ENUM_CONSTANT(FORMAT_IMA_ADPCM);
+ BIND_ENUM_CONSTANT(FORMAT_QOA);
BIND_ENUM_CONSTANT(LOOP_DISABLED);
BIND_ENUM_CONSTANT(LOOP_FORWARD);
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index 959d1ceca0..146142d8a4 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -31,7 +31,11 @@
#ifndef AUDIO_STREAM_WAV_H
#define AUDIO_STREAM_WAV_H
+#define QOA_IMPLEMENTATION
+#define QOA_NO_STDIO
+
#include "servers/audio/audio_stream.h"
+#include "thirdparty/misc/qoa.h"
class AudioStreamWAV;
@@ -54,14 +58,25 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
int32_t window_ofs = 0;
} ima_adpcm[2];
+ struct QOA_State {
+ qoa_desc *desc = nullptr;
+ uint32_t data_ofs = 0;
+ uint32_t frame_len = 0;
+ int16_t *dec = nullptr;
+ uint32_t dec_len = 0;
+ int64_t cache_pos = -1;
+ int16_t cache[2] = { 0, 0 };
+ int16_t cache_r[2] = { 0, 0 };
+ } qoa;
+
int64_t offset = 0;
int sign = 1;
bool active = false;
friend class AudioStreamWAV;
Ref<AudioStreamWAV> base;
- template <typename Depth, bool is_stereo, bool is_ima_adpcm>
- void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm);
+ template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa>
+ void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa);
public:
virtual void start(double p_from_pos = 0.0) override;
@@ -78,6 +93,7 @@ public:
virtual void tag_used_streams() override;
AudioStreamPlaybackWAV();
+ ~AudioStreamPlaybackWAV();
};
class AudioStreamWAV : public AudioStream {
@@ -88,7 +104,8 @@ public:
enum Format {
FORMAT_8_BITS,
FORMAT_16_BITS,
- FORMAT_IMA_ADPCM
+ FORMAT_IMA_ADPCM,
+ FORMAT_QOA,
};
// Keep the ResourceImporterWAV `edit/loop_mode` enum hint in sync with these options.
diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp
index 31c8e68ea5..76e99aca92 100644
--- a/scene/resources/canvas_item_material.cpp
+++ b/scene/resources/canvas_item_material.cpp
@@ -33,13 +33,11 @@
#include "core/version.h"
Mutex CanvasItemMaterial::material_mutex;
-SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr;
+SelfList<CanvasItemMaterial>::List CanvasItemMaterial::dirty_materials;
HashMap<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData, CanvasItemMaterial::MaterialKey> CanvasItemMaterial::shader_map;
CanvasItemMaterial::ShaderNames *CanvasItemMaterial::shader_names = nullptr;
void CanvasItemMaterial::init_shaders() {
- dirty_materials = memnew(SelfList<CanvasItemMaterial>::List);
-
shader_names = memnew(ShaderNames);
shader_names->particles_anim_h_frames = "particles_anim_h_frames";
@@ -48,14 +46,13 @@ void CanvasItemMaterial::init_shaders() {
}
void CanvasItemMaterial::finish_shaders() {
- memdelete(dirty_materials);
+ dirty_materials.clear();
+
memdelete(shader_names);
- dirty_materials = nullptr;
+ shader_names = nullptr;
}
void CanvasItemMaterial::_update_shader() {
- dirty_materials->remove(&element);
-
MaterialKey mk = _compute_key();
if (mk.key == current_key.key) {
return; //no update required in the end
@@ -153,8 +150,9 @@ void CanvasItemMaterial::_update_shader() {
void CanvasItemMaterial::flush_changes() {
MutexLock lock(material_mutex);
- while (dirty_materials->first()) {
- dirty_materials->first()->self()->_update_shader();
+ while (dirty_materials.first()) {
+ dirty_materials.first()->self()->_update_shader();
+ dirty_materials.first()->remove_from_list();
}
}
@@ -162,16 +160,10 @@ void CanvasItemMaterial::_queue_shader_change() {
MutexLock lock(material_mutex);
if (_is_initialized() && !element.in_list()) {
- dirty_materials->add(&element);
+ dirty_materials.add(&element);
}
}
-bool CanvasItemMaterial::_is_shader_dirty() const {
- MutexLock lock(material_mutex);
-
- return element.in_list();
-}
-
void CanvasItemMaterial::set_blend_mode(BlendMode p_blend_mode) {
blend_mode = p_blend_mode;
_queue_shader_change();
@@ -288,7 +280,7 @@ CanvasItemMaterial::CanvasItemMaterial() :
current_key.invalid_key = 1;
- _mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change));
+ _mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change), callable_mp(this, &CanvasItemMaterial::_update_shader));
}
CanvasItemMaterial::~CanvasItemMaterial() {
diff --git a/scene/resources/canvas_item_material.h b/scene/resources/canvas_item_material.h
index 7dddd74a31..ef498c2ff6 100644
--- a/scene/resources/canvas_item_material.h
+++ b/scene/resources/canvas_item_material.h
@@ -98,12 +98,11 @@ private:
}
static Mutex material_mutex;
- static SelfList<CanvasItemMaterial>::List *dirty_materials;
+ static SelfList<CanvasItemMaterial>::List dirty_materials;
SelfList<CanvasItemMaterial> element;
void _update_shader();
_FORCE_INLINE_ void _queue_shader_change();
- _FORCE_INLINE_ bool _is_shader_dirty() const;
BlendMode blend_mode = BLEND_MODE_MIX;
LightMode light_mode = LIGHT_MODE_NORMAL;
diff --git a/scene/resources/curve_texture.cpp b/scene/resources/curve_texture.cpp
index 488a527bbb..4ba5393110 100644
--- a/scene/resources/curve_texture.cpp
+++ b/scene/resources/curve_texture.cpp
@@ -30,8 +30,6 @@
#include "curve_texture.h"
-#include "core/core_string_names.h"
-
void CurveTexture::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_width", "width"), &CurveTexture::set_width);
diff --git a/scene/resources/gradient_texture.cpp b/scene/resources/gradient_texture.cpp
index 327a99c6ad..6ec9422d2d 100644
--- a/scene/resources/gradient_texture.cpp
+++ b/scene/resources/gradient_texture.cpp
@@ -30,7 +30,6 @@
#include "gradient_texture.h"
-#include "core/core_string_names.h"
#include "core/math/geometry_2d.h"
GradientTexture1D::GradientTexture1D() {
@@ -137,6 +136,7 @@ void GradientTexture1D::_update() {
texture = RS::get_singleton()->texture_2d_create(image);
}
}
+ RS::get_singleton()->texture_set_path(texture, get_path());
}
void GradientTexture1D::set_width(int p_width) {
@@ -276,6 +276,7 @@ void GradientTexture2D::_update() {
} else {
texture = RS::get_singleton()->texture_2d_create(image);
}
+ RS::get_singleton()->texture_set_path(texture, get_path());
}
float GradientTexture2D::_get_gradient_offset_at(int x, int y) const {
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index b381096df8..27da825bfe 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -35,7 +35,6 @@
#include "core/error/error_macros.h"
#include "core/version.h"
#include "scene/main/scene_tree.h"
-#include "scene/scene_string_names.h"
void Material::set_next_pass(const Ref<Material> &p_pass) {
for (Ref<Material> pass_child = p_pass; pass_child != nullptr; pass_child = pass_child->get_next_pass()) {
@@ -82,24 +81,25 @@ void Material::_validate_property(PropertyInfo &p_property) const {
}
}
-void Material::_mark_initialized(const Callable &p_queue_shader_change_callable) {
+void Material::_mark_ready() {
+ init_state = INIT_STATE_INITIALIZING;
+}
+
+void Material::_mark_initialized(const Callable &p_add_to_dirty_list, const Callable &p_update_shader) {
// If this is happening as part of resource loading, it is not safe to queue the update
- // as an addition to the dirty list, unless the load is happening on the main thread.
- if (ResourceLoader::is_within_load() && Thread::get_caller_id() != Thread::get_main_id()) {
+ // as an addition to the dirty list. It would be if the load is happening on the main thread,
+ // but even so we'd rather perform the update directly instead of using the dirty list.
+ if (ResourceLoader::is_within_load()) {
DEV_ASSERT(init_state != INIT_STATE_READY);
if (init_state == INIT_STATE_UNINITIALIZED) { // Prevent queueing twice.
- // Let's mark this material as being initialized.
init_state = INIT_STATE_INITIALIZING;
- // Knowing that the ResourceLoader will eventually feed deferred calls into the main message queue, let's do these:
- // 1. Queue setting the init state to INIT_STATE_READY finally.
- callable_mp(this, &Material::_mark_initialized).bind(p_queue_shader_change_callable).call_deferred();
- // 2. Queue an individual update of this material.
- p_queue_shader_change_callable.call_deferred();
+ callable_mp(this, &Material::_mark_ready).call_deferred();
+ p_update_shader.call_deferred();
}
} else {
// Straightforward conditions.
init_state = INIT_STATE_READY;
- p_queue_shader_change_callable.callv(Array());
+ p_add_to_dirty_list.call();
}
}
@@ -603,8 +603,6 @@ void BaseMaterial3D::finish_shaders() {
}
void BaseMaterial3D::_update_shader() {
- dirty_materials.remove(&element);
-
MaterialKey mk = _compute_key();
if (mk == current_key) {
return; //no update required in the end
@@ -688,6 +686,9 @@ void BaseMaterial3D::_update_shader() {
case BLEND_MODE_MUL:
code += "blend_mul";
break;
+ case BLEND_MODE_PREMULT_ALPHA:
+ code += "blend_premul_alpha";
+ break;
case BLEND_MODE_MAX:
break; // Internal value, skip.
}
@@ -1819,6 +1820,11 @@ void fragment() {)";
vec3 detail = mix(ALBEDO.rgb, ALBEDO.rgb * detail_tex.rgb, detail_tex.a);
)";
} break;
+ case BLEND_MODE_PREMULT_ALPHA: {
+ // This is unlikely to ever be used for detail textures, and in order for it to function in the editor, another bit must be used in MaterialKey,
+ // but there are only 5 bits left, so I'm going to leave this disabled unless it's actually requested.
+ //code += "\tvec3 detail = (1.0-detail_tex.a)*ALBEDO.rgb+detail_tex.rgb;\n";
+ } break;
case BLEND_MODE_MAX:
break; // Internal value, skip.
}
@@ -1847,6 +1853,7 @@ void BaseMaterial3D::flush_changes() {
while (dirty_materials.first()) {
dirty_materials.first()->self()->_update_shader();
+ dirty_materials.first()->remove_from_list();
}
}
@@ -1858,12 +1865,6 @@ void BaseMaterial3D::_queue_shader_change() {
}
}
-bool BaseMaterial3D::_is_shader_dirty() const {
- MutexLock lock(material_mutex);
-
- return element.in_list();
-}
-
void BaseMaterial3D::set_albedo(const Color &p_albedo) {
albedo = p_albedo;
@@ -2816,7 +2817,7 @@ BaseMaterial3D::EmissionOperator BaseMaterial3D::get_emission_operator() const {
RID BaseMaterial3D::get_shader_rid() const {
MutexLock lock(material_mutex);
- if (element.in_list()) { // _is_shader_dirty() would create anoder mutex lock
+ if (element.in_list()) {
((BaseMaterial3D *)this)->_update_shader();
}
ERR_FAIL_COND_V(!shader_map.has(current_key), RID());
@@ -3040,7 +3041,7 @@ void BaseMaterial3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_hash_scale", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_alpha_hash_scale", "get_alpha_hash_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_antialiasing_mode", PROPERTY_HINT_ENUM, "Disabled,Alpha Edge Blend,Alpha Edge Clip"), "set_alpha_antialiasing", "get_alpha_antialiasing");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_antialiasing_edge", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_alpha_antialiasing_edge", "get_alpha_antialiasing_edge");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply"), "set_blend_mode", "get_blend_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply,Premultiplied Alpha"), "set_blend_mode", "get_blend_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mode", PROPERTY_HINT_ENUM, "Back,Front,Disabled"), "set_cull_mode", "get_cull_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "depth_draw_mode", PROPERTY_HINT_ENUM, "Opaque Only,Always,Never"), "set_depth_draw_mode", "get_depth_draw_mode");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_flag", "get_flag", FLAG_DISABLE_DEPTH_TEST);
@@ -3269,6 +3270,7 @@ void BaseMaterial3D::_bind_methods() {
BIND_ENUM_CONSTANT(BLEND_MODE_ADD);
BIND_ENUM_CONSTANT(BLEND_MODE_SUB);
BIND_ENUM_CONSTANT(BLEND_MODE_MUL);
+ BIND_ENUM_CONSTANT(BLEND_MODE_PREMULT_ALPHA);
BIND_ENUM_CONSTANT(ALPHA_ANTIALIASING_OFF);
BIND_ENUM_CONSTANT(ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE);
@@ -3403,7 +3405,7 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) :
current_key.invalid_key = 1;
- _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change));
+ _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change), callable_mp(this, &BaseMaterial3D::_update_shader));
}
BaseMaterial3D::~BaseMaterial3D() {
diff --git a/scene/resources/material.h b/scene/resources/material.h
index 073403f71e..50a774e961 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -62,7 +62,8 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
- void _mark_initialized(const Callable &p_queue_shader_change_callable);
+ void _mark_ready();
+ void _mark_initialized(const Callable &p_add_to_dirty_list, const Callable &p_update_shader);
bool _is_initialized() { return init_state == INIT_STATE_READY; }
GDVIRTUAL0RC(RID, _get_shader_rid)
@@ -219,6 +220,7 @@ public:
BLEND_MODE_ADD,
BLEND_MODE_SUB,
BLEND_MODE_MUL,
+ BLEND_MODE_PREMULT_ALPHA,
BLEND_MODE_MAX
};
@@ -465,7 +467,6 @@ private:
void _update_shader();
_FORCE_INLINE_ void _queue_shader_change();
- _FORCE_INLINE_ bool _is_shader_dirty() const;
bool orm;
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index f766d1d2c7..8b5e438aea 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -1880,7 +1880,7 @@ void ArrayMesh::set_blend_shape_name(int p_index, const StringName &p_name) {
do {
shape_name = String(p_name) + " " + itos(count);
count++;
- } while (blend_shapes.find(shape_name) != -1);
+ } while (blend_shapes.has(shape_name));
}
blend_shapes.write[p_index] = shape_name;
@@ -2086,7 +2086,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo
Array arrays = surface_get_arrays(i);
s.material = surface_get_material(i);
- SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format);
+ SurfaceTool::create_vertex_array_from_arrays(arrays, s.vertices, &s.format);
PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];
int vc = rvertices.size();
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 94a031947a..0c57c6b7ba 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -32,7 +32,6 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
-#include "core/core_string_names.h"
#include "core/io/missing_resource.h"
#include "core/io/resource_loader.h"
#include "core/templates/local_vector.h"
@@ -191,6 +190,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
Node *node = nullptr;
MissingNode *missing_node = nullptr;
+ bool is_inherited_scene = false;
if (i == 0 && base_scene_idx >= 0) {
// Scene inheritance on root node.
@@ -201,7 +201,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
if (p_edit_state != GEN_EDIT_STATE_DISABLED) {
node->set_scene_inherited_state(sdata->get_state());
}
-
+ is_inherited_scene = true;
} else if (n.instance >= 0) {
// Instance a scene into this node.
if (n.instance & FLAG_INSTANCE_IS_PLACEHOLDER) {
@@ -314,6 +314,16 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr);
if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) {
+ if (!Engine::get_singleton()->is_editor_hint() && node->get_scene_instance_load_placeholder()) {
+ // We cannot know if the referenced nodes exist yet, so instead of deferring, we write the NodePaths directly.
+
+ uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1);
+ ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr);
+
+ node->set(snames[name_idx], props[nprops[j].value], &valid);
+ continue;
+ }
+
uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1);
ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr);
@@ -327,7 +337,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr);
- if (snames[nprops[j].name] == CoreStringNames::get_singleton()->_script) {
+ if (snames[nprops[j].name] == CoreStringName(script)) {
//work around to avoid old script variables from disappearing, should be the proper fix to:
//https://github.com/godotengine/godot/issues/2958
@@ -346,6 +356,12 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
} else {
Variant value = props[nprops[j].value];
+ // Making sure that instances of inherited scenes don't share the same
+ // reference between them.
+ if (is_inherited_scene) {
+ value = value.duplicate(true);
+ }
+
if (value.get_type() == Variant::OBJECT) {
//handle resources that are local to scene by duplicating them if needed
Ref<Resource> res = value;
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index 5acb08de14..01d26a8bed 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -33,14 +33,12 @@
#include "core/version.h"
Mutex ParticleProcessMaterial::material_mutex;
-SelfList<ParticleProcessMaterial>::List *ParticleProcessMaterial::dirty_materials = nullptr;
+SelfList<ParticleProcessMaterial>::List ParticleProcessMaterial::dirty_materials;
HashMap<ParticleProcessMaterial::MaterialKey, ParticleProcessMaterial::ShaderData, ParticleProcessMaterial::MaterialKey> ParticleProcessMaterial::shader_map;
RBSet<String> ParticleProcessMaterial::min_max_properties;
ParticleProcessMaterial::ShaderNames *ParticleProcessMaterial::shader_names = nullptr;
void ParticleProcessMaterial::init_shaders() {
- dirty_materials = memnew(SelfList<ParticleProcessMaterial>::List);
-
shader_names = memnew(ShaderNames);
shader_names->direction = "direction";
@@ -140,15 +138,13 @@ void ParticleProcessMaterial::init_shaders() {
}
void ParticleProcessMaterial::finish_shaders() {
- memdelete(dirty_materials);
- dirty_materials = nullptr;
+ dirty_materials.clear();
memdelete(shader_names);
+ shader_names = nullptr;
}
void ParticleProcessMaterial::_update_shader() {
- dirty_materials->remove(&element);
-
MaterialKey mk = _compute_key();
if (mk == current_key) {
return; //no update required in the end
@@ -634,10 +630,10 @@ void ParticleProcessMaterial::_update_shader() {
if (emission_shape == EMISSION_SHAPE_RING) {
code += " \n";
code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n";
- code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n";
- code += " vec3 axis = normalize(emission_ring_axis);\n";
+ code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n";
+ code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n";
code += " vec3 ortho_axis = vec3(0.0);\n";
- code += " if (axis == vec3(1.0, 0.0, 0.0)) {\n";
+ code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n";
code += " ortho_axis = cross(axis, vec3(0.0, 1.0, 0.0));\n";
code += " } else {\n";
code += " ortho_axis = cross(axis, vec3(1.0, 0.0, 0.0));\n";
@@ -741,26 +737,33 @@ void ParticleProcessMaterial::_update_shader() {
code += "vec3 get_random_direction_from_spread(inout uint alt_seed, float spread_angle){\n";
code += " float pi = 3.14159;\n";
code += " float degree_to_rad = pi / 180.0;\n";
- code += " vec3 velocity = vec3(0.);\n";
code += " float spread_rad = spread_angle * degree_to_rad;\n";
- code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
- code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n";
- code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n";
- code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n";
- code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n";
- code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n";
- code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n";
- code += " // rotate spread to direction\n";
- code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n";
- code += " if (length(binormal) < 0.0001) {\n";
- code += " // direction is parallel to Y. Choose Z as the binormal.\n";
- code += " binormal = vec3(0.0, 0.0, 1.0);\n";
- code += " }\n";
- code += " binormal = normalize(binormal);\n";
- code += " vec3 normal = cross(binormal, direction_nrm);\n";
- code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n";
- code += " return spread_direction;\n";
-
+ if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
+ // Spread calculation for 2D.
+ code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
+ code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n";
+ code += " vec3 spread_direction = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n";
+ code += " return spread_direction;\n";
+ } else {
+ // Spread calculation for 3D.
+ code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
+ code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n";
+ code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n";
+ code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n";
+ code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n";
+ code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n";
+ code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n";
+ code += " // rotate spread to direction\n";
+ code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n";
+ code += " if (length(binormal) < 0.0001) {\n";
+ code += " // direction is parallel to Y. Choose Z as the binormal.\n";
+ code += " binormal = vec3(0.0, 0.0, 1.0);\n";
+ code += " }\n";
+ code += " binormal = normalize(binormal);\n";
+ code += " vec3 normal = cross(binormal, direction_nrm);\n";
+ code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n";
+ code += " return normalize(spread_direction);\n";
+ }
code += "}\n";
code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform, float delta){\n";
@@ -988,13 +991,15 @@ void ParticleProcessMaterial::_update_shader() {
code += " \n";
if (collision_mode == COLLISION_RIGID) {
code += " if (COLLIDED) {\n";
- code += " if (length(VELOCITY) > 3.0) {\n";
- code += " TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n";
- code += " VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n";
- code += " VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n";
- code += " } else {\n";
- code += " VELOCITY = vec3(0.0);\n";
- code += " }\n";
+ code += " float collision_response = dot(COLLISION_NORMAL, VELOCITY);\n";
+ code += " float slide_to_bounce_trigger = step(2.0/clamp(collision_bounce + 1.0, 1.0, 2.0), abs(collision_response));\n";
+ code += " TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n";
+ code += " // Remove all components of VELOCITY that is not tangent to COLLISION_NORMAL\n";
+ code += " VELOCITY -= COLLISION_NORMAL * collision_response;\n";
+ code += " // Apply friction only to VELOCITY across the surface (Effectively decouples friction and bounce behavior).\n";
+ code += " VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n";
+ code += " // Add bounce velocity to VELOCITY\n";
+ code += " VELOCITY -= COLLISION_NORMAL * collision_response * (collision_bounce * slide_to_bounce_trigger);\n";
code += " }\n";
} else if (collision_mode == COLLISION_HIDE_ON_CONTACT) {
code += " if (COLLIDED) {\n";
@@ -1129,9 +1134,9 @@ void ParticleProcessMaterial::_update_shader() {
code += " if (COLLIDED) emit_count = sub_emitter_amount_at_collision;\n";
} break;
case SUB_EMITTER_AT_END: {
- code += " float unit_delta = DELTA/LIFETIME;\n";
- code += " float end_time = CUSTOM.w * 0.95;\n"; // if we do at the end we might miss it, as it can just get deactivated by emitter
- code += " if (CUSTOM.y < end_time && (CUSTOM.y + unit_delta) >= end_time) emit_count = sub_emitter_amount_at_end;\n";
+ code += " if ((CUSTOM.y / CUSTOM.w * LIFETIME) > (LIFETIME - DELTA)) {\n";
+ code += " emit_count = sub_emitter_amount_at_end;\n";
+ code += " }\n";
} break;
default: {
}
@@ -1163,8 +1168,9 @@ void ParticleProcessMaterial::_update_shader() {
void ParticleProcessMaterial::flush_changes() {
MutexLock lock(material_mutex);
- while (dirty_materials->first()) {
- dirty_materials->first()->self()->_update_shader();
+ while (dirty_materials.first()) {
+ dirty_materials.first()->self()->_update_shader();
+ dirty_materials.first()->remove_from_list();
}
}
@@ -1172,16 +1178,10 @@ void ParticleProcessMaterial::_queue_shader_change() {
MutexLock lock(material_mutex);
if (_is_initialized() && !element.in_list()) {
- dirty_materials->add(&element);
+ dirty_materials.add(&element);
}
}
-bool ParticleProcessMaterial::_is_shader_dirty() const {
- MutexLock lock(material_mutex);
-
- return element.in_list();
-}
-
bool ParticleProcessMaterial::has_min_max_property(const String &p_name) {
return min_max_properties.has(p_name);
}
@@ -2323,7 +2323,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() :
current_key.invalid_key = 1;
- _mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change));
+ _mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change), callable_mp(this, &ParticleProcessMaterial::_update_shader));
}
ParticleProcessMaterial::~ParticleProcessMaterial() {
diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h
index 94b2009654..25046b51cd 100644
--- a/scene/resources/particle_process_material.h
+++ b/scene/resources/particle_process_material.h
@@ -186,7 +186,7 @@ private:
}
static Mutex material_mutex;
- static SelfList<ParticleProcessMaterial>::List *dirty_materials;
+ static SelfList<ParticleProcessMaterial>::List dirty_materials;
struct ShaderNames {
StringName direction;
@@ -293,7 +293,6 @@ private:
void _update_shader();
_FORCE_INLINE_ void _queue_shader_change();
- _FORCE_INLINE_ bool _is_shader_dirty() const;
Vector3 direction;
float spread = 0.0f;
diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp
index 918b5c0b41..002db30379 100644
--- a/scene/resources/portable_compressed_texture.cpp
+++ b/scene/resources/portable_compressed_texture.cpp
@@ -153,14 +153,14 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C
for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) {
Vector<uint8_t> data;
if (p_compression_mode == COMPRESSION_MODE_LOSSY) {
- data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality);
+ data = Image::webp_lossy_packer(i ? p_image->get_image_from_mipmap(i) : p_image, p_lossy_quality);
encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2);
} else {
if (use_webp) {
- data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i));
+ data = Image::webp_lossless_packer(i ? p_image->get_image_from_mipmap(i) : p_image);
encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2);
} else {
- data = Image::png_packer(p_image->get_image_from_mipmap(i));
+ data = Image::png_packer(i ? p_image->get_image_from_mipmap(i) : p_image);
encode_uint16(DATA_FORMAT_PNG, buffer.ptrw() + 2);
}
}
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 07868e7e49..2e27ac9198 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -37,10 +37,12 @@
#include "core/object/script_language.h"
#include "core/version.h"
-// Version 2: changed names for Basis, AABB, Vectors, etc.
-// Version 3: new string ID for ext/subresources, breaks forward compat.
-// Version 4: PackedByteArray is now stored as base64 encoded.
+// Version 2: Changed names for Basis, AABB, Vectors, etc.
+// Version 3: New string ID for ext/subresources, breaks forward compat.
+// Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added.
#define FORMAT_VERSION 4
+// For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray.
+#define FORMAT_VERSION_COMPAT 3
#define BINARY_FORMAT_VERSION 4
@@ -845,7 +847,7 @@ void ResourceLoaderText::set_translation_remapped(bool p_remapped) {
}
ResourceLoaderText::ResourceLoaderText() :
- stream(false) {}
+ stream(false), format_version(FORMAT_VERSION) {}
void ResourceLoaderText::get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types) {
open(p_f);
@@ -954,13 +956,13 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String
}
if (is_scene) {
- fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n");
+ fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(format_version) + uid_text + "]\n");
} else {
String script_res_text;
if (!script_class.is_empty()) {
script_res_text = "script_class=\"" + script_class + "\" ";
}
- fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + uid_text + "]\n");
+ fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(format_version) + uid_text + "]\n");
}
}
@@ -1063,13 +1065,15 @@ void ResourceLoaderText::open(Ref<FileAccess> p_f, bool p_skip_first_tag) {
}
if (tag.fields.has("format")) {
- int fmt = tag.fields["format"];
- if (fmt > FORMAT_VERSION) {
+ format_version = tag.fields["format"];
+ if (format_version > FORMAT_VERSION) {
error_text = "Saved with newer format version";
_printerr();
- error = ERR_PARSE_ERROR;
+ error = ERR_FILE_UNRECOGNIZED;
return;
}
+ } else {
+ format_version = FORMAT_VERSION;
}
if (tag.name == "gd_scene") {
@@ -1970,6 +1974,15 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant,
_find_resources(v);
}
} break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ // Balance between compatibility and performance.
+ if (use_compat && p_variant.operator PackedByteArray().size() > 64) {
+ use_compat = false;
+ }
+ } break;
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ use_compat = false;
+ } break;
default: {
}
}
@@ -2005,6 +2018,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
}
// Save resources.
+ use_compat = true; // _find_resources() changes this.
_find_resources(p_resource, true);
if (packed_scene.is_valid()) {
@@ -2037,7 +2051,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
if (load_steps > 1) {
title += "load_steps=" + itos(load_steps) + " ";
}
- title += "format=" + itos(FORMAT_VERSION) + "";
+ title += "format=" + itos(use_compat ? FORMAT_VERSION_COMPAT : FORMAT_VERSION) + "";
ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true);
@@ -2223,7 +2237,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
}
String vars;
- VariantWriter::write_to_string(value, vars, _write_resources, this);
+ VariantWriter::write_to_string(value, vars, _write_resources, this, use_compat);
f->store_string(name.property_name_encode() + " = " + vars + "\n");
}
}
@@ -2287,14 +2301,14 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
if (!instance_placeholder.is_empty()) {
String vars;
f->store_string(" instance_placeholder=");
- VariantWriter::write_to_string(instance_placeholder, vars, _write_resources, this);
+ VariantWriter::write_to_string(instance_placeholder, vars, _write_resources, this, use_compat);
f->store_string(vars);
}
if (instance.is_valid()) {
String vars;
f->store_string(" instance=");
- VariantWriter::write_to_string(instance, vars, _write_resources, this);
+ VariantWriter::write_to_string(instance, vars, _write_resources, this, use_compat);
f->store_string(vars);
}
@@ -2302,7 +2316,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
for (int j = 0; j < state->get_node_property_count(i); j++) {
String vars;
- VariantWriter::write_to_string(state->get_node_property_value(i, j), vars, _write_resources, this);
+ VariantWriter::write_to_string(state->get_node_property_value(i, j), vars, _write_resources, this, use_compat);
f->store_string(String(state->get_node_property_name(i, j)).property_name_encode() + " = " + vars + "\n");
}
@@ -2336,7 +2350,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
f->store_string(connstr);
if (binds.size()) {
String vars;
- VariantWriter::write_to_string(binds, vars, _write_resources, this);
+ VariantWriter::write_to_string(binds, vars, _write_resources, this, use_compat);
f->store_string(" binds= " + vars);
}
@@ -2368,14 +2382,14 @@ Error ResourceLoaderText::set_uid(Ref<FileAccess> p_f, ResourceUID::ID p_uid) {
fw = FileAccess::open(local_path + ".uidren", FileAccess::WRITE);
if (is_scene) {
- fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
+ fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(format_version) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
} else {
String script_res_text;
if (!script_class.is_empty()) {
script_res_text = "script_class=\"" + script_class + "\" ";
}
- fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
+ fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(format_version) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
}
uint8_t c = f->get_8();
diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h
index c05b7a24e1..41363fd975 100644
--- a/scene/resources/resource_format_text.h
+++ b/scene/resources/resource_format_text.h
@@ -54,6 +54,7 @@ class ResourceLoaderText {
};
bool is_scene = false;
+ int format_version;
String res_type;
bool ignore_resource_parsing = false;
@@ -178,6 +179,7 @@ class ResourceFormatSaverTextInstance {
List<Ref<Resource>> saved_resources;
HashMap<Ref<Resource>, String> external_resources;
HashMap<Ref<Resource>, String> internal_resources;
+ bool use_compat = true;
struct ResourceSort {
Ref<Resource> resource;
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index 5b375905cc..dfe5bd4a47 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -31,12 +31,20 @@
#include "shader.h"
#include "core/io/file_access.h"
-#include "scene/scene_string_names.h"
#include "servers/rendering/shader_language.h"
#include "servers/rendering/shader_preprocessor.h"
#include "servers/rendering_server.h"
#include "texture.h"
+#ifdef TOOLS_ENABLED
+#include "editor/editor_help.h"
+
+#include "modules/modules_enabled.gen.h" // For regex.
+#ifdef MODULE_REGEX_ENABLED
+#include "modules/regex/regex.h"
+#endif
+#endif
+
Shader::Mode Shader::get_mode() const {
return mode;
}
@@ -121,6 +129,12 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr
List<PropertyInfo> local;
RenderingServer::get_singleton()->get_shader_parameter_list(shader, &local);
+#ifdef TOOLS_ENABLED
+ DocData::ClassDoc class_doc;
+ class_doc.name = get_path();
+ class_doc.is_script_doc = true;
+#endif
+
for (PropertyInfo &pi : local) {
bool is_group = pi.usage == PROPERTY_USAGE_GROUP || pi.usage == PROPERTY_USAGE_SUBGROUP;
if (!p_get_groups && is_group) {
@@ -136,9 +150,33 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr
if (pi.type == Variant::RID) {
pi.type = Variant::OBJECT;
}
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ DocData::PropertyDoc prop_doc;
+ prop_doc.name = "shader_parameter/" + pi.name;
+#ifdef MODULE_REGEX_ENABLED
+ const RegEx pattern("/\\*\\*\\s([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+/\\s*uniform\\s+\\w+\\s+" + pi.name + "(?=[\\s:;=])");
+ Ref<RegExMatch> pattern_ref = pattern.search(code);
+ if (pattern_ref != nullptr) {
+ RegExMatch *match = pattern_ref.ptr();
+ const RegEx pattern_tip("\\/\\*\\*([\\s\\S]*?)\\*/");
+ Ref<RegExMatch> pattern_tip_ref = pattern_tip.search(match->get_string(0));
+ RegExMatch *match_tip = pattern_tip_ref.ptr();
+ const RegEx pattern_stripped("\\n\\s*\\*\\s*");
+ prop_doc.description = pattern_stripped.sub(match_tip->get_string(1), "\n", true);
+ }
+#endif
+ class_doc.properties.push_back(prop_doc);
+ }
+#endif
p_params->push_back(pi);
}
}
+#ifdef TOOLS_ENABLED
+ if (EditorHelp::get_doc_data() != nullptr && Engine::get_singleton()->is_editor_hint() && !class_doc.name.is_empty() && p_params) {
+ EditorHelp::get_doc_data()->add_doc(class_doc);
+ }
+#endif
}
RID Shader::get_rid() const {
diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp
index 42ffa2d25a..6e43ea9b17 100644
--- a/scene/resources/sprite_frames.cpp
+++ b/scene/resources/sprite_frames.cpp
@@ -277,5 +277,5 @@ void SpriteFrames::_bind_methods() {
}
SpriteFrames::SpriteFrames() {
- add_animation(SceneStringNames::get_singleton()->_default);
+ add_animation(SceneStringName(default_));
}
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index 06d53e4e2f..b83be8b6ef 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -802,7 +802,9 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local
const uint32_t SurfaceTool::custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 };
const uint32_t SurfaceTool::custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT };
-void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) {
+void SurfaceTool::create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) {
+ ERR_FAIL_INDEX(RS::ARRAY_WEIGHTS, p_arrays.size());
+
ret.clear();
Vector<Vector3> varr = p_arrays[RS::ARRAY_VERTEX];
@@ -932,7 +934,7 @@ void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays
}
void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) {
- create_vertex_array_from_triangle_arrays(arr, *r_vertex, &lformat);
+ create_vertex_array_from_arrays(arr, *r_vertex, &lformat);
ERR_FAIL_COND(r_vertex->size() == 0);
//indices
@@ -949,9 +951,9 @@ void SurfaceTool::_create_list_from_arrays(Array arr, LocalVector<Vertex> *r_ver
}
}
-void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
+void SurfaceTool::create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type) {
clear();
- primitive = Mesh::PRIMITIVE_TRIANGLES;
+ primitive = p_primitive_type;
_create_list_from_arrays(p_arrays, &vertex_array, &index_array, format);
for (int j = 0; j < RS::ARRAY_CUSTOM_COUNT; j++) {
@@ -961,6 +963,10 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
}
}
+void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
+ create_from_arrays(p_arrays, Mesh::PRIMITIVE_TRIANGLES);
+}
+
void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) {
ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh");
@@ -1377,6 +1383,7 @@ void SurfaceTool::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &SurfaceTool::clear);
ClassDB::bind_method(D_METHOD("create_from", "existing", "surface"), &SurfaceTool::create_from);
+ ClassDB::bind_method(D_METHOD("create_from_arrays", "arrays", "primitive_type"), &SurfaceTool::create_from_arrays, DEFVAL(Mesh::PRIMITIVE_TRIANGLES));
ClassDB::bind_method(D_METHOD("create_from_blend_shape", "existing", "surface", "blend_shape"), &SurfaceTool::create_from_blend_shape);
ClassDB::bind_method(D_METHOD("append_from", "existing", "surface", "transform"), &SurfaceTool::append_from);
ClassDB::bind_method(D_METHOD("commit", "existing", "flags"), &SurfaceTool::commit, DEFVAL(Variant()), DEFVAL(0));
diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h
index 9dfb298b9e..a072df5bee 100644
--- a/scene/resources/surface_tool.h
+++ b/scene/resources/surface_tool.h
@@ -219,7 +219,8 @@ public:
LocalVector<Vertex> &get_vertex_array() { return vertex_array; }
void create_from_triangle_arrays(const Array &p_arrays);
- static void create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr);
+ void create_from_arrays(const Array &p_arrays, Mesh::PrimitiveType p_primitive_type = Mesh::PRIMITIVE_TRIANGLES);
+ static void create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<Vertex> &ret, uint64_t *r_format = nullptr);
Array commit_to_arrays();
void create_from(const Ref<Mesh> &p_existing, int p_surface);
void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name);
diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp
index 441c8859cf..6da4215031 100644
--- a/scene/resources/syntax_highlighter.cpp
+++ b/scene/resources/syntax_highlighter.cpp
@@ -303,7 +303,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
}
// Check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation.
- if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'f' || str[j] == 'e') && !in_word && prev_is_number && !is_number) {
+ if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'f' || str[j] == 'e' || (uint_suffix_enabled && str[j] == 'u')) && !in_word && prev_is_number && !is_number) {
is_number = true;
is_a_symbol = false;
is_char = false;
@@ -313,7 +313,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
}
}
- if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) {
+ if (!in_word && (is_ascii_alphabet_char(str[j]) || is_underscore(str[j])) && !is_number) {
in_word = true;
}
@@ -617,6 +617,10 @@ void CodeHighlighter::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "color_regions"), "set_color_regions", "get_color_regions");
}
+void CodeHighlighter::set_uint_suffix_enabled(bool p_enabled) {
+ uint_suffix_enabled = p_enabled;
+}
+
void CodeHighlighter::set_number_color(Color p_color) {
number_color = p_color;
clear_highlighting_cache();
diff --git a/scene/resources/syntax_highlighter.h b/scene/resources/syntax_highlighter.h
index cac2807ee2..02afd9045e 100644
--- a/scene/resources/syntax_highlighter.h
+++ b/scene/resources/syntax_highlighter.h
@@ -93,6 +93,8 @@ private:
Color symbol_color;
Color number_color;
+ bool uint_suffix_enabled = false;
+
protected:
static void _bind_methods();
@@ -139,6 +141,8 @@ public:
void set_member_variable_color(Color p_color);
Color get_member_variable_color() const;
+
+ void set_uint_suffix_enabled(bool p_enabled);
};
#endif // SYNTAX_HIGHLIGHTER_H
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 9ac899ad78..601e8c52a4 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -585,12 +585,12 @@ int VisualShaderNodeCustom::get_input_port_count() const {
VisualShaderNodeCustom::PortType VisualShaderNodeCustom::get_input_port_type(int p_port) const {
ERR_FAIL_INDEX_V(p_port, input_ports.size(), PORT_TYPE_SCALAR);
- return (PortType)input_ports[p_port].type;
+ return (PortType)input_ports.get(p_port).type;
}
String VisualShaderNodeCustom::get_input_port_name(int p_port) const {
ERR_FAIL_INDEX_V(p_port, input_ports.size(), "");
- return input_ports[p_port].name;
+ return input_ports.get(p_port).name;
}
int VisualShaderNodeCustom::get_default_input_port(PortType p_type) const {
@@ -605,12 +605,12 @@ int VisualShaderNodeCustom::get_output_port_count() const {
VisualShaderNodeCustom::PortType VisualShaderNodeCustom::get_output_port_type(int p_port) const {
ERR_FAIL_INDEX_V(p_port, output_ports.size(), PORT_TYPE_SCALAR);
- return (PortType)output_ports[p_port].type;
+ return (PortType)output_ports.get(p_port).type;
}
String VisualShaderNodeCustom::get_output_port_name(int p_port) const {
ERR_FAIL_INDEX_V(p_port, output_ports.size(), "");
- return output_ports[p_port].name;
+ return output_ports.get(p_port).name;
}
String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
@@ -865,7 +865,7 @@ int VisualShader::get_varyings_count() const {
const VisualShader::Varying *VisualShader::get_varying_by_index(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, varyings_list.size(), nullptr);
- return &varyings_list[p_idx];
+ return &varyings_list.get(p_idx);
}
void VisualShader::set_varying_mode(const String &p_name, VaryingMode p_mode) {
@@ -1101,6 +1101,42 @@ bool VisualShader::is_nodes_connected_relatively(const Graph *p_graph, int p_nod
return result;
}
+bool VisualShader::_check_reroute_subgraph(Type p_type, int p_target_port_type, int p_reroute_node, List<int> *r_visited_reroute_nodes) const {
+ const Graph *g = &graph[p_type];
+
+ // BFS to check whether connecting to the given subgraph (rooted at p_reroute_node) is valid.
+ List<int> queue;
+ queue.push_back(p_reroute_node);
+ if (r_visited_reroute_nodes != nullptr) {
+ r_visited_reroute_nodes->push_back(p_reroute_node);
+ }
+ while (!queue.is_empty()) {
+ int current_node_id = queue.front()->get();
+ VisualShader::Node current_node = g->nodes[current_node_id];
+ queue.pop_front();
+ for (const int &next_node_id : current_node.next_connected_nodes) {
+ Ref<VisualShaderNodeReroute> next_vsnode = g->nodes[next_node_id].node;
+ if (next_vsnode.is_valid()) {
+ queue.push_back(next_node_id);
+ if (r_visited_reroute_nodes != nullptr) {
+ r_visited_reroute_nodes->push_back(next_node_id);
+ }
+ continue;
+ }
+ // Check whether all ports connected with the reroute node are compatible.
+ for (const Connection &c : g->connections) {
+ VisualShaderNode::PortType to_port_type = g->nodes[next_node_id].node->get_input_port_type(c.to_port);
+ if (c.from_node == current_node_id &&
+ c.to_node == next_node_id &&
+ !is_port_types_compatible(p_target_port_type, to_port_type)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const {
ERR_FAIL_INDEX_V(p_type, TYPE_MAX, false);
const Graph *g = &graph[p_type];
@@ -1128,7 +1164,12 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po
VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port);
VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port);
- if (!is_port_types_compatible(from_port_type, to_port_type)) {
+ Ref<VisualShaderNodeReroute> to_node_reroute = g->nodes[p_to_node].node;
+ if (to_node_reroute.is_valid()) {
+ if (!_check_reroute_subgraph(p_type, from_port_type, p_to_node)) {
+ return false;
+ }
+ } else if (!is_port_types_compatible(from_port_type, to_port_type)) {
return false;
}
@@ -1141,7 +1182,6 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po
if (is_nodes_connected_relatively(g, p_from_node, p_to_node)) {
return false;
}
-
return true;
}
@@ -1179,6 +1219,28 @@ void VisualShader::detach_node_from_frame(Type p_type, int p_node) {
g->nodes[p_node].node->set_frame(-1);
}
+String VisualShader::get_reroute_parameter_name(Type p_type, int p_reroute_node) const {
+ ERR_FAIL_INDEX_V(p_type, TYPE_MAX, "");
+ const Graph *g = &graph[p_type];
+
+ ERR_FAIL_COND_V(!g->nodes.has(p_reroute_node), "");
+
+ const VisualShader::Node *node = &g->nodes[p_reroute_node];
+ while (node->prev_connected_nodes.size() > 0) {
+ int connected_node_id = node->prev_connected_nodes[0];
+ node = &g->nodes[connected_node_id];
+ Ref<VisualShaderNodeParameter> parameter_node = node->node;
+ if (parameter_node.is_valid() && parameter_node->get_output_port_type(0) == VisualShaderNode::PORT_TYPE_SAMPLER) {
+ return parameter_node->get_parameter_name();
+ }
+ Ref<VisualShaderNodeInput> input_node = node->node;
+ if (input_node.is_valid() && input_node->get_output_port_type(0) == VisualShaderNode::PORT_TYPE_SAMPLER) {
+ return input_node->get_input_real_name();
+ }
+ }
+ return "";
+}
+
void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
ERR_FAIL_INDEX(p_type, TYPE_MAX);
Graph *g = &graph[p_type];
@@ -1217,10 +1279,30 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port,
ERR_FAIL_COND_V(!g->nodes.has(p_to_node), ERR_INVALID_PARAMETER);
ERR_FAIL_INDEX_V(p_to_port, g->nodes[p_to_node].node->get_input_port_count(), ERR_INVALID_PARAMETER);
+ Ref<VisualShaderNodeReroute> from_node_reroute = g->nodes[p_from_node].node;
+ Ref<VisualShaderNodeReroute> to_node_reroute = g->nodes[p_to_node].node;
+
+ // Allow connection with incompatible port types only if the reroute node isn't connected to anything.
VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port);
VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port);
+ bool port_types_are_compatible = is_port_types_compatible(from_port_type, to_port_type);
+
+ if (to_node_reroute.is_valid()) {
+ List<int> visited_reroute_nodes;
+ port_types_are_compatible = _check_reroute_subgraph(p_type, from_port_type, p_to_node, &visited_reroute_nodes);
+ if (port_types_are_compatible) {
+ // Set the port type of all reroute nodes.
+ for (const int &E : visited_reroute_nodes) {
+ Ref<VisualShaderNodeReroute> reroute_node = g->nodes[E].node;
+ reroute_node->_set_port_type(from_port_type);
+ }
+ }
+ } else if (from_node_reroute.is_valid() && !from_node_reroute->is_input_port_connected(0)) {
+ from_node_reroute->_set_port_type(to_port_type);
+ port_types_are_compatible = true;
+ }
- ERR_FAIL_COND_V_MSG(!is_port_types_compatible(from_port_type, to_port_type), ERR_INVALID_PARAMETER, "Incompatible port types (scalar/vec/bool) with transform.");
+ ERR_FAIL_COND_V_MSG(!port_types_are_compatible, ERR_INVALID_PARAMETER, "Incompatible port types.");
for (const Connection &E : g->connections) {
if (E.from_node == p_from_node && E.from_port == p_from_port && E.to_node == p_to_node && E.to_port == p_to_port) {
@@ -1463,7 +1545,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN
return String();
}
- while (port_name.length() && !is_ascii_char(port_name[0])) {
+ while (port_name.length() && !is_ascii_alphabet_char(port_name[0])) {
port_name = port_name.substr(1, port_name.length() - 1);
}
@@ -1508,7 +1590,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN
String VisualShader::validate_parameter_name(const String &p_name, const Ref<VisualShaderNodeParameter> &p_parameter) const {
String param_name = p_name; //validate name first
- while (param_name.length() && !is_ascii_char(param_name[0])) {
+ while (param_name.length() && !is_ascii_alphabet_char(param_name[0])) {
param_name = param_name.substr(1, param_name.length() - 1);
}
if (!param_name.is_empty()) {
@@ -1904,12 +1986,18 @@ Error VisualShader::_write_node(Type type, StringBuilder *p_global_code, StringB
if (in_type == VisualShaderNode::PORT_TYPE_SAMPLER && out_type == VisualShaderNode::PORT_TYPE_SAMPLER) {
VisualShaderNode *ptr = const_cast<VisualShaderNode *>(graph[type].nodes[from_node].node.ptr());
+ // FIXME: This needs to be refactored at some point.
if (ptr->has_method("get_input_real_name")) {
inputs[i] = ptr->call("get_input_real_name");
} else if (ptr->has_method("get_parameter_name")) {
inputs[i] = ptr->call("get_parameter_name");
} else {
- inputs[i] = "";
+ Ref<VisualShaderNodeReroute> reroute = graph[type].nodes[from_node].node;
+ if (reroute.is_valid()) {
+ inputs[i] = get_reroute_parameter_name(type, from_node);
+ } else {
+ inputs[i] = "";
+ }
}
} else if (in_type == out_type) {
inputs[i] = src_var;
@@ -2478,10 +2566,11 @@ void VisualShader::_update_shader() const {
}
}
- for (int i = 0; i < parameters.size(); i++) {
- VisualShaderNodeParameter *parameter = parameters[i];
+ int idx = 0;
+ for (List<VisualShaderNodeParameter *>::Iterator itr = parameters.begin(); itr != parameters.end(); ++itr, ++idx) {
+ VisualShaderNodeParameter *parameter = *itr;
if (used_parameter_names.has(parameter->get_parameter_name())) {
- global_code += parameter->generate_global(get_mode(), Type(i), -1);
+ global_code += parameter->generate_global(get_mode(), Type(idx), -1);
const_cast<VisualShaderNodeParameter *>(parameter)->set_global_code_generated(true);
} else {
const_cast<VisualShaderNodeParameter *>(parameter)->set_global_code_generated(false);
@@ -2791,12 +2880,13 @@ void VisualShader::_update_shader() const {
const_cast<VisualShader *>(this)->set_code(final_code);
for (int i = 0; i < default_tex_params.size(); i++) {
- for (int j = 0; j < default_tex_params[i].params.size(); j++) {
- const_cast<VisualShader *>(this)->set_default_texture_parameter(default_tex_params[i].name, default_tex_params[i].params[j], j);
+ int j = 0;
+ for (List<Ref<Texture2D>>::ConstIterator itr = default_tex_params[i].params.begin(); itr != default_tex_params[i].params.end(); ++itr, ++j) {
+ const_cast<VisualShader *>(this)->set_default_texture_parameter(default_tex_params[i].name, *itr, j);
}
}
if (previous_code != final_code) {
- const_cast<VisualShader *>(this)->emit_signal(SNAME("changed"));
+ const_cast<VisualShader *>(this)->emit_signal(CoreStringName(changed));
}
previous_code = final_code;
}
@@ -3684,7 +3774,7 @@ String VisualShaderNodeParameterRef::get_parameter_name_by_index(int p_idx) cons
ERR_FAIL_COND_V(!shader_rid.is_valid(), String());
if (p_idx >= 0 && p_idx < parameters[shader_rid].size()) {
- return parameters[shader_rid][p_idx].name;
+ return parameters[shader_rid].get(p_idx).name;
}
return "";
}
@@ -3692,9 +3782,9 @@ String VisualShaderNodeParameterRef::get_parameter_name_by_index(int p_idx) cons
VisualShaderNodeParameterRef::ParameterType VisualShaderNodeParameterRef::get_parameter_type_by_name(const String &p_name) const {
ERR_FAIL_COND_V(!shader_rid.is_valid(), PARAMETER_TYPE_FLOAT);
- for (int i = 0; i < parameters[shader_rid].size(); i++) {
- if (parameters[shader_rid][i].name == p_name) {
- return parameters[shader_rid][i].type;
+ for (const VisualShaderNodeParameterRef::Parameter &parameter : parameters[shader_rid]) {
+ if (parameter.name == p_name) {
+ return parameter.type;
}
}
return PARAMETER_TYPE_FLOAT;
@@ -3704,7 +3794,7 @@ VisualShaderNodeParameterRef::ParameterType VisualShaderNodeParameterRef::get_pa
ERR_FAIL_COND_V(!shader_rid.is_valid(), PARAMETER_TYPE_FLOAT);
if (p_idx >= 0 && p_idx < parameters[shader_rid].size()) {
- return parameters[shader_rid][p_idx].type;
+ return parameters[shader_rid].get(p_idx).type;
}
return PARAMETER_TYPE_FLOAT;
}
@@ -3713,7 +3803,7 @@ VisualShaderNodeParameterRef::PortType VisualShaderNodeParameterRef::get_port_ty
ERR_FAIL_COND_V(!shader_rid.is_valid(), PORT_TYPE_SCALAR);
if (p_idx >= 0 && p_idx < parameters[shader_rid].size()) {
- switch (parameters[shader_rid][p_idx].type) {
+ switch (parameters[shader_rid].get(p_idx).type) {
case PARAMETER_TYPE_FLOAT:
return PORT_TYPE_SCALAR;
case PARAMETER_TYPE_INT:
@@ -4206,7 +4296,7 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() {
set_allow_v_resize(true);
}
-////////////// Comment
+////////////// Frame
String VisualShaderNodeFrame::get_caption() const {
return title;
@@ -4323,6 +4413,25 @@ void VisualShaderNodeFrame::_bind_methods() {
VisualShaderNodeFrame::VisualShaderNodeFrame() {
}
+////////////// Comment (Deprecated)
+
+#ifndef DISABLE_DEPRECATED
+void VisualShaderNodeComment::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description);
+ ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description");
+}
+
+void VisualShaderNodeComment::set_description(const String &p_description) {
+ description = p_description;
+}
+
+String VisualShaderNodeComment::get_description() const {
+ return description;
+}
+#endif
+
////////////// GroupBase
void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) {
@@ -4909,6 +5018,10 @@ String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShad
return code;
}
+bool VisualShaderNodeExpression::is_output_port_expandable(int p_port) const {
+ return false;
+}
+
void VisualShaderNodeExpression::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_expression", "expression"), &VisualShaderNodeExpression::set_expression);
ClassDB::bind_method(D_METHOD("get_expression"), &VisualShaderNodeExpression::get_expression);
@@ -4961,15 +5074,15 @@ int VisualShaderNodeVarying::get_varyings_count() const {
String VisualShaderNodeVarying::get_varying_name_by_index(int p_idx) const {
if (p_idx >= 0 && p_idx < varyings.size()) {
- return varyings[p_idx].name;
+ return varyings.get(p_idx).name;
}
return "";
}
VisualShader::VaryingType VisualShaderNodeVarying::get_varying_type_by_name(const String &p_name) const {
- for (int i = 0; i < varyings.size(); i++) {
- if (varyings[i].name == p_name) {
- return varyings[i].type;
+ for (const VisualShaderNodeVarying::Varying &varying : varyings) {
+ if (varying.name == p_name) {
+ return varying.type;
}
}
return VisualShader::VARYING_TYPE_FLOAT;
@@ -4977,15 +5090,15 @@ VisualShader::VaryingType VisualShaderNodeVarying::get_varying_type_by_name(cons
VisualShader::VaryingType VisualShaderNodeVarying::get_varying_type_by_index(int p_idx) const {
if (p_idx >= 0 && p_idx < varyings.size()) {
- return varyings[p_idx].type;
+ return varyings.get(p_idx).type;
}
return VisualShader::VARYING_TYPE_FLOAT;
}
VisualShader::VaryingMode VisualShaderNodeVarying::get_varying_mode_by_name(const String &p_name) const {
- for (int i = 0; i < varyings.size(); i++) {
- if (varyings[i].name == p_name) {
- return varyings[i].mode;
+ for (const VisualShaderNodeVarying::Varying &varying : varyings) {
+ if (varying.name == p_name) {
+ return varying.mode;
}
}
return VisualShader::VARYING_MODE_VERTEX_TO_FRAG_LIGHT;
@@ -4993,14 +5106,14 @@ VisualShader::VaryingMode VisualShaderNodeVarying::get_varying_mode_by_name(cons
VisualShader::VaryingMode VisualShaderNodeVarying::get_varying_mode_by_index(int p_idx) const {
if (p_idx >= 0 && p_idx < varyings.size()) {
- return varyings[p_idx].mode;
+ return varyings.get(p_idx).mode;
}
return VisualShader::VARYING_MODE_VERTEX_TO_FRAG_LIGHT;
}
VisualShaderNodeVarying::PortType VisualShaderNodeVarying::get_port_type_by_index(int p_idx) const {
if (p_idx >= 0 && p_idx < varyings.size()) {
- return get_port_type(varyings[p_idx].type, 0);
+ return get_port_type(varyings.get(p_idx).type, 0);
}
return PORT_TYPE_SCALAR;
}
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index 3ef6dcd4f9..18cdc8342b 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -163,6 +163,8 @@ private:
void _input_type_changed(Type p_type, int p_id);
bool has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const;
+ bool _check_reroute_subgraph(Type p_type, int p_target_port_type, int p_reroute_node, List<int> *r_visited_reroute_nodes = nullptr) const;
+
protected:
virtual void _update_shader() const override;
static void _bind_methods();
@@ -229,6 +231,8 @@ public: // internal methods
void attach_node_to_frame(Type p_type, int p_node, int p_frame);
void detach_node_from_frame(Type p_type, int p_node);
+ String get_reroute_parameter_name(Type p_type, int p_reroute_node) const;
+
void rebuild();
void get_node_connections(Type p_type, List<Connection> *r_connections) const;
@@ -767,6 +771,28 @@ public:
VisualShaderNodeFrame();
};
+#ifndef DISABLE_DEPRECATED
+// Deprecated, for compatibility only.
+class VisualShaderNodeComment : public VisualShaderNodeFrame {
+ GDCLASS(VisualShaderNodeComment, VisualShaderNodeFrame);
+
+ String description;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const override { return "Comment(Deprecated)"; }
+
+ virtual Category get_category() const override { return CATEGORY_NONE; }
+
+ void set_description(const String &p_description);
+ String get_description() const;
+
+ VisualShaderNodeComment() {}
+};
+#endif
+
class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase {
GDCLASS(VisualShaderNodeGroupBase, VisualShaderNodeResizableBase);
@@ -856,6 +882,7 @@ public:
String get_expression() const;
virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+ virtual bool is_output_port_expandable(int p_port) const override;
VisualShaderNodeExpression();
};
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index ccd730eef2..d5394c8af5 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -8150,3 +8150,82 @@ VisualShaderNodeRotationByAxis::VisualShaderNodeRotationByAxis() {
simple_decl = false;
}
+
+String VisualShaderNodeReroute::get_caption() const {
+ return "Reroute";
+}
+
+int VisualShaderNodeReroute::get_input_port_count() const {
+ return 1;
+}
+
+VisualShaderNodeReroute::PortType VisualShaderNodeReroute::get_input_port_type(int p_port) const {
+ return input_port_type;
+}
+
+String VisualShaderNodeReroute::get_input_port_name(int p_port) const {
+ return String();
+}
+
+int VisualShaderNodeReroute::get_output_port_count() const {
+ return 1;
+}
+
+VisualShaderNodeReroute::PortType VisualShaderNodeReroute::get_output_port_type(int p_port) const {
+ return input_port_type;
+}
+
+String VisualShaderNodeReroute::get_output_port_name(int p_port) const {
+ return String();
+}
+
+String VisualShaderNodeReroute::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+ String code;
+ for (int i = 0; i < get_output_port_count(); i++) {
+ if (input_port_type == PORT_TYPE_SAMPLER) {
+ continue;
+ }
+
+ String input = p_input_vars[0];
+ if (input.is_empty()) {
+ code += vformat(" %s;\n", p_output_vars[i]);
+ continue;
+ }
+ code += vformat(" %s = %s;\n", p_output_vars[i], input);
+ }
+ return code;
+}
+
+void VisualShaderNodeReroute::_set_port_type(PortType p_type) {
+ input_port_type = p_type;
+ switch (p_type) {
+ case PORT_TYPE_SCALAR:
+ set_input_port_default_value(0, 0.0);
+ break;
+ case PORT_TYPE_VECTOR_2D:
+ set_input_port_default_value(0, Vector2());
+ break;
+ case PORT_TYPE_VECTOR_3D:
+ set_input_port_default_value(0, Vector3());
+ break;
+ case PORT_TYPE_VECTOR_4D:
+ set_input_port_default_value(0, Vector4());
+ break;
+ case PORT_TYPE_TRANSFORM:
+ set_input_port_default_value(0, Transform3D());
+ break;
+ default:
+ break;
+ }
+}
+
+void VisualShaderNodeReroute::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_set_port_type", "port_type"), &VisualShaderNodeReroute::_set_port_type);
+ ClassDB::bind_method(D_METHOD("get_port_type"), &VisualShaderNodeReroute::get_port_type);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "port_type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_port_type", "get_port_type");
+}
+
+VisualShaderNodeReroute::VisualShaderNodeReroute() {
+ set_input_port_default_value(0, 0.0);
+}
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index 0bd0c631b8..a23ae72def 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -3092,4 +3092,35 @@ public:
VisualShaderNodeRotationByAxis();
};
+class VisualShaderNodeReroute : public VisualShaderNode {
+ GDCLASS(VisualShaderNodeReroute, VisualShaderNode);
+
+ PortType input_port_type = PORT_TYPE_SCALAR;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const override;
+
+ virtual int get_input_port_count() const override;
+ virtual PortType get_input_port_type(int p_port) const override;
+ virtual String get_input_port_name(int p_port) const override;
+
+ virtual int get_output_port_count() const override;
+ virtual PortType get_output_port_type(int p_port) const override;
+ virtual String get_output_port_name(int p_port) const override;
+ virtual bool has_output_port_preview(int p_port) const override { return false; }
+ virtual bool is_output_port_expandable(int p_port) const override { return false; }
+
+ virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+ virtual Category get_category() const override { return CATEGORY_SPECIAL; }
+
+ void _set_port_type(PortType p_type);
+ PortType get_port_type() const { return input_port_type; }
+
+ VisualShaderNodeReroute();
+};
+
#endif // VISUAL_SHADER_NODES_H
diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp
index 5ff1f79a22..cc88876232 100644
--- a/scene/resources/visual_shader_particle_nodes.cpp
+++ b/scene/resources/visual_shader_particle_nodes.cpp
@@ -1641,11 +1641,11 @@ String VisualShaderNodeParticleEmit::generate_code(Shader::Mode p_mode, VisualSh
String flags_str;
- for (int i = 0; i < flags_arr.size(); i++) {
- if (i > 0) {
+ for (List<String>::ConstIterator itr = flags_arr.begin(); itr != flags_arr.end(); ++itr) {
+ if (itr != flags_arr.begin()) {
flags_str += "|";
}
- flags_str += flags_arr[i];
+ flags_str += *itr;
}
if (flags_str.is_empty()) {
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index cf8ece0d4c..2ee27c95e1 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -33,20 +33,12 @@
SceneStringNames *SceneStringNames::singleton = nullptr;
SceneStringNames::SceneStringNames() {
- _estimate_cost = StaticCString::create("_estimate_cost");
- _compute_cost = StaticCString::create("_compute_cost");
-
resized = StaticCString::create("resized");
- dot = StaticCString::create(".");
- doubledot = StaticCString::create("..");
draw = StaticCString::create("draw");
- _draw = StaticCString::create("_draw");
hidden = StaticCString::create("hidden");
visibility_changed = StaticCString::create("visibility_changed");
input_event = StaticCString::create("input_event");
shader = StaticCString::create("shader");
- shader_unshaded = StaticCString::create("shader/unshaded");
- shading_mode = StaticCString::create("shader/shading_mode");
tree_entered = StaticCString::create("tree_entered");
tree_exiting = StaticCString::create("tree_exiting");
tree_exited = StaticCString::create("tree_exited");
@@ -63,7 +55,7 @@ SceneStringNames::SceneStringNames() {
RESET = StaticCString::create("RESET");
pose_updated = StaticCString::create("pose_updated");
- bone_pose_changed = StaticCString::create("bone_pose_changed");
+ skeleton_updated = StaticCString::create("skeleton_updated");
bone_enabled_changed = StaticCString::create("bone_enabled_changed");
show_rest_only_changed = StaticCString::create("show_rest_only_changed");
@@ -86,103 +78,33 @@ SceneStringNames::SceneStringNames() {
area_shape_entered = StaticCString::create("area_shape_entered");
area_shape_exited = StaticCString::create("area_shape_exited");
- _body_inout = StaticCString::create("_body_inout");
- _area_inout = StaticCString::create("_area_inout");
-
- idle = StaticCString::create("idle");
- iteration = StaticCString::create("iteration");
update = StaticCString::create("update");
updated = StaticCString::create("updated");
- _physics_process = StaticCString::create("_physics_process");
- _process = StaticCString::create("_process");
-
- _enter_tree = StaticCString::create("_enter_tree");
- _exit_tree = StaticCString::create("_exit_tree");
- _enter_world = StaticCString::create("_enter_world");
- _exit_world = StaticCString::create("_exit_world");
_ready = StaticCString::create("_ready");
- _update_scroll = StaticCString::create("_update_scroll");
- _update_xform = StaticCString::create("_update_xform");
-
- _structured_text_parser = StaticCString::create("_structured_text_parser");
-
- _proxgroup_add = StaticCString::create("_proxgroup_add");
- _proxgroup_remove = StaticCString::create("_proxgroup_remove");
-
- grouped = StaticCString::create("grouped");
- ungrouped = StaticCString::create("ungrouped");
-
screen_entered = StaticCString::create("screen_entered");
screen_exited = StaticCString::create("screen_exited");
- viewport_entered = StaticCString::create("viewport_entered");
- viewport_exited = StaticCString::create("viewport_exited");
-
- camera_entered = StaticCString::create("camera_entered");
- camera_exited = StaticCString::create("camera_exited");
-
- _input = StaticCString::create("_input");
- _input_event = StaticCString::create("_input_event");
-
gui_input = StaticCString::create("gui_input");
- _gui_input = StaticCString::create("_gui_input");
-
- _unhandled_input = StaticCString::create("_unhandled_input");
- _unhandled_key_input = StaticCString::create("_unhandled_key_input");
-
- changed = StaticCString::create("changed");
- _shader_changed = StaticCString::create("_shader_changed");
_spatial_editor_group = StaticCString::create("_spatial_editor_group");
_request_gizmo = StaticCString::create("_request_gizmo");
- _set_subgizmo_selection = StaticCString::create("_set_subgizmo_selection");
- _clear_subgizmo_selection = StaticCString::create("_clear_subgizmo_selection");
offset = StaticCString::create("offset");
- unit_offset = StaticCString::create("unit_offset");
rotation_mode = StaticCString::create("rotation_mode");
rotate = StaticCString::create("rotate");
h_offset = StaticCString::create("h_offset");
v_offset = StaticCString::create("v_offset");
- transform_pos = StaticCString::create("position");
- transform_rot = StaticCString::create("rotation");
- transform_scale = StaticCString::create("scale");
-
- _update_remote = StaticCString::create("_update_remote");
- _update_pairs = StaticCString::create("_update_pairs");
-
- _get_minimum_size = StaticCString::create("_get_minimum_size");
-
area_entered = StaticCString::create("area_entered");
area_exited = StaticCString::create("area_exited");
- _has_point = StaticCString::create("_has_point");
-
line_separation = StaticCString::create("line_separation");
- _get_drag_data = StaticCString::create("_get_drag_data");
- _drop_data = StaticCString::create("_drop_data");
- _can_drop_data = StaticCString::create("_can_drop_data");
-
- baked_light_changed = StaticCString::create("baked_light_changed");
- _baked_light_changed = StaticCString::create("_baked_light_changed");
-
- _mouse_enter = StaticCString::create("_mouse_enter");
- _mouse_exit = StaticCString::create("_mouse_exit");
- _mouse_shape_enter = StaticCString::create("_mouse_shape_enter");
- _mouse_shape_exit = StaticCString::create("_mouse_shape_exit");
-
- _pressed = StaticCString::create("_pressed");
- _toggled = StaticCString::create("_toggled");
-
frame_changed = StaticCString::create("frame_changed");
texture_changed = StaticCString::create("texture_changed");
- playback_speed = StaticCString::create("playback/speed");
- playback_active = StaticCString::create("playback/active");
autoplay = StaticCString::create("autoplay");
blend_times = StaticCString::create("blend_times");
speed = StaticCString::create("speed");
@@ -196,13 +118,9 @@ SceneStringNames::SceneStringNames() {
// Audio bus name.
Master = StaticCString::create("Master");
- _default = StaticCString::create("default");
+ default_ = StaticCString::create("default");
- _window_group = StaticCString::create("_window_group");
- _window_input = StaticCString::create("_window_input");
window_input = StaticCString::create("window_input");
- _window_unhandled_input = StaticCString::create("_window_unhandled_input");
- _get_contents_minimum_size = StaticCString::create("_get_contents_minimum_size");
theme_changed = StaticCString::create("theme_changed");
parameters_base_path = "parameters/";
@@ -210,8 +128,5 @@ SceneStringNames::SceneStringNames() {
shader_overrides_group = StaticCString::create("_shader_overrides_group_");
shader_overrides_group_active = StaticCString::create("_shader_overrides_group_active_");
-#ifndef DISABLE_DEPRECATED
- use_in_baked_light = StaticCString::create("use_in_baked_light");
- use_dynamic_gi = StaticCString::create("use_dynamic_gi");
-#endif
+ pressed = StaticCString::create("pressed");
}
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index 10b71e2a2a..7ed86cde46 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -51,23 +51,14 @@ class SceneStringNames {
public:
_FORCE_INLINE_ static SceneStringNames *get_singleton() { return singleton; }
- StringName _estimate_cost;
- StringName _compute_cost;
-
StringName resized;
- StringName dot;
- StringName doubledot;
StringName draw;
StringName hidden;
StringName visibility_changed;
StringName input_event;
- StringName _input_event;
StringName gui_input;
- StringName _gui_input;
StringName item_rect_changed;
StringName shader;
- StringName shader_unshaded;
- StringName shading_mode;
StringName tree_entered;
StringName tree_exiting;
StringName tree_exited;
@@ -75,8 +66,6 @@ public:
StringName size_flags_changed;
StringName minimum_size_changed;
StringName sleeping_state_changed;
- StringName idle;
- StringName iteration;
StringName update;
StringName updated;
@@ -99,7 +88,7 @@ public:
StringName RESET;
StringName pose_updated;
- StringName bone_pose_changed;
+ StringName skeleton_updated;
StringName bone_enabled_changed;
StringName show_rest_only_changed;
@@ -111,94 +100,33 @@ public:
StringName area_shape_entered;
StringName area_shape_exited;
- StringName _body_inout;
- StringName _area_inout;
-
- StringName _physics_process;
- StringName _process;
- StringName _enter_world;
- StringName _exit_world;
- StringName _enter_tree;
- StringName _exit_tree;
- StringName _draw;
- StringName _input;
StringName _ready;
- StringName _unhandled_input;
- StringName _unhandled_key_input;
-
- StringName _pressed;
- StringName _toggled;
-
- StringName _update_scroll;
- StringName _update_xform;
-
- StringName _structured_text_parser;
-
- StringName _proxgroup_add;
- StringName _proxgroup_remove;
-
- StringName grouped;
- StringName ungrouped;
-
- StringName _has_point;
- StringName _get_drag_data;
- StringName _can_drop_data;
- StringName _drop_data;
StringName screen_entered;
StringName screen_exited;
- StringName viewport_entered;
- StringName viewport_exited;
- StringName camera_entered;
- StringName camera_exited;
-
- StringName changed;
- StringName _shader_changed;
StringName _spatial_editor_group;
StringName _request_gizmo;
- StringName _set_subgizmo_selection;
- StringName _clear_subgizmo_selection;
StringName offset;
- StringName unit_offset;
StringName rotation_mode;
StringName rotate;
StringName v_offset;
StringName h_offset;
- StringName transform_pos;
- StringName transform_rot;
- StringName transform_scale;
-
- StringName _update_remote;
- StringName _update_pairs;
-
StringName area_entered;
StringName area_exited;
- StringName _get_minimum_size;
-
- StringName baked_light_changed;
- StringName _baked_light_changed;
-
- StringName _mouse_enter;
- StringName _mouse_exit;
- StringName _mouse_shape_enter;
- StringName _mouse_shape_exit;
-
StringName frame_changed;
StringName texture_changed;
- StringName playback_speed;
- StringName playback_active;
StringName autoplay;
StringName blend_times;
StringName speed;
NodePath path_pp;
- StringName _default;
+ StringName default_; // "default", conflict with C++ keyword.
StringName node_configuration_warning_changed;
@@ -207,21 +135,15 @@ public:
StringName Master;
StringName parameters_base_path;
-
- StringName _window_group;
- StringName _window_input;
- StringName _window_unhandled_input;
StringName window_input;
- StringName _get_contents_minimum_size;
StringName theme_changed;
StringName shader_overrides_group;
StringName shader_overrides_group_active;
-#ifndef DISABLE_DEPRECATED
- StringName use_in_baked_light;
- StringName use_dynamic_gi;
-#endif
+ StringName pressed;
};
+#define SceneStringName(m_name) SceneStringNames::get_singleton()->m_name
+
#endif // SCENE_STRING_NAMES_H
diff --git a/scene/theme/SCsub b/scene/theme/SCsub
index 5f62ae4b05..2372d1820a 100644
--- a/scene/theme/SCsub
+++ b/scene/theme/SCsub
@@ -4,7 +4,6 @@ Import("env")
import default_theme_builders
-
env.add_source_files(env.scene_sources, "*.cpp")
SConscript("icons/SCsub")
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index d1d8854b18..aa4d669238 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -39,6 +39,7 @@
#include "scene/resources/style_box_flat.h"
#include "scene/resources/style_box_line.h"
#include "scene/resources/theme.h"
+#include "scene/scene_string_names.h"
#include "scene/theme/theme_db.h"
#include "servers/text_server.h"
@@ -160,7 +161,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox("normal", "Button", button_normal);
theme->set_stylebox("hover", "Button", button_hover);
- theme->set_stylebox("pressed", "Button", button_pressed);
+ theme->set_stylebox(SceneStringName(pressed), "Button", button_pressed);
theme->set_stylebox("disabled", "Button", button_disabled);
theme->set_stylebox("focus", "Button", focus);
@@ -189,7 +190,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
// MenuBar
theme->set_stylebox("normal", "MenuBar", button_normal);
theme->set_stylebox("hover", "MenuBar", button_hover);
- theme->set_stylebox("pressed", "MenuBar", button_pressed);
+ theme->set_stylebox(SceneStringName(pressed), "MenuBar", button_pressed);
theme->set_stylebox("disabled", "MenuBar", button_disabled);
theme->set_font("font", "MenuBar", Ref<Font>());
@@ -232,7 +233,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox("normal", "OptionButton", sb_optbutton_normal);
theme->set_stylebox("hover", "OptionButton", sb_optbutton_hover);
- theme->set_stylebox("pressed", "OptionButton", sb_optbutton_pressed);
+ theme->set_stylebox(SceneStringName(pressed), "OptionButton", sb_optbutton_pressed);
theme->set_stylebox("disabled", "OptionButton", sb_optbutton_disabled);
Ref<StyleBox> sb_optbutton_normal_mirrored = make_flat_stylebox(style_normal_color, 2 * default_margin, default_margin, 2 * default_margin, default_margin);
@@ -266,7 +267,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
// MenuButton
theme->set_stylebox("normal", "MenuButton", button_normal);
- theme->set_stylebox("pressed", "MenuButton", button_pressed);
+ theme->set_stylebox(SceneStringName(pressed), "MenuButton", button_pressed);
theme->set_stylebox("hover", "MenuButton", button_hover);
theme->set_stylebox("disabled", "MenuButton", button_disabled);
theme->set_stylebox("focus", "MenuButton", focus);
@@ -292,7 +293,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
cbx_focus->set_content_margin_all(Math::round(4 * scale));
theme->set_stylebox("normal", "CheckBox", cbx_empty);
- theme->set_stylebox("pressed", "CheckBox", cbx_empty);
+ theme->set_stylebox(SceneStringName(pressed), "CheckBox", cbx_empty);
theme->set_stylebox("disabled", "CheckBox", cbx_empty);
theme->set_stylebox("hover", "CheckBox", cbx_empty);
theme->set_stylebox("hover_pressed", "CheckBox", cbx_empty);
@@ -328,7 +329,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
cb_empty->set_content_margin_individual(Math::round(6 * scale), Math::round(4 * scale), Math::round(6 * scale), Math::round(4 * scale));
theme->set_stylebox("normal", "CheckButton", cb_empty);
- theme->set_stylebox("pressed", "CheckButton", cb_empty);
+ theme->set_stylebox(SceneStringName(pressed), "CheckButton", cb_empty);
theme->set_stylebox("disabled", "CheckButton", cb_empty);
theme->set_stylebox("hover", "CheckButton", cb_empty);
theme->set_stylebox("hover_pressed", "CheckButton", cb_empty);
@@ -373,12 +374,12 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox("normal", "FlatButton", flat_button_normal);
theme->set_stylebox("hover", "FlatButton", flat_button_normal);
- theme->set_stylebox("pressed", "FlatButton", flat_button_pressed);
+ theme->set_stylebox(SceneStringName(pressed), "FlatButton", flat_button_pressed);
theme->set_stylebox("disabled", "FlatButton", flat_button_normal);
theme->set_stylebox("normal", "FlatMenuButton", flat_button_normal);
theme->set_stylebox("hover", "FlatMenuButton", flat_button_normal);
- theme->set_stylebox("pressed", "FlatMenuButton", flat_button_pressed);
+ theme->set_stylebox(SceneStringName(pressed), "FlatMenuButton", flat_button_pressed);
theme->set_stylebox("disabled", "FlatMenuButton", flat_button_normal);
// Label
@@ -864,7 +865,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("h_separation", "ItemList", Math::round(4 * scale));
theme->set_constant("v_separation", "ItemList", Math::round(4 * scale));
theme->set_constant("icon_margin", "ItemList", Math::round(4 * scale));
- theme->set_constant("line_separation", "ItemList", Math::round(2 * scale));
+ theme->set_constant(SceneStringName(line_separation), "ItemList", Math::round(2 * scale));
theme->set_font("font", "ItemList", Ref<Font>());
theme->set_font_size("font_size", "ItemList", -1);
@@ -1050,7 +1051,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("bg", "ColorPickerButton", icons["mini_checkerboard"]);
theme->set_stylebox("normal", "ColorPickerButton", button_normal);
- theme->set_stylebox("pressed", "ColorPickerButton", button_pressed);
+ theme->set_stylebox(SceneStringName(pressed), "ColorPickerButton", button_pressed);
theme->set_stylebox("hover", "ColorPickerButton", button_hover);
theme->set_stylebox("disabled", "ColorPickerButton", button_disabled);
theme->set_stylebox("focus", "ColorPickerButton", focus);
@@ -1125,7 +1126,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("shadow_offset_y", "RichTextLabel", Math::round(1 * scale));
theme->set_constant("shadow_outline_size", "RichTextLabel", Math::round(1 * scale));
- theme->set_constant("line_separation", "RichTextLabel", 0);
+ theme->set_constant(SceneStringName(line_separation), "RichTextLabel", 0);
theme->set_constant("table_h_separation", "RichTextLabel", Math::round(3 * scale));
theme->set_constant("table_v_separation", "RichTextLabel", Math::round(3 * scale));
diff --git a/scene/theme/icons/SCsub b/scene/theme/icons/SCsub
index 46133ccceb..1f3b7f6d17 100644
--- a/scene/theme/icons/SCsub
+++ b/scene/theme/icons/SCsub
@@ -4,7 +4,6 @@ Import("env")
import default_theme_icons_builders
-
env["BUILDERS"]["MakeDefaultThemeIconsBuilder"] = Builder(
action=env.Run(default_theme_icons_builders.make_default_theme_icons_action),
suffix=".h",
diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp
index 4c82e5ba5f..7249fd7ba8 100644
--- a/scene/theme/theme_db.cpp
+++ b/scene/theme/theme_db.cpp
@@ -225,7 +225,7 @@ ThemeContext *ThemeDB::create_theme_context(Node *p_node, List<Ref<Theme>> &p_th
theme_contexts[p_node] = context;
_propagate_theme_context(p_node, context);
- p_node->connect("tree_exited", callable_mp(this, &ThemeDB::destroy_theme_context).bind(p_node));
+ p_node->connect(SceneStringName(tree_exited), callable_mp(this, &ThemeDB::destroy_theme_context).bind(p_node));
return context;
}
@@ -233,7 +233,7 @@ ThemeContext *ThemeDB::create_theme_context(Node *p_node, List<Ref<Theme>> &p_th
void ThemeDB::destroy_theme_context(Node *p_node) {
ERR_FAIL_COND(!theme_contexts.has(p_node));
- p_node->disconnect("tree_exited", callable_mp(this, &ThemeDB::destroy_theme_context));
+ p_node->disconnect(SceneStringName(tree_exited), callable_mp(this, &ThemeDB::destroy_theme_context));
ThemeContext *context = theme_contexts[p_node];
@@ -472,7 +472,7 @@ ThemeDB::~ThemeDB() {
}
void ThemeContext::_emit_changed() {
- emit_signal(SNAME("changed"));
+ emit_signal(CoreStringName(changed));
}
void ThemeContext::set_themes(List<Ref<Theme>> &p_themes) {
diff --git a/scene/theme/theme_owner.cpp b/scene/theme/theme_owner.cpp
index 8cdffe8c73..a25adddc09 100644
--- a/scene/theme/theme_owner.cpp
+++ b/scene/theme/theme_owner.cpp
@@ -69,18 +69,18 @@ bool ThemeOwner::has_owner_node() const {
void ThemeOwner::set_owner_context(ThemeContext *p_context, bool p_propagate) {
ThemeContext *default_context = ThemeDB::get_singleton()->get_default_theme_context();
- if (owner_context && owner_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) {
- owner_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
- } else if (default_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) {
- default_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
+ if (owner_context && owner_context->is_connected(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) {
+ owner_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
+ } else if (default_context->is_connected(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) {
+ default_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
}
owner_context = p_context;
if (owner_context) {
- owner_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
+ owner_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
} else {
- default_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
+ default_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
}
if (p_propagate) {