diff options
Diffstat (limited to 'scene')
113 files changed, 2430 insertions, 831 deletions
diff --git a/scene/2d/SCsub b/scene/2d/SCsub index 94e1ab6c96..6f6bf9818c 100644 --- a/scene/2d/SCsub +++ b/scene/2d/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 9c9ba93b41..754afb0527 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -1288,7 +1288,7 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,suffix:s"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index bfbdb49f22..cfdcbee86a 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -821,19 +821,17 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amount_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_amount_ratio", "get_amount_ratio"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticleProcessMaterial,ShaderMaterial"), "set_process_material", "get_process_material"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.001"), "set_interp_to_end", "get_interp_to_end"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,suffix:s"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.001"), "set_interp_to_end", "get_interp_to_end"); ADD_GROUP("Collision", "collision_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size"); ADD_GROUP("Drawing", ""); @@ -845,7 +843,9 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_lifetime", PROPERTY_HINT_RANGE, "0.01,10,0.01,or_greater,suffix:s"), "set_trail_lifetime", "get_trail_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_trail_sections", "get_trail_sections"); ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_section_subdivisions", PROPERTY_HINT_RANGE, "1,1024,1"), "set_trail_section_subdivisions", "get_trail_section_subdivisions"); - + ADD_GROUP("Process Material", ""); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticleProcessMaterial,ShaderMaterial"), "set_process_material", "get_process_material"); BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME); diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp index fdb2d2cdd0..c6176390dc 100644 --- a/scene/2d/parallax_2d.cpp +++ b/scene/2d/parallax_2d.cpp @@ -83,7 +83,11 @@ void Parallax2D::_validate_property(PropertyInfo &p_property) const { void Parallax2D::_camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset, const Point2 &p_adj_screen_pos) { if (!ignore_camera_scroll) { if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { - set_screen_offset((p_adj_screen_pos + Vector2(0.5, 0.5)).floor()); + Size2 vps = get_viewport_rect().size; + Vector2 offset; + offset.x = ((int)vps.width % 2) ? 0.0 : 0.5; + offset.y = ((int)vps.height % 2) ? 0.0 : 0.5; + set_screen_offset((p_adj_screen_pos + offset).floor()); } else { set_screen_offset(p_adj_screen_pos); } diff --git a/scene/2d/physics/SCsub b/scene/2d/physics/SCsub index e7fd3fe643..5f9747514a 100644 --- a/scene/2d/physics/SCsub +++ b/scene/2d/physics/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/2d/physics/joints/SCsub b/scene/2d/physics/joints/SCsub index fc61250247..374dc2119d 100644 --- a/scene/2d/physics/joints/SCsub +++ b/scene/2d/physics/joints/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/2d/physics/shape_cast_2d.cpp b/scene/2d/physics/shape_cast_2d.cpp index b92978bcad..dd9d589165 100644 --- a/scene/2d/physics/shape_cast_2d.cpp +++ b/scene/2d/physics/shape_cast_2d.cpp @@ -34,7 +34,7 @@ #include "scene/2d/physics/collision_object_2d.h" #include "scene/2d/physics/physics_body_2d.h" #include "scene/resources/2d/circle_shape_2d.h" -#include "servers/physics_2d/godot_physics_server_2d.h" +#include "servers/physics_server_2d.h" void ShapeCast2D::set_target_position(const Vector2 &p_point) { target_position = p_point; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 90bfb4c84c..39cfccf983 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -159,7 +159,7 @@ void Bone2D::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { if (skeleton) { - for (int i = 0; i < skeleton->bones.size(); i++) { + for (uint32_t i = 0; i < skeleton->bones.size(); i++) { if (skeleton->bones[i].bone == this) { skeleton->bones.remove_at(i); break; @@ -555,17 +555,17 @@ void Skeleton2D::_update_bone_setup() { bones.sort(); //sorting so that they are always in the same order/index - for (int i = 0; i < bones.size(); i++) { - bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose - bones.write[i].bone->skeleton_index = i; + for (uint32_t i = 0; i < bones.size(); i++) { + bones[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose + bones[i].bone->skeleton_index = i; Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent()); if (parent_bone) { - bones.write[i].parent_index = parent_bone->skeleton_index; + bones[i].parent_index = parent_bone->skeleton_index; } else { - bones.write[i].parent_index = -1; + bones[i].parent_index = -1; } - bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest(); + bones[i].local_pose_override = bones[i].bone->get_skeleton_rest(); } transform_dirty = true; @@ -594,16 +594,16 @@ void Skeleton2D::_update_transform() { transform_dirty = false; - for (int i = 0; i < bones.size(); i++) { - ERR_CONTINUE(bones[i].parent_index >= i); + for (uint32_t i = 0; i < bones.size(); i++) { + ERR_CONTINUE(bones[i].parent_index >= (int)i); if (bones[i].parent_index >= 0) { - bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform(); + bones[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform(); } else { - bones.write[i].accum_transform = bones[i].bone->get_transform(); + bones[i].accum_transform = bones[i].bone->get_transform(); } } - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse; RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform); } @@ -621,7 +621,7 @@ int Skeleton2D::get_bone_count() const { Bone2D *Skeleton2D::get_bone(int p_idx) { ERR_FAIL_COND_V(!is_inside_tree(), nullptr); - ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr); + ERR_FAIL_INDEX_V(p_idx, (int)bones.size(), nullptr); return bones[p_idx].bone; } @@ -733,14 +733,14 @@ RID Skeleton2D::get_skeleton() const { } void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent) { - ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!"); - bones.write[p_bone_idx].local_pose_override = p_override; - bones.write[p_bone_idx].local_pose_override_amount = p_amount; - bones.write[p_bone_idx].local_pose_override_persistent = p_persistent; + ERR_FAIL_INDEX_MSG(p_bone_idx, (int)bones.size(), "Bone index is out of range!"); + bones[p_bone_idx].local_pose_override = p_override; + bones[p_bone_idx].local_pose_override_amount = p_amount; + bones[p_bone_idx].local_pose_override_persistent = p_persistent; } Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) { - ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!"); + ERR_FAIL_INDEX_V_MSG(p_bone_idx, (int)bones.size(), Transform2D(), "Bone index is out of range!"); return bones[p_bone_idx].local_pose_override; } @@ -771,7 +771,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) { } // Do not cache the transform changes caused by the modifications! - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { bones[i].bone->copy_transform_to_cache = false; } @@ -783,7 +783,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) { // Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform. if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) { - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { if (bones[i].local_pose_override_amount > 0) { bones[i].bone->set_meta("_local_pose_override_enabled_", true); @@ -793,7 +793,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) { bones[i].bone->propagate_call("force_update_transform"); if (bones[i].local_pose_override_persistent) { - bones.write[i].local_pose_override_amount = 0.0; + bones[i].local_pose_override_amount = 0.0; } } else { // TODO: see if there is a way to undo the override without having to resort to setting every bone's transform. @@ -804,7 +804,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) { } // Cache any future transform changes - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { bones[i].bone->copy_transform_to_cache = true; } diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 033bdff41d..16bd6fd34b 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -123,7 +123,7 @@ class Skeleton2D : public Node2D { bool local_pose_override_persistent = false; }; - Vector<Bone> bones; + LocalVector<Bone> bones; bool bone_setup_dirty = true; void _make_bone_setup_dirty(); diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index cc0a5b49fb..6cb481849c 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -90,7 +90,7 @@ public: TerrainConstraint(Ref<TileSet> p_tile_set, const Vector2i &p_position, int p_terrain); // For the center terrain bit TerrainConstraint(Ref<TileSet> p_tile_set, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits - TerrainConstraint(){}; + TerrainConstraint() {} }; #ifdef DEBUG_ENABLED diff --git a/scene/3d/SCsub b/scene/3d/SCsub index 94e1ab6c96..6f6bf9818c 100644 --- a/scene/3d/SCsub +++ b/scene/3d/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 591528b915..98bee2115c 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -401,10 +401,19 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { total_max = MAX(total_max, listener_area_pos.length()); } - if (total_max > max_distance) { + if (dist > total_max || total_max > max_distance) { + if (!was_further_than_max_distance_last_frame) { + HashMap<StringName, Vector<AudioFrame>> bus_volumes; + for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) { + // So the player gets muted and mostly stops mixing when out of range. + AudioServer::get_singleton()->set_playback_bus_volumes_linear(playback, bus_volumes); + } + was_further_than_max_distance_last_frame = true; // Cache so we don't set the volume over and over. + } continue; //can't hear this sound in this listener } } + was_further_than_max_distance_last_frame = false; float multiplier = Math::db_to_linear(_get_attenuation_db(dist)); if (max_distance > 0) { diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index 72356faad7..91104a06c7 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -105,6 +105,7 @@ private: float linear_attenuation = 0; float max_distance = 0.0; + bool was_further_than_max_distance_last_frame = false; Ref<VelocityTracker3D> velocity_tracker; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index acbc443a93..5b84bf903f 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -1479,6 +1479,7 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles3D::get_mesh); ClassDB::bind_method(D_METHOD("restart"), &CPUParticles3D::restart); + ClassDB::bind_method(D_METHOD("capture_aabb"), &CPUParticles3D::capture_aabb); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 26a574cd26..a1f32fccbe 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1602,8 +1602,17 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const { PackedStringArray LightmapGI::get_configuration_warnings() const { PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); - if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { - warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail.")); +#ifndef MODULE_LIGHTMAPPER_RD_ENABLED +#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) + warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name())); +#else + warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work.")); +#endif + return warnings; +#endif + + if (!DisplayServer::get_singleton()->can_create_rendering_device()) { + warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name())); return warnings; } diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index f551cb401c..e8166802f8 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -332,8 +332,8 @@ void MeshInstance3D::create_multiple_convex_collisions(const Ref<MeshConvexDecom void MeshInstance3D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - _resolve_skeleton_path(); + case NOTIFICATION_READY: { + callable_mp(this, &MeshInstance3D::_resolve_skeleton_path).call_deferred(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { if (mesh.is_valid()) { diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 86ce8a881a..85de85a9a6 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -1181,15 +1181,16 @@ void Node3D::_validate_property(PropertyInfo &p_property) const { } bool Node3D::_property_can_revert(const StringName &p_name) const { - if (p_name == "basis") { + const String sname = p_name; + if (sname == "basis") { return true; - } else if (p_name == "scale") { + } else if (sname == "scale") { return true; - } else if (p_name == "quaternion") { + } else if (sname == "quaternion") { return true; - } else if (p_name == "rotation") { + } else if (sname == "rotation") { return true; - } else if (p_name == "position") { + } else if (sname == "position") { return true; } return false; @@ -1198,35 +1199,36 @@ bool Node3D::_property_can_revert(const StringName &p_name) const { bool Node3D::_property_get_revert(const StringName &p_name, Variant &r_property) const { bool valid = false; - if (p_name == "basis") { + const String sname = p_name; + if (sname == "basis") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { r_property = Transform3D(variant).get_basis(); } else { r_property = Basis(); } - } else if (p_name == "scale") { + } else if (sname == "scale") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { r_property = Transform3D(variant).get_basis().get_scale(); } else { r_property = Vector3(1.0, 1.0, 1.0); } - } else if (p_name == "quaternion") { + } else if (sname == "quaternion") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { r_property = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion()); } else { r_property = Quaternion(); } - } else if (p_name == "rotation") { + } else if (sname == "rotation") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { r_property = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order); } else { r_property = Vector3(); } - } else if (p_name == "position") { + } else if (sname == "position") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid) { r_property = Transform3D(variant).get_origin(); diff --git a/scene/3d/physics/SCsub b/scene/3d/physics/SCsub index e7fd3fe643..5f9747514a 100644 --- a/scene/3d/physics/SCsub +++ b/scene/3d/physics/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/3d/physics/character_body_3d.cpp b/scene/3d/physics/character_body_3d.cpp index dda3ea9cca..e3815e8219 100644 --- a/scene/3d/physics/character_body_3d.cpp +++ b/scene/3d/physics/character_body_3d.cpp @@ -60,8 +60,13 @@ bool CharacterBody3D::move_and_slide() { // We need to check the platform_rid object still exists before accessing. // A valid RID is no guarantee that the object has not been deleted. - if (ObjectDB::get_instance(platform_object_id)) { - //this approach makes sure there is less delay between the actual body velocity and the one we saved + + // We can only perform the ObjectDB lifetime check on Object derived objects. + // Note that physics also creates RIDs for non-Object derived objects, these cannot + // be lifetime checked through ObjectDB, and therefore there is a still a vulnerability + // to dangling RIDs (access after free) in this scenario. + if (platform_object_id.is_null() || ObjectDB::get_instance(platform_object_id)) { + // This approach makes sure there is less delay between the actual body velocity and the one we saved. bs = PhysicsServer3D::get_singleton()->body_get_direct_state(platform_rid); } diff --git a/scene/3d/physics/joints/SCsub b/scene/3d/physics/joints/SCsub index fc61250247..374dc2119d 100644 --- a/scene/3d/physics/joints/SCsub +++ b/scene/3d/physics/joints/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp index 5073705145..981e872af2 100644 --- a/scene/3d/physics/vehicle_body_3d.cpp +++ b/scene/3d/physics/vehicle_body_3d.cpp @@ -297,11 +297,11 @@ void VehicleWheel3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "wheel_friction_slip"), "set_friction_slip", "get_friction_slip"); ADD_GROUP("Suspension", "suspension_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_travel", PROPERTY_HINT_NONE, "suffix:m"), "set_suspension_travel", "get_suspension_travel"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness"), "set_suspension_stiffness", "get_suspension_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_stiffness", PROPERTY_HINT_NONE, U"suffix:N/mm"), "set_suspension_stiffness", "get_suspension_stiffness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "suspension_max_force", PROPERTY_HINT_NONE, U"suffix:kg\u22C5m/s\u00B2 (N)"), "set_suspension_max_force", "get_suspension_max_force"); ADD_GROUP("Damping", "damping_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression"), "set_damping_compression", "get_damping_compression"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation"), "set_damping_relaxation", "get_damping_relaxation"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_compression", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_compression", "get_damping_compression"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping_relaxation", PROPERTY_HINT_NONE, U"suffix:N\u22C5s/mm"), "set_damping_relaxation", "get_damping_relaxation"); } void VehicleWheel3D::set_engine_force(real_t p_engine_force) { diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index db9c4db30d..9e4c9b1832 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -81,7 +81,7 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { return false; } - int which = path.get_slicec('/', 1).to_int(); + uint32_t which = path.get_slicec('/', 1).to_int(); String what = path.get_slicec('/', 2); if (which == bones.size() && what == "name") { @@ -89,7 +89,7 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) { return true; } - ERR_FAIL_INDEX_V(which, bones.size(), false); + ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false); if (what == "parent") { set_bone_parent(which, p_value); @@ -153,10 +153,10 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const { return false; } - int which = path.get_slicec('/', 1).to_int(); + uint32_t which = path.get_slicec('/', 1).to_int(); String what = path.get_slicec('/', 2); - ERR_FAIL_INDEX_V(which, bones.size(), false); + ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false); if (what == "name") { r_ret = get_bone_name(which); @@ -182,7 +182,7 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const { } void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { const String prep = vformat("%s/%d/", PNAME("bones"), i); p_list->push_back(PropertyInfo(Variant::STRING, prep + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); p_list->push_back(PropertyInfo(Variant::INT, prep + PNAME("parent"), PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR)); @@ -241,7 +241,7 @@ void Skeleton3D::_update_process_order() { return; } - Bone *bonesptr = bones.ptrw(); + Bone *bonesptr = bones.ptr(); int len = bones.size(); parentless_bones.clear(); @@ -276,6 +276,8 @@ void Skeleton3D::_update_process_order() { concatenated_bone_names = StringName(); + _update_bones_nested_set(); + process_order_dirty = false; emit_signal("bone_list_changed"); @@ -283,7 +285,7 @@ void Skeleton3D::_update_process_order() { void Skeleton3D::_update_bone_names() const { String names; - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { if (i > 0) { names += ","; } @@ -331,16 +333,21 @@ void Skeleton3D::_notification(int p_what) { updating = true; - Bone *bonesptr = bones.ptrw(); + Bone *bonesptr = bones.ptr(); int len = bones.size(); + thread_local LocalVector<bool> bone_global_pose_dirty_backup; + // Process modifiers. _find_modifiers(); if (!modifiers.is_empty()) { // Store unmodified bone poses. - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { bones_backup[i].save(bones[i]); } + // Store dirty flags for global bone poses. + bone_global_pose_dirty_backup = bone_global_pose_dirty; + _process_modifiers(); } @@ -412,9 +419,11 @@ void Skeleton3D::_notification(int p_what) { if (!modifiers.is_empty()) { // Restore unmodified bone poses. - for (int i = 0; i < bones.size(); i++) { - bones_backup[i].restore(bones.write[i]); + for (uint32_t i = 0; i < bones.size(); i++) { + bones_backup[i].restore(bones[i]); } + // Restore dirty flags for global bone poses. + bone_global_pose_dirty = bone_global_pose_dirty_backup; } updating = false; @@ -457,10 +466,111 @@ void Skeleton3D::_make_modifiers_dirty() { _update_deferred(UPDATE_FLAG_MODIFIER); } +void Skeleton3D::_update_bones_nested_set() { + nested_set_offset_to_bone_index.resize(bones.size()); + bone_global_pose_dirty.resize(bones.size()); + _make_bone_global_poses_dirty(); + + int offset = 0; + for (int bone : parentless_bones) { + offset += _update_bone_nested_set(bone, offset); + } +} + +int Skeleton3D::_update_bone_nested_set(int p_bone, int p_offset) { + Bone &bone = bones[p_bone]; + int offset = p_offset + 1; + int span = 1; + + for (int child_bone : bone.child_bones) { + int subspan = _update_bone_nested_set(child_bone, offset); + offset += subspan; + span += subspan; + } + + nested_set_offset_to_bone_index[p_offset] = p_bone; + bone.nested_set_offset = p_offset; + bone.nested_set_span = span; + + return span; +} + +void Skeleton3D::_make_bone_global_poses_dirty() { + for (uint32_t i = 0; i < bone_global_pose_dirty.size(); i++) { + bone_global_pose_dirty[i] = true; + } +} + +void Skeleton3D::_make_bone_global_pose_subtree_dirty(int p_bone) { + if (process_order_dirty) { + return; + } + + const Bone &bone = bones[p_bone]; + int span_offset = bone.nested_set_offset; + // No need to make subtree dirty when bone is already dirty. + if (bone_global_pose_dirty[span_offset]) { + return; + } + + // Make global poses of subtree dirty. + int span_end = span_offset + bone.nested_set_span; + for (int i = span_offset; i < span_end; i++) { + bone_global_pose_dirty[i] = true; + } +} + +void Skeleton3D::_update_bone_global_pose(int p_bone) { + const int bone_size = bones.size(); + ERR_FAIL_INDEX(p_bone, bone_size); + + _update_process_order(); + + // Global pose is already calculated. + int nested_set_offset = bones[p_bone].nested_set_offset; + if (!bone_global_pose_dirty[nested_set_offset]) { + return; + } + + thread_local LocalVector<int> bone_list; + bone_list.clear(); + Transform3D global_pose; + + // Create list of parent bones for which the global pose needs to be recalculated. + for (int bone = p_bone; bone >= 0; bone = bones[bone].parent) { + int offset = bones[bone].nested_set_offset; + // Stop searching when global pose is not dirty. + if (!bone_global_pose_dirty[offset]) { + global_pose = bones[bone].global_pose; + break; + } + + bone_list.push_back(bone); + } + + // Calculate global poses for all parent bones and the current bone. + for (int i = bone_list.size() - 1; i >= 0; i--) { + int bone_idx = bone_list[i]; + Bone &bone = bones[bone_idx]; + bool bone_enabled = bone.enabled && !show_rest_only; + Transform3D bone_pose = bone_enabled ? get_bone_pose(bone_idx) : get_bone_rest(bone_idx); + + global_pose *= bone_pose; +#ifndef DISABLE_DEPRECATED + if (bone.global_pose_override_amount >= CMP_EPSILON) { + global_pose = global_pose.interpolate_with(bone.global_pose_override, bone.global_pose_override_amount); + } +#endif // _DISABLE_DEPRECATED + + bone.global_pose = global_pose; + bone_global_pose_dirty[bone.nested_set_offset] = false; + } +} + Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const { const int bone_size = bones.size(); ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - const_cast<Skeleton3D *>(this)->force_update_all_dirty_bones(); + const_cast<Skeleton3D *>(this)->_update_bone_global_pose(p_bone); return bones[p_bone].global_pose; } @@ -534,7 +644,7 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) { } name_to_bone_index.erase(bones[p_bone].name); - bones.write[p_bone].name = p_name; + bones[p_bone].name = p_name; name_to_bone_index.insert(p_name, p_bone); version++; @@ -582,13 +692,13 @@ void Skeleton3D::set_bone_meta(int p_bone, const StringName &p_key, const Varian ERR_FAIL_INDEX(p_bone, bone_size); if (p_value.get_type() == Variant::NIL) { - if (bones.write[p_bone].metadata.has(p_key)) { - bones.write[p_bone].metadata.erase(p_key); + if (bones[p_bone].metadata.has(p_key)) { + bones[p_bone].metadata.erase(p_key); } return; } - bones.write[p_bone].metadata.insert(p_key, p_value, false); + bones[p_bone].metadata.insert(p_key, p_value, false); } bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const { @@ -615,7 +725,7 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) { ERR_FAIL_COND(p_parent != -1 && (p_parent < 0)); ERR_FAIL_COND(p_bone == p_parent); - bones.write[p_bone].parent = p_parent; + bones[p_bone].parent = p_parent; process_order_dirty = true; rest_dirty = true; _make_dirty(); @@ -629,11 +739,11 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) { int parent = bones[p_bone].parent; while (parent >= 0) { - bones.write[p_bone].rest = bones[parent].rest * bones[p_bone].rest; + bones[p_bone].rest = bones[parent].rest * bones[p_bone].rest; parent = bones[parent].parent; } - bones.write[p_bone].parent = -1; + bones[p_bone].parent = -1; process_order_dirty = true; rest_dirty = true; @@ -669,9 +779,10 @@ void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].rest = p_rest; + bones[p_bone].rest = p_rest; rest_dirty = true; _make_dirty(); + _make_bone_global_pose_subtree_dirty(p_bone); } Transform3D Skeleton3D::get_bone_rest(int p_bone) const { const int bone_size = bones.size(); @@ -692,9 +803,10 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].enabled = p_enabled; + bones[p_bone].enabled = p_enabled; emit_signal(SceneStringName(bone_enabled_changed), p_bone); _make_dirty(); + _make_bone_global_pose_subtree_dirty(p_bone); } bool Skeleton3D::is_bone_enabled(int p_bone) const { @@ -707,6 +819,7 @@ void Skeleton3D::set_show_rest_only(bool p_enabled) { show_rest_only = p_enabled; emit_signal(SceneStringName(show_rest_only_changed)); _make_dirty(); + _make_bone_global_poses_dirty(); } bool Skeleton3D::is_show_rest_only() const { @@ -727,12 +840,13 @@ void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].pose_position = p_pose.origin; - bones.write[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion(); - bones.write[p_bone].pose_scale = p_pose.basis.get_scale(); - bones.write[p_bone].pose_cache_dirty = true; + bones[p_bone].pose_position = p_pose.origin; + bones[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion(); + bones[p_bone].pose_scale = p_pose.basis.get_scale(); + bones[p_bone].pose_cache_dirty = true; if (is_inside_tree()) { _make_dirty(); + _make_bone_global_pose_subtree_dirty(p_bone); } } @@ -740,30 +854,33 @@ void Skeleton3D::set_bone_pose_position(int p_bone, const Vector3 &p_position) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].pose_position = p_position; - bones.write[p_bone].pose_cache_dirty = true; + bones[p_bone].pose_position = p_position; + bones[p_bone].pose_cache_dirty = true; if (is_inside_tree()) { _make_dirty(); + _make_bone_global_pose_subtree_dirty(p_bone); } } void Skeleton3D::set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].pose_rotation = p_rotation; - bones.write[p_bone].pose_cache_dirty = true; + bones[p_bone].pose_rotation = p_rotation; + bones[p_bone].pose_cache_dirty = true; if (is_inside_tree()) { _make_dirty(); + _make_bone_global_pose_subtree_dirty(p_bone); } } void Skeleton3D::set_bone_pose_scale(int p_bone, const Vector3 &p_scale) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].pose_scale = p_scale; - bones.write[p_bone].pose_cache_dirty = true; + bones[p_bone].pose_scale = p_scale; + bones[p_bone].pose_cache_dirty = true; if (is_inside_tree()) { _make_dirty(); + _make_bone_global_pose_subtree_dirty(p_bone); } } @@ -794,7 +911,7 @@ void Skeleton3D::reset_bone_pose(int p_bone) { } void Skeleton3D::reset_bone_poses() { - for (int i = 0; i < bones.size(); i++) { + for (uint32_t i = 0; i < bones.size(); i++) { reset_bone_pose(i); } } @@ -802,7 +919,7 @@ void Skeleton3D::reset_bone_poses() { Transform3D Skeleton3D::get_bone_pose(int p_bone) const { const int bone_size = bones.size(); ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - const_cast<Skeleton3D *>(this)->bones.write[p_bone].update_pose_cache(); + const_cast<Skeleton3D *>(this)->bones[p_bone].update_pose_cache(); return bones[p_bone].pose_cache; } @@ -853,7 +970,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() { // Pose changed, rebuild cache of inverses. const Bone *bonesptr = bones.ptr(); - int len = bones.size(); + uint32_t len = bones.size(); // Calculate global rests and invert them. LocalVector<int> bones_to_process; @@ -877,7 +994,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() { } } - for (int i = 0; i < len; i++) { + for (uint32_t i = 0; i < len; i++) { // The inverse is what is actually required. skin->set_bind_bone(i, i); skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse()); @@ -937,15 +1054,15 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone_idx, bone_size); - Bone *bonesptr = bones.ptrw(); - thread_local LocalVector<int> bones_to_process; - bones_to_process.clear(); - bones_to_process.push_back(p_bone_idx); + Bone *bonesptr = bones.ptr(); - uint32_t index = 0; - while (index < bones_to_process.size()) { - int current_bone_idx = bones_to_process[index]; + // Loop through nested set. + for (int offset = 0; offset < bone_size; offset++) { + if (!bone_global_pose_dirty[offset]) { + continue; + } + int current_bone_idx = nested_set_offset_to_bone_index[offset]; Bone &b = bonesptr[current_bone_idx]; bool bone_enabled = b.enabled && !show_rest_only; @@ -992,13 +1109,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { } #endif // _DISABLE_DEPRECATED - // Add the bone's children to the list of bones to be processed. - int child_bone_size = b.child_bones.size(); - for (int i = 0; i < child_bone_size; i++) { - bones_to_process.push_back(b.child_bones[i]); - } - - index++; + bone_global_pose_dirty[offset] = false; } } @@ -1171,20 +1282,22 @@ void Skeleton3D::_bind_methods() { #ifndef DISABLE_DEPRECATED void Skeleton3D::clear_bones_global_pose_override() { - for (int i = 0; i < bones.size(); i += 1) { - bones.write[i].global_pose_override_amount = 0; - bones.write[i].global_pose_override_reset = true; + for (uint32_t i = 0; i < bones.size(); i += 1) { + bones[i].global_pose_override_amount = 0; + bones[i].global_pose_override_reset = true; } _make_dirty(); + _make_bone_global_poses_dirty(); } void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].global_pose_override_amount = p_amount; - bones.write[p_bone].global_pose_override = p_pose; - bones.write[p_bone].global_pose_override_reset = !p_persistent; + bones[p_bone].global_pose_override_amount = p_amount; + bones[p_bone].global_pose_override = p_pose; + bones[p_bone].global_pose_override_reset = !p_persistent; _make_dirty(); + _make_bone_global_pose_subtree_dirty(p_bone); } Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const { diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 07bdeccf2f..0db12600c3 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -107,6 +107,8 @@ private: Quaternion pose_rotation; Vector3 pose_scale = Vector3(1, 1, 1); Transform3D global_pose; + int nested_set_offset = 0; // Offset in nested set of bone hierarchy. + int nested_set_span = 0; // Subtree span in nested set of bone hierarchy. void update_pose_cache() { if (pose_cache_dirty) { @@ -153,7 +155,7 @@ private: HashSet<SkinReference *> skin_bindings; void _skin_changed(); - Vector<Bone> bones; + LocalVector<Bone> bones; bool process_order_dirty = false; Vector<int> parentless_bones; @@ -183,6 +185,15 @@ private: void _make_modifiers_dirty(); LocalVector<BonePoseBackup> bones_backup; + // Global bone pose calculation. + LocalVector<int> nested_set_offset_to_bone_index; // Map from Bone::nested_set_offset to bone index. + LocalVector<bool> bone_global_pose_dirty; // Indexable with Bone::nested_set_offset. + void _update_bones_nested_set(); + int _update_bone_nested_set(int p_bone, int p_offset); + void _make_bone_global_poses_dirty(); + void _make_bone_global_pose_subtree_dirty(int p_bone); + void _update_bone_global_pose(int p_bone); + #ifndef DISABLE_DEPRECATED void _add_bone_bind_compat_88791(const String &p_name); diff --git a/scene/SCsub b/scene/SCsub index b4b2d6dd0a..1eb4ffa53d 100644 --- a/scene/SCsub +++ b/scene/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/animation/SCsub b/scene/animation/SCsub index d0aa0bc8aa..dd2b22c2e3 100644 --- a/scene/animation/SCsub +++ b/scene/animation/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index cdc85d2b2d..a2aef60417 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -245,6 +245,8 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe if (!p_test_only) { AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.start = 0.0; + pi.end = cur_len; if (play_mode == PLAY_MODE_FORWARD) { pi.time = cur_playback_time; pi.delta = cur_delta; @@ -1139,7 +1141,11 @@ void AnimationNodeTransition::remove_input(int p_index) { bool AnimationNodeTransition::set_input_name(int p_input, const String &p_name) { pending_update = true; - return AnimationNode::set_input_name(p_input, p_name); + if (!AnimationNode::set_input_name(p_input, p_name)) { + return false; + } + emit_signal(SNAME("tree_changed")); // For updating enum options. + return true; } void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) { diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 664302d45e..eb8bc8c382 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -1117,6 +1117,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Ref<Animation> a = ai.animation_data.animation; double time = ai.playback_info.time; double delta = ai.playback_info.delta; + double start = ai.playback_info.start; + double end = ai.playback_info.end; bool seeked = ai.playback_info.seeked; Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag; bool is_external_seeking = ai.playback_info.is_external_seeking; @@ -1168,32 +1170,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { if (track->root_motion && calc_root) { double prev_time = time - delta; if (!backward) { - if (Animation::is_less_approx(prev_time, 0)) { + if (Animation::is_less_approx(prev_time, start)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = 0; + prev_time = start; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a_length); + prev_time = Math::fposmod(prev_time - start, end - start) + start; } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a_length); + prev_time = Math::pingpong(prev_time - start, end - start) + start; } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a_length)) { + if (Animation::is_greater_approx(prev_time, end)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a_length; + prev_time = end; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a_length); + prev_time = Math::fposmod(prev_time - start, end - start) + start; } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a_length); + prev_time = Math::pingpong(prev_time - start, end - start) + start; } break; default: break; @@ -1208,10 +1210,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, (double)a_length, &loc[1]); + a->try_position_track_interpolate(i, end, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = 0; + prev_time = start; } } else { if (Animation::is_less_approx(prev_time, time)) { @@ -1220,10 +1222,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, 0, &loc[1]); + a->try_position_track_interpolate(i, start, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = (double)a_length; + prev_time = end; } } Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); @@ -1234,7 +1236,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_position_track_interpolate(i, time, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = !backward ? 0 : (double)a_length; + prev_time = !backward ? start : end; } { Vector3 loc; @@ -1256,32 +1258,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { if (track->root_motion && calc_root) { double prev_time = time - delta; if (!backward) { - if (Animation::is_less_approx(prev_time, 0)) { + if (Animation::is_less_approx(prev_time, start)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = 0; + prev_time = start; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a_length); + prev_time = Math::fposmod(prev_time - start, end - start) + start; } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a_length); + prev_time = Math::pingpong(prev_time - start, end - start) + start; } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a_length)) { + if (Animation::is_greater_approx(prev_time, end)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a_length; + prev_time = end; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a_length); + prev_time = Math::fposmod(prev_time - start, end - start) + start; } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a_length); + prev_time = Math::pingpong(prev_time - start, end - start) + start; } break; default: break; @@ -1296,10 +1298,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(i, (double)a_length, &rot[1]); + a->try_rotation_track_interpolate(i, end, &rot[1]); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = 0; + prev_time = start; } } else { if (Animation::is_less_approx(prev_time, time)) { @@ -1308,9 +1310,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(i, 0, &rot[1]); + a->try_rotation_track_interpolate(i, start, &rot[1]); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = (double)a_length; + prev_time = end; } } Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); @@ -1321,7 +1323,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_rotation_track_interpolate(i, time, &rot[1]); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = !backward ? 0 : (double)a_length; + prev_time = !backward ? start : end; } { Quaternion rot; @@ -1343,32 +1345,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { if (track->root_motion && calc_root) { double prev_time = time - delta; if (!backward) { - if (Animation::is_less_approx(prev_time, 0)) { + if (Animation::is_less_approx(prev_time, start)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = 0; + prev_time = start; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a_length); + prev_time = Math::fposmod(prev_time - start, end - start) + start; } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a_length); + prev_time = Math::pingpong(prev_time - start, end - start) + start; } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a_length)) { + if (Animation::is_greater_approx(prev_time, end)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a_length; + prev_time = end; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a_length); + prev_time = Math::fposmod(prev_time - start, end - start) + start; } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a_length); + prev_time = Math::pingpong(prev_time - start, end - start) + start; } break; default: break; @@ -1383,10 +1385,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx); - a->try_scale_track_interpolate(i, (double)a_length, &scale[1]); + a->try_scale_track_interpolate(i, end, &scale[1]); root_motion_cache.scale += (scale[1] - scale[0]) * blend; scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); - prev_time = 0; + prev_time = start; } } else { if (Animation::is_less_approx(prev_time, time)) { @@ -1395,10 +1397,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx); - a->try_scale_track_interpolate(i, 0, &scale[1]); + a->try_scale_track_interpolate(i, start, &scale[1]); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = (double)a_length; + prev_time = end; } } Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); @@ -1409,7 +1411,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_scale_track_interpolate(i, time, &scale[1]); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = !backward ? 0 : (double)a_length; + prev_time = !backward ? start : end; } { Vector3 scale; @@ -1671,6 +1673,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { if (!player2) { continue; } + // TODO: Make it possible to embed section info in animation track keys. if (seeked) { // Seek. int idx = a->track_find_key(i, time, Animation::FIND_MODE_NEAREST, true); @@ -1683,19 +1686,19 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } Ref<Animation> anim = player2->get_animation(anim_name); - double at_anim_pos = 0.0; + double at_anim_pos = start; switch (anim->get_loop_mode()) { case Animation::LOOP_NONE: { - if (!is_external_seeking && ((!backward && Animation::is_greater_or_equal_approx(time, pos + (double)anim->get_length())) || (backward && Animation::is_less_or_equal_approx(time, pos)))) { + if (!is_external_seeking && ((!backward && Animation::is_greater_or_equal_approx(time, pos + end)) || (backward && Animation::is_less_or_equal_approx(time, pos + start)))) { 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. + at_anim_pos = MIN(end, time - pos); // Seek to end. } break; case Animation::LOOP_LINEAR: { - at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop. + at_anim_pos = Math::fposmod(time - pos - start, end - start) + start; // Seek to loop. } break; case Animation::LOOP_PINGPONG: { - at_anim_pos = Math::pingpong(time - pos, (double)a_length); + at_anim_pos = Math::pingpong(time - pos - start, end - start) + start; } break; default: break; @@ -2092,6 +2095,8 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() { PlaybackInfo pi; pi.time = 0; pi.delta = 0; + pi.start = 0; + pi.end = reset_anim->get_length(); pi.seeked = true; pi.weight = 1.0; make_animation_instance(SceneStringName(RESET), pi); diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 5482197fbd..27c9a00a9c 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -85,6 +85,8 @@ public: struct PlaybackInfo { double time = 0.0; double delta = 0.0; + double start = 0.0; + double end = 0.0; bool seeked = false; bool is_external_seeking = false; Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index a4aa383a9d..8a2ca47920 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -164,39 +164,41 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f double delta = p_started ? 0 : p_delta * speed; double next_pos = cd.pos + delta; - double len = cd.from->animation->get_length(); + double start = get_section_start_time(); + double end = get_section_end_time(); + Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; switch (cd.from->animation->get_loop_mode()) { case Animation::LOOP_NONE: { - if (Animation::is_less_approx(next_pos, 0)) { - next_pos = 0; - } else if (Animation::is_greater_approx(next_pos, len)) { - next_pos = len; + if (Animation::is_less_approx(next_pos, start)) { + next_pos = start; + } else if (Animation::is_greater_approx(next_pos, end)) { + next_pos = end; } delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here). } break; case Animation::LOOP_LINEAR: { - if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) { + if (Animation::is_less_approx(next_pos, start) && Animation::is_greater_or_equal_approx(cd.pos, start)) { looped_flag = Animation::LOOPED_FLAG_START; } - if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) { + if (Animation::is_greater_approx(next_pos, end) && Animation::is_less_or_equal_approx(cd.pos, end)) { looped_flag = Animation::LOOPED_FLAG_END; } - next_pos = Math::fposmod(next_pos, (double)len); + next_pos = Math::fposmod(next_pos - start, end - start) + start; } break; case Animation::LOOP_PINGPONG: { - if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) { + if (Animation::is_less_approx(next_pos, start) && Animation::is_greater_or_equal_approx(cd.pos, start)) { cd.speed_scale *= -1.0; looped_flag = Animation::LOOPED_FLAG_START; } - if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) { + if (Animation::is_greater_approx(next_pos, end) && Animation::is_less_or_equal_approx(cd.pos, end)) { cd.speed_scale *= -1.0; looped_flag = Animation::LOOPED_FLAG_END; } - next_pos = Math::pingpong(next_pos, (double)len); + next_pos = Math::pingpong(next_pos - start, end - start) + start; } break; default: @@ -208,18 +210,18 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f // End detection. if (p_is_current) { if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) { - if (!backwards && Animation::is_less_or_equal_approx(prev_pos, len) && Math::is_equal_approx(next_pos, len)) { + if (!backwards && Animation::is_less_or_equal_approx(prev_pos, end) && Math::is_equal_approx(next_pos, end)) { // Playback finished. - next_pos = len; // Snap to the edge. + next_pos = end; // Snap to the edge. end_reached = true; - end_notify = Animation::is_less_approx(prev_pos, len); // Notify only if not already at the end. + end_notify = Animation::is_less_approx(prev_pos, end); // Notify only if not already at the end. p_blend = 1.0; } - if (backwards && Animation::is_greater_or_equal_approx(prev_pos, 0) && Math::is_equal_approx(next_pos, 0)) { + if (backwards && Animation::is_greater_or_equal_approx(prev_pos, start) && Math::is_equal_approx(next_pos, start)) { // Playback finished. - next_pos = 0; // Snap to the edge. + next_pos = start; // Snap to the edge. end_reached = true; - end_notify = Animation::is_greater_approx(prev_pos, 0); // Notify only if not already at the beginning. + end_notify = Animation::is_greater_approx(prev_pos, start); // Notify only if not already at the beginning. p_blend = 1.0; } } @@ -231,10 +233,14 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f if (p_started) { pi.time = prev_pos; pi.delta = 0; + pi.start = start; + pi.end = end; pi.seeked = true; } else { pi.time = next_pos; pi.delta = delta; + pi.start = start; + pi.end = end; pi.seeked = p_seeked; } if (Math::is_zero_approx(pi.delta) && backwards) { @@ -378,6 +384,14 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b play(p_name, p_custom_blend, -1, true); } +void AnimationPlayer::play_section_with_markers_backwards(const StringName &p_name, const StringName &p_start_marker, const StringName &p_end_marker, double p_custom_blend) { + play_section_with_markers(p_name, p_start_marker, p_end_marker, p_custom_blend, -1, true); +} + +void AnimationPlayer::play_section_backwards(const StringName &p_name, double p_start_time, double p_end_time, double p_custom_blend) { + play_section(p_name, p_start_time, p_end_time, -1, true); +} + 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); @@ -387,6 +401,10 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa } void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { + play_section_with_markers(p_name, StringName(), StringName(), p_custom_blend, p_custom_scale, p_from_end); +} + +void AnimationPlayer::play_section_with_markers(const StringName &p_name, const StringName &p_start_marker, const StringName &p_end_marker, double p_custom_blend, float p_custom_scale, bool p_from_end) { StringName name = p_name; if (name == StringName()) { @@ -395,6 +413,38 @@ void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, flo ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); + Ref<Animation> animation = animation_set[name].animation; + + ERR_FAIL_COND_MSG(p_start_marker == p_end_marker && p_start_marker, vformat("Start marker and end marker cannot be the same marker: %s.", p_start_marker)); + ERR_FAIL_COND_MSG(p_start_marker && !animation->has_marker(p_start_marker), vformat("Marker %s not found in animation: %s.", p_start_marker, name)); + ERR_FAIL_COND_MSG(p_end_marker && !animation->has_marker(p_end_marker), vformat("Marker %s not found in animation: %s.", p_end_marker, name)); + + double start_time = p_start_marker ? animation->get_marker_time(p_start_marker) : -1; + double end_time = p_end_marker ? animation->get_marker_time(p_end_marker) : -1; + + ERR_FAIL_COND_MSG(p_start_marker && p_end_marker && Animation::is_greater_approx(start_time, end_time), vformat("End marker %s is placed earlier than start marker %s in animation: %s.", p_end_marker, p_start_marker, name)); + + if (p_start_marker && Animation::is_less_approx(start_time, 0)) { + WARN_PRINT_ED(vformat("Negative time start marker: %s is invalid in the section, so the start of the animation: %s is used instead.", p_start_marker, playback.current.from->animation->get_name())); + } + if (p_end_marker && Animation::is_less_approx(end_time, 0)) { + WARN_PRINT_ED(vformat("Negative time end marker: %s is invalid in the section, so the end of the animation: %s is used instead.", p_end_marker, playback.current.from->animation->get_name())); + } + + play_section(name, start_time, end_time, p_custom_blend, p_custom_scale, p_from_end); +} + +void AnimationPlayer::play_section(const StringName &p_name, double p_start_time, double p_end_time, double p_custom_blend, float p_custom_scale, bool p_from_end) { + StringName name = p_name; + + if (name == StringName()) { + name = playback.assigned; + } + + ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); + ERR_FAIL_COND_MSG(p_start_time >= 0 && p_end_time >= 0 && Math::is_equal_approx(p_start_time, p_end_time), "Start time and end time must not equal to each other."); + ERR_FAIL_COND_MSG(p_start_time >= 0 && p_end_time >= 0 && Animation::is_greater_approx(p_start_time, p_end_time), vformat("Start time %f is greater than end time %f.", p_start_time, p_end_time)); + Playback &c = playback; if (c.current.from) { @@ -442,22 +492,27 @@ void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, flo c.current.from = &animation_set[name]; c.current.speed_scale = p_custom_scale; + c.current.start_time = p_start_time; + c.current.end_time = p_end_time; + + double start = get_section_start_time(); + double end = get_section_end_time(); if (!end_reached) { playback_queue.clear(); } if (c.assigned != name) { // Reset. - c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0; + c.current.pos = p_from_end ? end : start; c.assigned = name; emit_signal(SNAME("current_animation_changed"), c.assigned); } else { - if (p_from_end && Math::is_zero_approx(c.current.pos)) { + if (p_from_end && Math::is_equal_approx(c.current.pos, start)) { // Animation reset but played backwards, set position to the end. - seek_internal(c.current.from->animation->get_length(), true, true, true); - } else if (!p_from_end && Math::is_equal_approx(c.current.pos, (double)c.current.from->animation->get_length())) { + seek_internal(end, true, true, true); + } else if (!p_from_end && Math::is_equal_approx(c.current.pos, end)) { // Animation resumed but already ended, set position to the beginning. - seek_internal(0, true, true, true); + seek_internal(start, true, true, true); } else if (playing) { return; } @@ -551,6 +606,8 @@ void AnimationPlayer::set_assigned_animation(const String &p_animation) { ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation)); playback.current.pos = 0; playback.current.from = &animation_set[p_animation]; + playback.current.start_time = -1; + playback.current.end_time = -1; playback.assigned = p_animation; emit_signal(SNAME("current_animation_changed"), playback.assigned); } @@ -603,6 +660,12 @@ void AnimationPlayer::seek_internal(double p_time, bool p_update, bool p_update_ } } + double start = get_section_start_time(); + double end = get_section_end_time(); + + // Clamp the seek position. + p_time = CLAMP(p_time, start, end); + playback.seeked = true; playback.internal_seeked = p_is_internal_seek; @@ -641,6 +704,55 @@ double AnimationPlayer::get_current_animation_length() const { return playback.current.from->animation->get_length(); } +void AnimationPlayer::set_section_with_markers(const StringName &p_start_marker, const StringName &p_end_marker) { + ERR_FAIL_NULL_MSG(playback.current.from, "AnimationPlayer has no current animation."); + ERR_FAIL_COND_MSG(p_start_marker == p_end_marker && p_start_marker, vformat("Start marker and end marker cannot be the same marker: %s.", p_start_marker)); + ERR_FAIL_COND_MSG(p_start_marker && !playback.current.from->animation->has_marker(p_start_marker), vformat("Marker %s not found in animation: %s.", p_start_marker, playback.current.from->animation->get_name())); + ERR_FAIL_COND_MSG(p_end_marker && !playback.current.from->animation->has_marker(p_end_marker), vformat("Marker %s not found in animation: %s.", p_end_marker, playback.current.from->animation->get_name())); + double start_time = p_start_marker ? playback.current.from->animation->get_marker_time(p_start_marker) : -1; + double end_time = p_end_marker ? playback.current.from->animation->get_marker_time(p_end_marker) : -1; + if (p_start_marker && Animation::is_less_approx(start_time, 0)) { + WARN_PRINT_ONCE_ED(vformat("Marker %s time must be positive in animation: %s.", p_start_marker, playback.current.from->animation->get_name())); + } + if (p_end_marker && Animation::is_less_approx(end_time, 0)) { + WARN_PRINT_ONCE_ED(vformat("Marker %s time must be positive in animation: %s.", p_end_marker, playback.current.from->animation->get_name())); + } + set_section(start_time, end_time); +} + +void AnimationPlayer::set_section(double p_start_time, double p_end_time) { + ERR_FAIL_NULL_MSG(playback.current.from, "AnimationPlayer has no current animation."); + ERR_FAIL_COND_MSG(Animation::is_greater_or_equal_approx(p_start_time, 0) && Animation::is_greater_or_equal_approx(p_end_time, 0) && Animation::is_greater_or_equal_approx(p_start_time, p_end_time), vformat("Start time %f is greater than end time %f.", p_start_time, p_end_time)); + playback.current.start_time = p_start_time; + playback.current.end_time = p_end_time; + playback.current.pos = CLAMP(playback.current.pos, get_section_start_time(), get_section_end_time()); +} + +void AnimationPlayer::reset_section() { + playback.current.start_time = -1; + playback.current.end_time = -1; +} + +double AnimationPlayer::get_section_start_time() const { + ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.start_time, "AnimationPlayer has no current animation."); + if (Animation::is_less_approx(playback.current.start_time, 0) || playback.current.start_time > playback.current.from->animation->get_length()) { + return 0; + } + return playback.current.start_time; +} + +double AnimationPlayer::get_section_end_time() const { + ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.end_time, "AnimationPlayer has no current animation."); + if (Animation::is_less_approx(playback.current.end_time, 0) || playback.current.end_time > playback.current.from->animation->get_length()) { + return playback.current.from->animation->get_length(); + } + return playback.current.end_time; +} + +bool AnimationPlayer::has_section() const { + return Animation::is_greater_or_equal_approx(playback.current.start_time, 0) || Animation::is_greater_or_equal_approx(playback.current.end_time, 0); +} + void AnimationPlayer::set_autoplay(const String &p_name) { if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect."); @@ -665,13 +777,14 @@ void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) { _clear_caches(); Playback &c = playback; // c.blend.clear(); + double start = c.current.from ? get_section_start_time() : 0; if (p_reset) { c.blend.clear(); if (p_keep_state) { - c.current.pos = 0; + c.current.pos = start; } else { is_stopping = true; - seek_internal(0, true, true, true); + seek_internal(start, true, true, true); is_stopping = false; } c.current.from = nullptr; @@ -763,20 +876,6 @@ 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; - if (p_idx == 0 && (pf == "play" || pf == "play_backwards" || pf == "has_animation" || pf == "queue")) { - List<StringName> al; - get_animation_list(&al); - for (const StringName &name : al) { - r_options->push_back(String(name).quote()); - } - } - AnimationMixer::get_argument_options(p_function, p_idx, r_options); -} -#endif - void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) { AnimationMixer::_animation_removed(p_name, p_library); @@ -863,7 +962,11 @@ void AnimationPlayer::_bind_methods() { 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_section_with_markers", "name", "start_marker", "end_marker", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play_section_with_markers, DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("play_section", "name", "start_time", "end_time", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play_section, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(-1), 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_section_with_markers_backwards", "name", "start_marker", "end_marker", "custom_blend"), &AnimationPlayer::play_section_with_markers_backwards, DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("play_section_backwards", "name", "start_time", "end_time", "custom_blend"), &AnimationPlayer::play_section_backwards, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(-1), 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(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)); @@ -893,6 +996,14 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position); ClassDB::bind_method(D_METHOD("get_current_animation_length"), &AnimationPlayer::get_current_animation_length); + ClassDB::bind_method(D_METHOD("set_section_with_markers", "start_marker", "end_marker"), &AnimationPlayer::set_section_with_markers, DEFVAL(StringName()), DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("set_section", "start_time", "end_time"), &AnimationPlayer::set_section, DEFVAL(-1), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("reset_section"), &AnimationPlayer::reset_section); + + ClassDB::bind_method(D_METHOD("get_section_start_time"), &AnimationPlayer::get_section_start_time); + ClassDB::bind_method(D_METHOD("get_section_end_time"), &AnimationPlayer::get_section_end_time); + ClassDB::bind_method(D_METHOD("has_section"), &AnimationPlayer::has_section); + ClassDB::bind_method(D_METHOD("seek", "seconds", "update", "update_only"), &AnimationPlayer::seek, DEFVAL(false), DEFVAL(false)); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR), "set_current_animation", "get_current_animation"); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index e05a2c9935..3223e2522d 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -68,6 +68,8 @@ private: AnimationData *from = nullptr; double pos = 0.0; float speed_scale = 1.0; + double start_time = 0.0; + double end_time = 0.0; }; struct Blend { @@ -177,7 +179,11 @@ public: 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_section_with_markers(const StringName &p_name = StringName(), const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); + void play_section(const StringName &p_name = StringName(), double p_start_time = -1, double p_end_time = -1, 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_section_with_markers_backwards(const StringName &p_name = StringName(), const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName(), double p_custom_blend = -1); + void play_section_backwards(const StringName &p_name = StringName(), double p_start_time = -1, double p_end_time = -1, double p_custom_blend = -1); 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(); @@ -207,9 +213,13 @@ public: double get_current_animation_position() const; double get_current_animation_length() const; -#ifdef TOOLS_ENABLED - void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; -#endif + void set_section_with_markers(const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName()); + void set_section(double p_start_time = -1, double p_end_time = -1); + void reset_section(); + + double get_section_start_time() const; + double get_section_end_time() const; + bool has_section() const; virtual void advance(double p_time) override; diff --git a/scene/audio/SCsub b/scene/audio/SCsub index fc61250247..374dc2119d 100644 --- a/scene/audio/SCsub +++ b/scene/audio/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/debugger/SCsub b/scene/debugger/SCsub index fc61250247..374dc2119d 100644 --- a/scene/debugger/SCsub +++ b/scene/debugger/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/gui/SCsub b/scene/gui/SCsub index fc61250247..374dc2119d 100644 --- a/scene/gui/SCsub +++ b/scene/gui/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 72299f788d..34f5095493 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/os/keyboard.h" +#include "scene/gui/label.h" #include "scene/main/window.h" void BaseButton::_unpress_group() { @@ -390,16 +391,31 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) { } } -String BaseButton::get_tooltip(const Point2 &p_pos) const { - String tooltip = Control::get_tooltip(p_pos); - if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->has_valid_event()) { - String text = shortcut->get_name() + " (" + shortcut->get_as_text() + ")"; - if (!tooltip.is_empty() && shortcut->get_name().nocasecmp_to(tooltip) != 0) { - text += "\n" + atr(tooltip); - } - tooltip = text; +Control *BaseButton::make_custom_tooltip(const String &p_text) const { + Control *control = Control::make_custom_tooltip(p_text); + if (control) { + return control; + } + if (!shortcut_in_tooltip || shortcut.is_null() || !shortcut->has_valid_event()) { + return nullptr; // Use the default tooltip label. } - return tooltip; + + String text = atr(shortcut->get_name()) + " (" + shortcut->get_as_text() + ")"; + if (!p_text.is_empty() && shortcut->get_name().nocasecmp_to(p_text) != 0) { + text += "\n" + atr(p_text); + } + + // Make a label similar to the default tooltip label. + // Auto translation is disabled because we already did that manually above. + // + // We can't customize the tooltip text by overriding `get_tooltip()` + // because otherwise user-defined `_make_custom_tooltip()` would receive + // the translated and annotated text. + Label *label = memnew(Label(text)); + label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); + label->set_theme_type_variation(SNAME("TooltipLabel")); + + return label; } void BaseButton::set_button_group(const Ref<ButtonGroup> &p_group) { diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index a8d5cee44c..8405acb21d 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -134,7 +134,7 @@ public: void set_shortcut(const Ref<Shortcut> &p_shortcut); Ref<Shortcut> get_shortcut() const; - virtual String get_tooltip(const Point2 &p_pos) const override; + virtual Control *make_custom_tooltip(const String &p_text) const override; void set_button_group(const Ref<ButtonGroup> &p_group); Ref<ButtonGroup> get_button_group() const; diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index dd344121e1..0a5f2ec6c7 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -30,9 +30,7 @@ #include "button.h" -#include "core/string/translation.h" #include "scene/theme/theme_db.h" -#include "servers/rendering_server.h" Size2 Button::get_minimum_size() const { Ref<Texture2D> _icon = icon; @@ -437,6 +435,9 @@ void Button::_notification(int p_what) { text_buf->set_alignment(align_rtl_checked); float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf. + if (autowrap_mode != TextServer::AUTOWRAP_OFF && !Math::is_equal_approx(text_buf_width, text_buf->get_width())) { + update_minimum_size(); + } text_buf->set_width(text_buf_width); Point2 text_ofs; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index dff1cf34c2..7346c9dcd3 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -833,6 +833,9 @@ void CodeEdit::_cut_internal(int p_caret) { delete_selection(p_caret); return; } + if (!is_empty_selection_clipboard_enabled()) { + return; + } if (p_caret == -1) { delete_lines(); } else { diff --git a/scene/gui/color_mode.h b/scene/gui/color_mode.h index 84e9d4542d..94193ccf74 100644 --- a/scene/gui/color_mode.h +++ b/scene/gui/color_mode.h @@ -50,14 +50,14 @@ public: virtual Color get_color() const = 0; - virtual void _value_changed(){}; + virtual void _value_changed() {} virtual void slider_draw(int p_which) = 0; virtual bool apply_theme() const { return false; } virtual ColorPicker::PickerShapeType get_shape_override() const { return ColorPicker::SHAPE_MAX; } ColorMode(ColorPicker *p_color_picker); - virtual ~ColorMode(){}; + virtual ~ColorMode() {} }; class ColorModeHSV : public ColorMode { @@ -81,7 +81,7 @@ public: virtual void slider_draw(int p_which) override; ColorModeHSV(ColorPicker *p_color_picker) : - ColorMode(p_color_picker){}; + ColorMode(p_color_picker) {} }; class ColorModeRGB : public ColorMode { @@ -100,7 +100,7 @@ public: virtual void slider_draw(int p_which) override; ColorModeRGB(ColorPicker *p_color_picker) : - ColorMode(p_color_picker){}; + ColorMode(p_color_picker) {} }; class ColorModeRAW : public ColorMode { @@ -122,7 +122,7 @@ public: virtual bool apply_theme() const override; ColorModeRAW(ColorPicker *p_color_picker) : - ColorMode(p_color_picker){}; + ColorMode(p_color_picker) {} }; class ColorModeOKHSL : public ColorMode { @@ -147,9 +147,9 @@ public: virtual ColorPicker::PickerShapeType get_shape_override() const override { return ColorPicker::SHAPE_OKHSL_CIRCLE; } ColorModeOKHSL(ColorPicker *p_color_picker) : - ColorMode(p_color_picker){}; + ColorMode(p_color_picker) {} - ~ColorModeOKHSL(){}; + ~ColorModeOKHSL() {} }; #endif // COLOR_MODE_H diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index c92dcbc153..fe4c91cb56 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -33,8 +33,6 @@ #include "core/input/input.h" #include "core/io/image.h" #include "core/math/color.h" -#include "core/os/keyboard.h" -#include "core/os/os.h" #include "scene/gui/color_mode.h" #include "scene/gui/margin_container.h" #include "scene/resources/image_texture.h" @@ -42,7 +40,6 @@ #include "scene/resources/style_box_texture.h" #include "scene/theme/theme_db.h" #include "servers/display_server.h" -#include "thirdparty/misc/ok_color.h" #include "thirdparty/misc/ok_color_shader.h" List<Color> ColorPicker::preset_cache; @@ -248,7 +245,21 @@ void ColorPicker::finish_shaders() { } void ColorPicker::set_focus_on_line_edit() { - callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + bool has_hardware_keyboard = true; +#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) + has_hardware_keyboard = DisplayServer::get_singleton()->has_hardware_keyboard(); +#endif // ANDROID_ENABLED || IOS_ENABLED + if (has_hardware_keyboard) { + callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + } else { + // A hack to avoid showing the virtual keyboard when the ColorPicker window popups and + // no hardware keyboard is detected on Android and IOS. + // This will only focus the LineEdit without editing, the virtual keyboard will only be visible when + // we touch the LineEdit to enter edit mode. + callable_mp(c_text, &LineEdit::set_editable).call_deferred(false); + callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + callable_mp(c_text, &LineEdit::set_editable).call_deferred(true); + } } void ColorPicker::_update_controls() { @@ -1567,7 +1578,7 @@ void ColorPicker::_pick_button_pressed_legacy() { picker_preview_label = memnew(Label); picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP); - picker_preview_label->set_text("Color Picking active"); + picker_preview_label->set_text(ETR("Color Picking active")); picker_preview->add_child(picker_preview_label); picker_preview_style_box = (Ref<StyleBoxFlat>)memnew(StyleBoxFlat); @@ -1905,7 +1916,7 @@ ColorPicker::ColorPicker() { mode_popup->add_radio_check_item(modes[i]->get_name(), i); } mode_popup->add_separator(); - mode_popup->add_check_item("Colorized Sliders", MODE_MAX); + mode_popup->add_check_item(ETR("Colorized Sliders"), MODE_MAX); mode_popup->set_item_checked(current_mode, true); mode_popup->set_item_checked(MODE_MAX + 1, true); mode_popup->connect(SceneStringName(id_pressed), callable_mp(this, &ColorPicker::_set_mode_popup_value)); @@ -1933,7 +1944,7 @@ ColorPicker::ColorPicker() { hex_hbc->set_alignment(ALIGNMENT_BEGIN); vbr->add_child(hex_hbc); - hex_hbc->add_child(memnew(Label("Hex"))); + hex_hbc->add_child(memnew(Label(ETR("Hex")))); text_type = memnew(Button); hex_hbc->add_child(text_type); @@ -1997,8 +2008,7 @@ ColorPicker::ColorPicker() { preset_group.instantiate(); - btn_preset = memnew(Button); - btn_preset->set_text("Swatches"); + btn_preset = memnew(Button(ETR("Swatches"))); btn_preset->set_flat(true); btn_preset->set_toggle_mode(true); btn_preset->set_focus_mode(FOCUS_NONE); @@ -2014,8 +2024,7 @@ ColorPicker::ColorPicker() { recent_preset_group.instantiate(); - btn_recent_preset = memnew(Button); - btn_recent_preset->set_text("Recent Colors"); + btn_recent_preset = memnew(Button(ETR("Recent Colors"))); btn_recent_preset->set_flat(true); btn_recent_preset->set_toggle_mode(true); btn_recent_preset->set_focus_mode(FOCUS_NONE); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index cecddebe88..c1d197ea9b 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -33,12 +33,8 @@ #include "container.h" #include "core/config/project_settings.h" #include "core/math/geometry_2d.h" -#include "core/os/keyboard.h" #include "core/os/os.h" -#include "core/string/print_string.h" #include "core/string/translation_server.h" -#include "scene/gui/label.h" -#include "scene/gui/panel.h" #include "scene/main/canvas_layer.h" #include "scene/main/window.h" #include "scene/theme/theme_db.h" @@ -3161,6 +3157,16 @@ bool Control::is_auto_translating() const { } #endif +void Control::set_tooltip_auto_translate_mode(AutoTranslateMode p_mode) { + ERR_MAIN_THREAD_GUARD; + data.tooltip_auto_translate_mode = p_mode; +} + +Node::AutoTranslateMode Control::get_tooltip_auto_translate_mode() const { + ERR_READ_THREAD_GUARD_V(AUTO_TRANSLATE_MODE_INHERIT); + return data.tooltip_auto_translate_mode; +} + // Extra properties. void Control::set_tooltip_text(const String &p_hint) { @@ -3510,6 +3516,8 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_grow_direction", "direction"), &Control::set_v_grow_direction); ClassDB::bind_method(D_METHOD("get_v_grow_direction"), &Control::get_v_grow_direction); + ClassDB::bind_method(D_METHOD("set_tooltip_auto_translate_mode", "mode"), &Control::set_tooltip_auto_translate_mode); + ClassDB::bind_method(D_METHOD("get_tooltip_auto_translate_mode"), &Control::get_tooltip_auto_translate_mode); ClassDB::bind_method(D_METHOD("set_tooltip_text", "hint"), &Control::set_tooltip_text); ClassDB::bind_method(D_METHOD("get_tooltip_text"), &Control::get_tooltip_text); ClassDB::bind_method(D_METHOD("get_tooltip", "at_position"), &Control::get_tooltip, DEFVAL(Point2())); @@ -3617,6 +3625,7 @@ void Control::_bind_methods() { ADD_GROUP("Tooltip", "tooltip_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip_text", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip_text", "get_tooltip_text"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tooltip_auto_translate_mode", PROPERTY_HINT_ENUM, "Inherit,Always,Disabled"), "set_tooltip_auto_translate_mode", "get_tooltip_auto_translate_mode"); ADD_GROUP("Focus", "focus_"); ADD_PROPERTYI(PropertyInfo(Variant::NODE_PATH, "focus_neighbor_left", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbor", "get_focus_neighbor", SIDE_LEFT); diff --git a/scene/gui/control.h b/scene/gui/control.h index 2655b14562..6e1621ce58 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -33,7 +33,6 @@ #include "core/math/transform_2d.h" #include "core/object/gdvirtual.gen.inc" -#include "core/templates/rid.h" #include "scene/main/canvas_item.h" #include "scene/main/timer.h" #include "scene/resources/theme.h" @@ -262,6 +261,7 @@ private: // Extra properties. String tooltip; + AutoTranslateMode tooltip_auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT; } data; @@ -634,6 +634,9 @@ public: bool is_auto_translating() const; #endif + void set_tooltip_auto_translate_mode(AutoTranslateMode p_mode); + AutoTranslateMode get_tooltip_auto_translate_mode() const; + // Extra properties. void set_tooltip_text(const String &text); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 1fc8586448..d8e9d1bcc0 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -208,6 +208,7 @@ void FileDialog::_notification(int p_what) { refresh->set_icon(theme_cache.reload); show_hidden->set_icon(theme_cache.toggle_hidden); makedir->set_icon(theme_cache.create_folder); + show_filename_filter_button->set_icon(theme_cache.toggle_filename_filter); dir_up->begin_bulk_theme_override(); dir_up->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); @@ -251,6 +252,13 @@ void FileDialog::_notification(int p_what) { makedir->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); makedir->end_bulk_theme_override(); + show_filename_filter_button->begin_bulk_theme_override(); + show_filename_filter_button->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + show_filename_filter_button->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); + show_filename_filter_button->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + show_filename_filter_button->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); + show_filename_filter_button->end_bulk_theme_override(); + invalidate(); } break; @@ -277,6 +285,14 @@ void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) { } } break; + case Key::F: { + if (k->is_command_or_control_pressed()) { + show_filename_filter_button->set_pressed(!show_filename_filter_button->is_pressed()); + } else { + handled = false; + } + + } break; case Key::F5: { invalidate(); } break; @@ -694,18 +710,24 @@ void FileDialog::update_file_list() { dirs.sort_custom<FileNoCaseComparator>(); files.sort_custom<FileNoCaseComparator>(); + String filename_filter_lower = file_name_filter.to_lower(); + while (!dirs.is_empty()) { - String &dir_name = dirs.front()->get(); - TreeItem *ti = tree->create_item(root); - ti->set_text(0, dir_name); - ti->set_icon(0, theme_cache.folder); - ti->set_icon_modulate(0, theme_cache.folder_icon_color); + const String &dir_name = dirs.front()->get(); + + if (filename_filter_lower.is_empty() || dir_name.to_lower().contains(filename_filter_lower)) { + TreeItem *ti = tree->create_item(root); - Dictionary d; - d["name"] = dir_name; - d["dir"] = true; + ti->set_text(0, dir_name); + ti->set_icon(0, theme_cache.folder); + ti->set_icon_modulate(0, theme_cache.folder_icon_color); - ti->set_metadata(0, d); + Dictionary d; + d["name"] = dir_name; + d["dir"] = true; + + ti->set_metadata(0, d); + } dirs.pop_front(); } @@ -750,7 +772,7 @@ void FileDialog::update_file_list() { } } - if (match) { + if (match && (filename_filter_lower.is_empty() || files.front()->get().to_lower().contains(filename_filter_lower))) { TreeItem *ti = tree->create_item(root); ti->set_text(0, files.front()->get()); @@ -792,6 +814,26 @@ void FileDialog::_filter_selected(int) { update_file_list(); } +void FileDialog::_filename_filter_changed() { + update_filename_filter(); + update_file_list(); + callable_mp(this, &FileDialog::_tree_select_first).call_deferred(); +} + +void FileDialog::_tree_select_first() { + if (tree->get_root() && tree->get_root()->get_first_child()) { + tree->get_root()->get_first_child()->select(0); + } +} + +void FileDialog::_filename_filter_selected() { + TreeItem *item = tree->get_selected(); + if (item) { + file->set_text(item->get_text(0)); + file->emit_signal("text_submitted", file->get_text()); + } +} + void FileDialog::update_filters() { filter->clear(); @@ -827,6 +869,30 @@ void FileDialog::update_filters() { filter->add_item(atr(ETR("All Files")) + " (*)"); } +void FileDialog::clear_filename_filter() { + set_filename_filter(""); + update_filename_filter_gui(); + invalidate(); +} + +void FileDialog::update_filename_filter_gui() { + filename_filter_box->set_visible(show_filename_filter); + if (!show_filename_filter) { + file_name_filter.clear(); + } + if (filename_filter->get_text() == file_name_filter) { + return; + } + filename_filter->set_text(file_name_filter); +} + +void FileDialog::update_filename_filter() { + if (filename_filter->get_text() == file_name_filter) { + return; + } + set_filename_filter(filename_filter->get_text()); +} + void FileDialog::clear_filters() { filters.clear(); update_filters(); @@ -853,10 +919,24 @@ void FileDialog::set_filters(const Vector<String> &p_filters) { invalidate(); } +void FileDialog::set_filename_filter(const String &p_filename_filter) { + if (file_name_filter == p_filename_filter) { + return; + } + file_name_filter = p_filename_filter; + update_filename_filter_gui(); + emit_signal(SNAME("filename_filter_changed"), filter); + invalidate(); +} + Vector<String> FileDialog::get_filters() const { return filters; } +String FileDialog::get_filename_filter() const { + return file_name_filter; +} + String FileDialog::get_current_dir() const { return dir->get_text(); } @@ -1266,6 +1346,9 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &FileDialog::add_filter, DEFVAL("")); ClassDB::bind_method(D_METHOD("set_filters", "filters"), &FileDialog::set_filters); ClassDB::bind_method(D_METHOD("get_filters"), &FileDialog::get_filters); + ClassDB::bind_method(D_METHOD("clear_filename_filter"), &FileDialog::clear_filename_filter); + ClassDB::bind_method(D_METHOD("set_filename_filter", "filter"), &FileDialog::set_filename_filter); + ClassDB::bind_method(D_METHOD("get_filename_filter"), &FileDialog::get_filename_filter); ClassDB::bind_method(D_METHOD("get_option_name", "option"), &FileDialog::get_option_name); ClassDB::bind_method(D_METHOD("get_option_values", "option"), &FileDialog::get_option_values); ClassDB::bind_method(D_METHOD("get_option_default", "option"), &FileDialog::get_option_default); @@ -1305,6 +1388,7 @@ void FileDialog::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder"), "set_root_subfolder", "get_root_subfolder"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename_filter"), "set_filename_filter", "get_filename_filter"); ADD_ARRAY_COUNT("Options", "option_count", "set_option_count", "get_option_count", "option_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_native_dialog"), "set_use_native_dialog", "get_use_native_dialog"); @@ -1315,6 +1399,7 @@ void FileDialog::_bind_methods() { ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path"))); ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths"))); ADD_SIGNAL(MethodInfo("dir_selected", PropertyInfo(Variant::STRING, "dir"))); + ADD_SIGNAL(MethodInfo("filename_filter_changed", PropertyInfo(Variant::STRING, "filter"))); BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILE); BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILES); @@ -1332,6 +1417,7 @@ void FileDialog::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, reload); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_hidden); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, folder); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_filename_filter); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, file); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, create_folder); @@ -1363,6 +1449,26 @@ void FileDialog::set_show_hidden_files(bool p_show) { invalidate(); } +void FileDialog::set_show_filename_filter(bool p_show) { + if (p_show == show_filename_filter) { + return; + } + if (p_show) { + filename_filter->grab_focus(); + } else { + if (filename_filter->has_focus()) { + tree->call_deferred("grab_focus"); + } + } + show_filename_filter = p_show; + update_filename_filter_gui(); + invalidate(); +} + +bool FileDialog::get_show_filename_filter() const { + return show_filename_filter; +} + bool FileDialog::is_showing_hidden_files() const { return show_hidden_files; } @@ -1446,6 +1552,14 @@ FileDialog::FileDialog() { show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); + show_filename_filter_button = memnew(Button); + show_filename_filter_button->set_theme_type_variation("FlatButton"); + show_filename_filter_button->set_toggle_mode(true); + show_filename_filter_button->set_pressed(false); + show_filename_filter_button->set_tooltip_text(RTR("Toggle the visibility of the filter for file names.")); + show_filename_filter_button->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_filename_filter)); + hbc->add_child(show_filename_filter_button); + shortcuts_container = memnew(HBoxContainer); hbc->add_child(shortcuts_container); @@ -1467,6 +1581,17 @@ FileDialog::FileDialog() { message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); tree->add_child(message); + filename_filter_box = memnew(HBoxContainer); + filename_filter_box->add_child(memnew(Label(RTR("Filter:")))); + filename_filter = memnew(LineEdit); + filename_filter->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); + filename_filter->set_stretch_ratio(4); + filename_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); + filename_filter->set_clear_button_enabled(true); + filename_filter_box->add_child(filename_filter); + filename_filter_box->set_visible(false); + vbox->add_child(filename_filter_box); + file_box = memnew(HBoxContainer); file_box->add_child(memnew(Label(ETR("File:")))); file = memnew(LineEdit); @@ -1495,6 +1620,8 @@ FileDialog::FileDialog() { tree->connect("item_activated", callable_mp(this, &FileDialog::_tree_item_activated)); tree->connect("nothing_selected", callable_mp(this, &FileDialog::deselect_all)); dir->connect("text_submitted", callable_mp(this, &FileDialog::_dir_submitted)); + filename_filter->connect(SceneStringName(text_changed), callable_mp(this, &FileDialog::_filename_filter_changed).unbind(1)); + filename_filter->connect("text_submitted", callable_mp(this, &FileDialog::_filename_filter_selected).unbind(1)); file->connect("text_submitted", callable_mp(this, &FileDialog::_file_submitted)); filter->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_filter_selected)); @@ -1523,9 +1650,11 @@ FileDialog::FileDialog() { add_child(exterr, false, INTERNAL_MODE_FRONT); update_filters(); + update_filename_filter_gui(); update_dir(); set_hide_on_ok(false); + set_size(Size2(640, 360)); if (register_func) { register_func(this); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 9680157f0a..6ef60a0f4f 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -80,6 +80,8 @@ private: HBoxContainer *shortcuts_container = nullptr; OptionButton *drives = nullptr; Tree *tree = nullptr; + HBoxContainer *filename_filter_box = nullptr; + LineEdit *filename_filter = nullptr; HBoxContainer *file_box = nullptr; LineEdit *file = nullptr; OptionButton *filter = nullptr; @@ -96,8 +98,11 @@ private: Button *refresh = nullptr; Button *show_hidden = nullptr; + Button *show_filename_filter_button = nullptr; Vector<String> filters; + String file_name_filter; + bool show_filename_filter = false; Vector<String> local_history; int local_history_pos = 0; @@ -119,6 +124,7 @@ private: Ref<Texture2D> back_folder; Ref<Texture2D> reload; Ref<Texture2D> toggle_hidden; + Ref<Texture2D> toggle_filename_filter; Ref<Texture2D> folder; Ref<Texture2D> file; Ref<Texture2D> create_folder; @@ -149,6 +155,8 @@ private: void update_dir(); void update_file_name(); void update_file_list(); + void update_filename_filter(); + void update_filename_filter_gui(); void update_filters(); void _focus_file_text(); @@ -164,6 +172,9 @@ private: void _save_confirm_pressed(); void _cancel_pressed(); void _filter_selected(int); + void _filename_filter_changed(); + void _filename_filter_selected(); + void _tree_select_first(); void _make_dir(); void _make_dir_confirm(); void _go_up(); @@ -208,6 +219,9 @@ public: void add_filter(const String &p_filter, const String &p_description = ""); void set_filters(const Vector<String> &p_filters); Vector<String> get_filters() const; + void clear_filename_filter(); + void set_filename_filter(const String &p_filename_filter); + String get_filename_filter() const; void set_enable_multiple_selection(bool p_enable); Vector<String> get_selected_files() const; @@ -253,6 +267,8 @@ public: void set_show_hidden_files(bool p_show); bool is_showing_hidden_files() const; + void set_show_filename_filter(bool p_show); + bool get_show_filename_filter() const; static void set_default_show_hidden_files(bool p_show); diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index d32c75fbcd..90d5b6b36d 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -261,6 +261,7 @@ void FlowContainer::_resort() { } cached_size = (vertical ? ofs.x : ofs.y) + line_height; cached_line_count = lines_data.size(); + cached_line_max_child_count = lines_data.size() > 0 ? lines_data[0].child_count : 0; } Size2 FlowContainer::get_minimum_size() const { @@ -339,6 +340,10 @@ int FlowContainer::get_line_count() const { return cached_line_count; } +int FlowContainer::get_line_max_child_count() const { + return cached_line_max_child_count; +} + void FlowContainer::set_alignment(AlignmentMode p_alignment) { if (alignment == p_alignment) { return; diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index 65ebc89c78..6a00e5b0e5 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -52,6 +52,8 @@ public: private: int cached_size = 0; int cached_line_count = 0; + int cached_line_max_child_count = 0; + int cached_items_on_last_row = 0; bool vertical = false; bool reverse_fill = false; @@ -74,6 +76,7 @@ protected: public: int get_line_count() const; + int get_line_max_child_count() const; void set_alignment(AlignmentMode p_alignment); AlignmentMode get_alignment() const; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 11a6411e65..646757008a 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -260,6 +260,8 @@ PackedStringArray GraphEdit::get_configuration_warnings() const { } Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { + ERR_FAIL_NULL_V_MSG(connections_layer, FAILED, "connections_layer is missing."); + if (is_node_connected(p_from, p_from_port, p_to, p_to_port)) { return OK; } @@ -313,6 +315,8 @@ bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, con } void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + for (const List<Ref<Connection>>::Element *E = connections.front(); E; E = E->next()) { if (E->get()->from_node == p_from && E->get()->from_port == p_from_port && E->get()->to_node == p_to && E->get()->to_port == p_to_port) { connection_map[p_from].erase(E->get()); @@ -356,6 +360,8 @@ void GraphEdit::_scroll_moved(double) { } void GraphEdit::_update_scroll_offset() { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + set_block_minimum_size_adjust(true); for (int i = 0; i < get_child_count(); i++) { @@ -524,6 +530,8 @@ void GraphEdit::_graph_element_resize_request(const Vector2 &p_new_minsize, Node } void GraphEdit::_graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, GraphFrame *p_frame) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + _update_graph_frame(p_frame); minimap->queue_redraw(); @@ -535,6 +543,7 @@ void GraphEdit::_graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, Gr void GraphEdit::_graph_element_moved(Node *p_node) { GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); ERR_FAIL_NULL(graph_element); + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); minimap->queue_redraw(); queue_redraw(); @@ -543,6 +552,7 @@ void GraphEdit::_graph_element_moved(Node *p_node) { } void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_node) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); GraphNode *graph_node = Object::cast_to<GraphNode>(p_node); ERR_FAIL_NULL(graph_node); @@ -558,6 +568,8 @@ void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_node) { } void GraphEdit::_graph_node_rect_changed(GraphNode *p_node) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + // Only invalidate the cache when zooming or the node is moved/resized in graph space. if (panner->is_panning()) { return; @@ -566,7 +578,6 @@ void GraphEdit::_graph_node_rect_changed(GraphNode *p_node) { for (Ref<Connection> &c : connection_map[p_node->get_name()]) { c->_cache.dirty = true; } - connections_layer->queue_redraw(); callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred(); @@ -623,7 +634,9 @@ 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(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + if (connections_layer != nullptr && connections_layer->is_inside_tree()) { + 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)); @@ -640,6 +653,9 @@ void GraphEdit::remove_child_notify(Node *p_child) { minimap = nullptr; } else if (p_child == connections_layer) { connections_layer = nullptr; + if (is_inside_tree()) { + WARN_PRINT("GraphEdit's connection_layer removed. This should not be done. If you like to remove all GraphElements from a GraphEdit node, do not simply remove all non-internal children but check their type since the connection layer has to be kept non-internal due to technical reasons."); + } } if (top_layer != nullptr && is_inside_tree()) { @@ -662,7 +678,9 @@ void GraphEdit::remove_child_notify(Node *p_child) { for (const Ref<Connection> &conn : connection_map[graph_node->get_name()]) { conn->_cache.dirty = true; } - connections_layer->queue_redraw(); + if (connections_layer != nullptr && connections_layer->is_inside_tree()) { + connections_layer->queue_redraw(); + } } GraphFrame *frame = Object::cast_to<GraphFrame>(graph_element); @@ -1690,6 +1708,8 @@ void GraphEdit::set_selected(Node *p_child) { } void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + ERR_FAIL_COND(p_ev.is_null()); if (panner->gui_input(p_ev, warped_panning ? get_global_rect() : Rect2())) { return; @@ -1999,6 +2019,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } else if (p_ev->is_action("ui_copy", true)) { emit_signal(SNAME("copy_nodes_request")); accept_event(); + } else if (p_ev->is_action("ui_cut", true)) { + emit_signal(SNAME("cut_nodes_request")); + accept_event(); } else if (p_ev->is_action("ui_paste", true)) { emit_signal(SNAME("paste_nodes_request")); accept_event(); @@ -2022,6 +2045,8 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } void GraphEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + h_scrollbar->set_value(h_scrollbar->get_value() - p_scroll_vec.x); v_scrollbar->set_value(v_scrollbar->get_value() - p_scroll_vec.y); @@ -2037,6 +2062,8 @@ void GraphEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputE } void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + for (Ref<Connection> &c : connection_map[p_from]) { if (c->from_node == p_from && c->from_port == p_from_port && c->to_node == p_to && c->to_port == p_to_port) { if (!Math::is_equal_approx(c->activity, p_activity)) { @@ -2053,6 +2080,8 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por } void GraphEdit::reset_all_connection_activity() { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + bool changed = false; for (Ref<Connection> &conn : connections) { if (conn->activity > 0) { @@ -2067,6 +2096,8 @@ void GraphEdit::reset_all_connection_activity() { } void GraphEdit::clear_connections() { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + for (Ref<Connection> &c : connections) { c->_cache.line->queue_free(); } @@ -2080,7 +2111,9 @@ void GraphEdit::clear_connections() { } void GraphEdit::force_connection_drag_end() { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); ERR_FAIL_COND_MSG(!connecting, "Drag end requested without active drag!"); + connecting = false; connecting_valid = false; minimap->queue_redraw(); @@ -2110,6 +2143,8 @@ void GraphEdit::set_zoom(float p_zoom) { } void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + p_zoom = CLAMP(p_zoom, zoom_min, zoom_max); if (zoom == p_zoom) { return; @@ -2518,6 +2553,8 @@ bool GraphEdit::is_showing_arrange_button() const { } void GraphEdit::override_connections_shader(const Ref<Shader> &p_shader) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + connections_shader = p_shader; _invalidate_connection_line_cache(); @@ -2536,6 +2573,8 @@ void GraphEdit::_minimap_toggled() { } void GraphEdit::set_connection_lines_curvature(float p_curvature) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + lines_curvature = p_curvature; _invalidate_connection_line_cache(); connections_layer->queue_redraw(); @@ -2547,7 +2586,9 @@ float GraphEdit::get_connection_lines_curvature() const { } void GraphEdit::set_connection_lines_thickness(float p_thickness) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); ERR_FAIL_COND_MSG(p_thickness < 0, "Connection lines thickness must be greater than or equal to 0."); + if (lines_thickness == p_thickness) { return; } @@ -2562,6 +2603,8 @@ float GraphEdit::get_connection_lines_thickness() const { } void GraphEdit::set_connection_lines_antialiased(bool p_antialiased) { + ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing."); + if (lines_antialiased == p_antialiased) { return; } @@ -2735,6 +2778,7 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("connection_drag_ended")); ADD_SIGNAL(MethodInfo("copy_nodes_request")); + ADD_SIGNAL(MethodInfo("cut_nodes_request")); ADD_SIGNAL(MethodInfo("paste_nodes_request")); ADD_SIGNAL(MethodInfo("duplicate_nodes_request")); ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName"))); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 72e59bfc8a..8f5023104a 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -208,7 +208,7 @@ void GraphNode::_resort() { // Avoid negative stretch space. stretch_diff = MAX(stretch_diff, 0); - available_stretch_space += stretch_diff - sb_panel->get_margin(SIDE_BOTTOM) - sb_panel->get_margin(SIDE_TOP); + available_stretch_space += stretch_diff - sb_panel->get_margin(SIDE_BOTTOM) - sb_panel->get_margin(SIDE_TOP) - titlebar_min_size.height - sb_titlebar->get_minimum_size().height; // Second pass, discard elements that can't be stretched, this will run while stretchable elements exist. diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index bf16c0699e..f3cf29d0cf 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -32,7 +32,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" -#include "core/string/translation.h" #include "scene/theme/theme_db.h" void ItemList::_shape_text(int p_idx) { @@ -58,12 +57,12 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo Item item; item.icon = p_texture; item.text = p_item; - item.xl_text = atr(p_item); item.selectable = p_selectable; items.push_back(item); int item_id = items.size() - 1; - _shape_text(items.size() - 1); + items.write[item_id].xl_text = _atr(item_id, p_item); + _shape_text(item_id); queue_redraw(); shape_changed = true; @@ -95,7 +94,7 @@ void ItemList::set_item_text(int p_idx, const String &p_text) { } items.write[p_idx].text = p_text; - items.write[p_idx].xl_text = atr(p_text); + items.write[p_idx].xl_text = _atr(p_idx, p_text); _shape_text(p_idx); queue_redraw(); shape_changed = true; @@ -141,6 +140,24 @@ String ItemList::get_item_language(int p_idx) const { return items[p_idx].language; } +void ItemList::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode) { + if (p_idx < 0) { + p_idx += get_item_count(); + } + ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].auto_translate_mode != p_mode) { + items.write[p_idx].auto_translate_mode = p_mode; + items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text); + _shape_text(p_idx); + queue_redraw(); + } +} + +Node::AutoTranslateMode ItemList::get_item_auto_translate_mode(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), AUTO_TRANSLATE_MODE_INHERIT); + return items[p_idx].auto_translate_mode; +} + void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) { if (p_idx < 0) { p_idx += get_item_count(); @@ -1022,7 +1039,7 @@ void ItemList::_notification(int p_what) { } break; case NOTIFICATION_TRANSLATION_CHANGED: { for (int i = 0; i < items.size(); i++) { - items.write[i].xl_text = atr(items[i].text); + items.write[i].xl_text = _atr(i, items[i].text); _shape_text(i); } shape_changed = true; @@ -1039,7 +1056,7 @@ void ItemList::_notification(int p_what) { scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM)); Size2 size = get_size(); - int width = size.width - theme_cache.panel_style->get_minimum_size().width; + int width = size.width - theme_cache.panel_style->get_margin(SIDE_RIGHT); if (scroll_bar->is_visible()) { width -= scroll_bar_minwidth; } @@ -1192,9 +1209,9 @@ void ItemList::_notification(int p_what) { Point2 pos = items[i].rect_cache.position + icon_ofs + base_ofs; if (icon_mode == ICON_MODE_TOP) { - pos.y += theme_cache.v_separation / 2; + pos.y += MAX(theme_cache.v_separation, 0) / 2; } else { - pos.x += theme_cache.h_separation / 2; + pos.x += MAX(theme_cache.h_separation, 0) / 2; } if (icon_mode == ICON_MODE_TOP) { @@ -1243,8 +1260,8 @@ void ItemList::_notification(int p_what) { } Point2 draw_pos = items[i].rect_cache.position; - draw_pos.x += theme_cache.h_separation / 2; - draw_pos.y += theme_cache.v_separation / 2; + draw_pos.x += MAX(theme_cache.h_separation, 0) / 2; + draw_pos.y += MAX(theme_cache.v_separation, 0) / 2; if (rtl) { draw_pos.x = size.width - draw_pos.x - tag_icon_size.x; } @@ -1283,8 +1300,7 @@ void ItemList::_notification(int p_what) { text_ofs += base_ofs; text_ofs += items[i].rect_cache.position; - text_ofs.x += theme_cache.h_separation / 2; - text_ofs.y += theme_cache.v_separation / 2; + text_ofs.y += MAX(theme_cache.v_separation, 0) / 2; if (rtl) { text_ofs.x = size.width - text_ofs.x - max_len; @@ -1292,7 +1308,7 @@ void ItemList::_notification(int p_what) { items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER); - float text_w = items[i].rect_cache.size.width - theme_cache.h_separation; + float text_w = items[i].rect_cache.size.width; items.write[i].text_buf->set_width(text_w); if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { @@ -1307,17 +1323,17 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2; - text_ofs.x += theme_cache.h_separation / 2; - text_ofs.y += theme_cache.v_separation / 2; + text_ofs.x += MAX(theme_cache.h_separation, 0) / 2; + text_ofs.y += MAX(theme_cache.v_separation, 0) / 2; } else { text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2; - text_ofs.x += theme_cache.h_separation / 2; + text_ofs.x += MAX(theme_cache.h_separation, 0) / 2; } text_ofs += base_ofs; text_ofs += items[i].rect_cache.position; - float text_w = width - text_ofs.x - theme_cache.h_separation; + float text_w = width - text_ofs.x; items.write[i].text_buf->set_width(text_w); if (rtl) { @@ -1410,14 +1426,14 @@ void ItemList::force_update_list_size() { max_column_width = MAX(max_column_width, minsize.x); // Elements need to adapt to the selected size. - minsize.y += theme_cache.v_separation; - minsize.x += theme_cache.h_separation; + minsize.y += MAX(theme_cache.v_separation, 0); + minsize.x += MAX(theme_cache.h_separation, 0); items.write[i].rect_cache.size = minsize; items.write[i].min_rect_cache.size = minsize; } - int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width - scroll_bar_minwidth; + int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width; //2-attempt best fit current_columns = 0x7FFFFFFF; @@ -1430,12 +1446,13 @@ void ItemList::force_update_list_size() { bool all_fit = true; Vector2 ofs; int col = 0; + int max_w = 0; int max_h = 0; separators.clear(); for (int i = 0; i < items.size(); i++) { - if (current_columns > 1 && items[i].rect_cache.size.width + ofs.x > fit_size) { + if (current_columns > 1 && items[i].rect_cache.size.width + ofs.x > fit_size && !auto_width) { // Went past. current_columns = MAX(col, 1); all_fit = false; @@ -1443,7 +1460,7 @@ void ItemList::force_update_list_size() { } if (same_column_width) { - items.write[i].rect_cache.size.x = max_column_width + theme_cache.h_separation; + items.write[i].rect_cache.size.x = max_column_width + MAX(theme_cache.h_separation, 0); } items.write[i].rect_cache.position = ofs; @@ -1461,6 +1478,7 @@ void ItemList::force_update_list_size() { items.write[j].rect_cache.size.y = max_h; } + max_w = MAX(max_w, ofs.x); ofs.x = 0; ofs.y += max_h; col = 0; @@ -1468,22 +1486,30 @@ void ItemList::force_update_list_size() { } } + float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height); + float max = MAX(page, ofs.y + max_h); + if (page >= max) { + fit_size -= scroll_bar_minwidth; + } + if (all_fit) { for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) { items.write[j].rect_cache.size.y = max_h; } - float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height); - float max = MAX(page, ofs.y + max_h); if (auto_height) { auto_height_value = ofs.y + max_h + theme_cache.panel_style->get_minimum_size().height; } + if (auto_width) { + auto_width_value = max_w + theme_cache.panel_style->get_minimum_size().width; + } scroll_bar->set_max(max); scroll_bar->set_page(page); if (max <= page) { scroll_bar->set_value(0); scroll_bar->hide(); } else { + auto_width_value += scroll_bar_minwidth; scroll_bar->show(); if (do_autoscroll_to_bottom) { @@ -1509,6 +1535,23 @@ void ItemList::_mouse_exited() { } } +String ItemList::_atr(int p_idx, const String &p_text) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), atr(p_text)); + switch (items[p_idx].auto_translate_mode) { + case AUTO_TRANSLATE_MODE_INHERIT: { + return atr(p_text); + } break; + case AUTO_TRANSLATE_MODE_ALWAYS: { + return tr(p_text); + } break; + case AUTO_TRANSLATE_MODE_DISABLED: { + return p_text; + } break; + } + + ERR_FAIL_V_MSG(atr(p_text), "Unexpected auto translate mode: " + itos(items[p_idx].auto_translate_mode)); +} + int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { Vector2 pos = p_pos; pos -= theme_cache.panel_style->get_offset(); @@ -1667,16 +1710,35 @@ bool ItemList::is_anything_selected() { } Size2 ItemList::get_minimum_size() const { + Size2 min_size; + if (auto_width) { + min_size.x = auto_width_value; + } + if (auto_height) { - return Size2(0, auto_height_value); + min_size.y = auto_height_value; } - return Size2(); + return min_size; } void ItemList::set_autoscroll_to_bottom(const bool p_enable) { do_autoscroll_to_bottom = p_enable; } +void ItemList::set_auto_width(bool p_enable) { + if (auto_width == p_enable) { + return; + } + + auto_width = p_enable; + shape_changed = true; + queue_redraw(); +} + +bool ItemList::has_auto_width() const { + return auto_width; +} + void ItemList::set_auto_height(bool p_enable) { if (auto_height == p_enable) { return; @@ -1748,6 +1810,9 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &ItemList::set_item_language); ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &ItemList::get_item_language); + ClassDB::bind_method(D_METHOD("set_item_auto_translate_mode", "idx", "mode"), &ItemList::set_item_auto_translate_mode); + ClassDB::bind_method(D_METHOD("get_item_auto_translate_mode", "idx"), &ItemList::get_item_auto_translate_mode); + ClassDB::bind_method(D_METHOD("set_item_icon_transposed", "idx", "transposed"), &ItemList::set_item_icon_transposed); ClassDB::bind_method(D_METHOD("is_item_icon_transposed", "idx"), &ItemList::is_item_icon_transposed); @@ -1829,6 +1894,9 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &ItemList::set_allow_search); ClassDB::bind_method(D_METHOD("get_allow_search"), &ItemList::get_allow_search); + ClassDB::bind_method(D_METHOD("set_auto_width", "enable"), &ItemList::set_auto_width); + ClassDB::bind_method(D_METHOD("has_auto_width"), &ItemList::has_auto_width); + ClassDB::bind_method(D_METHOD("set_auto_height", "enable"), &ItemList::set_auto_height); ClassDB::bind_method(D_METHOD("has_auto_height"), &ItemList::has_auto_height); @@ -1850,6 +1918,7 @@ void ItemList::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_width"), "set_auto_width", "has_auto_width"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_"); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index a20688b3d0..0836ea33d5 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -62,6 +62,7 @@ private: Ref<TextParagraph> text_buf; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; + AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT; bool selectable = true; bool selected = false; @@ -99,6 +100,9 @@ private: bool same_column_width = false; bool allow_search = true; + bool auto_width = false; + float auto_width_value = 0.0; + bool auto_height = false; float auto_height_value = 0.0; @@ -159,6 +163,8 @@ private: void _shape_text(int p_idx); void _mouse_exited(); + String _atr(int p_idx, const String &p_text) const; + protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); @@ -183,6 +189,9 @@ public: void set_item_language(int p_idx, const String &p_language); String get_item_language(int p_idx) const; + void set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode); + AutoTranslateMode get_item_auto_translate_mode(int p_idx) const; + void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon); Ref<Texture2D> get_item_icon(int p_idx) const; @@ -285,6 +294,9 @@ public: void set_icon_scale(real_t p_scale); real_t get_icon_scale() const; + void set_auto_width(bool p_enable); + bool has_auto_width() const; + void set_auto_height(bool p_enable); bool has_auto_height() const; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 1a066b0728..9967805134 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -45,28 +45,37 @@ #include "editor/editor_settings.h" #endif -void LineEdit::_edit() { +void LineEdit::edit() { if (!is_inside_tree()) { return; } if (!has_focus()) { grab_focus(); + return; } if (!editable || editing) { return; } + if (select_all_on_focus) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + // Select all when the mouse button is up. + pending_select_all_on_focus = true; + } else { + select_all(); + } + } + editing = true; _validate_caret_can_draw(); show_virtual_keyboard(); queue_redraw(); - emit_signal(SNAME("editing_toggled"), true); } -void LineEdit::_unedit() { +void LineEdit::unedit() { if (!editing) { return; } @@ -84,8 +93,6 @@ void LineEdit::_unedit() { if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { deselect(); } - - emit_signal(SNAME("editing_toggled"), false); } bool LineEdit::is_editing() const { @@ -390,7 +397,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editable && !editing) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); } accept_event(); @@ -406,7 +414,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { set_caret_at_pixel_pos(b->get_position().x); if (!editing) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); } if (!paste_buffer.is_empty()) { @@ -506,7 +515,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editable && !editing) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); return; } queue_redraw(); @@ -599,7 +609,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); + accept_event(); return; } @@ -734,7 +746,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } if (editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } accept_event(); @@ -743,7 +756,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { if (k->is_action("ui_cancel")) { if (editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } accept_event(); @@ -921,6 +935,7 @@ Variant LineEdit::get_drag_data(const Point2 &p_point) { String t = get_selected_text(); Label *l = memnew(Label); l->set_text(t); + l->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Don't translate user input. set_drag_preview(l); return t; } @@ -1331,24 +1346,17 @@ void LineEdit::_notification(int p_what) { } break; case NOTIFICATION_FOCUS_ENTER: { - if (select_all_on_focus) { - if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - // Select all when the mouse button is up. - pending_select_all_on_focus = true; - } else { - select_all(); - } - } - // Only allow editing if the LineEdit is not focused with arrow keys. if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) { - _edit(); + edit(); + emit_signal(SNAME("editing_toggled"), true); } } break; case NOTIFICATION_FOCUS_EXIT: { if (editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } } break; @@ -2137,7 +2145,8 @@ void LineEdit::set_editable(bool p_editable) { editable = p_editable; if (!editable && editing) { - _unedit(); + unedit(); + emit_signal(SNAME("editing_toggled"), false); } _validate_caret_can_draw(); @@ -2758,11 +2767,15 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("edit"), &LineEdit::edit); + ClassDB::bind_method(D_METHOD("unedit"), &LineEdit::unedit); ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing); ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear); ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all); ClassDB::bind_method(D_METHOD("deselect"), &LineEdit::deselect); + ClassDB::bind_method(D_METHOD("has_undo"), &LineEdit::has_undo); + ClassDB::bind_method(D_METHOD("has_redo"), &LineEdit::has_redo); ClassDB::bind_method(D_METHOD("has_selection"), &LineEdit::has_selection); ClassDB::bind_method(D_METHOD("get_selected_text"), &LineEdit::get_selected_text); ClassDB::bind_method(D_METHOD("get_selection_from_column"), &LineEdit::get_selection_from_column); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index ac7436646b..9253dd8711 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -207,9 +207,6 @@ private: float base_scale = 1.0; } theme_cache; - void _edit(); - void _unedit(); - void _close_ime_window(); void _update_ime_window_position(); @@ -265,6 +262,8 @@ protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; public: + void edit(); + void unedit(); bool is_editing() const; bool has_ime_text() const; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 0c3c90d070..26141663c1 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -2067,16 +2067,27 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { } } + bool scroll_value_modified = false; + double prev_scroll = vscroll->get_value(); + if (b->get_button_index() == MouseButton::WHEEL_UP) { if (scroll_active) { vscroll->scroll(-vscroll->get_page() * b->get_factor() * 0.5 / 8); + scroll_value_modified = true; } } if (b->get_button_index() == MouseButton::WHEEL_DOWN) { if (scroll_active) { vscroll->scroll(vscroll->get_page() * b->get_factor() * 0.5 / 8); + scroll_value_modified = true; } } + + if (scroll_value_modified && vscroll->get_value() != prev_scroll) { + accept_event(); + return; + } + if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { _update_context_menu(); menu->set_position(get_screen_position() + b->get_position()); @@ -5431,6 +5442,7 @@ Variant RichTextLabel::get_drag_data(const Point2 &p_point) { String t = get_selected_text(); Label *l = memnew(Label); l->set_text(t); + l->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Text is already translated. set_drag_preview(l); return t; } diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index af9f08e389..eb69dab478 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -472,7 +472,7 @@ double ScrollBar::get_area_size() const { } double ScrollBar::get_grabber_offset() const { - return (get_area_size()) * get_as_ratio(); + return get_area_size() * get_as_ratio(); } Size2 ScrollBar::get_minimum_size() const { diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index f1902bade4..1ac0e8b59f 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -58,7 +58,7 @@ Size2 ScrollContainer::get_minimum_size() const { if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { min_size.x = largest_child_min_size.x; - bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > size.y); + bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || vertical_scroll_mode == SCROLL_MODE_RESERVE || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > size.y); if (v_scroll_show && v_scroll->get_parent() == this) { min_size.x += v_scroll->get_minimum_size().x; } @@ -66,7 +66,7 @@ Size2 ScrollContainer::get_minimum_size() const { if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { min_size.y = largest_child_min_size.y; - bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > size.x); + bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || horizontal_scroll_mode == SCROLL_MODE_RESERVE || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > size.x); if (h_scroll_show && h_scroll->get_parent() == this) { min_size.y += h_scroll->get_minimum_size().y; } @@ -92,6 +92,15 @@ void ScrollContainer::_cancel_drag() { } } +bool ScrollContainer::_is_h_scroll_visible() const { + // Scrolls may have been moved out for reasons. + return h_scroll->is_visible() && h_scroll->get_parent() == this; +} + +bool ScrollContainer::_is_v_scroll_visible() const { + return v_scroll->is_visible() && v_scroll->get_parent() == this; +} + void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); @@ -298,11 +307,11 @@ void ScrollContainer::_reposition_children() { ofs += theme_cache.panel_style->get_offset(); bool rtl = is_layout_rtl(); - if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) { //scrolls may have been moved out for reasons + if (_is_h_scroll_visible() || horizontal_scroll_mode == SCROLL_MODE_RESERVE) { size.y -= h_scroll->get_minimum_size().y; } - if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) { //scrolls may have been moved out for reasons + if (_is_v_scroll_visible() || vertical_scroll_mode == SCROLL_MODE_RESERVE) { size.x -= v_scroll->get_minimum_size().x; } @@ -324,7 +333,7 @@ void ScrollContainer::_reposition_children() { r.size.height = MAX(size.height, minsize.height); } r.position += ofs; - if (rtl && v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) { + if (rtl && _is_v_scroll_visible()) { r.position.x += v_scroll->get_minimum_size().x; } r.position = r.position.floor(); @@ -436,14 +445,14 @@ void ScrollContainer::update_scrollbars() { Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); - h_scroll->set_visible(horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.width > size.width)); - v_scroll->set_visible(vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.height > size.height)); + h_scroll->set_visible(horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || ((horizontal_scroll_mode == SCROLL_MODE_AUTO || horizontal_scroll_mode == SCROLL_MODE_RESERVE) && largest_child_min_size.width > size.width)); + v_scroll->set_visible(vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || ((vertical_scroll_mode == SCROLL_MODE_AUTO || vertical_scroll_mode == SCROLL_MODE_RESERVE) && largest_child_min_size.height > size.height)); h_scroll->set_max(largest_child_min_size.width); - h_scroll->set_page((v_scroll->is_visible() && v_scroll->get_parent() == this) ? size.width - vmin.width : size.width); + h_scroll->set_page(_is_v_scroll_visible() ? size.width - vmin.width : size.width); v_scroll->set_max(largest_child_min_size.height); - v_scroll->set_page((h_scroll->is_visible() && h_scroll->get_parent() == this) ? size.height - hmin.height : size.height); + v_scroll->set_page(_is_h_scroll_visible() ? size.height - hmin.height : size.height); // Avoid scrollbar overlapping. _updating_scrollbars = true; @@ -538,7 +547,7 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const { int found = 0; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); if (!c) { continue; } @@ -603,14 +612,15 @@ void ScrollContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_horizontal_custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_horizontal_custom_step", "get_horizontal_custom_step"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical_custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_vertical_custom_step", "get_vertical_custom_step"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_horizontal_scroll_mode", "get_horizontal_scroll_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_vertical_scroll_mode", "get_vertical_scroll_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show,Reserve"), "set_horizontal_scroll_mode", "get_horizontal_scroll_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show,Reserve"), "set_vertical_scroll_mode", "get_vertical_scroll_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone"); BIND_ENUM_CONSTANT(SCROLL_MODE_DISABLED); BIND_ENUM_CONSTANT(SCROLL_MODE_AUTO); BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_ALWAYS); BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_NEVER); + BIND_ENUM_CONSTANT(SCROLL_MODE_RESERVE); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, panel_style, "panel"); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 02146618cd..afd3c8bd57 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -44,6 +44,7 @@ public: SCROLL_MODE_AUTO, SCROLL_MODE_SHOW_ALWAYS, SCROLL_MODE_SHOW_NEVER, + SCROLL_MODE_RESERVE, }; private: @@ -75,6 +76,9 @@ private: void _cancel_drag(); + bool _is_h_scroll_visible() const; + bool _is_v_scroll_visible() const; + protected: Size2 get_minimum_size() const override; diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 8ab9b4c1cd..be03db526d 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -32,6 +32,7 @@ #include "scene/gui/label.h" #include "scene/gui/margin_container.h" +#include "scene/main/window.h" #include "scene/theme/theme_db.h" void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { @@ -39,7 +40,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) { + if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { return; } @@ -48,8 +49,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - sc->_compute_middle_sep(true); + sc->_compute_split_offset(true); dragging = true; + sc->emit_signal(SNAME("drag_started")); drag_ofs = sc->split_offset; if (sc->vertical) { drag_from = get_transform().xform(mb->get_position()).y; @@ -59,6 +61,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { } else { dragging = false; queue_redraw(); + sc->emit_signal(SNAME("drag_ended")); } } } @@ -76,7 +79,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { } else { sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); } - sc->_compute_middle_sep(true); + sc->_compute_split_offset(true); sc->queue_sort(); sc->emit_signal(SNAME("dragged"), sc->get_split_offset()); } @@ -84,11 +87,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - - if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) { + if (!sc->collapsed && sc->dragging_enabled) { return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); } - return Control::get_cursor_shape(p_pos); } @@ -101,7 +102,6 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; - case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); @@ -109,22 +109,25 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; - case NOTIFICATION_DRAW: { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - if (!dragging && !mouse_inside && sc->theme_cache.autohide) { - return; + draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect); + if (sc->dragger_visibility == sc->DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide)) { + Ref<Texture2D> tex = sc->_get_grabber_icon(); + float available_size = sc->vertical ? (sc->get_size().x - tex->get_size().x) : (sc->get_size().y - tex->get_size().y); + if (available_size - sc->drag_area_margin_begin - sc->drag_area_margin_end > 0) { // Draw the grabber only if it fits. + draw_texture(tex, (split_bar_rect.get_position() + (split_bar_rect.get_size() - tex->get_size()) * 0.5)); + } + } + if (sc->show_drag_area && Engine::get_singleton()->is_editor_hint()) { + draw_rect(Rect2(Vector2(0, 0), get_size()), sc->dragging_enabled ? Color(1, 1, 0, 0.3) : Color(1, 0, 0, 0.3)); } - - Ref<Texture2D> tex = sc->_get_grabber_icon(); - draw_texture(tex, (get_size() - tex->get_size()) / 2); } break; } } Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode) const { int idx = 0; - for (int i = 0; i < get_child_count(false); i++) { Control *c = as_sortable_control(get_child(i, false), p_visibility_mode); if (!c) { @@ -137,7 +140,6 @@ Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_ idx++; } - return nullptr; } @@ -153,45 +155,48 @@ Ref<Texture2D> SplitContainer::_get_grabber_icon() const { } } -void SplitContainer::_compute_middle_sep(bool p_clamp) { +int SplitContainer::_get_separation() const { + if (dragger_visibility == DRAGGER_HIDDEN_COLLAPSED) { + return 0; + } + // DRAGGER_VISIBLE or DRAGGER_HIDDEN. + Ref<Texture2D> g = _get_grabber_icon(); + return MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()); +} + +void SplitContainer::_compute_split_offset(bool p_clamp) { Control *first = _get_sortable_child(0); Control *second = _get_sortable_child(1); + int axis_index = vertical ? 1 : 0; + int size = get_size()[axis_index]; + int sep = _get_separation(); - // Determine expanded children. - bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; - bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; - - // Compute the minimum size. - int axis = vertical ? 1 : 0; - int size = get_size()[axis]; - int ms_first = first->get_combined_minimum_size()[axis]; - int ms_second = second->get_combined_minimum_size()[axis]; - - // Determine the separation between items. - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; - - // Compute the wished separation_point. - int wished_middle_sep = 0; + // Compute the wished size. + int wished_size = 0; int split_offset_with_collapse = 0; if (!collapsed) { split_offset_with_collapse = split_offset; } - if (first_expanded && second_expanded) { + bool first_is_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; + bool second_is_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; + + if (first_is_expanded && second_is_expanded) { float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio()); - wished_middle_sep = size * ratio - sep / 2 + split_offset_with_collapse; - } else if (first_expanded) { - wished_middle_sep = size - sep + split_offset_with_collapse; + wished_size = size * ratio - sep * 0.5 + split_offset_with_collapse; + } else if (first_is_expanded) { + wished_size = size - sep + split_offset_with_collapse; } else { - wished_middle_sep = split_offset_with_collapse; + wished_size = split_offset_with_collapse; } - // Clamp the middle sep to acceptatble values. - middle_sep = CLAMP(wished_middle_sep, ms_first, size - sep - ms_second); + // Clamp the split offset to acceptable values. + int first_min_size = first->get_combined_minimum_size()[axis_index]; + int second_min_size = second->get_combined_minimum_size()[axis_index]; + computed_split_offset = CLAMP(wished_size, first_min_size, size - sep - second_min_size); // Clamp the split_offset if requested. if (p_clamp) { - split_offset -= wished_middle_sep - middle_sep; + split_offset -= wished_size - computed_split_offset; } } @@ -199,8 +204,7 @@ void SplitContainer::_resort() { Control *first = _get_sortable_child(0); Control *second = _get_sortable_child(1); - // If we have only one element. - if (!first || !second) { + if (!first || !second) { // Only one child. if (first) { fit_child_in_rect(first, Rect2(Point2(), get_size())); } else if (second) { @@ -209,53 +213,50 @@ void SplitContainer::_resort() { dragging_area_control->hide(); return; } + dragging_area_control->set_visible(!collapsed); - // If we have more that one. - _compute_middle_sep(false); + _compute_split_offset(false); // This recalculates and sets computed_split_offset. - // Determine the separation between items. - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; + int sep = _get_separation(); + bool is_rtl = is_layout_rtl(); - // Move the children, including the dragger. + // Move the children. if (vertical) { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep))); - int sofs = middle_sep + sep; + fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, computed_split_offset))); + int sofs = computed_split_offset + sep; fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs))); } else { - if (is_layout_rtl()) { - middle_sep = get_size().width - middle_sep - sep; - fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height))); - int sofs = middle_sep + sep; + if (is_rtl) { + computed_split_offset = get_size().width - computed_split_offset - sep; + fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); + int sofs = computed_split_offset + sep; fit_child_in_rect(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); } else { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height))); - int sofs = middle_sep + sep; + fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); + int sofs = computed_split_offset + sep; fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); } } - // Handle the dragger visibility and position. - if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) { - dragging_area_control->show(); - - int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); - if (vertical) { - dragging_area_control->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, dragger_ctrl_size))); - } else { - dragging_area_control->set_rect(Rect2(Point2(middle_sep - (dragger_ctrl_size - sep) / 2, 0), Size2(dragger_ctrl_size, get_size().height))); - } - - dragging_area_control->queue_redraw(); + dragging_area_control->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE); + const int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); + float split_bar_offset = (dragger_ctrl_size - sep) * 0.5; + if (vertical) { + Rect2 split_bar_rect = Rect2(is_rtl ? drag_area_margin_end : drag_area_margin_begin, computed_split_offset, get_size().width - drag_area_margin_begin - drag_area_margin_end, sep); + dragging_area_control->set_rect(Rect2(split_bar_rect.position.x, split_bar_rect.position.y - split_bar_offset + drag_area_offset, split_bar_rect.size.x, dragger_ctrl_size)); + dragging_area_control->split_bar_rect = Rect2(Vector2(0.0, int(split_bar_offset) - drag_area_offset), split_bar_rect.size); } else { - dragging_area_control->hide(); + Rect2 split_bar_rect = Rect2(computed_split_offset, drag_area_margin_begin, sep, get_size().height - drag_area_margin_begin - drag_area_margin_end); + dragging_area_control->set_rect(Rect2(split_bar_rect.position.x - split_bar_offset + drag_area_offset * (is_rtl ? -1 : 1), split_bar_rect.position.y, dragger_ctrl_size, split_bar_rect.size.y)); + dragging_area_control->split_bar_rect = Rect2(Vector2(int(split_bar_offset) - drag_area_offset * (is_rtl ? -1 : 1), 0.0), split_bar_rect.size); } + queue_redraw(); + dragging_area_control->queue_redraw(); } Size2 SplitContainer::get_minimum_size() const { Size2i minimum; - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; + int sep = _get_separation(); for (int i = 0; i < 2; i++) { Control *child = _get_sortable_child(i, SortableVisbilityMode::VISIBLE); @@ -297,11 +298,9 @@ void SplitContainer::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { queue_sort(); } break; - case NOTIFICATION_SORT_CHILDREN: { _resort(); } break; - case NOTIFICATION_THEME_CHANGED: { update_minimum_size(); } break; @@ -312,9 +311,7 @@ void SplitContainer::set_split_offset(int p_offset) { if (split_offset == p_offset) { return; } - split_offset = p_offset; - queue_sort(); } @@ -326,8 +323,7 @@ void SplitContainer::clamp_split_offset() { if (!_get_sortable_child(0) || !_get_sortable_child(1)) { return; } - - _compute_middle_sep(true); + _compute_split_offset(true); queue_sort(); } @@ -335,7 +331,6 @@ void SplitContainer::set_collapsed(bool p_collapsed) { if (collapsed == p_collapsed) { return; } - collapsed = p_collapsed; queue_sort(); } @@ -344,7 +339,6 @@ void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) { if (dragger_visibility == p_visibility) { return; } - dragger_visibility = p_visibility; queue_sort(); } @@ -368,6 +362,26 @@ bool SplitContainer::is_vertical() const { return vertical; } +void SplitContainer::set_dragging_enabled(bool p_enabled) { + if (dragging_enabled == p_enabled) { + return; + } + dragging_enabled = p_enabled; + if (!dragging_enabled && dragging_area_control->dragging) { + dragging_area_control->dragging = false; + // queue_redraw() is called by _resort(). + emit_signal(SNAME("drag_ended")); + } + if (get_viewport()) { + get_viewport()->update_mouse_cursor_state(); + } + _resort(); +} + +bool SplitContainer::is_dragging_enabled() const { + return dragging_enabled; +} + Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const { Vector<int> flags; flags.append(SIZE_FILL); @@ -392,6 +406,51 @@ Vector<int> SplitContainer::get_allowed_size_flags_vertical() const { return flags; } +void SplitContainer::set_drag_area_margin_begin(int p_margin) { + if (drag_area_margin_begin == p_margin) { + return; + } + drag_area_margin_begin = p_margin; + queue_sort(); +} + +int SplitContainer::get_drag_area_margin_begin() const { + return drag_area_margin_begin; +} + +void SplitContainer::set_drag_area_margin_end(int p_margin) { + if (drag_area_margin_end == p_margin) { + return; + } + drag_area_margin_end = p_margin; + queue_sort(); +} + +int SplitContainer::get_drag_area_margin_end() const { + return drag_area_margin_end; +} + +void SplitContainer::set_drag_area_offset(int p_offset) { + if (drag_area_offset == p_offset) { + return; + } + drag_area_offset = p_offset; + queue_sort(); +} + +int SplitContainer::get_drag_area_offset() const { + return drag_area_offset; +} + +void SplitContainer::set_show_drag_area_enabled(bool p_enabled) { + show_drag_area = p_enabled; + dragging_area_control->queue_redraw(); +} + +bool SplitContainer::is_show_drag_area_enabled() const { + return show_drag_area; +} + void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); @@ -406,13 +465,39 @@ void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical); + ClassDB::bind_method(D_METHOD("set_dragging_enabled", "dragging_enabled"), &SplitContainer::set_dragging_enabled); + ClassDB::bind_method(D_METHOD("is_dragging_enabled"), &SplitContainer::is_dragging_enabled); + + ClassDB::bind_method(D_METHOD("set_drag_area_margin_begin", "margin"), &SplitContainer::set_drag_area_margin_begin); + ClassDB::bind_method(D_METHOD("get_drag_area_margin_begin"), &SplitContainer::get_drag_area_margin_begin); + + ClassDB::bind_method(D_METHOD("set_drag_area_margin_end", "margin"), &SplitContainer::set_drag_area_margin_end); + ClassDB::bind_method(D_METHOD("get_drag_area_margin_end"), &SplitContainer::get_drag_area_margin_end); + + ClassDB::bind_method(D_METHOD("set_drag_area_offset", "offset"), &SplitContainer::set_drag_area_offset); + ClassDB::bind_method(D_METHOD("get_drag_area_offset"), &SplitContainer::get_drag_area_offset); + + ClassDB::bind_method(D_METHOD("set_drag_area_highlight_in_editor", "drag_area_highlight_in_editor"), &SplitContainer::set_show_drag_area_enabled); + ClassDB::bind_method(D_METHOD("is_drag_area_highlight_in_editor_enabled"), &SplitContainer::is_show_drag_area_enabled); + + ClassDB::bind_method(D_METHOD("get_drag_area_control"), &SplitContainer::get_drag_area_control); + ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset"))); + ADD_SIGNAL(MethodInfo("drag_started")); + ADD_SIGNAL(MethodInfo("drag_ended")); ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dragging_enabled"), "set_dragging_enabled", "is_dragging_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); + ADD_GROUP("Drag Area", "drag_area_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_begin", "get_drag_area_margin_begin"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_end", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_end", "get_drag_area_margin_end"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_offset", "get_drag_area_offset"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_area_highlight_in_editor"), "set_drag_area_highlight_in_editor", "is_drag_area_highlight_in_editor_enabled"); + BIND_ENUM_CONSTANT(DRAGGER_VISIBLE); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED); @@ -423,6 +508,7 @@ void SplitContainer::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon, "grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SplitContainer, split_bar_background, "split_bar_background"); } SplitContainer::SplitContainer(bool p_vertical) { diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index db870554c2..2bba96b4b8 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -35,6 +35,8 @@ class SplitContainerDragger : public Control { GDCLASS(SplitContainerDragger, Control); + friend class SplitContainer; + Rect2 split_bar_rect; protected: void _notification(int p_what); @@ -62,11 +64,16 @@ public: }; private: + int show_drag_area = false; + int drag_area_margin_begin = 0; + int drag_area_margin_end = 0; + int drag_area_offset = 0; int split_offset = 0; - int middle_sep = 0; + int computed_split_offset = 0; bool vertical = false; bool collapsed = false; DraggerVisibility dragger_visibility = DRAGGER_VISIBLE; + bool dragging_enabled = true; SplitContainerDragger *dragging_area_control = nullptr; @@ -77,10 +84,13 @@ private: Ref<Texture2D> grabber_icon; Ref<Texture2D> grabber_icon_h; Ref<Texture2D> grabber_icon_v; + float base_scale = 1.0; + Ref<StyleBox> split_bar_background; } theme_cache; Ref<Texture2D> _get_grabber_icon() const; - void _compute_middle_sep(bool p_clamp); + void _compute_split_offset(bool p_clamp); + int _get_separation() const; void _resort(); Control *_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const; @@ -105,11 +115,28 @@ public: void set_vertical(bool p_vertical); bool is_vertical() const; + void set_dragging_enabled(bool p_enabled); + bool is_dragging_enabled() const; + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; + void set_drag_area_margin_begin(int p_margin); + int get_drag_area_margin_begin() const; + + void set_drag_area_margin_end(int p_margin); + int get_drag_area_margin_end() const; + + void set_drag_area_offset(int p_offset); + int get_drag_area_offset() const; + + void set_show_drag_area_enabled(bool p_enabled); + bool is_show_drag_area_enabled() const; + + Control *get_drag_area_control() { return dragging_area_control; } + SplitContainer(bool p_vertical = false); }; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 3e0d6adf42..6cc7ec3bd5 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -354,6 +354,8 @@ void TabBar::_notification(int p_what) { if (scroll_to_selected) { ensure_tab_visible(current); } + // Set initialized even if no tabs were set. + initialized = true; } break; case NOTIFICATION_INTERNAL_PROCESS: { @@ -655,10 +657,10 @@ void TabBar::set_tab_count(int p_count) { } if (!initialized) { - if (queued_current != current) { - current = queued_current; - } initialized = true; + if (queued_current != CURRENT_TAB_UNINITIALIZED && queued_current != current) { + set_current_tab(queued_current); + } } queue_redraw(); @@ -740,6 +742,13 @@ bool TabBar::select_next_available() { return false; } +void TabBar::set_tab_offset(int p_offset) { + ERR_FAIL_INDEX(p_offset, tabs.size()); + offset = p_offset; + _update_cache(); + queue_redraw(); +} + int TabBar::get_tab_offset() const { return offset; } @@ -1250,6 +1259,7 @@ Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_poin } Label *label = memnew(Label(get_tab_title(tab_over))); + label->set_auto_translate_mode(get_auto_translate_mode()); // Reflect how the title is displayed. drag_preview->add_child(label); set_drag_preview(drag_preview); diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index d62b39ae16..f5ae75de51 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -117,8 +117,9 @@ private: bool scroll_to_selected = true; int tabs_rearrange_group = -1; + static const int CURRENT_TAB_UNINITIALIZED = -2; bool initialized = false; - int queued_current = -1; + int queued_current = CURRENT_TAB_UNINITIALIZED; const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5; const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20; @@ -249,6 +250,7 @@ public: bool select_previous_available(); bool select_next_available(); + void set_tab_offset(int p_offset); int get_tab_offset() const; bool get_offset_buttons_visible() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 23b99d1e53..33e72428cf 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -190,7 +190,7 @@ void TabContainer::_notification(int p_what) { } break; case NOTIFICATION_VISIBILITY_CHANGED: { - if (!is_visible() || setup_current_tab > -2) { + if (!is_visible()) { return; } @@ -200,7 +200,7 @@ void TabContainer::_notification(int p_what) { // 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(); + int current = setup_current_tab > -2 ? setup_current_tab : get_current_tab(); for (int i = 0; i < controls.size(); i++) { controls[i]->set_visible(i == current); } @@ -267,10 +267,14 @@ void TabContainer::_repaint() { Vector<Control *> controls = _get_tab_controls(); int current = get_current_tab(); + // Move the TabBar to the top or bottom. + // Don't change the left and right offsets since the TabBar will resize and may change tab offset. if (tabs_position == POSITION_BOTTOM) { - tab_bar->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); + tab_bar->set_anchor_and_offset(SIDE_BOTTOM, 1.0, 0.0); + tab_bar->set_anchor_and_offset(SIDE_TOP, 1.0, -_get_tab_height()); } else { - tab_bar->set_anchors_and_offsets_preset(PRESET_TOP_WIDE); + tab_bar->set_anchor_and_offset(SIDE_BOTTOM, 0.0, _get_tab_height()); + tab_bar->set_anchor_and_offset(SIDE_TOP, 0.0, 0.0); } updating_visibility = true; @@ -299,7 +303,6 @@ void TabContainer::_repaint() { } updating_visibility = false; - _update_margins(); update_minimum_size(); } @@ -909,7 +912,7 @@ Size2 TabContainer::get_minimum_size() const { for (int i = 0; i < controls.size(); i++) { Control *c = controls[i]; - if (!c->is_visible_in_tree() && !use_hidden_tabs_for_min_size) { + if (!c->is_visible() && !use_hidden_tabs_for_min_size) { continue; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index d7799588ea..6b5ff23436 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1265,7 +1265,7 @@ void TextEdit::_notification(int p_what) { } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word - if (is_ascii_alphabet_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { + if (is_unicode_identifier_start(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(); @@ -3034,6 +3034,7 @@ Variant TextEdit::get_drag_data(const Point2 &p_point) { if (has_selection() && selection_drag_attempt) { String t = get_selected_text(); Label *l = memnew(Label); + l->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Don't translate user input. l->set_text(t); set_drag_preview(l); return t; @@ -3366,6 +3367,14 @@ bool TextEdit::is_middle_mouse_paste_enabled() const { return middle_mouse_paste_enabled; } +void TextEdit::set_empty_selection_clipboard_enabled(bool p_enabled) { + empty_selection_clipboard_enabled = p_enabled; +} + +bool TextEdit::is_empty_selection_clipboard_enabled() const { + return empty_selection_clipboard_enabled; +} + // Text manipulation void TextEdit::clear() { setting_text = true; @@ -6568,6 +6577,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enabled"), &TextEdit::set_middle_mouse_paste_enabled); ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &TextEdit::is_middle_mouse_paste_enabled); + ClassDB::bind_method(D_METHOD("set_empty_selection_clipboard_enabled", "enabled"), &TextEdit::set_empty_selection_clipboard_enabled); + ClassDB::bind_method(D_METHOD("is_empty_selection_clipboard_enabled"), &TextEdit::is_empty_selection_clipboard_enabled); + // Text manipulation ClassDB::bind_method(D_METHOD("clear"), &TextEdit::clear); @@ -6961,6 +6973,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "empty_selection_clipboard_enabled"), "set_empty_selection_clipboard_enabled", "is_empty_selection_clipboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_wrapped_lines"), "set_indent_wrapped_lines", "is_indent_wrapped_lines"); @@ -7215,6 +7228,10 @@ void TextEdit::_cut_internal(int p_caret) { return; } + if (!empty_selection_clipboard_enabled) { + return; + } + // Remove full lines. begin_complex_operation(); begin_multicaret_edit(); @@ -7245,6 +7262,10 @@ void TextEdit::_copy_internal(int p_caret) { return; } + if (!empty_selection_clipboard_enabled) { + return; + } + // Copy full lines. StringBuilder clipboard; Vector<Point2i> line_ranges; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index c5f838020b..94b105d486 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -319,6 +319,7 @@ private: bool shortcut_keys_enabled = true; bool virtual_keyboard_enabled = true; bool middle_mouse_paste_enabled = true; + bool empty_selection_clipboard_enabled = true; // Overridable actions. String cut_copy_line = ""; @@ -770,6 +771,9 @@ public: void set_middle_mouse_paste_enabled(bool p_enabled); bool is_middle_mouse_paste_enabled() const; + void set_empty_selection_clipboard_enabled(bool p_enabled); + bool is_empty_selection_clipboard_enabled() const; + // Text manipulation void clear(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index e4f52ee8ee..646cd9c70e 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -185,6 +185,26 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { return cells[p_column].mode; } +/* auto translate mode */ +void TreeItem::set_auto_translate_mode(int p_column, Node::AutoTranslateMode p_mode) { + ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].auto_translate_mode == p_mode) { + return; + } + + cells.write[p_column].auto_translate_mode = p_mode; + cells.write[p_column].dirty = true; + cells.write[p_column].cached_minimum_size_dirty = true; + + _changed_notify(p_column); +} + +Node::AutoTranslateMode TreeItem::get_auto_translate_mode(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), Node::AUTO_TRANSLATE_MODE_INHERIT); + return cells[p_column].auto_translate_mode; +} + /* multiline editable */ void TreeItem::set_edit_multiline(int p_column, bool p_multiline) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -247,6 +267,24 @@ void TreeItem::propagate_check(int p_column, bool p_emit_signal) { _propagate_check_through_parents(p_column, p_emit_signal); } +String TreeItem::atr(int p_column, const String &p_text) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), tree->atr(p_text)); + + switch (cells[p_column].auto_translate_mode) { + case Node::AUTO_TRANSLATE_MODE_INHERIT: { + return tree->atr(p_text); + } break; + case Node::AUTO_TRANSLATE_MODE_ALWAYS: { + return tree->tr(p_text); + } break; + case Node::AUTO_TRANSLATE_MODE_DISABLED: { + return p_text; + } break; + } + + ERR_FAIL_V_MSG(tree->atr(p_text), "Unexpected auto translate mode: " + itos(cells[p_column].auto_translate_mode)); +} + void TreeItem::_propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal) { TreeItem *current = get_first_child(); while (current) { @@ -323,7 +361,7 @@ void TreeItem::set_text(int p_column, String p_text) { } else { // Don't auto translate if it's in string mode and editable, as the text can be changed to anything by the user. if (tree && (!cells[p_column].editable || cells[p_column].mode != TreeItem::CELL_MODE_STRING)) { - cells.write[p_column].xl_text = tree->atr(p_text); + cells.write[p_column].xl_text = atr(p_column, p_text); } else { cells.write[p_column].xl_text = p_text; } @@ -1621,6 +1659,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_mode", "column", "mode"), &TreeItem::set_cell_mode); ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode); + ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "column", "mode"), &TreeItem::set_auto_translate_mode); + ClassDB::bind_method(D_METHOD("get_auto_translate_mode", "column"), &TreeItem::get_auto_translate_mode); + ClassDB::bind_method(D_METHOD("set_edit_multiline", "column", "multiline"), &TreeItem::set_edit_multiline); ClassDB::bind_method(D_METHOD("is_edit_multiline", "column"), &TreeItem::is_edit_multiline); @@ -2009,7 +2050,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { int option = (int)p_item->cells[p_col].val; - valtext = atr(ETR("(Other)")); + valtext = p_item->atr(p_col, ETR("(Other)")); Vector<String> strings = p_item->cells[p_col].text.split(","); for (int j = 0; j < strings.size(); j++) { int value = j; @@ -2017,7 +2058,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { value = strings[j].get_slicec(':', 1).to_int(); } if (option == value) { - valtext = atr(strings[j].get_slicec(':', 0)); + valtext = p_item->atr(p_col, strings[j].get_slicec(':', 0)); break; } } @@ -2028,7 +2069,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { } else { // Don't auto translate if it's in string mode and editable, as the text can be changed to anything by the user. if (!p_item->cells[p_col].editable || p_item->cells[p_col].mode != TreeItem::CELL_MODE_STRING) { - p_item->cells.write[p_col].xl_text = atr(p_item->cells[p_col].text); + p_item->cells.write[p_col].xl_text = p_item->atr(p_col, p_item->cells[p_col].text); } else { p_item->cells.write[p_col].xl_text = p_item->cells[p_col].text; } @@ -3993,25 +4034,25 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } break; case MouseButton::WHEEL_UP: { - if (_scroll(false, -mb->get_factor() / 8)) { + if (_scroll(mb->is_shift_pressed(), -mb->get_factor() / 8)) { accept_event(); } } break; case MouseButton::WHEEL_DOWN: { - if (_scroll(false, mb->get_factor() / 8)) { + if (_scroll(mb->is_shift_pressed(), mb->get_factor() / 8)) { accept_event(); } } break; case MouseButton::WHEEL_LEFT: { - if (_scroll(true, -mb->get_factor() / 8)) { + if (_scroll(!mb->is_shift_pressed(), -mb->get_factor() / 8)) { accept_event(); } } break; case MouseButton::WHEEL_RIGHT: { - if (_scroll(true, mb->get_factor() / 8)) { + if (_scroll(!mb->is_shift_pressed(), mb->get_factor() / 8)) { accept_event(); } diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 17ea31a733..86efdfec52 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -64,6 +64,7 @@ private: Rect2i icon_region; String text; String xl_text; + Node::AutoTranslateMode auto_translate_mode = Node::AUTO_TRANSLATE_MODE_INHERIT; bool edit_multiline = false; String suffix; Ref<TextParagraph> text_buf; @@ -210,6 +211,10 @@ public: void set_cell_mode(int p_column, TreeCellMode p_mode); TreeCellMode get_cell_mode(int p_column) const; + /* auto translate mode */ + void set_auto_translate_mode(int p_column, Node::AutoTranslateMode p_mode); + Node::AutoTranslateMode get_auto_translate_mode(int p_column) const; + /* multiline editable */ void set_edit_multiline(int p_column, bool p_multiline); bool is_edit_multiline(int p_column) const; @@ -222,6 +227,8 @@ public: void propagate_check(int p_column, bool p_emit_signal = true); + String atr(int p_column, const String &p_text) const; + private: // Check helpers. void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal); diff --git a/scene/main/SCsub b/scene/main/SCsub index fc61250247..374dc2119d 100644 --- a/scene/main/SCsub +++ b/scene/main/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 3469b806a6..8526611093 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -49,7 +49,8 @@ Error HTTPRequest::_parse_url(const String &p_url) { redirections = 0; String scheme; - Error err = p_url.parse_url(scheme, url, port, request_string); + String fragment; + Error err = p_url.parse_url(scheme, url, port, request_string, fragment); ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing URL: '%s'.", p_url)); if (scheme == "https://") { diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 858fc2246b..d921cc5b67 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -111,6 +111,7 @@ void Node::_notification(int p_notification) { data.auto_translate_mode = AUTO_TRANSLATE_MODE_ALWAYS; } data.is_auto_translate_dirty = true; + data.is_translation_domain_dirty = true; #ifdef TOOLS_ENABLED // Don't translate UI elements when they're being edited. @@ -1320,6 +1321,51 @@ bool Node::can_auto_translate() const { return data.is_auto_translating; } +StringName Node::get_translation_domain() const { + ERR_READ_THREAD_GUARD_V(StringName()); + + if (data.is_translation_domain_inherited && data.is_translation_domain_dirty) { + const_cast<Node *>(this)->_translation_domain = data.parent ? data.parent->get_translation_domain() : StringName(); + data.is_translation_domain_dirty = false; + } + return _translation_domain; +} + +void Node::set_translation_domain(const StringName &p_domain) { + ERR_THREAD_GUARD + + if (!data.is_translation_domain_inherited && _translation_domain == p_domain) { + return; + } + + _translation_domain = p_domain; + data.is_translation_domain_inherited = false; + data.is_translation_domain_dirty = false; + _propagate_translation_domain_dirty(); +} + +void Node::set_translation_domain_inherited() { + ERR_THREAD_GUARD + + if (data.is_translation_domain_inherited) { + return; + } + data.is_translation_domain_inherited = true; + data.is_translation_domain_dirty = true; + _propagate_translation_domain_dirty(); +} + +void Node::_propagate_translation_domain_dirty() { + for (KeyValue<StringName, Node *> &K : data.children) { + Node *child = K.value; + if (child->data.is_translation_domain_inherited) { + child->data.is_translation_domain_dirty = true; + child->_propagate_translation_domain_dirty(); + } + } + notification(NOTIFICATION_TRANSLATION_CHANGED); +} + StringName Node::get_name() const { return data.name; } @@ -3610,6 +3656,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "mode"), &Node::set_auto_translate_mode); ClassDB::bind_method(D_METHOD("get_auto_translate_mode"), &Node::get_auto_translate_mode); + ClassDB::bind_method(D_METHOD("set_translation_domain_inherited"), &Node::set_translation_domain_inherited); ClassDB::bind_method(D_METHOD("get_window"), &Node::get_window); ClassDB::bind_method(D_METHOD("get_last_exclusive_window"), &Node::get_last_exclusive_window); @@ -3972,4 +4019,9 @@ bool Node::is_connected(const StringName &p_signal, const Callable &p_callable) return Object::is_connected(p_signal, p_callable); } +bool Node::has_connections(const StringName &p_signal) const { + ERR_THREAD_GUARD_V(false); + return Object::has_connections(p_signal); +} + #endif diff --git a/scene/main/node.h b/scene/main/node.h index dc65513fca..799478fa35 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -255,6 +255,9 @@ private: mutable bool is_auto_translating = true; mutable bool is_auto_translate_dirty = true; + mutable bool is_translation_domain_inherited = true; + mutable bool is_translation_domain_dirty = true; + mutable NodePath *path_cache = nullptr; } data; @@ -281,6 +284,7 @@ private: void _propagate_physics_interpolation_reset_requested(bool p_requested); void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification); void _propagate_groups_dirty(); + void _propagate_translation_domain_dirty(); Array _get_node_and_resource(const NodePath &p_path); void _duplicate_properties(const Node *p_root, const Node *p_original, Node *p_copy, int p_flags) const; @@ -735,8 +739,17 @@ public: AutoTranslateMode get_auto_translate_mode() const; bool can_auto_translate() const; - _FORCE_INLINE_ String atr(const String p_message, const StringName p_context = "") const { return can_auto_translate() ? tr(p_message, p_context) : p_message; } - _FORCE_INLINE_ String atr_n(const String p_message, const StringName &p_message_plural, int p_n, const StringName p_context = "") const { return can_auto_translate() ? tr_n(p_message, p_message_plural, p_n, p_context) : p_message; } + virtual StringName get_translation_domain() const override; + virtual void set_translation_domain(const StringName &p_domain) override; + void set_translation_domain_inherited(); + + _FORCE_INLINE_ String atr(const String &p_message, const StringName &p_context = "") const { return can_auto_translate() ? tr(p_message, p_context) : p_message; } + _FORCE_INLINE_ String atr_n(const String &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const { + if (can_auto_translate()) { + return tr_n(p_message, p_message_plural, p_n, p_context); + } + return p_n == 1 ? p_message : String(p_message_plural); + } /* THREADING */ @@ -789,6 +802,7 @@ public: virtual Error connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags = 0) override; virtual void disconnect(const StringName &p_signal, const Callable &p_callable) override; virtual bool is_connected(const StringName &p_signal, const Callable &p_callable) const override; + virtual bool has_connections(const StringName &p_signal) const override; #endif Node(); ~Node(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 106130872d..71d91b970e 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -302,11 +302,15 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } + Node *node = gr_nodes[i]; if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; - gr_nodes[i]->callp(p_function, p_args, p_argcount, ce); + node->callp(p_function, p_args, p_argcount, ce); + if (unlikely(ce.error != Callable::CallError::CALL_OK && ce.error != Callable::CallError::CALL_ERROR_INVALID_METHOD)) { + ERR_PRINT(vformat("Error calling group method on node \"%s\": %s.", node->get_name(), Variant::get_callable_error_text(Callable(node, p_function), p_args, p_argcount, ce))); + } } else { - MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount); + MessageQueue::get_singleton()->push_callp(node, p_function, p_args, p_argcount); } } @@ -316,11 +320,15 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro continue; } + Node *node = gr_nodes[i]; if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; - gr_nodes[i]->callp(p_function, p_args, p_argcount, ce); + node->callp(p_function, p_args, p_argcount, ce); + if (unlikely(ce.error != Callable::CallError::CALL_OK && ce.error != Callable::CallError::CALL_ERROR_INVALID_METHOD)) { + ERR_PRINT(vformat("Error calling group method on node \"%s\": %s.", node->get_name(), Variant::get_callable_error_text(Callable(node, p_function), p_args, p_argcount, ce))); + } } else { - MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount); + MessageQueue::get_singleton()->push_callp(node, p_function, p_args, p_argcount); } } } diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index a4e27a3d16..169a5dcb01 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -111,6 +111,9 @@ void ViewportTexture::set_viewport_path_in_scene(const NodePath &p_path) { if (get_local_scene() && !path.is_empty()) { setup_local_to_scene(); } else { + if (path.is_empty()) { + vp_changed = false; + } emit_changed(); } } @@ -121,9 +124,7 @@ NodePath ViewportTexture::get_viewport_path_in_scene() const { int ViewportTexture::get_width() const { if (!vp) { - if (!vp_pending) { - ERR_PRINT("Viewport Texture must be set to use it."); - } + _err_print_viewport_not_set(); return 0; } return vp->size.width; @@ -131,9 +132,7 @@ int ViewportTexture::get_width() const { int ViewportTexture::get_height() const { if (!vp) { - if (!vp_pending) { - ERR_PRINT("Viewport Texture must be set to use it."); - } + _err_print_viewport_not_set(); return 0; } return vp->size.height; @@ -141,9 +140,7 @@ int ViewportTexture::get_height() const { Size2 ViewportTexture::get_size() const { if (!vp) { - if (!vp_pending) { - ERR_PRINT("Viewport Texture must be set to use it."); - } + _err_print_viewport_not_set(); return Size2(); } return vp->size; @@ -163,14 +160,18 @@ bool ViewportTexture::has_alpha() const { Ref<Image> ViewportTexture::get_image() const { if (!vp) { - if (!vp_pending) { - ERR_PRINT("Viewport Texture must be set to use it."); - } + _err_print_viewport_not_set(); return Ref<Image>(); } return RS::get_singleton()->texture_2d_get(vp->texture_rid); } +void ViewportTexture::_err_print_viewport_not_set() const { + if (!vp_pending && !vp_changed) { + ERR_PRINT("Viewport Texture must be set to use it."); + } +} + void ViewportTexture::_setup_local_to_scene(const Node *p_loc_scene) { // Always reset this, even if this call fails with an error. vp_pending = false; @@ -1399,7 +1400,7 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont String tooltip; while (p_control) { - tooltip = p_control->atr(p_control->get_tooltip(pos)); + tooltip = p_control->get_tooltip(pos); // Temporary solution for PopupMenus. PopupMenu *menu = Object::cast_to<PopupMenu>(this); @@ -1482,6 +1483,7 @@ void Viewport::_gui_show_tooltip() { } base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + base_tooltip->set_auto_translate_mode(tooltip_owner->get_tooltip_auto_translate_mode()); panel->set_transient(true); panel->set_flag(Window::FLAG_NO_FOCUS, true); @@ -4670,6 +4672,7 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d); ClassDB::bind_method(D_METHOD("is_audio_listener_2d"), &Viewport::is_audio_listener_2d); + ClassDB::bind_method(D_METHOD("get_audio_listener_2d"), &Viewport::get_audio_listener_2d); ClassDB::bind_method(D_METHOD("get_camera_2d"), &Viewport::get_camera_2d); #ifndef _3D_DISABLED @@ -4680,6 +4683,7 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_own_world_3d", "enable"), &Viewport::set_use_own_world_3d); ClassDB::bind_method(D_METHOD("is_using_own_world_3d"), &Viewport::is_using_own_world_3d); + ClassDB::bind_method(D_METHOD("get_audio_listener_3d"), &Viewport::get_audio_listener_3d); ClassDB::bind_method(D_METHOD("get_camera_3d"), &Viewport::get_camera_3d); ClassDB::bind_method(D_METHOD("set_as_audio_listener_3d", "enable"), &Viewport::set_as_audio_listener_3d); ClassDB::bind_method(D_METHOD("is_audio_listener_3d"), &Viewport::is_audio_listener_3d); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index edacee2d88..a18dc1f6f0 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -63,6 +63,7 @@ class ViewportTexture : public Texture2D { bool vp_changed = false; void _setup_local_to_scene(const Node *p_loc_scene); + void _err_print_viewport_not_set() const; mutable RID proxy_ph; mutable RID proxy; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 76678e609a..6b1ce2b4ca 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -117,6 +117,7 @@ #include "scene/resources/compressed_texture.h" #include "scene/resources/curve_texture.h" #include "scene/resources/environment.h" +#include "scene/resources/external_texture.h" #include "scene/resources/font.h" #include "scene/resources/gradient.h" #include "scene/resources/gradient_texture.h" @@ -882,7 +883,6 @@ void register_scene_types() { GDREGISTER_CLASS(ProceduralSkyMaterial); GDREGISTER_CLASS(PanoramaSkyMaterial); GDREGISTER_CLASS(PhysicalSkyMaterial); - SceneTree::add_idle_callback(BaseMaterial3D::flush_changes); BaseMaterial3D::init_shaders(); GDREGISTER_CLASS(MeshLibrary); @@ -926,6 +926,7 @@ void register_scene_types() { GDREGISTER_CLASS(GradientTexture2D); GDREGISTER_CLASS(AnimatedTexture); GDREGISTER_CLASS(CameraTexture); + GDREGISTER_CLASS(ExternalTexture); GDREGISTER_VIRTUAL_CLASS(TextureLayered); GDREGISTER_ABSTRACT_CLASS(ImageTextureLayered); GDREGISTER_VIRTUAL_CLASS(Texture3D); diff --git a/scene/resources/2d/SCsub b/scene/resources/2d/SCsub index fdf20e0bde..408aa3cf7e 100644 --- a/scene/resources/2d/SCsub +++ b/scene/resources/2d/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h index 931495d020..15e1a16359 100644 --- a/scene/resources/2d/tile_set.h +++ b/scene/resources/2d/tile_set.h @@ -571,25 +571,25 @@ public: // Not exposed. virtual void set_tile_set(const TileSet *p_tile_set); TileSet *get_tile_set() const; - virtual void notify_tile_data_properties_should_change(){}; - virtual void add_occlusion_layer(int p_index){}; - virtual void move_occlusion_layer(int p_from_index, int p_to_pos){}; - virtual void remove_occlusion_layer(int p_index){}; - virtual void add_physics_layer(int p_index){}; - virtual void move_physics_layer(int p_from_index, int p_to_pos){}; - virtual void remove_physics_layer(int p_index){}; - virtual void add_terrain_set(int p_index){}; - virtual void move_terrain_set(int p_from_index, int p_to_pos){}; - virtual void remove_terrain_set(int p_index){}; - virtual void add_terrain(int p_terrain_set, int p_index){}; - virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){}; - virtual void remove_terrain(int p_terrain_set, int p_index){}; - virtual void add_navigation_layer(int p_index){}; - virtual void move_navigation_layer(int p_from_index, int p_to_pos){}; - virtual void remove_navigation_layer(int p_index){}; - virtual void add_custom_data_layer(int p_index){}; - virtual void move_custom_data_layer(int p_from_index, int p_to_pos){}; - virtual void remove_custom_data_layer(int p_index){}; + virtual void notify_tile_data_properties_should_change() {} + virtual void add_occlusion_layer(int p_index) {} + virtual void move_occlusion_layer(int p_from_index, int p_to_pos) {} + virtual void remove_occlusion_layer(int p_index) {} + virtual void add_physics_layer(int p_index) {} + virtual void move_physics_layer(int p_from_index, int p_to_pos) {} + virtual void remove_physics_layer(int p_index) {} + virtual void add_terrain_set(int p_index) {} + virtual void move_terrain_set(int p_from_index, int p_to_pos) {} + virtual void remove_terrain_set(int p_index) {} + virtual void add_terrain(int p_terrain_set, int p_index) {} + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) {} + virtual void remove_terrain(int p_terrain_set, int p_index) {} + virtual void add_navigation_layer(int p_index) {} + virtual void move_navigation_layer(int p_from_index, int p_to_pos) {} + virtual void remove_navigation_layer(int p_index) {} + virtual void add_custom_data_layer(int p_index) {} + virtual void move_custom_data_layer(int p_from_index, int p_to_pos) {} + virtual void remove_custom_data_layer(int p_index) {} virtual void reset_state() override; // Tiles. diff --git a/scene/resources/3d/SCsub b/scene/resources/3d/SCsub index fdf20e0bde..408aa3cf7e 100644 --- a/scene/resources/3d/SCsub +++ b/scene/resources/3d/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp index 92246b50db..6c6e98b50d 100644 --- a/scene/resources/3d/fog_material.cpp +++ b/scene/resources/3d/fog_material.cpp @@ -168,6 +168,8 @@ void fog() { } FogMaterial::FogMaterial() { + _set_material(RS::get_singleton()->material_create()); + set_density(1.0); set_albedo(Color(1, 1, 1, 1)); set_emission(Color(0, 0, 0, 1)); diff --git a/scene/resources/3d/primitive_meshes.h b/scene/resources/3d/primitive_meshes.h index fc2489923a..85f46a482a 100644 --- a/scene/resources/3d/primitive_meshes.h +++ b/scene/resources/3d/primitive_meshes.h @@ -77,7 +77,7 @@ protected: Vector2 get_uv2_scale(Vector2 p_margin_scale = Vector2(1.0, 1.0)) const; float get_lightmap_texel_size() const; - virtual void _update_lightmap_size(){}; + virtual void _update_lightmap_size() {} void _on_settings_changed(); @@ -541,7 +541,7 @@ private: Vector2 point; bool sharp = false; - ContourPoint(){}; + ContourPoint() {} ContourPoint(const Vector2 &p_pt, bool p_sharp) { point = p_pt; sharp = p_sharp; @@ -551,7 +551,7 @@ private: struct ContourInfo { real_t length = 0.0; bool ccw = true; - ContourInfo(){}; + ContourInfo() {} ContourInfo(real_t p_len, bool p_ccw) { length = p_len; ccw = p_ccw; diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp index c470db5d7f..10ef516f7a 100644 --- a/scene/resources/3d/sky_material.cpp +++ b/scene/resources/3d/sky_material.cpp @@ -357,6 +357,7 @@ void sky() { } ProceduralSkyMaterial::ProceduralSkyMaterial() { + _set_material(RS::get_singleton()->material_create()); set_sky_top_color(Color(0.385, 0.454, 0.55)); set_sky_horizon_color(Color(0.6463, 0.6558, 0.6708)); set_sky_curve(0.15); @@ -486,6 +487,7 @@ void sky() { } PanoramaSkyMaterial::PanoramaSkyMaterial() { + _set_material(RS::get_singleton()->material_create()); set_energy_multiplier(1.0); } @@ -785,6 +787,7 @@ void sky() { } PhysicalSkyMaterial::PhysicalSkyMaterial() { + _set_material(RS::get_singleton()->material_create()); set_rayleigh_coefficient(2.0); set_rayleigh_color(Color(0.3, 0.405, 0.6)); set_mie_coefficient(0.005); diff --git a/scene/resources/SCsub b/scene/resources/SCsub index 2b6aa88d2c..46f6251b91 100644 --- a/scene/resources/SCsub +++ b/scene/resources/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index a2ed6af23c..57a4e35f7a 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -63,6 +63,23 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } compression.enabled = true; return true; + } else if (prop_name == SNAME("markers")) { + Array markers = p_value; + for (const Dictionary marker : markers) { + ERR_FAIL_COND_V(!marker.has("name"), false); + ERR_FAIL_COND_V(!marker.has("time"), false); + StringName marker_name = marker["name"]; + double time = marker["time"]; + _marker_insert(time, marker_names, MarkerKey(time, marker_name)); + marker_times.insert(marker_name, time); + Color color = Color(1, 1, 1); + if (marker.has("color")) { + color = marker["color"]; + } + marker_colors.insert(marker_name, color); + } + + return true; } else if (prop_name.begins_with("tracks/")) { int track = prop_name.get_slicec('/', 1).to_int(); String what = prop_name.get_slicec('/', 2); @@ -321,8 +338,12 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { Vector<real_t> times = d["times"]; Vector<real_t> values = d["points"]; #ifdef TOOLS_ENABLED - ERR_FAIL_COND_V(!d.has("handle_modes"), false); - Vector<int> handle_modes = d["handle_modes"]; + Vector<int> handle_modes; + if (d.has("handle_modes")) { + handle_modes = d["handle_modes"]; + } else { + handle_modes.resize_zeroed(times.size()); + } #endif // TOOLS_ENABLED ERR_FAIL_COND_V(times.size() * 5 != values.size(), false); @@ -466,6 +487,18 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { r_ret = comp; return true; + } else if (prop_name == SNAME("markers")) { + Array markers; + + for (HashMap<StringName, double>::ConstIterator E = marker_times.begin(); E; ++E) { + Dictionary d; + d["name"] = E->key; + d["time"] = E->value; + d["color"] = marker_colors[E->key]; + markers.push_back(d); + } + + r_ret = markers; } else if (prop_name == "length") { r_ret = length; } else if (prop_name == "loop_mode") { @@ -835,6 +868,7 @@ void Animation::_get_property_list(List<PropertyInfo> *p_list) const { if (compression.enabled) { p_list->push_back(PropertyInfo(Variant::DICTIONARY, "_compression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } + p_list->push_back(PropertyInfo(Variant::ARRAY, "markers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); for (int i = 0; i < tracks.size(); i++) { p_list->push_back(PropertyInfo(Variant::STRING, "tracks/" + itos(i) + "/type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/imported", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); @@ -1083,6 +1117,27 @@ int Animation::_insert(double p_time, T &p_keys, const V &p_value) { return -1; } +int Animation::_marker_insert(double p_time, Vector<MarkerKey> &p_keys, const MarkerKey &p_value) { + int idx = p_keys.size(); + + while (true) { + // Condition for replacement. + if (idx > 0 && Math::is_equal_approx((double)p_keys[idx - 1].time, p_time)) { + p_keys.write[idx - 1] = p_value; + return idx - 1; + + // Condition for insert. + } else if (idx == 0 || p_keys[idx - 1].time < p_time) { + p_keys.insert(idx, p_value); + return idx; + } + + idx--; + } + + return -1; +} + template <typename T> void Animation::_clear(T &p_keys) { p_keys.clear(); @@ -3159,6 +3214,90 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl } } +void Animation::add_marker(const StringName &p_name, double p_time) { + int idx = _find(marker_names, p_time); + + if (idx >= 0 && idx < marker_names.size() && Math::is_equal_approx(p_time, marker_names[idx].time)) { + marker_times.erase(marker_names[idx].name); + marker_colors.erase(marker_names[idx].name); + marker_names.write[idx].name = p_name; + marker_times.insert(p_name, p_time); + marker_colors.insert(p_name, Color(1, 1, 1)); + } else { + _marker_insert(p_time, marker_names, MarkerKey(p_time, p_name)); + marker_times.insert(p_name, p_time); + marker_colors.insert(p_name, Color(1, 1, 1)); + } +} + +void Animation::remove_marker(const StringName &p_name) { + HashMap<StringName, double>::Iterator E = marker_times.find(p_name); + ERR_FAIL_COND(!E); + int idx = _find(marker_names, E->value); + bool success = idx >= 0 && idx < marker_names.size() && Math::is_equal_approx(marker_names[idx].time, E->value); + ERR_FAIL_COND(!success); + marker_names.remove_at(idx); + marker_times.remove(E); + marker_colors.erase(p_name); +} + +bool Animation::has_marker(const StringName &p_name) const { + return marker_times.has(p_name); +} + +StringName Animation::get_marker_at_time(double p_time) const { + int idx = _find(marker_names, p_time); + + if (idx >= 0 && idx < marker_names.size() && Math::is_equal_approx(marker_names[idx].time, p_time)) { + return marker_names[idx].name; + } + + return StringName(); +} + +StringName Animation::get_next_marker(double p_time) const { + int idx = _find(marker_names, p_time); + + if (idx >= -1 && idx < marker_names.size() - 1) { + // _find ensures that the time at idx is always the closest time to p_time that is also smaller to it. + // So we add 1 to get the next marker. + return marker_names[idx + 1].name; + } + return StringName(); +} + +StringName Animation::get_prev_marker(double p_time) const { + int idx = _find(marker_names, p_time); + + if (idx >= 0 && idx < marker_names.size()) { + return marker_names[idx].name; + } + return StringName(); +} + +double Animation::get_marker_time(const StringName &p_name) const { + ERR_FAIL_COND_V(!marker_times.has(p_name), -1); + return marker_times.get(p_name); +} + +PackedStringArray Animation::get_marker_names() const { + PackedStringArray names; + // We iterate on marker_names so the result is sorted by time. + for (const MarkerKey &marker_name : marker_names) { + names.push_back(marker_name.name); + } + return names; +} + +Color Animation::get_marker_color(const StringName &p_name) const { + ERR_FAIL_COND_V(!marker_colors.has(p_name), Color()); + return marker_colors[p_name]; +} + +void Animation::set_marker_color(const StringName &p_name, const Color &p_color) { + marker_colors[p_name] = p_color; +} + Vector<Variant> Animation::method_track_get_params(int p_track, int p_key_idx) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector<Variant>()); Track *t = tracks[p_track]; @@ -3890,6 +4029,17 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation); ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "track_idx", "key_idx"), &Animation::animation_track_get_key_animation); + ClassDB::bind_method(D_METHOD("add_marker", "name", "time"), &Animation::add_marker); + ClassDB::bind_method(D_METHOD("remove_marker", "name"), &Animation::remove_marker); + ClassDB::bind_method(D_METHOD("has_marker", "name"), &Animation::has_marker); + ClassDB::bind_method(D_METHOD("get_marker_at_time", "time"), &Animation::get_marker_at_time); + ClassDB::bind_method(D_METHOD("get_next_marker", "time"), &Animation::get_next_marker); + ClassDB::bind_method(D_METHOD("get_prev_marker", "time"), &Animation::get_prev_marker); + ClassDB::bind_method(D_METHOD("get_marker_time", "name"), &Animation::get_marker_time); + ClassDB::bind_method(D_METHOD("get_marker_names"), &Animation::get_marker_names); + ClassDB::bind_method(D_METHOD("get_marker_color", "name"), &Animation::get_marker_color); + ClassDB::bind_method(D_METHOD("set_marker_color", "name", "color"), &Animation::set_marker_color); + ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length); ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length); @@ -3902,6 +4052,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &Animation::clear); ClassDB::bind_method(D_METHOD("copy_track", "track_idx", "to_animation"), &Animation::copy_track); + ClassDB::bind_method(D_METHOD("optimize", "allowed_velocity_err", "allowed_angular_err", "precision"), &Animation::optimize, DEFVAL(0.01), DEFVAL(0.01), DEFVAL(3)); 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("is_capture_included"), &Animation::is_capture_included); @@ -4804,9 +4955,9 @@ void Animation::compress(uint32_t p_page_size, uint32_t p_fps, float p_split_tol continue; // This track is exhausted (all keys were added already), don't consider. } } - - uint32_t key_frame = double(track_get_key_time(uncomp_track, time_tracks[i].key_index)) / frame_len; - + double key_time = track_get_key_time(uncomp_track, time_tracks[i].key_index); + double result = key_time / frame_len; + uint32_t key_frame = Math::fast_ftoi(result); if (time_tracks[i].needs_start_frame && key_frame > base_page_frame) { start_frame = true; best_frame = base_page_frame; diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 0c29790ea4..618dc9ca17 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -237,6 +237,20 @@ private: } }; + /* Marker */ + + struct MarkerKey { + double time; + StringName name; + MarkerKey(double p_time, const StringName &p_name) : + time(p_time), name(p_name) {} + MarkerKey() = default; + }; + + Vector<MarkerKey> marker_names; // time -> name + HashMap<StringName, double> marker_times; // name -> time + HashMap<StringName, Color> marker_colors; // name -> color + Vector<Track *> tracks; template <typename T> @@ -245,6 +259,8 @@ private: template <typename T, typename V> int _insert(double p_time, T &p_keys, const V &p_value); + int _marker_insert(double p_time, Vector<MarkerKey> &p_keys, const MarkerKey &p_value); + template <typename K> inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false, bool p_limit = false) const; @@ -501,6 +517,17 @@ public: void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE) const; + void add_marker(const StringName &p_name, double p_time); + void remove_marker(const StringName &p_name); + bool has_marker(const StringName &p_name) const; + StringName get_marker_at_time(double p_time) const; + StringName get_next_marker(double p_time) const; + StringName get_prev_marker(double p_time) const; + double get_marker_time(const StringName &p_time) const; + PackedStringArray get_marker_names() const; + Color get_marker_color(const StringName &p_name) const; + void set_marker_color(const StringName &p_name, const Color &p_color); + void set_length(real_t p_length); real_t get_length() const; diff --git a/scene/resources/camera_attributes.h b/scene/resources/camera_attributes.h index 0f819134e2..de57b0ce8f 100644 --- a/scene/resources/camera_attributes.h +++ b/scene/resources/camera_attributes.h @@ -53,7 +53,7 @@ protected: float auto_exposure_max = 64.0; float auto_exposure_speed = 0.5; float auto_exposure_scale = 0.4; - virtual void _update_auto_exposure(){}; + virtual void _update_auto_exposure() {} public: virtual RID get_rid() const override; diff --git a/scene/resources/camera_texture.cpp b/scene/resources/camera_texture.cpp index b575a099ed..b219f89e59 100644 --- a/scene/resources/camera_texture.cpp +++ b/scene/resources/camera_texture.cpp @@ -47,6 +47,11 @@ void CameraTexture::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "camera_is_active"), "set_camera_active", "get_camera_active"); } +void CameraTexture::_on_format_changed() { + // FIXME: `emit_changed` is more appropriate, but causes errors for some reason. + callable_mp((Resource *)this, &Resource::emit_changed).call_deferred(); +} + int CameraTexture::get_width() const { Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); if (feed.is_valid()) { @@ -82,13 +87,26 @@ RID CameraTexture::get_rid() const { } Ref<Image> CameraTexture::get_image() const { - // not (yet) supported - return Ref<Image>(); + return RenderingServer::get_singleton()->texture_2d_get(get_rid()); } void CameraTexture::set_camera_feed_id(int p_new_id) { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + if (feed->is_connected("format_changed", callable_mp(this, &CameraTexture::_on_format_changed))) { + feed->disconnect("format_changed", callable_mp(this, &CameraTexture::_on_format_changed)); + } + } + camera_feed_id = p_new_id; + + feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + feed->connect("format_changed", callable_mp(this, &CameraTexture::_on_format_changed)); + } + notify_property_list_changed(); + callable_mp((Resource *)this, &Resource::emit_changed).call_deferred(); } int CameraTexture::get_camera_feed_id() const { @@ -98,6 +116,7 @@ int CameraTexture::get_camera_feed_id() const { void CameraTexture::set_which_feed(CameraServer::FeedImage p_which) { which_feed = p_which; notify_property_list_changed(); + callable_mp((Resource *)this, &Resource::emit_changed).call_deferred(); } CameraServer::FeedImage CameraTexture::get_which_feed() const { @@ -109,6 +128,7 @@ void CameraTexture::set_camera_active(bool p_active) { if (feed.is_valid()) { feed->set_active(p_active); notify_property_list_changed(); + callable_mp((Resource *)this, &Resource::emit_changed).call_deferred(); } } diff --git a/scene/resources/camera_texture.h b/scene/resources/camera_texture.h index 521121f9ea..dd216a72d6 100644 --- a/scene/resources/camera_texture.h +++ b/scene/resources/camera_texture.h @@ -43,6 +43,7 @@ private: protected: static void _bind_methods(); + void _on_format_changed(); public: virtual int get_width() const override; diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp index 76e99aca92..6f43106ea9 100644 --- a/scene/resources/canvas_item_material.cpp +++ b/scene/resources/canvas_item_material.cpp @@ -274,6 +274,8 @@ void CanvasItemMaterial::_bind_methods() { CanvasItemMaterial::CanvasItemMaterial() : element(this) { + _set_material(RS::get_singleton()->material_create()); + set_particles_anim_h_frames(1); set_particles_anim_v_frames(1); set_particles_anim_loop(false); diff --git a/scene/resources/external_texture.cpp b/scene/resources/external_texture.cpp new file mode 100644 index 0000000000..0552bbd081 --- /dev/null +++ b/scene/resources/external_texture.cpp @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* external_texture.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "external_texture.h" + +void ExternalTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &ExternalTexture::set_size); + ClassDB::bind_method(D_METHOD("get_external_texture_id"), &ExternalTexture::get_external_texture_id); + ClassDB::bind_method(D_METHOD("set_external_buffer_id", "external_buffer_id"), &ExternalTexture::set_external_buffer_id); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); +} + +uint64_t ExternalTexture::get_external_texture_id() const { + return RenderingServer::get_singleton()->texture_get_native_handle(texture); +} + +void ExternalTexture::set_size(const Size2 &p_size) { + if (p_size.width > 0 && p_size.height > 0 && p_size != size) { + size = p_size; + RenderingServer::get_singleton()->texture_external_update(texture, size.width, size.height, external_buffer); + emit_changed(); + } +} + +Size2 ExternalTexture::get_size() const { + return size; +} + +void ExternalTexture::set_external_buffer_id(uint64_t p_external_buffer) { + if (p_external_buffer != external_buffer) { + external_buffer = p_external_buffer; + RenderingServer::get_singleton()->texture_external_update(texture, size.width, size.height, external_buffer); + } +} + +int ExternalTexture::get_width() const { + return size.width; +} + +int ExternalTexture::get_height() const { + return size.height; +} + +bool ExternalTexture::has_alpha() const { + return false; +} + +RID ExternalTexture::get_rid() const { + return texture; +} + +ExternalTexture::ExternalTexture() { + texture = RenderingServer::get_singleton()->texture_external_create(size.width, size.height); +} + +ExternalTexture::~ExternalTexture() { + if (texture.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RenderingServer::get_singleton()->free(texture); + } +} diff --git a/scene/resources/external_texture.h b/scene/resources/external_texture.h new file mode 100644 index 0000000000..96bcd8d0fe --- /dev/null +++ b/scene/resources/external_texture.h @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* external_texture.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef EXTERNAL_TEXTURE_H +#define EXTERNAL_TEXTURE_H + +#include "scene/resources/texture.h" + +// External textures as defined by OES_EGL_image_external (GLES) or VK_ANDROID_external_memory_android_hardware_buffer (Vulkan). +class ExternalTexture : public Texture2D { + GDCLASS(ExternalTexture, Texture2D); + +private: + RID texture; + Size2 size = Size2(256, 256); + uint64_t external_buffer = 0; + +protected: + static void _bind_methods(); + +public: + uint64_t get_external_texture_id() const; + + virtual Size2 get_size() const override; + void set_size(const Size2 &p_size); + + void set_external_buffer_id(uint64_t p_external_buffer); + + virtual int get_width() const override; + virtual int get_height() const override; + + virtual RID get_rid() const override; + virtual bool has_alpha() const override; + + ExternalTexture(); + ~ExternalTexture(); +}; + +#endif // EXTERNAL_TEXTURE_H diff --git a/scene/resources/gradient_texture.cpp b/scene/resources/gradient_texture.cpp index 6ec9422d2d..2b0e455efb 100644 --- a/scene/resources/gradient_texture.cpp +++ b/scene/resources/gradient_texture.cpp @@ -85,7 +85,7 @@ void GradientTexture1D::_queue_update() { callable_mp(this, &GradientTexture1D::update_now).call_deferred(); } -void GradientTexture1D::_update() { +void GradientTexture1D::_update() const { update_pending = false; if (gradient.is_null()) { @@ -172,14 +172,14 @@ RID GradientTexture1D::get_rid() const { } Ref<Image> GradientTexture1D::get_image() const { - const_cast<GradientTexture1D *>(this)->update_now(); + update_now(); if (!texture.is_valid()) { return Ref<Image>(); } return RenderingServer::get_singleton()->texture_2d_get(texture); } -void GradientTexture1D::update_now() { +void GradientTexture1D::update_now() const { if (update_pending) { _update(); } @@ -225,7 +225,7 @@ void GradientTexture2D::_queue_update() { callable_mp(this, &GradientTexture2D::update_now).call_deferred(); } -void GradientTexture2D::_update() { +void GradientTexture2D::_update() const { update_pending = false; if (gradient.is_null()) { @@ -405,14 +405,14 @@ RID GradientTexture2D::get_rid() const { } Ref<Image> GradientTexture2D::get_image() const { - const_cast<GradientTexture2D *>(this)->update_now(); + update_now(); if (!texture.is_valid()) { return Ref<Image>(); } return RenderingServer::get_singleton()->texture_2d_get(texture); } -void GradientTexture2D::update_now() { +void GradientTexture2D::update_now() const { if (update_pending) { _update(); } diff --git a/scene/resources/gradient_texture.h b/scene/resources/gradient_texture.h index 761e8d995b..764e5e6645 100644 --- a/scene/resources/gradient_texture.h +++ b/scene/resources/gradient_texture.h @@ -38,13 +38,13 @@ class GradientTexture1D : public Texture2D { private: Ref<Gradient> gradient; - bool update_pending = false; + mutable bool update_pending = false; mutable RID texture; int width = 256; bool use_hdr = false; void _queue_update(); - void _update(); + void _update() const; protected: static void _bind_methods(); @@ -64,7 +64,7 @@ public: virtual bool has_alpha() const override { return true; } virtual Ref<Image> get_image() const override; - void update_now(); + void update_now() const; GradientTexture1D(); virtual ~GradientTexture1D(); @@ -102,9 +102,9 @@ private: float _get_gradient_offset_at(int x, int y) const; - bool update_pending = false; + mutable bool update_pending = false; void _queue_update(); - void _update(); + void _update() const; protected: static void _bind_methods(); @@ -134,7 +134,7 @@ public: virtual RID get_rid() const override; virtual bool has_alpha() const override { return true; } virtual Ref<Image> get_image() const override; - void update_now(); + void update_now() const; GradientTexture2D(); virtual ~GradientTexture2D(); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 927e76e4b2..ecc1982aa5 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -46,11 +46,15 @@ void Material::set_next_pass(const Ref<Material> &p_pass) { } next_pass = p_pass; - RID next_pass_rid; - if (next_pass.is_valid()) { - next_pass_rid = next_pass->get_rid(); + + if (material.is_valid()) { + RID next_pass_rid; + if (next_pass.is_valid()) { + next_pass_rid = next_pass->get_rid(); + } + + RS::get_singleton()->material_set_next_pass(material, next_pass_rid); } - RS::get_singleton()->material_set_next_pass(material, next_pass_rid); } Ref<Material> Material::get_next_pass() const { @@ -61,7 +65,10 @@ void Material::set_render_priority(int p_priority) { ERR_FAIL_COND(p_priority < RENDER_PRIORITY_MIN); ERR_FAIL_COND(p_priority > RENDER_PRIORITY_MAX); render_priority = p_priority; - RS::get_singleton()->material_set_render_priority(material, p_priority); + + if (material.is_valid()) { + RS::get_singleton()->material_set_render_priority(material, p_priority); + } } int Material::get_render_priority() const { @@ -113,12 +120,12 @@ void Material::inspect_native_shader_code() { RID Material::get_shader_rid() const { RID ret; - GDVIRTUAL_REQUIRED_CALL(_get_shader_rid, ret); + GDVIRTUAL_CALL(_get_shader_rid, ret); return ret; } Shader::Mode Material::get_shader_mode() const { Shader::Mode ret = Shader::MODE_MAX; - GDVIRTUAL_REQUIRED_CALL(_get_shader_mode, ret); + GDVIRTUAL_CALL(_get_shader_mode, ret); return ret; } @@ -165,13 +172,14 @@ void Material::_bind_methods() { } Material::Material() { - material = RenderingServer::get_singleton()->material_create(); render_priority = 0; } Material::~Material() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RenderingServer::get_singleton()->free(material); + if (material.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RenderingServer::get_singleton()->free(material); + } } /////////////////////////////////// @@ -374,14 +382,11 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const { bool ShaderMaterial::_property_can_revert(const StringName &p_name) const { if (shader.is_valid()) { - const StringName *pr = remap_cache.getptr(p_name); - if (pr) { - Variant default_value = RenderingServer::get_singleton()->shader_get_parameter_default(shader->get_rid(), *pr); - Variant current_value = get_shader_parameter(*pr); - return default_value.get_type() != Variant::NIL && default_value != current_value; - } else if (p_name == "render_priority" || p_name == "next_pass") { + if (remap_cache.has(p_name)) { return true; } + const String sname = p_name; + return sname == "render_priority" || sname == "next_pass"; } return false; } @@ -422,7 +427,11 @@ void ShaderMaterial::set_shader(const Ref<Shader> &p_shader) { } } - RS::get_singleton()->material_set_shader(_get_material(), rid); + RID material_rid = _get_material(); + if (material_rid.is_valid()) { + RS::get_singleton()->material_set_shader(material_rid, rid); + } + notify_property_list_changed(); //properties for shader exposed emit_changed(); } @@ -432,9 +441,12 @@ Ref<Shader> ShaderMaterial::get_shader() const { } void ShaderMaterial::set_shader_parameter(const StringName &p_param, const Variant &p_value) { + RID material_rid = _get_material(); if (p_value.get_type() == Variant::NIL) { param_cache.erase(p_param); - RS::get_singleton()->material_set_param(_get_material(), p_param, Variant()); + if (material_rid.is_valid()) { + RS::get_singleton()->material_set_param(material_rid, p_param, Variant()); + } } else { Variant *v = param_cache.getptr(p_param); if (!v) { @@ -449,12 +461,15 @@ void ShaderMaterial::set_shader_parameter(const StringName &p_param, const Varia RID tex_rid = p_value; if (tex_rid == RID()) { param_cache.erase(p_param); - RS::get_singleton()->material_set_param(_get_material(), p_param, Variant()); - } else { - RS::get_singleton()->material_set_param(_get_material(), p_param, tex_rid); + + if (material_rid.is_valid()) { + RS::get_singleton()->material_set_param(material_rid, p_param, Variant()); + } + } else if (material_rid.is_valid()) { + RS::get_singleton()->material_set_param(material_rid, p_param, tex_rid); } - } else { - RS::get_singleton()->material_set_param(_get_material(), p_param, p_value); + } else if (material_rid.is_valid()) { + RS::get_singleton()->material_set_param(material_rid, p_param, p_value); } } } @@ -471,6 +486,32 @@ void ShaderMaterial::_shader_changed() { notify_property_list_changed(); //update all properties } +void ShaderMaterial::_check_material_rid() const { + MutexLock lock(material_rid_mutex); + if (_get_material().is_null()) { + RID shader_rid = shader.is_valid() ? shader->get_rid() : RID(); + RID next_pass_rid; + if (get_next_pass().is_valid()) { + next_pass_rid = get_next_pass()->get_rid(); + } + + _set_material(RS::get_singleton()->material_create_from_shader(next_pass_rid, get_render_priority(), shader_rid)); + + for (KeyValue<StringName, Variant> param : param_cache) { + if (param.value.get_type() == Variant::OBJECT) { + RID tex_rid = param.value; + if (tex_rid.is_valid()) { + RS::get_singleton()->material_set_param(_get_material(), param.key, tex_rid); + } else { + RS::get_singleton()->material_set_param(_get_material(), param.key, Variant()); + } + } else { + RS::get_singleton()->material_set_param(_get_material(), param.key, param.value); + } + } + } +} + void ShaderMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shader", "shader"), &ShaderMaterial::set_shader); ClassDB::bind_method(D_METHOD("get_shader"), &ShaderMaterial::get_shader); @@ -511,6 +552,12 @@ Shader::Mode ShaderMaterial::get_shader_mode() const { return Shader::MODE_SPATIAL; } } + +RID ShaderMaterial::get_rid() const { + _check_material_rid(); + return Material::get_rid(); +} + RID ShaderMaterial::get_shader_rid() const { if (shader.is_valid()) { return shader->get_rid(); @@ -520,6 +567,7 @@ RID ShaderMaterial::get_shader_rid() const { } ShaderMaterial::ShaderMaterial() { + // Material RID will be empty until it is required. } ShaderMaterial::~ShaderMaterial() { @@ -527,9 +575,8 @@ ShaderMaterial::~ShaderMaterial() { ///////////////////////////////// -Mutex BaseMaterial3D::material_mutex; -SelfList<BaseMaterial3D>::List BaseMaterial3D::dirty_materials; HashMap<BaseMaterial3D::MaterialKey, BaseMaterial3D::ShaderData, BaseMaterial3D::MaterialKey> BaseMaterial3D::shader_map; +Mutex BaseMaterial3D::shader_map_mutex; BaseMaterial3D::ShaderNames *BaseMaterial3D::shader_names = nullptr; void BaseMaterial3D::init_shaders() { @@ -619,22 +666,31 @@ HashMap<uint64_t, Ref<StandardMaterial3D>> BaseMaterial3D::materials_for_2d; void BaseMaterial3D::finish_shaders() { materials_for_2d.clear(); - dirty_materials.clear(); - memdelete(shader_names); shader_names = nullptr; } +void BaseMaterial3D::_mark_dirty() { + dirty = true; +} + void BaseMaterial3D::_update_shader() { + if (!dirty) { + return; + } + + dirty = false; + MaterialKey mk = _compute_key(); if (mk == current_key) { return; //no update required in the end } + MutexLock lock(shader_map_mutex); if (shader_map.has(current_key)) { shader_map[current_key].users--; if (shader_map[current_key].users == 0) { - //deallocate shader, as it's no longer in use + // Deallocate shader which is no longer in use. RS::get_singleton()->free(shader_map[current_key].shader); shader_map.erase(current_key); } @@ -643,8 +699,13 @@ void BaseMaterial3D::_update_shader() { current_key = mk; if (shader_map.has(mk)) { - RS::get_singleton()->material_set_shader(_get_material(), shader_map[mk].shader); + shader_rid = shader_map[mk].shader; shader_map[mk].users++; + + if (_get_material().is_valid()) { + RS::get_singleton()->material_set_shader(_get_material(), shader_rid); + } + return; } @@ -946,7 +1007,7 @@ uniform vec4 refraction_texture_channel; code += "uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear_mipmap;\n"; } - if (proximity_fade_enabled) { + if (features[FEATURE_REFRACTION] || proximity_fade_enabled) { code += "uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;\n"; } @@ -1627,7 +1688,14 @@ void fragment() {)"; } code += R"( float ref_amount = 1.0 - albedo.a * albedo_tex.a; - EMISSION += textureLod(screen_texture, ref_ofs, ROUGHNESS * 8.0).rgb * ref_amount * EXPOSURE; + + float refraction_depth_tex = textureLod(depth_texture, ref_ofs, 0.0).r; + vec4 refraction_view_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, refraction_depth_tex, 1.0); + refraction_view_pos.xyz /= refraction_view_pos.w; + + // If the depth buffer is lower then the model's Z position, use the refracted UV, otherwise use the normal screen UV. + // At low depth differences, decrease refraction intensity to avoid sudden discontinuities. + EMISSION += textureLod(screen_texture, mix(SCREEN_UV, ref_ofs, smoothstep(0.0, 1.0, VERTEX.z - refraction_view_pos.z)), ROUGHNESS * 8.0).rgb * ref_amount * EXPOSURE; ALBEDO *= 1.0 - ref_amount; // Force transparency on the material (required for refraction). ALPHA = 1.0; @@ -1649,10 +1717,10 @@ void fragment() {)"; if (proximity_fade_enabled) { code += R"( // Proximity Fade: Enabled - float depth_tex = textureLod(depth_texture, SCREEN_UV, 0.0).r; - vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_tex, 1.0); - world_pos.xyz /= world_pos.w; - ALPHA *= clamp(1.0 - smoothstep(world_pos.z + proximity_fade_distance, world_pos.z, VERTEX.z), 0.0, 1.0); + float proximity_depth_tex = textureLod(depth_texture, SCREEN_UV, 0.0).r; + vec4 proximity_view_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, proximity_depth_tex, 1.0); + proximity_view_pos.xyz /= proximity_view_pos.w; + ALPHA *= clamp(1.0 - smoothstep(proximity_view_pos.z + proximity_fade_distance, proximity_view_pos.z, VERTEX.z), 0.0, 1.0); )"; } @@ -1866,37 +1934,45 @@ void fragment() {)"; code += "}\n"; ShaderData shader_data; - shader_data.shader = RS::get_singleton()->shader_create(); + shader_data.shader = RS::get_singleton()->shader_create_from_code(code); shader_data.users = 1; - - RS::get_singleton()->shader_set_code(shader_data.shader, code); - shader_map[mk] = shader_data; + shader_rid = shader_data.shader; - RS::get_singleton()->material_set_shader(_get_material(), shader_data.shader); + if (_get_material().is_valid()) { + RS::get_singleton()->material_set_shader(_get_material(), shader_rid); + } } -void BaseMaterial3D::flush_changes() { - MutexLock lock(material_mutex); +void BaseMaterial3D::_check_material_rid() { + MutexLock lock(material_rid_mutex); + if (_get_material().is_null()) { + RID next_pass_rid; + if (get_next_pass().is_valid()) { + next_pass_rid = get_next_pass()->get_rid(); + } + + _set_material(RS::get_singleton()->material_create_from_shader(next_pass_rid, get_render_priority(), shader_rid)); + + for (KeyValue<StringName, Variant> param : pending_params) { + RS::get_singleton()->material_set_param(_get_material(), param.key, param.value); + } - while (dirty_materials.first()) { - dirty_materials.first()->self()->_update_shader(); - dirty_materials.first()->remove_from_list(); + pending_params.clear(); } } -void BaseMaterial3D::_queue_shader_change() { - MutexLock lock(material_mutex); - - if (_is_initialized() && !element.in_list()) { - dirty_materials.add(&element); +void BaseMaterial3D::_material_set_param(const StringName &p_name, const Variant &p_value) { + if (_get_material().is_valid()) { + RS::get_singleton()->material_set_param(_get_material(), p_name, p_value); + } else { + pending_params[p_name] = p_value; } } void BaseMaterial3D::set_albedo(const Color &p_albedo) { albedo = p_albedo; - - RS::get_singleton()->material_set_param(_get_material(), shader_names->albedo, p_albedo); + _material_set_param(shader_names->albedo, p_albedo); } Color BaseMaterial3D::get_albedo() const { @@ -1905,7 +1981,7 @@ Color BaseMaterial3D::get_albedo() const { void BaseMaterial3D::set_specular(float p_specular) { specular = p_specular; - RS::get_singleton()->material_set_param(_get_material(), shader_names->specular, p_specular); + _material_set_param(shader_names->specular, p_specular); } float BaseMaterial3D::get_specular() const { @@ -1914,7 +1990,7 @@ float BaseMaterial3D::get_specular() const { void BaseMaterial3D::set_roughness(float p_roughness) { roughness = p_roughness; - RS::get_singleton()->material_set_param(_get_material(), shader_names->roughness, p_roughness); + _material_set_param(shader_names->roughness, p_roughness); } float BaseMaterial3D::get_roughness() const { @@ -1923,7 +1999,7 @@ float BaseMaterial3D::get_roughness() const { void BaseMaterial3D::set_metallic(float p_metallic) { metallic = p_metallic; - RS::get_singleton()->material_set_param(_get_material(), shader_names->metallic, p_metallic); + _material_set_param(shader_names->metallic, p_metallic); } float BaseMaterial3D::get_metallic() const { @@ -1932,7 +2008,7 @@ float BaseMaterial3D::get_metallic() const { void BaseMaterial3D::set_emission(const Color &p_emission) { emission = p_emission; - RS::get_singleton()->material_set_param(_get_material(), shader_names->emission, p_emission); + _material_set_param(shader_names->emission, p_emission); } Color BaseMaterial3D::get_emission() const { @@ -1941,10 +2017,11 @@ Color BaseMaterial3D::get_emission() const { void BaseMaterial3D::set_emission_energy_multiplier(float p_emission_energy_multiplier) { emission_energy_multiplier = p_emission_energy_multiplier; + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { - RS::get_singleton()->material_set_param(_get_material(), shader_names->emission_energy, p_emission_energy_multiplier * emission_intensity); + _material_set_param(shader_names->emission_energy, p_emission_energy_multiplier * emission_intensity); } else { - RS::get_singleton()->material_set_param(_get_material(), shader_names->emission_energy, p_emission_energy_multiplier); + _material_set_param(shader_names->emission_energy, p_emission_energy_multiplier); } } @@ -1955,7 +2032,7 @@ float BaseMaterial3D::get_emission_energy_multiplier() const { void BaseMaterial3D::set_emission_intensity(float p_emission_intensity) { ERR_FAIL_COND_EDMSG(!GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units"), "Cannot set material emission intensity when Physical Light Units disabled."); emission_intensity = p_emission_intensity; - RS::get_singleton()->material_set_param(_get_material(), shader_names->emission_energy, emission_energy_multiplier * emission_intensity); + _material_set_param(shader_names->emission_energy, emission_energy_multiplier * emission_intensity); } float BaseMaterial3D::get_emission_intensity() const { @@ -1964,7 +2041,7 @@ float BaseMaterial3D::get_emission_intensity() const { void BaseMaterial3D::set_normal_scale(float p_normal_scale) { normal_scale = p_normal_scale; - RS::get_singleton()->material_set_param(_get_material(), shader_names->normal_scale, p_normal_scale); + _material_set_param(shader_names->normal_scale, p_normal_scale); } float BaseMaterial3D::get_normal_scale() const { @@ -1973,7 +2050,7 @@ float BaseMaterial3D::get_normal_scale() const { void BaseMaterial3D::set_rim(float p_rim) { rim = p_rim; - RS::get_singleton()->material_set_param(_get_material(), shader_names->rim, p_rim); + _material_set_param(shader_names->rim, p_rim); } float BaseMaterial3D::get_rim() const { @@ -1982,7 +2059,7 @@ float BaseMaterial3D::get_rim() const { void BaseMaterial3D::set_rim_tint(float p_rim_tint) { rim_tint = p_rim_tint; - RS::get_singleton()->material_set_param(_get_material(), shader_names->rim_tint, p_rim_tint); + _material_set_param(shader_names->rim_tint, p_rim_tint); } float BaseMaterial3D::get_rim_tint() const { @@ -1991,7 +2068,7 @@ float BaseMaterial3D::get_rim_tint() const { void BaseMaterial3D::set_ao_light_affect(float p_ao_light_affect) { ao_light_affect = p_ao_light_affect; - RS::get_singleton()->material_set_param(_get_material(), shader_names->ao_light_affect, p_ao_light_affect); + _material_set_param(shader_names->ao_light_affect, p_ao_light_affect); } float BaseMaterial3D::get_ao_light_affect() const { @@ -2000,7 +2077,7 @@ float BaseMaterial3D::get_ao_light_affect() const { void BaseMaterial3D::set_clearcoat(float p_clearcoat) { clearcoat = p_clearcoat; - RS::get_singleton()->material_set_param(_get_material(), shader_names->clearcoat, p_clearcoat); + _material_set_param(shader_names->clearcoat, p_clearcoat); } float BaseMaterial3D::get_clearcoat() const { @@ -2009,7 +2086,7 @@ float BaseMaterial3D::get_clearcoat() const { void BaseMaterial3D::set_clearcoat_roughness(float p_clearcoat_roughness) { clearcoat_roughness = p_clearcoat_roughness; - RS::get_singleton()->material_set_param(_get_material(), shader_names->clearcoat_roughness, p_clearcoat_roughness); + _material_set_param(shader_names->clearcoat_roughness, p_clearcoat_roughness); } float BaseMaterial3D::get_clearcoat_roughness() const { @@ -2018,7 +2095,7 @@ float BaseMaterial3D::get_clearcoat_roughness() const { void BaseMaterial3D::set_anisotropy(float p_anisotropy) { anisotropy = p_anisotropy; - RS::get_singleton()->material_set_param(_get_material(), shader_names->anisotropy, p_anisotropy); + _material_set_param(shader_names->anisotropy, p_anisotropy); } float BaseMaterial3D::get_anisotropy() const { @@ -2027,7 +2104,7 @@ float BaseMaterial3D::get_anisotropy() const { void BaseMaterial3D::set_heightmap_scale(float p_heightmap_scale) { heightmap_scale = p_heightmap_scale; - RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_scale, p_heightmap_scale); + _material_set_param(shader_names->heightmap_scale, p_heightmap_scale); } float BaseMaterial3D::get_heightmap_scale() const { @@ -2036,7 +2113,7 @@ float BaseMaterial3D::get_heightmap_scale() const { void BaseMaterial3D::set_subsurface_scattering_strength(float p_subsurface_scattering_strength) { subsurface_scattering_strength = p_subsurface_scattering_strength; - RS::get_singleton()->material_set_param(_get_material(), shader_names->subsurface_scattering_strength, subsurface_scattering_strength); + _material_set_param(shader_names->subsurface_scattering_strength, subsurface_scattering_strength); } float BaseMaterial3D::get_subsurface_scattering_strength() const { @@ -2045,7 +2122,7 @@ float BaseMaterial3D::get_subsurface_scattering_strength() const { void BaseMaterial3D::set_transmittance_color(const Color &p_color) { transmittance_color = p_color; - RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_color, p_color); + _material_set_param(shader_names->transmittance_color, p_color); } Color BaseMaterial3D::get_transmittance_color() const { @@ -2054,7 +2131,7 @@ Color BaseMaterial3D::get_transmittance_color() const { void BaseMaterial3D::set_transmittance_depth(float p_depth) { transmittance_depth = p_depth; - RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_depth, p_depth); + _material_set_param(shader_names->transmittance_depth, p_depth); } float BaseMaterial3D::get_transmittance_depth() const { @@ -2063,7 +2140,7 @@ float BaseMaterial3D::get_transmittance_depth() const { void BaseMaterial3D::set_transmittance_boost(float p_boost) { transmittance_boost = p_boost; - RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_boost, p_boost); + _material_set_param(shader_names->transmittance_boost, p_boost); } float BaseMaterial3D::get_transmittance_boost() const { @@ -2072,7 +2149,7 @@ float BaseMaterial3D::get_transmittance_boost() const { void BaseMaterial3D::set_backlight(const Color &p_backlight) { backlight = p_backlight; - RS::get_singleton()->material_set_param(_get_material(), shader_names->backlight, backlight); + _material_set_param(shader_names->backlight, backlight); } Color BaseMaterial3D::get_backlight() const { @@ -2081,7 +2158,7 @@ Color BaseMaterial3D::get_backlight() const { void BaseMaterial3D::set_refraction(float p_refraction) { refraction = p_refraction; - RS::get_singleton()->material_set_param(_get_material(), shader_names->refraction, refraction); + _material_set_param(shader_names->refraction, refraction); } float BaseMaterial3D::get_refraction() const { @@ -2094,7 +2171,7 @@ void BaseMaterial3D::set_detail_uv(DetailUV p_detail_uv) { } detail_uv = p_detail_uv; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::DetailUV BaseMaterial3D::get_detail_uv() const { @@ -2107,7 +2184,7 @@ void BaseMaterial3D::set_blend_mode(BlendMode p_mode) { } blend_mode = p_mode; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::BlendMode BaseMaterial3D::get_blend_mode() const { @@ -2116,7 +2193,7 @@ BaseMaterial3D::BlendMode BaseMaterial3D::get_blend_mode() const { void BaseMaterial3D::set_detail_blend_mode(BlendMode p_mode) { detail_blend_mode = p_mode; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::BlendMode BaseMaterial3D::get_detail_blend_mode() const { @@ -2129,7 +2206,7 @@ void BaseMaterial3D::set_transparency(Transparency p_transparency) { } transparency = p_transparency; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2143,7 +2220,7 @@ void BaseMaterial3D::set_alpha_antialiasing(AlphaAntiAliasing p_alpha_aa) { } alpha_antialiasing_mode = p_alpha_aa; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2157,7 +2234,7 @@ void BaseMaterial3D::set_shading_mode(ShadingMode p_shading_mode) { } shading_mode = p_shading_mode; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2171,7 +2248,7 @@ void BaseMaterial3D::set_depth_draw_mode(DepthDrawMode p_mode) { } depth_draw_mode = p_mode; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::DepthDrawMode BaseMaterial3D::get_depth_draw_mode() const { @@ -2184,7 +2261,7 @@ void BaseMaterial3D::set_cull_mode(CullMode p_mode) { } cull_mode = p_mode; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::CullMode BaseMaterial3D::get_cull_mode() const { @@ -2197,7 +2274,7 @@ void BaseMaterial3D::set_diffuse_mode(DiffuseMode p_mode) { } diffuse_mode = p_mode; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::DiffuseMode BaseMaterial3D::get_diffuse_mode() const { @@ -2210,7 +2287,7 @@ void BaseMaterial3D::set_specular_mode(SpecularMode p_mode) { } specular_mode = p_mode; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::SpecularMode BaseMaterial3D::get_specular_mode() const { @@ -2240,7 +2317,7 @@ void BaseMaterial3D::set_flag(Flags p_flag, bool p_enabled) { update_configuration_warning(); } - _queue_shader_change(); + _mark_dirty(); } bool BaseMaterial3D::get_flag(Flags p_flag) const { @@ -2256,7 +2333,7 @@ void BaseMaterial3D::set_feature(Feature p_feature, bool p_enabled) { features[p_feature] = p_enabled; notify_property_list_changed(); - _queue_shader_change(); + _mark_dirty(); } bool BaseMaterial3D::get_feature(Feature p_feature) const { @@ -2269,15 +2346,14 @@ void BaseMaterial3D::set_texture(TextureParam p_param, const Ref<Texture2D> &p_t textures[p_param] = p_texture; Variant rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); - RS::get_singleton()->material_set_param(_get_material(), shader_names->texture_names[p_param], rid); + _material_set_param(shader_names->texture_names[p_param], rid); if (p_texture.is_valid() && p_param == TEXTURE_ALBEDO) { - RS::get_singleton()->material_set_param(_get_material(), shader_names->albedo_texture_size, - Vector2i(p_texture->get_width(), p_texture->get_height())); + _material_set_param(shader_names->albedo_texture_size, Vector2i(p_texture->get_width(), p_texture->get_height())); } notify_property_list_changed(); - _queue_shader_change(); + _mark_dirty(); } Ref<Texture2D> BaseMaterial3D::get_texture(TextureParam p_param) const { @@ -2297,7 +2373,7 @@ Ref<Texture2D> BaseMaterial3D::get_texture_by_name(const StringName &p_name) con void BaseMaterial3D::set_texture_filter(TextureFilter p_filter) { texture_filter = p_filter; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::TextureFilter BaseMaterial3D::get_texture_filter() const { @@ -2469,7 +2545,7 @@ void BaseMaterial3D::_validate_property(PropertyInfo &p_property) const { void BaseMaterial3D::set_point_size(float p_point_size) { point_size = p_point_size; - RS::get_singleton()->material_set_param(_get_material(), shader_names->point_size, p_point_size); + _material_set_param(shader_names->point_size, p_point_size); } float BaseMaterial3D::get_point_size() const { @@ -2478,7 +2554,7 @@ float BaseMaterial3D::get_point_size() const { void BaseMaterial3D::set_uv1_scale(const Vector3 &p_scale) { uv1_scale = p_scale; - RS::get_singleton()->material_set_param(_get_material(), shader_names->uv1_scale, p_scale); + _material_set_param(shader_names->uv1_scale, p_scale); } Vector3 BaseMaterial3D::get_uv1_scale() const { @@ -2487,7 +2563,7 @@ Vector3 BaseMaterial3D::get_uv1_scale() const { void BaseMaterial3D::set_uv1_offset(const Vector3 &p_offset) { uv1_offset = p_offset; - RS::get_singleton()->material_set_param(_get_material(), shader_names->uv1_offset, p_offset); + _material_set_param(shader_names->uv1_offset, p_offset); } Vector3 BaseMaterial3D::get_uv1_offset() const { @@ -2497,7 +2573,7 @@ Vector3 BaseMaterial3D::get_uv1_offset() const { void BaseMaterial3D::set_uv1_triplanar_blend_sharpness(float p_sharpness) { // Negative values or values higher than 150 can result in NaNs, leading to broken rendering. uv1_triplanar_sharpness = CLAMP(p_sharpness, 0.0, 150.0); - RS::get_singleton()->material_set_param(_get_material(), shader_names->uv1_blend_sharpness, uv1_triplanar_sharpness); + _material_set_param(shader_names->uv1_blend_sharpness, uv1_triplanar_sharpness); } float BaseMaterial3D::get_uv1_triplanar_blend_sharpness() const { @@ -2506,7 +2582,7 @@ float BaseMaterial3D::get_uv1_triplanar_blend_sharpness() const { void BaseMaterial3D::set_uv2_scale(const Vector3 &p_scale) { uv2_scale = p_scale; - RS::get_singleton()->material_set_param(_get_material(), shader_names->uv2_scale, p_scale); + _material_set_param(shader_names->uv2_scale, p_scale); } Vector3 BaseMaterial3D::get_uv2_scale() const { @@ -2515,7 +2591,7 @@ Vector3 BaseMaterial3D::get_uv2_scale() const { void BaseMaterial3D::set_uv2_offset(const Vector3 &p_offset) { uv2_offset = p_offset; - RS::get_singleton()->material_set_param(_get_material(), shader_names->uv2_offset, p_offset); + _material_set_param(shader_names->uv2_offset, p_offset); } Vector3 BaseMaterial3D::get_uv2_offset() const { @@ -2525,7 +2601,7 @@ Vector3 BaseMaterial3D::get_uv2_offset() const { void BaseMaterial3D::set_uv2_triplanar_blend_sharpness(float p_sharpness) { // Negative values or values higher than 150 can result in NaNs, leading to broken rendering. uv2_triplanar_sharpness = CLAMP(p_sharpness, 0.0, 150.0); - RS::get_singleton()->material_set_param(_get_material(), shader_names->uv2_blend_sharpness, uv2_triplanar_sharpness); + _material_set_param(shader_names->uv2_blend_sharpness, uv2_triplanar_sharpness); } float BaseMaterial3D::get_uv2_triplanar_blend_sharpness() const { @@ -2534,7 +2610,7 @@ float BaseMaterial3D::get_uv2_triplanar_blend_sharpness() const { void BaseMaterial3D::set_billboard_mode(BillboardMode p_mode) { billboard_mode = p_mode; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2544,7 +2620,7 @@ BaseMaterial3D::BillboardMode BaseMaterial3D::get_billboard_mode() const { void BaseMaterial3D::set_particles_anim_h_frames(int p_frames) { particles_anim_h_frames = p_frames; - RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_h_frames, p_frames); + _material_set_param(shader_names->particles_anim_h_frames, p_frames); } int BaseMaterial3D::get_particles_anim_h_frames() const { @@ -2553,7 +2629,7 @@ int BaseMaterial3D::get_particles_anim_h_frames() const { void BaseMaterial3D::set_particles_anim_v_frames(int p_frames) { particles_anim_v_frames = p_frames; - RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_v_frames, p_frames); + _material_set_param(shader_names->particles_anim_v_frames, p_frames); } int BaseMaterial3D::get_particles_anim_v_frames() const { @@ -2562,7 +2638,7 @@ int BaseMaterial3D::get_particles_anim_v_frames() const { void BaseMaterial3D::set_particles_anim_loop(bool p_loop) { particles_anim_loop = p_loop; - RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_loop, particles_anim_loop); + _material_set_param(shader_names->particles_anim_loop, particles_anim_loop); } bool BaseMaterial3D::get_particles_anim_loop() const { @@ -2571,7 +2647,7 @@ bool BaseMaterial3D::get_particles_anim_loop() const { void BaseMaterial3D::set_heightmap_deep_parallax(bool p_enable) { deep_parallax = p_enable; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2581,7 +2657,7 @@ bool BaseMaterial3D::is_heightmap_deep_parallax_enabled() const { void BaseMaterial3D::set_heightmap_deep_parallax_min_layers(int p_layer) { deep_parallax_min_layers = p_layer; - RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_min_layers, p_layer); + _material_set_param(shader_names->heightmap_min_layers, p_layer); } int BaseMaterial3D::get_heightmap_deep_parallax_min_layers() const { @@ -2590,7 +2666,7 @@ int BaseMaterial3D::get_heightmap_deep_parallax_min_layers() const { void BaseMaterial3D::set_heightmap_deep_parallax_max_layers(int p_layer) { deep_parallax_max_layers = p_layer; - RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_max_layers, p_layer); + _material_set_param(shader_names->heightmap_max_layers, p_layer); } int BaseMaterial3D::get_heightmap_deep_parallax_max_layers() const { @@ -2599,7 +2675,7 @@ int BaseMaterial3D::get_heightmap_deep_parallax_max_layers() const { void BaseMaterial3D::set_heightmap_deep_parallax_flip_tangent(bool p_flip) { heightmap_parallax_flip_tangent = p_flip; - RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1)); + _material_set_param(shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1)); } bool BaseMaterial3D::get_heightmap_deep_parallax_flip_tangent() const { @@ -2608,7 +2684,7 @@ bool BaseMaterial3D::get_heightmap_deep_parallax_flip_tangent() const { void BaseMaterial3D::set_heightmap_deep_parallax_flip_binormal(bool p_flip) { heightmap_parallax_flip_binormal = p_flip; - RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1)); + _material_set_param(shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1)); } bool BaseMaterial3D::get_heightmap_deep_parallax_flip_binormal() const { @@ -2617,7 +2693,7 @@ bool BaseMaterial3D::get_heightmap_deep_parallax_flip_binormal() const { void BaseMaterial3D::set_grow_enabled(bool p_enable) { grow_enabled = p_enable; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2627,7 +2703,7 @@ bool BaseMaterial3D::is_grow_enabled() const { void BaseMaterial3D::set_alpha_scissor_threshold(float p_threshold) { alpha_scissor_threshold = p_threshold; - RS::get_singleton()->material_set_param(_get_material(), shader_names->alpha_scissor_threshold, p_threshold); + _material_set_param(shader_names->alpha_scissor_threshold, p_threshold); } float BaseMaterial3D::get_alpha_scissor_threshold() const { @@ -2636,7 +2712,7 @@ float BaseMaterial3D::get_alpha_scissor_threshold() const { void BaseMaterial3D::set_alpha_hash_scale(float p_scale) { alpha_hash_scale = p_scale; - RS::get_singleton()->material_set_param(_get_material(), shader_names->alpha_hash_scale, p_scale); + _material_set_param(shader_names->alpha_hash_scale, p_scale); } float BaseMaterial3D::get_alpha_hash_scale() const { @@ -2645,7 +2721,7 @@ float BaseMaterial3D::get_alpha_hash_scale() const { void BaseMaterial3D::set_alpha_antialiasing_edge(float p_edge) { alpha_antialiasing_edge = p_edge; - RS::get_singleton()->material_set_param(_get_material(), shader_names->alpha_antialiasing_edge, p_edge); + _material_set_param(shader_names->alpha_antialiasing_edge, p_edge); } float BaseMaterial3D::get_alpha_antialiasing_edge() const { @@ -2654,7 +2730,7 @@ float BaseMaterial3D::get_alpha_antialiasing_edge() const { void BaseMaterial3D::set_grow(float p_grow) { grow = p_grow; - RS::get_singleton()->material_set_param(_get_material(), shader_names->grow, p_grow); + _material_set_param(shader_names->grow, p_grow); } float BaseMaterial3D::get_grow() const { @@ -2676,7 +2752,7 @@ static Plane _get_texture_mask(BaseMaterial3D::TextureChannel p_channel) { void BaseMaterial3D::set_metallic_texture_channel(TextureChannel p_channel) { ERR_FAIL_INDEX(p_channel, 5); metallic_texture_channel = p_channel; - RS::get_singleton()->material_set_param(_get_material(), shader_names->metallic_texture_channel, _get_texture_mask(p_channel)); + _material_set_param(shader_names->metallic_texture_channel, _get_texture_mask(p_channel)); } BaseMaterial3D::TextureChannel BaseMaterial3D::get_metallic_texture_channel() const { @@ -2686,7 +2762,7 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_metallic_texture_channel() co void BaseMaterial3D::set_roughness_texture_channel(TextureChannel p_channel) { ERR_FAIL_INDEX(p_channel, 5); roughness_texture_channel = p_channel; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::TextureChannel BaseMaterial3D::get_roughness_texture_channel() const { @@ -2696,7 +2772,7 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_roughness_texture_channel() c void BaseMaterial3D::set_ao_texture_channel(TextureChannel p_channel) { ERR_FAIL_INDEX(p_channel, 5); ao_texture_channel = p_channel; - RS::get_singleton()->material_set_param(_get_material(), shader_names->ao_texture_channel, _get_texture_mask(p_channel)); + _material_set_param(shader_names->ao_texture_channel, _get_texture_mask(p_channel)); } BaseMaterial3D::TextureChannel BaseMaterial3D::get_ao_texture_channel() const { @@ -2706,7 +2782,7 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_ao_texture_channel() const { void BaseMaterial3D::set_refraction_texture_channel(TextureChannel p_channel) { ERR_FAIL_INDEX(p_channel, 5); refraction_texture_channel = p_channel; - RS::get_singleton()->material_set_param(_get_material(), shader_names->refraction_texture_channel, _get_texture_mask(p_channel)); + _material_set_param(shader_names->refraction_texture_channel, _get_texture_mask(p_channel)); } BaseMaterial3D::TextureChannel BaseMaterial3D::get_refraction_texture_channel() const { @@ -2768,7 +2844,7 @@ void BaseMaterial3D::set_on_top_of_alpha() { void BaseMaterial3D::set_proximity_fade_enabled(bool p_enable) { proximity_fade_enabled = p_enable; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2778,7 +2854,7 @@ bool BaseMaterial3D::is_proximity_fade_enabled() const { void BaseMaterial3D::set_proximity_fade_distance(float p_distance) { proximity_fade_distance = MAX(p_distance, 0.01); - RS::get_singleton()->material_set_param(_get_material(), shader_names->proximity_fade_distance, proximity_fade_distance); + _material_set_param(shader_names->proximity_fade_distance, proximity_fade_distance); } float BaseMaterial3D::get_proximity_fade_distance() const { @@ -2787,7 +2863,7 @@ float BaseMaterial3D::get_proximity_fade_distance() const { void BaseMaterial3D::set_msdf_pixel_range(float p_range) { msdf_pixel_range = p_range; - RS::get_singleton()->material_set_param(_get_material(), shader_names->msdf_pixel_range, p_range); + _material_set_param(shader_names->msdf_pixel_range, p_range); } float BaseMaterial3D::get_msdf_pixel_range() const { @@ -2796,7 +2872,7 @@ float BaseMaterial3D::get_msdf_pixel_range() const { void BaseMaterial3D::set_msdf_outline_size(float p_size) { msdf_outline_size = p_size; - RS::get_singleton()->material_set_param(_get_material(), shader_names->msdf_outline_size, p_size); + _material_set_param(shader_names->msdf_outline_size, p_size); } float BaseMaterial3D::get_msdf_outline_size() const { @@ -2805,7 +2881,7 @@ float BaseMaterial3D::get_msdf_outline_size() const { void BaseMaterial3D::set_distance_fade(DistanceFadeMode p_mode) { distance_fade = p_mode; - _queue_shader_change(); + _mark_dirty(); notify_property_list_changed(); } @@ -2815,7 +2891,7 @@ BaseMaterial3D::DistanceFadeMode BaseMaterial3D::get_distance_fade() const { void BaseMaterial3D::set_distance_fade_max_distance(float p_distance) { distance_fade_max_distance = p_distance; - RS::get_singleton()->material_set_param(_get_material(), shader_names->distance_fade_max, distance_fade_max_distance); + _material_set_param(shader_names->distance_fade_max, distance_fade_max_distance); } float BaseMaterial3D::get_distance_fade_max_distance() const { @@ -2824,7 +2900,7 @@ float BaseMaterial3D::get_distance_fade_max_distance() const { void BaseMaterial3D::set_distance_fade_min_distance(float p_distance) { distance_fade_min_distance = p_distance; - RS::get_singleton()->material_set_param(_get_material(), shader_names->distance_fade_min, distance_fade_min_distance); + _material_set_param(shader_names->distance_fade_min, distance_fade_min_distance); } float BaseMaterial3D::get_distance_fade_min_distance() const { @@ -2836,20 +2912,22 @@ void BaseMaterial3D::set_emission_operator(EmissionOperator p_op) { return; } emission_op = p_op; - _queue_shader_change(); + _mark_dirty(); } BaseMaterial3D::EmissionOperator BaseMaterial3D::get_emission_operator() const { return emission_op; } +RID BaseMaterial3D::get_rid() const { + const_cast<BaseMaterial3D *>(this)->_update_shader(); + const_cast<BaseMaterial3D *>(this)->_check_material_rid(); + return _get_material(); +} + RID BaseMaterial3D::get_shader_rid() const { - MutexLock lock(material_mutex); - if (element.in_list()) { - ((BaseMaterial3D *)this)->_update_shader(); - } - ERR_FAIL_COND_V(!shader_map.has(current_key), RID()); - return shader_map[current_key].shader; + const_cast<BaseMaterial3D *>(this)->_update_shader(); + return shader_rid; } Shader::Mode BaseMaterial3D::get_shader_mode() const { @@ -3365,8 +3443,7 @@ void BaseMaterial3D::_bind_methods() { BIND_ENUM_CONSTANT(DISTANCE_FADE_OBJECT_DITHER); } -BaseMaterial3D::BaseMaterial3D(bool p_orm) : - element(this) { +BaseMaterial3D::BaseMaterial3D(bool p_orm) { orm = p_orm; // Initialize to the same values as the shader set_albedo(Color(1.0, 1.0, 1.0, 1.0)); @@ -3433,21 +3510,25 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : current_key.invalid_key = 1; - _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change), callable_mp(this, &BaseMaterial3D::_update_shader)); + _mark_dirty(); } BaseMaterial3D::~BaseMaterial3D() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - MutexLock lock(material_mutex); + ERR_FAIL_NULL(RS::get_singleton()); - if (shader_map.has(current_key)) { - shader_map[current_key].users--; - if (shader_map[current_key].users == 0) { - //deallocate shader, as it's no longer in use - RS::get_singleton()->free(shader_map[current_key].shader); - shader_map.erase(current_key); + { + MutexLock lock(shader_map_mutex); + if (shader_map.has(current_key)) { + shader_map[current_key].users--; + if (shader_map[current_key].users == 0) { + // Deallocate shader which is no longer in use. + RS::get_singleton()->free(shader_map[current_key].shader); + shader_map.erase(current_key); + } } + } + if (_get_material().is_valid()) { RS::get_singleton()->material_set_shader(_get_material(), RID()); } } diff --git a/scene/resources/material.h b/scene/resources/material.h index 50a774e961..edd82b779e 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -42,7 +42,7 @@ class Material : public Resource { RES_BASE_EXTENSION("material") OBJ_SAVE_TYPE(Material); - RID material; + mutable RID material; Ref<Material> next_pass; int render_priority; @@ -55,6 +55,7 @@ class Material : public Resource { void inspect_native_shader_code(); protected: + _FORCE_INLINE_ void _set_material(RID p_material) const { material = p_material; } _FORCE_INLINE_ RID _get_material() const { return material; } static void _bind_methods(); virtual bool _can_do_next_pass() const; @@ -66,8 +67,8 @@ protected: 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) - GDVIRTUAL0RC(Shader::Mode, _get_shader_mode) + GDVIRTUAL0RC_REQUIRED(RID, _get_shader_rid) + GDVIRTUAL0RC_REQUIRED(Shader::Mode, _get_shader_mode) GDVIRTUAL0RC(bool, _can_do_next_pass) GDVIRTUAL0RC(bool, _can_use_render_priority) public: @@ -97,6 +98,7 @@ class ShaderMaterial : public Material { mutable HashMap<StringName, StringName> remap_cache; mutable HashMap<StringName, Variant> param_cache; + mutable Mutex material_rid_mutex; protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -115,6 +117,7 @@ protected: virtual bool _can_use_render_priority() const override; void _shader_changed(); + void _check_material_rid() const; public: void set_shader(const Ref<Shader> &p_shader); @@ -125,6 +128,7 @@ public: virtual Shader::Mode get_shader_mode() const override; + virtual RID get_rid() const override; virtual RID get_shader_rid() const override; ShaderMaterial(); @@ -136,6 +140,9 @@ class StandardMaterial3D; class BaseMaterial3D : public Material { GDCLASS(BaseMaterial3D, Material); +private: + mutable Mutex material_rid_mutex; + public: enum TextureParam { TEXTURE_ALBEDO, @@ -361,6 +368,7 @@ private: }; static HashMap<MaterialKey, ShaderData, MaterialKey> shader_map; + static Mutex shader_map_mutex; MaterialKey current_key; @@ -459,16 +467,17 @@ private: StringName albedo_texture_size; }; - static Mutex material_mutex; - static SelfList<BaseMaterial3D>::List dirty_materials; static ShaderNames *shader_names; - SelfList<BaseMaterial3D> element; - + void _mark_dirty(); void _update_shader(); - _FORCE_INLINE_ void _queue_shader_change(); + void _check_material_rid(); + void _material_set_param(const StringName &p_name, const Variant &p_value); bool orm; + bool dirty = true; + RID shader_rid; + HashMap<StringName, Variant> pending_params; Color albedo; float specular = 0.0f; @@ -771,10 +780,10 @@ public: static void init_shaders(); static void finish_shaders(); - static void flush_changes(); static Ref<Material> get_material_for_2d(bool p_shaded, Transparency p_transparency, bool p_double_sided, bool p_billboard = false, bool p_billboard_y = false, bool p_msdf = false, bool p_no_depth = false, bool p_fixed_size = false, TextureFilter p_filter = TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, AlphaAntiAliasing p_alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF, RID *r_shader_rid = nullptr); + virtual RID get_rid() const override; virtual RID get_shader_rid() const override; virtual Shader::Mode get_shader_mode() const override; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 22e2e9138f..4d1d733f8b 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -205,81 +205,81 @@ Mesh::ConvexDecompositionFunc Mesh::convex_decomposition_function = nullptr; int Mesh::get_surface_count() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_surface_count, ret); + GDVIRTUAL_CALL(_get_surface_count, ret); return ret; } int Mesh::surface_get_array_len(int p_idx) const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_surface_get_array_len, p_idx, ret); + GDVIRTUAL_CALL(_surface_get_array_len, p_idx, ret); return ret; } int Mesh::surface_get_array_index_len(int p_idx) const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_surface_get_array_index_len, p_idx, ret); + GDVIRTUAL_CALL(_surface_get_array_index_len, p_idx, ret); return ret; } Array Mesh::surface_get_arrays(int p_surface) const { Array ret; - GDVIRTUAL_REQUIRED_CALL(_surface_get_arrays, p_surface, ret); + GDVIRTUAL_CALL(_surface_get_arrays, p_surface, ret); return ret; } TypedArray<Array> Mesh::surface_get_blend_shape_arrays(int p_surface) const { TypedArray<Array> ret; - GDVIRTUAL_REQUIRED_CALL(_surface_get_blend_shape_arrays, p_surface, ret); + GDVIRTUAL_CALL(_surface_get_blend_shape_arrays, p_surface, ret); return ret; } Dictionary Mesh::surface_get_lods(int p_surface) const { Dictionary ret; - GDVIRTUAL_REQUIRED_CALL(_surface_get_lods, p_surface, ret); + GDVIRTUAL_CALL(_surface_get_lods, p_surface, ret); return ret; } BitField<Mesh::ArrayFormat> Mesh::surface_get_format(int p_idx) const { uint32_t ret = 0; - GDVIRTUAL_REQUIRED_CALL(_surface_get_format, p_idx, ret); + GDVIRTUAL_CALL(_surface_get_format, p_idx, ret); return ret; } Mesh::PrimitiveType Mesh::surface_get_primitive_type(int p_idx) const { uint32_t ret = PRIMITIVE_MAX; - GDVIRTUAL_REQUIRED_CALL(_surface_get_primitive_type, p_idx, ret); + GDVIRTUAL_CALL(_surface_get_primitive_type, p_idx, ret); return (Mesh::PrimitiveType)ret; } void Mesh::surface_set_material(int p_idx, const Ref<Material> &p_material) { - GDVIRTUAL_REQUIRED_CALL(_surface_set_material, p_idx, p_material); + GDVIRTUAL_CALL(_surface_set_material, p_idx, p_material); } Ref<Material> Mesh::surface_get_material(int p_idx) const { Ref<Material> ret; - GDVIRTUAL_REQUIRED_CALL(_surface_get_material, p_idx, ret); + GDVIRTUAL_CALL(_surface_get_material, p_idx, ret); return ret; } int Mesh::get_blend_shape_count() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_blend_shape_count, ret); + GDVIRTUAL_CALL(_get_blend_shape_count, ret); return ret; } StringName Mesh::get_blend_shape_name(int p_index) const { StringName ret; - GDVIRTUAL_REQUIRED_CALL(_get_blend_shape_name, p_index, ret); + GDVIRTUAL_CALL(_get_blend_shape_name, p_index, ret); return ret; } void Mesh::set_blend_shape_name(int p_index, const StringName &p_name) { - GDVIRTUAL_REQUIRED_CALL(_set_blend_shape_name, p_index, p_name); + GDVIRTUAL_CALL(_set_blend_shape_name, p_index, p_name); } AABB Mesh::get_aabb() const { AABB ret; - GDVIRTUAL_REQUIRED_CALL(_get_aabb, ret); + GDVIRTUAL_CALL(_get_aabb, ret); return ret; } diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 13fd986e81..068bfb6708 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -68,20 +68,20 @@ public: protected: static void _bind_methods(); - GDVIRTUAL0RC(int, _get_surface_count) - GDVIRTUAL1RC(int, _surface_get_array_len, int) - GDVIRTUAL1RC(int, _surface_get_array_index_len, int) - GDVIRTUAL1RC(Array, _surface_get_arrays, int) - GDVIRTUAL1RC(TypedArray<Array>, _surface_get_blend_shape_arrays, int) - GDVIRTUAL1RC(Dictionary, _surface_get_lods, int) - GDVIRTUAL1RC(uint32_t, _surface_get_format, int) - GDVIRTUAL1RC(uint32_t, _surface_get_primitive_type, int) - GDVIRTUAL2(_surface_set_material, int, Ref<Material>) - GDVIRTUAL1RC(Ref<Material>, _surface_get_material, int) - GDVIRTUAL0RC(int, _get_blend_shape_count) - GDVIRTUAL1RC(StringName, _get_blend_shape_name, int) - GDVIRTUAL2(_set_blend_shape_name, int, StringName) - GDVIRTUAL0RC(AABB, _get_aabb) + GDVIRTUAL0RC_REQUIRED(int, _get_surface_count) + GDVIRTUAL1RC_REQUIRED(int, _surface_get_array_len, int) + GDVIRTUAL1RC_REQUIRED(int, _surface_get_array_index_len, int) + GDVIRTUAL1RC_REQUIRED(Array, _surface_get_arrays, int) + GDVIRTUAL1RC_REQUIRED(TypedArray<Array>, _surface_get_blend_shape_arrays, int) + GDVIRTUAL1RC_REQUIRED(Dictionary, _surface_get_lods, int) + GDVIRTUAL1RC_REQUIRED(uint32_t, _surface_get_format, int) + GDVIRTUAL1RC_REQUIRED(uint32_t, _surface_get_primitive_type, int) + GDVIRTUAL2_REQUIRED(_surface_set_material, int, Ref<Material>) + GDVIRTUAL1RC_REQUIRED(Ref<Material>, _surface_get_material, int) + GDVIRTUAL0RC_REQUIRED(int, _get_blend_shape_count) + GDVIRTUAL1RC_REQUIRED(StringName, _get_blend_shape_name, int) + GDVIRTUAL2_REQUIRED(_set_blend_shape_name, int, StringName) + GDVIRTUAL0RC_REQUIRED(AABB, _get_aabb) public: enum { diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 8cfe4c92b7..09bc1fa8e4 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -2261,6 +2261,8 @@ void ParticleProcessMaterial::_bind_methods() { ParticleProcessMaterial::ParticleProcessMaterial() : element(this) { + _set_material(RS::get_singleton()->material_create()); + set_direction(Vector3(1, 0, 0)); set_spread(45); set_flatness(0); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index d531eea311..e234a81c88 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -612,29 +612,27 @@ Error ResourceLoaderText::load() { } } - if (ClassDB::has_property(res->get_class_name(), assign)) { - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = res->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); - } + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = res->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); } } + } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary set_dict = value; - bool is_get_valid = false; - Variant get_value = res->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { - Dictionary get_dict = get_value; - if (!set_dict.is_same_typed(get_dict)) { - value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), - get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); - } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); } } } @@ -754,29 +752,27 @@ Error ResourceLoaderText::load() { } } - if (ClassDB::has_property(resource->get_class_name(), assign)) { - if (value.get_type() == Variant::ARRAY) { - Array set_array = value; - bool is_get_valid = false; - Variant get_value = resource->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::ARRAY) { - Array get_array = get_value; - if (!set_array.is_same_typed(get_array)) { - value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); - } + if (value.get_type() == Variant::ARRAY) { + Array set_array = value; + bool is_get_valid = false; + Variant get_value = resource->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { + Array get_array = get_value; + if (!set_array.is_same_typed(get_array)) { + value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script()); } } + } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary set_dict = value; - bool is_get_valid = false; - Variant get_value = resource->get(assign, &is_get_valid); - if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { - Dictionary get_dict = get_value; - if (!set_dict.is_same_typed(get_dict)) { - value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), - get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); - } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = resource->get(assign, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); } } } @@ -1708,6 +1704,8 @@ static String _resource_get_class(Ref<Resource> p_resource) { } Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { + Resource::seed_scene_unique_id(p_path.hash()); // Seeding for save path should make it deterministic for importers. + if (p_path.ends_with(".tscn")) { packed_scene = p_resource; } diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 46d38146a6..24d17108d5 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -50,6 +50,14 @@ Shader::Mode Shader::get_mode() const { return mode; } +void Shader::_check_shader_rid() const { + MutexLock lock(shader_rid_mutex); + if (shader_rid.is_null() && !preprocessed_code.is_empty()) { + shader_rid = RenderingServer::get_singleton()->shader_create_from_code(preprocessed_code, get_path()); + preprocessed_code = String(); + } +} + void Shader::_dependency_changed() { // Preprocess and compile the code again because a dependency has changed. It also calls emit_changed() for us. _recompile(); @@ -61,7 +69,10 @@ void Shader::_recompile() { void Shader::set_path(const String &p_path, bool p_take_over) { Resource::set_path(p_path, p_take_over); - RS::get_singleton()->shader_set_path_hint(shader, p_path); + + if (shader_rid.is_valid()) { + RS::get_singleton()->shader_set_path_hint(shader_rid, p_path); + } } void Shader::set_include_path(const String &p_path) { @@ -76,7 +87,7 @@ void Shader::set_code(const String &p_code) { } code = p_code; - String pp_code = p_code; + preprocessed_code = p_code; { String path = get_path(); @@ -88,7 +99,7 @@ void Shader::set_code(const String &p_code) { // 2) Server does not do interaction with Resource filetypes, this is a scene level feature. HashSet<Ref<ShaderInclude>> new_include_dependencies; ShaderPreprocessor preprocessor; - Error result = preprocessor.preprocess(p_code, path, pp_code, nullptr, nullptr, nullptr, &new_include_dependencies); + Error result = preprocessor.preprocess(p_code, path, preprocessed_code, nullptr, nullptr, nullptr, &new_include_dependencies); if (result == OK) { // This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower) include_dependencies = new_include_dependencies; @@ -96,7 +107,7 @@ void Shader::set_code(const String &p_code) { } // Try to get the shader type from the final, fully preprocessed shader code. - String type = ShaderLanguage::get_shader_type(pp_code); + String type = ShaderLanguage::get_shader_type(preprocessed_code); if (type == "canvas_item") { mode = MODE_CANVAS_ITEM; @@ -114,7 +125,10 @@ void Shader::set_code(const String &p_code) { E->connect_changed(callable_mp(this, &Shader::_dependency_changed)); } - RenderingServer::get_singleton()->shader_set_code(shader, pp_code); + if (shader_rid.is_valid()) { + RenderingServer::get_singleton()->shader_set_code(shader_rid, preprocessed_code); + preprocessed_code = String(); + } emit_changed(); } @@ -126,9 +140,10 @@ String Shader::get_code() const { void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups) const { _update_shader(); + _check_shader_rid(); List<PropertyInfo> local; - RenderingServer::get_singleton()->get_shader_parameter_list(shader, &local); + RenderingServer::get_singleton()->get_shader_parameter_list(shader_rid, &local); #ifdef TOOLS_ENABLED DocData::ClassDoc class_doc; @@ -182,17 +197,20 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr RID Shader::get_rid() const { _update_shader(); + _check_shader_rid(); - return shader; + return shader_rid; } void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index) { + _check_shader_rid(); + if (p_texture.is_valid()) { if (!default_textures.has(p_name)) { default_textures[p_name] = HashMap<int, Ref<Texture>>(); } default_textures[p_name][p_index] = p_texture; - RS::get_singleton()->shader_set_default_texture_parameter(shader, p_name, p_texture->get_rid(), p_index); + RS::get_singleton()->shader_set_default_texture_parameter(shader_rid, p_name, p_texture->get_rid(), p_index); } else { if (default_textures.has(p_name) && default_textures[p_name].has(p_index)) { default_textures[p_name].erase(p_index); @@ -201,7 +219,7 @@ void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<T default_textures.erase(p_name); } } - RS::get_singleton()->shader_set_default_texture_parameter(shader, p_name, RID(), p_index); + RS::get_singleton()->shader_set_default_texture_parameter(shader_rid, p_name, RID(), p_index); } emit_changed(); @@ -225,6 +243,7 @@ bool Shader::is_text_shader() const { } void Shader::_update_shader() const { + // Base implementation does nothing. } Array Shader::_get_shader_uniform_list(bool p_get_groups) { @@ -258,12 +277,14 @@ void Shader::_bind_methods() { } Shader::Shader() { - shader = RenderingServer::get_singleton()->shader_create(); + // Shader RID will be empty until it is required. } Shader::~Shader() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RenderingServer::get_singleton()->free(shader); + if (shader_rid.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RenderingServer::get_singleton()->free(shader_rid); + } } //////////// diff --git a/scene/resources/shader.h b/scene/resources/shader.h index 682fbd7ea6..18197419f3 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -52,7 +52,10 @@ public: }; private: - RID shader; + mutable RID shader_rid; + mutable String preprocessed_code; + mutable Mutex shader_rid_mutex; + Mode mode = MODE_SPATIAL; HashSet<Ref<ShaderInclude>> include_dependencies; String code; @@ -60,6 +63,7 @@ private: HashMap<StringName, HashMap<int, Ref<Texture>>> default_textures; + void _check_shader_rid() const; void _dependency_changed(); void _recompile(); virtual void _update_shader() const; //used for visual shader diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index f87bf1ee05..1203c21a1b 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -90,7 +90,7 @@ Point2 StyleBox::get_offset() const { } void StyleBox::draw(RID p_canvas_item, const Rect2 &p_rect) const { - GDVIRTUAL_REQUIRED_CALL(_draw, p_canvas_item, p_rect); + GDVIRTUAL_CALL(_draw, p_canvas_item, p_rect); } Rect2 StyleBox::get_draw_rect(const Rect2 &p_rect) const { diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index 3d3a059d0b..e09b271c47 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -48,7 +48,7 @@ protected: static void _bind_methods(); virtual float get_style_margin(Side p_side) const { return 0; } - GDVIRTUAL2C(_draw, RID, Rect2) + GDVIRTUAL2C_REQUIRED(_draw, RID, Rect2) GDVIRTUAL1RC(Rect2, _get_draw_rect, Rect2) GDVIRTUAL0RC(Size2, _get_minimum_size) GDVIRTUAL2RC(bool, _test_mask, Point2, Rect2) diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp index 60b91ef0cb..15816925c1 100644 --- a/scene/resources/style_box_flat.cpp +++ b/scene/resources/style_box_flat.cpp @@ -226,33 +226,16 @@ inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_re real_t border_right = style_rect.size.width - inner_rect.size.width - border_left; real_t border_bottom = style_rect.size.height - inner_rect.size.height - border_top; - real_t rad; - - // Top left. - rad = MIN(border_top, border_left); - inner_corner_radius[0] = MAX(corner_radius[0] - rad, 0); - - // Top right; - rad = MIN(border_top, border_right); - inner_corner_radius[1] = MAX(corner_radius[1] - rad, 0); - - // Bottom right. - rad = MIN(border_bottom, border_right); - inner_corner_radius[2] = MAX(corner_radius[2] - rad, 0); - - // Bottom left. - rad = MIN(border_bottom, border_left); - inner_corner_radius[3] = MAX(corner_radius[3] - rad, 0); + inner_corner_radius[0] = MAX(corner_radius[0] - MIN(border_top, border_left), 0); // Top left. + inner_corner_radius[1] = MAX(corner_radius[1] - MIN(border_top, border_right), 0); // Top right. + inner_corner_radius[2] = MAX(corner_radius[2] - MIN(border_bottom, border_right), 0); // Bottom right. + inner_corner_radius[3] = MAX(corner_radius[3] - MIN(border_bottom, border_left), 0); // Bottom left. } inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const real_t corner_radius[4], const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const Vector2 &skew, bool is_filled = false) { int vert_offset = verts.size(); - if (!vert_offset) { - vert_offset = 0; - } - - int adapted_corner_detail = (corner_radius[0] == 0 && corner_radius[1] == 0 && corner_radius[2] == 0 && corner_radius[3] == 0) ? 1 : corner_detail; + int adapted_corner_detail = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0) ? corner_detail : 1; bool draw_border = !is_filled; @@ -280,30 +263,44 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, // If the center is filled, we do not draw the border and directly use the inner ring as reference. Because all calls to this // method either draw a ring or a filled rounded rectangle, but not both. - int max_inner_outer = draw_border ? 2 : 1; - - for (int corner_index = 0; corner_index < 4; corner_index++) { + real_t quarter_arc_rad = Math_PI / 2.0; + Point2 style_rect_center = style_rect.get_center(); + + int colors_size = colors.size(); + int verts_size = verts.size(); + int new_verts_amount = (adapted_corner_detail + 1) * (draw_border ? 8 : 4); + colors.resize(colors_size + new_verts_amount); + verts.resize(verts_size + new_verts_amount); + Color *colors_ptr = colors.ptrw(); + Vector2 *verts_ptr = verts.ptrw(); + + for (int corner_idx = 0; corner_idx < 4; corner_idx++) { for (int detail = 0; detail <= adapted_corner_detail; detail++) { - for (int inner_outer = 0; inner_outer < max_inner_outer; inner_outer++) { - real_t radius; - Color color; - Point2 corner_point; - if (inner_outer == 0) { - radius = inner_corner_radius[corner_index]; - color = inner_color; - corner_point = inner_points[corner_index]; - } else { - radius = ring_corner_radius[corner_index]; - color = outer_color; - corner_point = outer_points[corner_index]; - } + int idx_ofs = (adapted_corner_detail + 1) * corner_idx + detail; + if (draw_border) { + idx_ofs *= 2; + } - const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; - const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; - const float x_skew = -skew.x * (y - style_rect.get_center().y); - const float y_skew = -skew.y * (x - style_rect.get_center().x); - verts.push_back(Vector2(x + x_skew, y + y_skew)); - colors.push_back(color); + const real_t pt_angle = (corner_idx + detail / (double)adapted_corner_detail) * quarter_arc_rad + Math_PI; + const real_t angle_cosine = cos(pt_angle); + const real_t angle_sine = sin(pt_angle); + + { + const real_t x = inner_corner_radius[corner_idx] * angle_cosine + inner_points[corner_idx].x; + const real_t y = inner_corner_radius[corner_idx] * angle_sine + inner_points[corner_idx].y; + const float x_skew = -skew.x * (y - style_rect_center.y); + const float y_skew = -skew.y * (x - style_rect_center.x); + verts_ptr[verts_size + idx_ofs] = Vector2(x + x_skew, y + y_skew); + colors_ptr[colors_size + idx_ofs] = inner_color; + } + + if (draw_border) { + const real_t x = ring_corner_radius[corner_idx] * angle_cosine + outer_points[corner_idx].x; + const real_t y = ring_corner_radius[corner_idx] * angle_sine + outer_points[corner_idx].y; + const float x_skew = -skew.x * (y - style_rect_center.y); + const float y_skew = -skew.y * (x - style_rect_center.x); + verts_ptr[verts_size + idx_ofs + 1] = Vector2(x + x_skew, y + y_skew); + colors_ptr[colors_size + idx_ofs + 1] = outer_color; } } } @@ -313,10 +310,15 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, // Fill the indices and the colors for the border. if (draw_border) { + int indices_size = indices.size(); + indices.resize(indices_size + ring_vert_count * 3); + int *indices_ptr = indices.ptrw(); + for (int i = 0; i < ring_vert_count; i++) { - indices.push_back(vert_offset + ((i + 0) % ring_vert_count)); - indices.push_back(vert_offset + ((i + 2) % ring_vert_count)); - indices.push_back(vert_offset + ((i + 1) % ring_vert_count)); + int idx_ofs = indices_size + i * 3; + indices_ptr[idx_ofs] = vert_offset + i % ring_vert_count; + indices_ptr[idx_ofs + 1] = vert_offset + (i + 2) % ring_vert_count; + indices_ptr[idx_ofs + 2] = vert_offset + (i + 1) % ring_vert_count; } } @@ -327,40 +329,30 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, int stripes_count = ring_vert_count / 2 - 1; int last_vert_id = ring_vert_count - 1; + int indices_size = indices.size(); + indices.resize(indices_size + stripes_count * 6); + int *indices_ptr = indices.ptrw(); + for (int i = 0; i < stripes_count; i++) { + int idx_ofs = indices_size + i * 6; // Polygon 1. - indices.push_back(vert_offset + i); - indices.push_back(vert_offset + last_vert_id - i - 1); - indices.push_back(vert_offset + i + 1); + indices_ptr[idx_ofs] = vert_offset + i; + indices_ptr[idx_ofs + 1] = vert_offset + last_vert_id - i - 1; + indices_ptr[idx_ofs + 2] = vert_offset + i + 1; // Polygon 2. - indices.push_back(vert_offset + i); - indices.push_back(vert_offset + last_vert_id - 0 - i); - indices.push_back(vert_offset + last_vert_id - 1 - i); + indices_ptr[idx_ofs + 3] = vert_offset + i; + indices_ptr[idx_ofs + 4] = vert_offset + last_vert_id - i; + indices_ptr[idx_ofs + 5] = vert_offset + last_vert_id - i - 1; } } } inline void adapt_values(int p_index_a, int p_index_b, real_t *adapted_values, const real_t *p_values, const real_t p_width, const real_t p_max_a, const real_t p_max_b) { - if (p_values[p_index_a] + p_values[p_index_b] > p_width) { - real_t factor; - real_t new_value; - - factor = (real_t)p_width / (real_t)(p_values[p_index_a] + p_values[p_index_b]); - - new_value = (p_values[p_index_a] * factor); - if (new_value < adapted_values[p_index_a]) { - adapted_values[p_index_a] = new_value; - } - new_value = (p_values[p_index_b] * factor); - if (new_value < adapted_values[p_index_b]) { - adapted_values[p_index_b] = new_value; - } - } else { - adapted_values[p_index_a] = MIN(p_values[p_index_a], adapted_values[p_index_a]); - adapted_values[p_index_b] = MIN(p_values[p_index_b], adapted_values[p_index_b]); - } - adapted_values[p_index_a] = MIN(p_max_a, adapted_values[p_index_a]); - adapted_values[p_index_b] = MIN(p_max_b, adapted_values[p_index_b]); + real_t value_a = p_values[p_index_a]; + real_t value_b = p_values[p_index_b]; + real_t factor = MIN(1.0, p_width / (value_a + value_b)); + adapted_values[p_index_a] = MIN(MIN(value_a * factor, p_max_a), adapted_values[p_index_a]); + adapted_values[p_index_b] = MIN(MIN(value_b * factor, p_max_b), adapted_values[p_index_b]); } Rect2 StyleBoxFlat::get_draw_rect(const Rect2 &p_rect) const { @@ -388,7 +380,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { } const bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); - // Only enable antialiasing if it is actually needed. This improve performances + // Only enable antialiasing if it is actually needed. This improves performance // and maximizes sharpness for non-skewed StyleBoxes with sharp corners. const bool aa_on = (rounded_corners || !skew.is_zero_approx()) && anti_aliased; @@ -428,7 +420,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Vector<Color> colors; Vector<Point2> uvs; - // Create shadow + // Create shadow. if (draw_shadow) { Rect2 shadow_inner_rect = style_rect; shadow_inner_rect.position += shadow_offset; @@ -538,9 +530,10 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { // Compute UV coordinates. Rect2 uv_rect = style_rect.grow(aa_on ? aa_size : 0); uvs.resize(verts.size()); + Point2 *uvs_ptr = uvs.ptrw(); for (int i = 0; i < verts.size(); i++) { - uvs.write[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width; - uvs.write[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height; + uvs_ptr[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width; + uvs_ptr[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height; } // Draw stylebox. diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index 6da4215031..da90ba1ef2 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -442,7 +442,6 @@ Color CodeHighlighter::get_keyword_color(const String &p_keyword) const { } void CodeHighlighter::set_keyword_colors(const Dictionary p_keywords) { - keywords.clear(); keywords = p_keywords; clear_highlighting_cache(); } @@ -476,7 +475,6 @@ Color CodeHighlighter::get_member_keyword_color(const String &p_member_keyword) } void CodeHighlighter::set_member_keyword_colors(const Dictionary &p_member_keywords) { - member_keywords.clear(); member_keywords = p_member_keywords; clear_highlighting_cache(); } diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 0efaad61fe..7713181a4b 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -34,13 +34,13 @@ int Texture2D::get_width() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_width, ret); + GDVIRTUAL_CALL(_get_width, ret); return ret; } int Texture2D::get_height() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_height, ret); + GDVIRTUAL_CALL(_get_height, ret); return ret; } @@ -133,37 +133,37 @@ TypedArray<Image> Texture3D::_get_datai() const { Image::Format Texture3D::get_format() const { Image::Format ret = Image::FORMAT_MAX; - GDVIRTUAL_REQUIRED_CALL(_get_format, ret); + GDVIRTUAL_CALL(_get_format, ret); return ret; } int Texture3D::get_width() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_width, ret); + GDVIRTUAL_CALL(_get_width, ret); return ret; } int Texture3D::get_height() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_height, ret); + GDVIRTUAL_CALL(_get_height, ret); return ret; } int Texture3D::get_depth() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_depth, ret); + GDVIRTUAL_CALL(_get_depth, ret); return ret; } bool Texture3D::has_mipmaps() const { bool ret = false; - GDVIRTUAL_REQUIRED_CALL(_has_mipmaps, ret); + GDVIRTUAL_CALL(_has_mipmaps, ret); return ret; } Vector<Ref<Image>> Texture3D::get_data() const { TypedArray<Image> ret; - GDVIRTUAL_REQUIRED_CALL(_get_data, ret); + GDVIRTUAL_CALL(_get_data, ret); Vector<Ref<Image>> data; data.resize(ret.size()); for (int i = 0; i < data.size(); i++) { @@ -198,43 +198,43 @@ Ref<Resource> Texture3D::create_placeholder() const { Image::Format TextureLayered::get_format() const { Image::Format ret = Image::FORMAT_MAX; - GDVIRTUAL_REQUIRED_CALL(_get_format, ret); + GDVIRTUAL_CALL(_get_format, ret); return ret; } TextureLayered::LayeredType TextureLayered::get_layered_type() const { uint32_t ret = LAYERED_TYPE_2D_ARRAY; - GDVIRTUAL_REQUIRED_CALL(_get_layered_type, ret); + GDVIRTUAL_CALL(_get_layered_type, ret); return (LayeredType)ret; } int TextureLayered::get_width() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_width, ret); + GDVIRTUAL_CALL(_get_width, ret); return ret; } int TextureLayered::get_height() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_height, ret); + GDVIRTUAL_CALL(_get_height, ret); return ret; } int TextureLayered::get_layers() const { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_get_layers, ret); + GDVIRTUAL_CALL(_get_layers, ret); return ret; } bool TextureLayered::has_mipmaps() const { bool ret = false; - GDVIRTUAL_REQUIRED_CALL(_has_mipmaps, ret); + GDVIRTUAL_CALL(_has_mipmaps, ret); return ret; } Ref<Image> TextureLayered::get_layer_data(int p_layer) const { Ref<Image> ret; - GDVIRTUAL_REQUIRED_CALL(_get_layer_data, p_layer, ret); + GDVIRTUAL_CALL(_get_layer_data, p_layer, ret); return ret; } diff --git a/scene/resources/texture.h b/scene/resources/texture.h index e7840804bf..cdad884e71 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -57,8 +57,8 @@ class Texture2D : public Texture { protected: static void _bind_methods(); - GDVIRTUAL0RC(int, _get_width) - GDVIRTUAL0RC(int, _get_height) + GDVIRTUAL0RC_REQUIRED(int, _get_width) + GDVIRTUAL0RC_REQUIRED(int, _get_height) GDVIRTUAL2RC(bool, _is_pixel_opaque, int, int) GDVIRTUAL0RC(bool, _has_alpha) @@ -93,13 +93,13 @@ class TextureLayered : public Texture { protected: static void _bind_methods(); - GDVIRTUAL0RC(Image::Format, _get_format) - GDVIRTUAL0RC(uint32_t, _get_layered_type) - GDVIRTUAL0RC(int, _get_width) - GDVIRTUAL0RC(int, _get_height) - GDVIRTUAL0RC(int, _get_layers) - GDVIRTUAL0RC(bool, _has_mipmaps) - GDVIRTUAL1RC(Ref<Image>, _get_layer_data, int) + GDVIRTUAL0RC_REQUIRED(Image::Format, _get_format) + GDVIRTUAL0RC_REQUIRED(uint32_t, _get_layered_type) + GDVIRTUAL0RC_REQUIRED(int, _get_width) + GDVIRTUAL0RC_REQUIRED(int, _get_height) + GDVIRTUAL0RC_REQUIRED(int, _get_layers) + GDVIRTUAL0RC_REQUIRED(bool, _has_mipmaps) + GDVIRTUAL1RC_REQUIRED(Ref<Image>, _get_layer_data, int) public: enum LayeredType { LAYERED_TYPE_2D_ARRAY, @@ -128,12 +128,12 @@ protected: TypedArray<Image> _get_datai() const; - GDVIRTUAL0RC(Image::Format, _get_format) - GDVIRTUAL0RC(int, _get_width) - GDVIRTUAL0RC(int, _get_height) - GDVIRTUAL0RC(int, _get_depth) - GDVIRTUAL0RC(bool, _has_mipmaps) - GDVIRTUAL0RC(TypedArray<Image>, _get_data) + GDVIRTUAL0RC_REQUIRED(Image::Format, _get_format) + GDVIRTUAL0RC_REQUIRED(int, _get_width) + GDVIRTUAL0RC_REQUIRED(int, _get_height) + GDVIRTUAL0RC_REQUIRED(int, _get_depth) + GDVIRTUAL0RC_REQUIRED(bool, _has_mipmaps) + GDVIRTUAL0RC_REQUIRED(TypedArray<Image>, _get_data) public: virtual Image::Format get_format() const; virtual int get_width() const; diff --git a/scene/resources/video_stream.cpp b/scene/resources/video_stream.cpp index 3d31fe0491..1fcc1821dc 100644 --- a/scene/resources/video_stream.cpp +++ b/scene/resources/video_stream.cpp @@ -119,7 +119,7 @@ Ref<Texture2D> VideoStreamPlayback::get_texture() const { } void VideoStreamPlayback::update(double p_delta) { - GDVIRTUAL_REQUIRED_CALL(_update, p_delta); + GDVIRTUAL_CALL(_update, p_delta); } void VideoStreamPlayback::set_mix_callback(AudioMixCallback p_callback, void *p_userdata) { diff --git a/scene/resources/video_stream.h b/scene/resources/video_stream.h index dc54f4b769..3843cc99c4 100644 --- a/scene/resources/video_stream.h +++ b/scene/resources/video_stream.h @@ -56,7 +56,7 @@ protected: GDVIRTUAL1(_seek, double); GDVIRTUAL1(_set_audio_track, int); GDVIRTUAL0RC(Ref<Texture2D>, _get_texture); - GDVIRTUAL1(_update, double); + GDVIRTUAL1_REQUIRED(_update, double); GDVIRTUAL0RC(int, _get_channels); GDVIRTUAL0RC(int, _get_mix_rate); diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 26666538af..3db1ab9338 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -8081,17 +8081,28 @@ int VisualShaderNodeRemap::get_input_port_count() const { } VisualShaderNodeRemap::PortType VisualShaderNodeRemap::get_input_port_type(int p_port) const { - switch (p_port) { - case 0: - return PORT_TYPE_SCALAR; - case 1: - return PORT_TYPE_SCALAR; - case 2: - return PORT_TYPE_SCALAR; - case 3: - return PORT_TYPE_SCALAR; - case 4: - return PORT_TYPE_SCALAR; + switch (op_type) { + case OP_TYPE_VECTOR_2D: + return PORT_TYPE_VECTOR_2D; + case OP_TYPE_VECTOR_2D_SCALAR: + if (p_port == 0) { + return PORT_TYPE_VECTOR_2D; + } + break; + case OP_TYPE_VECTOR_3D: + return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_3D_SCALAR: + if (p_port == 0) { + return PORT_TYPE_VECTOR_3D; + } + break; + case OP_TYPE_VECTOR_4D: + return PORT_TYPE_VECTOR_4D; + case OP_TYPE_VECTOR_4D_SCALAR: + if (p_port == 0) { + return PORT_TYPE_VECTOR_4D; + } + break; default: break; } @@ -8123,23 +8134,159 @@ int VisualShaderNodeRemap::get_output_port_count() const { } VisualShaderNodeRemap::PortType VisualShaderNodeRemap::get_output_port_type(int p_port) const { - return PORT_TYPE_SCALAR; + switch (op_type) { + case OP_TYPE_VECTOR_2D: + case OP_TYPE_VECTOR_2D_SCALAR: + return PORT_TYPE_VECTOR_2D; + case OP_TYPE_VECTOR_3D: + case OP_TYPE_VECTOR_3D_SCALAR: + return PORT_TYPE_VECTOR_3D; + case OP_TYPE_VECTOR_4D: + case OP_TYPE_VECTOR_4D_SCALAR: + return PORT_TYPE_VECTOR_4D; + default: + return PORT_TYPE_SCALAR; + } } String VisualShaderNodeRemap::get_output_port_name(int p_port) const { return "value"; } +void VisualShaderNodeRemap::set_op_type(OpType p_op_type) { + ERR_FAIL_INDEX(int(p_op_type), int(OP_TYPE_MAX)); + if (op_type == p_op_type) { + return; + } + switch (p_op_type) { + case OP_TYPE_SCALAR: { + set_input_port_default_value(0, 0.0, get_input_port_default_value(0)); + set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); + set_input_port_default_value(2, 1.0, get_input_port_default_value(2)); + set_input_port_default_value(3, 0.0, get_input_port_default_value(3)); + set_input_port_default_value(4, 1.0, get_input_port_default_value(4)); + } break; + case OP_TYPE_VECTOR_2D: { + set_input_port_default_value(0, Vector2(), get_input_port_default_value(0)); + set_input_port_default_value(1, Vector2(), get_input_port_default_value(1)); + set_input_port_default_value(2, Vector2(1.0, 1.0), get_input_port_default_value(2)); + set_input_port_default_value(3, Vector2(), get_input_port_default_value(3)); + set_input_port_default_value(4, Vector2(1.0, 1.0), get_input_port_default_value(4)); + } break; + case OP_TYPE_VECTOR_2D_SCALAR: { + set_input_port_default_value(0, Vector2(), get_input_port_default_value(0)); + set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); + set_input_port_default_value(2, 1.0, get_input_port_default_value(2)); + set_input_port_default_value(3, 0.0, get_input_port_default_value(3)); + set_input_port_default_value(4, 1.0, get_input_port_default_value(4)); + } break; + case OP_TYPE_VECTOR_3D: { + set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); + set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); + set_input_port_default_value(2, Vector3(1.0, 1.0, 1.0), get_input_port_default_value(2)); + set_input_port_default_value(3, Vector3(), get_input_port_default_value(3)); + set_input_port_default_value(4, Vector3(1.0, 1.0, 1.0), get_input_port_default_value(4)); + } break; + case OP_TYPE_VECTOR_3D_SCALAR: { + set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); + set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); + set_input_port_default_value(2, 1.0, get_input_port_default_value(2)); + set_input_port_default_value(3, 0.0, get_input_port_default_value(3)); + set_input_port_default_value(4, 1.0, get_input_port_default_value(4)); + } break; + case OP_TYPE_VECTOR_4D: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); + set_input_port_default_value(2, Quaternion(1.0, 1.0, 1.0, 1.0), get_input_port_default_value(2)); + set_input_port_default_value(3, Quaternion(), get_input_port_default_value(3)); + set_input_port_default_value(4, Quaternion(1.0, 1.0, 1.0, 1.0), get_input_port_default_value(4)); + } break; + case OP_TYPE_VECTOR_4D_SCALAR: { + set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); + set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); + set_input_port_default_value(2, 1.0, get_input_port_default_value(2)); + set_input_port_default_value(3, 0.0, get_input_port_default_value(3)); + set_input_port_default_value(4, 1.0, get_input_port_default_value(4)); + } break; + default: + break; + } + op_type = p_op_type; + emit_changed(); +} + +VisualShaderNodeRemap::OpType VisualShaderNodeRemap::get_op_type() const { + return op_type; +} + String VisualShaderNodeRemap::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; code += " {\n"; - code += vformat(" float __input_range = %s - %s;\n", p_input_vars[2], p_input_vars[1]); - code += vformat(" float __output_range = %s - %s;\n", p_input_vars[4], p_input_vars[3]); - code += vformat(" %s = %s + __output_range * ((%s - %s) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + switch (op_type) { + case OP_TYPE_SCALAR: { + code += vformat(" float __input_range = %s - %s;\n", p_input_vars[2], p_input_vars[1]); + code += vformat(" float __output_range = %s - %s;\n", p_input_vars[4], p_input_vars[3]); + code += vformat(" %s = %s + __output_range * ((%s - %s) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_2D: { + code += vformat(" vec2 __input_range = %s - %s;\n", p_input_vars[2], p_input_vars[1]); + code += vformat(" vec2 __output_range = %s - %s;\n", p_input_vars[4], p_input_vars[3]); + code += vformat(" %s = %s + __output_range * ((%s - %s) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_2D_SCALAR: { + code += vformat(" vec2 __input_range = vec2(%s - %s);\n", p_input_vars[2], p_input_vars[1]); + code += vformat(" vec2 __output_range = vec2(%s - %s);\n", p_input_vars[4], p_input_vars[3]); + code += vformat(" %s = vec2(%s) + __output_range * ((%s - vec2(%s)) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_3D: { + code += vformat(" vec3 __input_range = %s - %s;\n", p_input_vars[2], p_input_vars[1]); + code += vformat(" vec3 __output_range = %s - %s;\n", p_input_vars[4], p_input_vars[3]); + code += vformat(" %s = %s + __output_range * ((%s - %s) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_3D_SCALAR: { + code += vformat(" vec3 __input_range = vec3(%s - %s);\n", p_input_vars[2], p_input_vars[1]); + code += vformat(" vec3 __output_range = vec3(%s - %s);\n", p_input_vars[4], p_input_vars[3]); + code += vformat(" %s = vec3(%s) + __output_range * ((%s - vec3(%s)) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_4D: { + code += vformat(" vec4 __input_range = %s - %s;\n", p_input_vars[2], p_input_vars[1]); + code += vformat(" vec4 __output_range = %s - %s;\n", p_input_vars[4], p_input_vars[3]); + code += vformat(" %s = %s + __output_range * ((%s - %s) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + } break; + case OP_TYPE_VECTOR_4D_SCALAR: { + code += vformat(" vec4 __input_range = vec4(%s - %s);\n", p_input_vars[2], p_input_vars[1]); + code += vformat(" vec4 __output_range = vec4(%s - %s);\n", p_input_vars[4], p_input_vars[3]); + code += vformat(" %s = vec4(%s) + __output_range * ((%s - vec4(%s)) / __input_range);\n", p_output_vars[0], p_input_vars[3], p_input_vars[0], p_input_vars[1]); + } break; + default: + break; + } code += " }\n"; return code; } +Vector<StringName> VisualShaderNodeRemap::get_editable_properties() const { + Vector<StringName> props; + props.push_back("op_type"); + return props; +} + +void VisualShaderNodeRemap::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_op_type", "op_type"), &VisualShaderNodeRemap::set_op_type); + ClassDB::bind_method(D_METHOD("get_op_type"), &VisualShaderNodeRemap::get_op_type); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "op_type", PROPERTY_HINT_ENUM, "Scalar,Vector2,Vector2Scalar,Vector3,Vector3Scalar,Vector4,Vector4Scalar"), "set_op_type", "get_op_type"); + + BIND_ENUM_CONSTANT(OP_TYPE_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_2D_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_3D_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D); + BIND_ENUM_CONSTANT(OP_TYPE_VECTOR_4D_SCALAR); + BIND_ENUM_CONSTANT(OP_TYPE_MAX); +} + VisualShaderNodeRemap::VisualShaderNodeRemap() { set_input_port_default_value(1, 0.0); set_input_port_default_value(2, 1.0); diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index ff02e55fb2..67dc8f7353 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -3068,10 +3068,30 @@ public: VisualShaderNodeRandomRange(); }; +/////////////////////////////////////// +/// Remap +/////////////////////////////////////// + class VisualShaderNodeRemap : public VisualShaderNode { GDCLASS(VisualShaderNodeRemap, VisualShaderNode); public: + enum OpType { + OP_TYPE_SCALAR, + OP_TYPE_VECTOR_2D, + OP_TYPE_VECTOR_2D_SCALAR, + OP_TYPE_VECTOR_3D, + OP_TYPE_VECTOR_3D_SCALAR, + OP_TYPE_VECTOR_4D, + OP_TYPE_VECTOR_4D_SCALAR, + OP_TYPE_MAX, + }; + +protected: + OpType op_type = OP_TYPE_SCALAR; + static void _bind_methods(); + +public: virtual String get_caption() const override; virtual int get_input_port_count() const override; @@ -3082,13 +3102,26 @@ public: virtual PortType get_output_port_type(int p_port) const override; virtual String get_output_port_name(int p_port) const override; + void set_op_type(OpType p_op_type); + OpType get_op_type() const; + + virtual Vector<StringName> get_editable_properties() const override; + 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_UTILITY; } + virtual Category get_category() const override { + if (op_type == OP_TYPE_SCALAR) { + return CATEGORY_SCALAR; + } else { + return CATEGORY_VECTOR; + } + } VisualShaderNodeRemap(); }; +VARIANT_ENUM_CAST(VisualShaderNodeRemap::OpType) + class VisualShaderNodeRotationByAxis : public VisualShaderNode { GDCLASS(VisualShaderNodeRotationByAxis, VisualShaderNode); diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index f8a0336b37..140e588291 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -141,5 +141,6 @@ SceneStringNames::SceneStringNames() { confirmed = StaticCString::create("confirmed"); text_changed = StaticCString::create("text_changed"); + text_submitted = StaticCString::create("text_submitted"); value_changed = StaticCString::create("value_changed"); } diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 381a161ad5..fc22be33b2 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -154,6 +154,7 @@ public: StringName confirmed; StringName text_changed; + StringName text_submitted; StringName value_changed; }; diff --git a/scene/theme/SCsub b/scene/theme/SCsub index 2372d1820a..fb0914c0ee 100644 --- a/scene/theme/SCsub +++ b/scene/theme/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index 749d4e3530..caf44ac392 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -689,6 +689,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("forward_folder", "FileDialog", icons["arrow_right"]); theme->set_icon("reload", "FileDialog", icons["reload"]); theme->set_icon("toggle_hidden", "FileDialog", icons["visibility_visible"]); + theme->set_icon("toggle_filename_filter", "FileDialog", icons["toggle_filename_filter"]); theme->set_icon("folder", "FileDialog", icons["folder"]); theme->set_icon("file", "FileDialog", icons["file"]); theme->set_icon("create_folder", "FileDialog", icons["folder_create"]); @@ -1211,6 +1212,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("v_separation", "VFlowContainer", Math::round(4 * scale)); theme->set_stylebox(SceneStringName(panel), "PanelContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0)); + theme->set_stylebox("split_bar_background", "SplitContainer", make_empty_stylebox(0, 0, 0, 0)); + theme->set_stylebox("split_bar_background", "VSplitContainer", make_empty_stylebox(0, 0, 0, 0)); + theme->set_stylebox("split_bar_background", "HSplitContainer", make_empty_stylebox(0, 0, 0, 0)); theme->set_icon("zoom_out", "GraphEdit", icons["zoom_less"]); theme->set_icon("zoom_in", "GraphEdit", icons["zoom_more"]); diff --git a/scene/theme/icons/SCsub b/scene/theme/icons/SCsub index 1f3b7f6d17..19aca74e57 100644 --- a/scene/theme/icons/SCsub +++ b/scene/theme/icons/SCsub @@ -1,4 +1,5 @@ #!/usr/bin/env python +from misc.utility.scons_hints import * Import("env") diff --git a/scene/theme/icons/toggle_filename_filter.svg b/scene/theme/icons/toggle_filename_filter.svg new file mode 100644 index 0000000000..351cc44ced --- /dev/null +++ b/scene/theme/icons/toggle_filename_filter.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#e0e0e0"><path d="m13.297.714h-13.013a.454.454 0 0 0 -.318.779l4.615 4.507v7.086a.45.45 0 0 0 .738.354l3.511-2.812a.454.454 0 0 0 .17-.354v-4.274l4.614-4.506a.454.454 0 0 0 -.317-.779z"/><path d="m11.085832 14.18196c3.399443 1.97457 6.855925-2.441094 4.074102-5.1815164-2.781825-2.7404217-7.2642008.6646174-5.2597994 4.0134654l-1.9001346 1.871854 1.1856973 1.168051zm1.699723-4.4945981c2.236109 0 2.236109 3.3042441 0 3.3042441-2.236108 0-2.236108-3.3042441 0-3.3042441z"/></g></svg>
\ No newline at end of file |