diff options
Diffstat (limited to 'scene')
281 files changed, 8806 insertions, 5455 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index f84e82a6bb..7ee9861d3f 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -454,8 +454,8 @@ StringName AnimatedSprite2D::get_animation() const { return animation; } -TypedArray<String> AnimatedSprite2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray AnimatedSprite2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (frames.is_null()) { warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite2D to display frames.")); diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index be1cc5353e..11c4adb816 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -109,7 +109,7 @@ public: void set_flip_v(bool p_flip); bool is_flipped_v() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; AnimatedSprite2D(); diff --git a/scene/2d/audio_listener_2d.cpp b/scene/2d/audio_listener_2d.cpp index f7dd20d7c0..3cc64b2170 100644 --- a/scene/2d/audio_listener_2d.cpp +++ b/scene/2d/audio_listener_2d.cpp @@ -103,7 +103,6 @@ bool AudioListener2D::is_current() const { } else { return current; } - return false; } void AudioListener2D::_bind_methods() { diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 85ec745aee..9ac3083718 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -477,7 +477,7 @@ void AudioStreamPlayer2D::_bind_methods() { AudioStreamPlayer2D::AudioStreamPlayer2D() { AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer2D::_bus_layout_changed)); - cached_global_panning_strength = ProjectSettings::get_singleton()->get("audio/general/2d_panning_strength"); + cached_global_panning_strength = GLOBAL_GET("audio/general/2d_panning_strength"); } AudioStreamPlayer2D::~AudioStreamPlayer2D() { diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 616d7fdb60..5bc9083488 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -82,7 +82,7 @@ private: float attenuation = 1.0; float panning_strength = 1.0f; - float cached_global_panning_strength = 1.0f; + float cached_global_panning_strength = 0.5f; protected: void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index a11b2b66bf..4b31bbddac 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -155,11 +155,11 @@ Transform2D Camera2D::get_camera_transform() { } } - if (smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { - real_t c = smoothing * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); + if (follow_smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { + real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos; ret_camera_pos = smoothed_camera_pos; - //camera_pos=camera_pos*(1.0-smoothing)+new_camera_pos*smoothing; + //camera_pos=camera_pos*(1.0-position_smoothing_speed)+new_camera_pos*position_smoothing_speed; } else { ret_camera_pos = smoothed_camera_pos = camera_pos; } @@ -171,14 +171,19 @@ Transform2D Camera2D::get_camera_transform() { Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom_scale) : Point2()); - real_t angle = get_global_rotation(); - if (rotating) { - screen_offset = screen_offset.rotated(angle); + if (!ignore_rotation) { + if (rotation_smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { + real_t step = rotation_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); + camera_angle = Math::lerp_angle(camera_angle, get_global_rotation(), step); + } else { + camera_angle = get_global_rotation(); + } + screen_offset = screen_offset.rotated(camera_angle); } Rect2 screen_rect(-screen_offset + ret_camera_pos, screen_size * zoom_scale); - if (!smoothing_enabled || !limit_smoothing_enabled) { + if (!follow_smoothing_enabled || !limit_smoothing_enabled) { if (screen_rect.position.x < limit[SIDE_LEFT]) { screen_rect.position.x = limit[SIDE_LEFT]; } @@ -204,8 +209,8 @@ Transform2D Camera2D::get_camera_transform() { Transform2D xform; xform.scale_basis(zoom_scale); - if (rotating) { - xform.set_rotation(angle); + if (!ignore_rotation) { + xform.set_rotation(camera_angle); } xform.set_origin(screen_rect.position); @@ -363,15 +368,21 @@ Camera2D::AnchorMode Camera2D::get_anchor_mode() const { return anchor_mode; } -void Camera2D::set_rotating(bool p_rotating) { - rotating = p_rotating; +void Camera2D::set_ignore_rotation(bool p_ignore) { + ignore_rotation = p_ignore; Point2 old_smoothed_camera_pos = smoothed_camera_pos; + + // Reset back to zero so it matches the camera rotation when ignore_rotation is enabled. + if (ignore_rotation) { + camera_angle = 0.0; + } + _update_scroll(); smoothed_camera_pos = old_smoothed_camera_pos; } -bool Camera2D::is_rotating() const { - return rotating; +bool Camera2D::is_ignoring_rotation() const { + return ignore_rotation; } void Camera2D::set_process_callback(Camera2DProcessCallback p_mode) { @@ -415,6 +426,14 @@ void Camera2D::set_current(bool p_current) { } } +void Camera2D::_update_process_internal_for_smoothing() { + bool is_not_in_scene_or_editor = !(is_inside_tree() && Engine::get_singleton()->is_editor_hint()); + bool is_any_smoothing_valid = position_smoothing_speed > 0 || rotation_smoothing_speed > 0; + + bool enabled = is_any_smoothing_valid && is_not_in_scene_or_editor; + set_process_internal(enabled); +} + bool Camera2D::is_current() const { return current; } @@ -506,17 +525,31 @@ void Camera2D::align() { _update_scroll(); } -void Camera2D::set_follow_smoothing(real_t p_speed) { - smoothing = p_speed; - if (smoothing > 0 && !(is_inside_tree() && Engine::get_singleton()->is_editor_hint())) { - set_process_internal(true); - } else { - set_process_internal(false); - } +void Camera2D::set_position_smoothing_speed(real_t p_speed) { + position_smoothing_speed = p_speed; + _update_process_internal_for_smoothing(); +} + +real_t Camera2D::get_position_smoothing_speed() const { + return position_smoothing_speed; +} + +void Camera2D::set_rotation_smoothing_speed(real_t p_speed) { + rotation_smoothing_speed = p_speed; + _update_process_internal_for_smoothing(); } -real_t Camera2D::get_follow_smoothing() const { - return smoothing; +real_t Camera2D::get_rotation_smoothing_speed() const { + return rotation_smoothing_speed; +} + +void Camera2D::set_rotation_smoothing_enabled(bool p_enabled) { + rotation_smoothing_enabled = p_enabled; + notify_property_list_changed(); +} + +bool Camera2D::is_rotation_smoothing_enabled() const { + return rotation_smoothing_enabled; } Point2 Camera2D::get_camera_screen_center() const { @@ -526,7 +559,7 @@ Point2 Camera2D::get_camera_screen_center() const { Size2 Camera2D::_get_camera_screen_size() const { // special case if the camera2D is in the root viewport if (Engine::get_singleton()->is_editor_hint() && get_viewport()->get_parent_viewport() == get_tree()->get_root()) { - return Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); + return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); } return get_viewport_rect().size; } @@ -574,18 +607,18 @@ real_t Camera2D::get_drag_horizontal_offset() const { void Camera2D::_set_old_smoothing(real_t p_enable) { //compatibility if (p_enable > 0) { - smoothing_enabled = true; - set_follow_smoothing(p_enable); + follow_smoothing_enabled = true; + set_position_smoothing_speed(p_enable); } } -void Camera2D::set_enable_follow_smoothing(bool p_enabled) { - smoothing_enabled = p_enabled; +void Camera2D::set_position_smoothing_enabled(bool p_enabled) { + follow_smoothing_enabled = p_enabled; notify_property_list_changed(); } -bool Camera2D::is_follow_smoothing_enabled() const { - return smoothing_enabled; +bool Camera2D::is_position_smoothing_enabled() const { + return follow_smoothing_enabled; } void Camera2D::set_custom_viewport(Node *p_viewport) { @@ -656,7 +689,10 @@ bool Camera2D::is_margin_drawing_enabled() const { } void Camera2D::_validate_property(PropertyInfo &p_property) const { - if (!smoothing_enabled && p_property.name == "smoothing_speed") { + if (!follow_smoothing_enabled && p_property.name == "smoothing_speed") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } + if (!rotation_smoothing_enabled && p_property.name == "rotation_smoothing_speed") { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } } @@ -668,8 +704,8 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_anchor_mode", "anchor_mode"), &Camera2D::set_anchor_mode); ClassDB::bind_method(D_METHOD("get_anchor_mode"), &Camera2D::get_anchor_mode); - ClassDB::bind_method(D_METHOD("set_rotating", "rotating"), &Camera2D::set_rotating); - ClassDB::bind_method(D_METHOD("is_rotating"), &Camera2D::is_rotating); + ClassDB::bind_method(D_METHOD("set_ignore_rotation", "ignore"), &Camera2D::set_ignore_rotation); + ClassDB::bind_method(D_METHOD("is_ignoring_rotation"), &Camera2D::is_ignoring_rotation); ClassDB::bind_method(D_METHOD("_update_scroll"), &Camera2D::_update_scroll); @@ -710,11 +746,17 @@ void Camera2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_custom_viewport", "viewport"), &Camera2D::set_custom_viewport); ClassDB::bind_method(D_METHOD("get_custom_viewport"), &Camera2D::get_custom_viewport); - ClassDB::bind_method(D_METHOD("set_follow_smoothing", "follow_smoothing"), &Camera2D::set_follow_smoothing); - ClassDB::bind_method(D_METHOD("get_follow_smoothing"), &Camera2D::get_follow_smoothing); + ClassDB::bind_method(D_METHOD("set_position_smoothing_speed", "position_smoothing_speed"), &Camera2D::set_position_smoothing_speed); + ClassDB::bind_method(D_METHOD("get_position_smoothing_speed"), &Camera2D::get_position_smoothing_speed); - ClassDB::bind_method(D_METHOD("set_enable_follow_smoothing", "follow_smoothing"), &Camera2D::set_enable_follow_smoothing); - ClassDB::bind_method(D_METHOD("is_follow_smoothing_enabled"), &Camera2D::is_follow_smoothing_enabled); + ClassDB::bind_method(D_METHOD("set_position_smoothing_enabled", "position_smoothing_speed"), &Camera2D::set_position_smoothing_enabled); + ClassDB::bind_method(D_METHOD("is_position_smoothing_enabled"), &Camera2D::is_position_smoothing_enabled); + + ClassDB::bind_method(D_METHOD("set_rotation_smoothing_enabled", "enabled"), &Camera2D::set_rotation_smoothing_enabled); + ClassDB::bind_method(D_METHOD("is_rotation_smoothing_enabled"), &Camera2D::is_rotation_smoothing_enabled); + + ClassDB::bind_method(D_METHOD("set_rotation_smoothing_speed", "speed"), &Camera2D::set_rotation_smoothing_speed); + ClassDB::bind_method(D_METHOD("get_rotation_smoothing_speed"), &Camera2D::get_rotation_smoothing_speed); ClassDB::bind_method(D_METHOD("force_update_scroll"), &Camera2D::force_update_scroll); ClassDB::bind_method(D_METHOD("reset_smoothing"), &Camera2D::reset_smoothing); @@ -733,7 +775,7 @@ void Camera2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_mode", PROPERTY_HINT_ENUM, "Fixed TopLeft,Drag Center"), "set_anchor_mode", "get_anchor_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotating"), "set_rotating", "is_rotating"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_rotation"), "set_ignore_rotation", "is_ignoring_rotation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "zoom", PROPERTY_HINT_LINK), "set_zoom", "get_zoom"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", PROPERTY_USAGE_NONE), "set_custom_viewport", "get_custom_viewport"); @@ -746,9 +788,13 @@ void Camera2D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_bottom", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_BOTTOM); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "limit_smoothed"), "set_limit_smoothing_enabled", "is_limit_smoothing_enabled"); - ADD_GROUP("Smoothing", "smoothing_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smoothing_enabled"), "set_enable_follow_smoothing", "is_follow_smoothing_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "smoothing_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_follow_smoothing", "get_follow_smoothing"); + ADD_GROUP("Follow Smoothing", "follow_smoothing_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "position_smoothing_enabled"), "set_position_smoothing_enabled", "is_position_smoothing_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "position_smoothing_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_position_smoothing_speed", "get_position_smoothing_speed"); + + ADD_GROUP("Rotation Smoothing", "rotation_smoothing_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotation_smoothing_enabled"), "set_rotation_smoothing_enabled", "is_rotation_smoothing_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_smoothing_speed"), "set_rotation_smoothing_speed", "get_rotation_smoothing_speed"); ADD_GROUP("Drag", "drag_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_horizontal_enabled"), "set_drag_horizontal_enabled", "is_drag_horizontal_enabled"); diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 78654ee606..ae172f47b1 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -63,10 +63,15 @@ protected: Vector2 zoom = Vector2(1, 1); Vector2 zoom_scale = Vector2(1, 1); AnchorMode anchor_mode = ANCHOR_MODE_DRAG_CENTER; - bool rotating = false; + bool ignore_rotation = true; bool current = false; - real_t smoothing = 5.0; - bool smoothing_enabled = false; + real_t position_smoothing_speed = 5.0; + bool follow_smoothing_enabled = false; + + real_t camera_angle = 0.0; + real_t rotation_smoothing_speed = 5.0; + bool rotation_smoothing_enabled = false; + int limit[4]; bool limit_smoothing_enabled = false; @@ -87,6 +92,8 @@ protected: void _set_old_smoothing(real_t p_enable); + void _update_process_internal_for_smoothing(); + bool screen_drawing_enabled = true; bool limit_drawing_enabled = false; bool margin_drawing_enabled = false; @@ -109,8 +116,8 @@ public: void set_anchor_mode(AnchorMode p_anchor_mode); AnchorMode get_anchor_mode() const; - void set_rotating(bool p_rotating); - bool is_rotating() const; + void set_ignore_rotation(bool p_ignore); + bool is_ignoring_rotation() const; void set_limit(Side p_side, int p_limit); int get_limit(Side p_side) const; @@ -133,11 +140,17 @@ public: void set_drag_vertical_offset(real_t p_offset); real_t get_drag_vertical_offset() const; - void set_enable_follow_smoothing(bool p_enabled); - bool is_follow_smoothing_enabled() const; + void set_position_smoothing_enabled(bool p_enabled); + bool is_position_smoothing_enabled() const; + + void set_position_smoothing_speed(real_t p_speed); + real_t get_position_smoothing_speed() const; + + void set_rotation_smoothing_speed(real_t p_speed); + real_t get_rotation_smoothing_speed() const; - void set_follow_smoothing(real_t p_speed); - real_t get_follow_smoothing() const; + void set_rotation_smoothing_enabled(bool p_enabled); + bool is_rotation_smoothing_enabled() const; void set_process_callback(Camera2DProcessCallback p_mode); Camera2DProcessCallback get_process_callback() const; diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 61a17a4845..330afe4a1b 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -78,8 +78,8 @@ Color CanvasModulate::get_color() const { return color; } -TypedArray<String> CanvasModulate::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray CanvasModulate::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { List<Node *> nodes; diff --git a/scene/2d/canvas_modulate.h b/scene/2d/canvas_modulate.h index 1fd54898f8..4f522ca1c7 100644 --- a/scene/2d/canvas_modulate.h +++ b/scene/2d/canvas_modulate.h @@ -46,7 +46,7 @@ public: void set_color(const Color &p_color); Color get_color() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CanvasModulate(); ~CanvasModulate(); diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index a79c81e8bd..df75d34e0f 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -36,12 +36,12 @@ void CollisionObject2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - Transform2D global_transform = get_global_transform(); + Transform2D gl_transform = get_global_transform(); if (area) { - PhysicsServer2D::get_singleton()->area_set_transform(rid, global_transform); + PhysicsServer2D::get_singleton()->area_set_transform(rid, gl_transform); } else { - PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, global_transform); + PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, gl_transform); } bool disabled = !is_enabled(); @@ -81,12 +81,12 @@ void CollisionObject2D::_notification(int p_what) { return; } - Transform2D global_transform = get_global_transform(); + Transform2D gl_transform = get_global_transform(); if (area) { - PhysicsServer2D::get_singleton()->area_set_transform(rid, global_transform); + PhysicsServer2D::get_singleton()->area_set_transform(rid, gl_transform); } else { - PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, global_transform); + PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, gl_transform); } } break; @@ -153,13 +153,13 @@ uint32_t CollisionObject2D::get_collision_mask() const { void CollisionObject2D::set_collision_layer_value(int p_layer_number, bool p_value) { ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); - uint32_t collision_layer = get_collision_layer(); + uint32_t collision_layer_new = get_collision_layer(); if (p_value) { - collision_layer |= 1 << (p_layer_number - 1); + collision_layer_new |= 1 << (p_layer_number - 1); } else { - collision_layer &= ~(1 << (p_layer_number - 1)); + collision_layer_new &= ~(1 << (p_layer_number - 1)); } - set_collision_layer(collision_layer); + set_collision_layer(collision_layer_new); } bool CollisionObject2D::get_collision_layer_value(int p_layer_number) const { @@ -565,8 +565,8 @@ void CollisionObject2D::_update_pickable() { } } -TypedArray<String> CollisionObject2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionObject2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape.")); diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 48ea59e040..6b778d1b60 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -157,7 +157,7 @@ public: void set_pickable(bool p_enabled); bool is_pickable() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; _FORCE_INLINE_ RID get_rid() const { return rid; } diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index b69b19d30d..d06461b566 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -235,8 +235,8 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl } #endif -TypedArray<String> CollisionPolygon2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionPolygon2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); diff --git a/scene/2d/collision_polygon_2d.h b/scene/2d/collision_polygon_2d.h index e18022ab7e..066f7271c6 100644 --- a/scene/2d/collision_polygon_2d.h +++ b/scene/2d/collision_polygon_2d.h @@ -77,7 +77,7 @@ public: void set_polygon(const Vector<Point2> &p_polygon); Vector<Point2> get_polygon() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_disabled(bool p_disabled); bool is_disabled() const; diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index 039bfee451..5fe05c4ddd 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -49,6 +49,11 @@ void CollisionShape2D::_update_in_shape_owner(bool p_xform_only) { parent->shape_owner_set_one_way_collision_margin(owner_id, one_way_collision_margin); } +Color CollisionShape2D::_get_default_debug_color() const { + SceneTree *st = SceneTree::get_singleton(); + return st ? st->get_debug_collisions_color() : Color(); +} + void CollisionShape2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_PARENTED: { @@ -95,7 +100,7 @@ void CollisionShape2D::_notification(int p_what) { rect = Rect2(); - Color draw_col = get_tree()->get_debug_collisions_color(); + Color draw_col = debug_color; if (disabled) { float g = draw_col.get_v(); draw_col.r = g; @@ -110,7 +115,7 @@ void CollisionShape2D::_notification(int p_what) { if (one_way_collision) { // Draw an arrow indicating the one-way collision direction - draw_col = get_tree()->get_debug_collisions_color().inverted(); + draw_col = debug_color.inverted(); if (disabled) { draw_col = draw_col.darkened(0.25); } @@ -168,8 +173,8 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double return shape->_edit_is_selected_on_click(p_point, p_tolerance); } -TypedArray<String> CollisionShape2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionShape2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { warnings.push_back(RTR("CollisionShape2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); @@ -226,6 +231,40 @@ real_t CollisionShape2D::get_one_way_collision_margin() const { return one_way_collision_margin; } +void CollisionShape2D::set_debug_color(const Color &p_color) { + debug_color = p_color; + queue_redraw(); +} + +Color CollisionShape2D::get_debug_color() const { + return debug_color; +} + +bool CollisionShape2D::_property_can_revert(const StringName &p_name) const { + if (p_name == "debug_color") { + return true; + } + return false; +} + +bool CollisionShape2D::_property_get_revert(const StringName &p_name, Variant &r_property) const { + if (p_name == "debug_color") { + r_property = _get_default_debug_color(); + return true; + } + return false; +} + +void CollisionShape2D::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "debug_color") { + if (debug_color == _get_default_debug_color()) { + p_property.usage = PROPERTY_USAGE_DEFAULT & ~PROPERTY_USAGE_STORAGE; + } else { + p_property.usage = PROPERTY_USAGE_DEFAULT; + } + } +} + void CollisionShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shape", "shape"), &CollisionShape2D::set_shape); ClassDB::bind_method(D_METHOD("get_shape"), &CollisionShape2D::get_shape); @@ -235,13 +274,19 @@ void CollisionShape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_one_way_collision_enabled"), &CollisionShape2D::is_one_way_collision_enabled); ClassDB::bind_method(D_METHOD("set_one_way_collision_margin", "margin"), &CollisionShape2D::set_one_way_collision_margin); ClassDB::bind_method(D_METHOD("get_one_way_collision_margin"), &CollisionShape2D::get_one_way_collision_margin); + ClassDB::bind_method(D_METHOD("set_debug_color", "color"), &CollisionShape2D::set_debug_color); + ClassDB::bind_method(D_METHOD("get_debug_color"), &CollisionShape2D::get_debug_color); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D"), "set_shape", "get_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_way_collision"), "set_one_way_collision", "is_one_way_collision_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "one_way_collision_margin", PROPERTY_HINT_RANGE, "0,128,0.1,suffix:px"), "set_one_way_collision_margin", "get_one_way_collision_margin"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_color"), "set_debug_color", "get_debug_color"); + // Default value depends on a project setting, override for doc generation purposes. + ADD_PROPERTY_DEFAULT("debug_color", Color()); } CollisionShape2D::CollisionShape2D() { set_notify_local_transform(true); + debug_color = _get_default_debug_color(); } diff --git a/scene/2d/collision_shape_2d.h b/scene/2d/collision_shape_2d.h index dbc81e8424..b0b8a7cb0f 100644 --- a/scene/2d/collision_shape_2d.h +++ b/scene/2d/collision_shape_2d.h @@ -42,15 +42,20 @@ class CollisionShape2D : public Node2D { Rect2 rect = Rect2(-Point2(10, 10), Point2(20, 20)); uint32_t owner_id = 0; CollisionObject2D *parent = nullptr; - void _shape_changed(); bool disabled = false; bool one_way_collision = false; real_t one_way_collision_margin = 1.0; + Color debug_color; + void _shape_changed(); void _update_in_shape_owner(bool p_xform_only = false); + Color _get_default_debug_color() const; protected: void _notification(int p_what); + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); public: @@ -72,7 +77,10 @@ public: void set_one_way_collision_margin(real_t p_margin); real_t get_one_way_collision_margin() const; - TypedArray<String> get_configuration_warnings() const override; + void set_debug_color(const Color &p_color); + Color get_debug_color() const; + + PackedStringArray get_configuration_warnings() const override; CollisionShape2D(); }; diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 4523e5dfe9..c6296f4732 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -242,8 +242,8 @@ bool CPUParticles2D::get_fractional_delta() const { return fractional_delta; } -TypedArray<String> CPUParticles2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node2D::get_configuration_warnings(); +PackedStringArray CPUParticles2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); CanvasItemMaterial *mat = Object::cast_to<CanvasItemMaterial>(get_material().ptr()); @@ -1162,74 +1162,73 @@ void CPUParticles2D::_notification(int p_what) { } void CPUParticles2D::convert_from_particles(Node *p_particles) { - GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(p_particles); - ERR_FAIL_COND_MSG(!particles, "Only GPUParticles2D nodes can be converted to CPUParticles2D."); - - set_emitting(particles->is_emitting()); - set_amount(particles->get_amount()); - set_lifetime(particles->get_lifetime()); - set_one_shot(particles->get_one_shot()); - set_pre_process_time(particles->get_pre_process_time()); - set_explosiveness_ratio(particles->get_explosiveness_ratio()); - set_randomness_ratio(particles->get_randomness_ratio()); - set_use_local_coordinates(particles->get_use_local_coordinates()); - set_fixed_fps(particles->get_fixed_fps()); - set_fractional_delta(particles->get_fractional_delta()); - set_speed_scale(particles->get_speed_scale()); - set_draw_order(DrawOrder(particles->get_draw_order())); - set_texture(particles->get_texture()); - - Ref<Material> mat = particles->get_material(); + GPUParticles2D *gpu_particles = Object::cast_to<GPUParticles2D>(p_particles); + ERR_FAIL_COND_MSG(!gpu_particles, "Only GPUParticles2D nodes can be converted to CPUParticles2D."); + + set_emitting(gpu_particles->is_emitting()); + set_amount(gpu_particles->get_amount()); + set_lifetime(gpu_particles->get_lifetime()); + set_one_shot(gpu_particles->get_one_shot()); + set_pre_process_time(gpu_particles->get_pre_process_time()); + set_explosiveness_ratio(gpu_particles->get_explosiveness_ratio()); + set_randomness_ratio(gpu_particles->get_randomness_ratio()); + set_use_local_coordinates(gpu_particles->get_use_local_coordinates()); + set_fixed_fps(gpu_particles->get_fixed_fps()); + set_fractional_delta(gpu_particles->get_fractional_delta()); + set_speed_scale(gpu_particles->get_speed_scale()); + set_draw_order(DrawOrder(gpu_particles->get_draw_order())); + set_texture(gpu_particles->get_texture()); + + Ref<Material> mat = gpu_particles->get_material(); if (mat.is_valid()) { set_material(mat); } - Ref<ParticleProcessMaterial> material = particles->get_process_material(); - if (material.is_null()) { + Ref<ParticleProcessMaterial> proc_mat = gpu_particles->get_process_material(); + if (proc_mat.is_null()) { return; } - Vector3 dir = material->get_direction(); + Vector3 dir = proc_mat->get_direction(); set_direction(Vector2(dir.x, dir.y)); - set_spread(material->get_spread()); + set_spread(proc_mat->get_spread()); - set_color(material->get_color()); + set_color(proc_mat->get_color()); - Ref<GradientTexture1D> gt = material->get_color_ramp(); + Ref<GradientTexture1D> gt = proc_mat->get_color_ramp(); if (gt.is_valid()) { set_color_ramp(gt->get_gradient()); } - Ref<GradientTexture1D> gti = material->get_color_initial_ramp(); + Ref<GradientTexture1D> gti = proc_mat->get_color_initial_ramp(); if (gti.is_valid()) { set_color_initial_ramp(gti->get_gradient()); } - set_particle_flag(PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, material->get_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY)); + set_particle_flag(PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY, proc_mat->get_particle_flag(ParticleProcessMaterial::PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY)); - set_emission_shape(EmissionShape(material->get_emission_shape())); - set_emission_sphere_radius(material->get_emission_sphere_radius()); - Vector2 rect_extents = Vector2(material->get_emission_box_extents().x, material->get_emission_box_extents().y); + set_emission_shape(EmissionShape(proc_mat->get_emission_shape())); + set_emission_sphere_radius(proc_mat->get_emission_sphere_radius()); + Vector2 rect_extents = Vector2(proc_mat->get_emission_box_extents().x, proc_mat->get_emission_box_extents().y); set_emission_rect_extents(rect_extents); - Ref<CurveXYZTexture> scale3D = material->get_param_texture(ParticleProcessMaterial::PARAM_SCALE); + Ref<CurveXYZTexture> scale3D = proc_mat->get_param_texture(ParticleProcessMaterial::PARAM_SCALE); if (scale3D.is_valid()) { split_scale = true; scale_curve_x = scale3D->get_curve_x(); scale_curve_y = scale3D->get_curve_y(); } - Vector2 gravity = Vector2(material->get_gravity().x, material->get_gravity().y); - set_gravity(gravity); - set_lifetime_randomness(material->get_lifetime_randomness()); + set_gravity(Vector2(proc_mat->get_gravity().x, proc_mat->get_gravity().y)); + set_lifetime_randomness(proc_mat->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ - set_param_min(m_param, material->get_param_min(ParticleProcessMaterial::m_param)); \ + set_param_min(m_param, proc_mat->get_param_min(ParticleProcessMaterial::m_param)); \ { \ - Ref<CurveTexture> ctex = material->get_param_texture(ParticleProcessMaterial::m_param); \ + Ref<CurveTexture> ctex = proc_mat->get_param_texture(ParticleProcessMaterial::m_param); \ if (ctex.is_valid()) \ set_param_curve(m_param, ctex->get_curve()); \ } \ - set_param_max(m_param, material->get_param_max(ParticleProcessMaterial::m_param)); + set_param_max(m_param, proc_mat->get_param_max(ParticleProcessMaterial::m_param)); CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 3fd1c7fd0f..141e5f9139 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -152,8 +152,8 @@ private: Vector2 direction = Vector2(1, 0); real_t spread = 45.0; - real_t parameters_min[PARAM_MAX]; - real_t parameters_max[PARAM_MAX]; + real_t parameters_min[PARAM_MAX] = {}; + real_t parameters_max[PARAM_MAX] = {}; Ref<Curve> curve_parameters[PARAM_MAX]; Color color; @@ -282,7 +282,7 @@ public: void set_gravity(const Vector2 &p_gravity); Vector2 get_gravity() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void restart(); diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index bed68b4ee0..c8f5d7f5a6 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -141,16 +141,16 @@ void GPUParticles2D::set_process_material(const Ref<Material> &p_material) { void GPUParticles2D::set_trail_enabled(bool p_enabled) { trail_enabled = p_enabled; - RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); + RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime); queue_redraw(); RS::get_singleton()->particles_set_transform_align(particles, p_enabled ? RS::PARTICLES_TRANSFORM_ALIGN_Y_TO_VELOCITY : RS::PARTICLES_TRANSFORM_ALIGN_DISABLED); } -void GPUParticles2D::set_trail_length(double p_seconds) { +void GPUParticles2D::set_trail_lifetime(double p_seconds) { ERR_FAIL_COND(p_seconds < 0.001); - trail_length = p_seconds; - RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); + trail_lifetime = p_seconds; + RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime); queue_redraw(); } @@ -181,8 +181,8 @@ bool GPUParticles2D::is_trail_enabled() const { return trail_enabled; } -double GPUParticles2D::get_trail_length() const { - return trail_length; +double GPUParticles2D::get_trail_lifetime() const { + return trail_lifetime; } void GPUParticles2D::_update_collision_size() { @@ -296,12 +296,8 @@ bool GPUParticles2D::get_interpolate() const { return interpolate; } -TypedArray<String> GPUParticles2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node2D::get_configuration_warnings(); - - if (RenderingServer::get_singleton()->is_low_end()) { - warnings.push_back(RTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles2D\" option for this purpose.")); - } +PackedStringArray GPUParticles2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (process_material.is_null()) { warnings.push_back(RTR("A material to process the particles is not assigned, so no behavior is imprinted.")); @@ -353,13 +349,13 @@ void GPUParticles2D::_validate_property(PropertyInfo &p_property) const { } void GPUParticles2D::emit_particle(const Transform2D &p_transform2d, const Vector2 &p_velocity2d, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) { - Transform3D transform; - transform.basis.set_column(0, Vector3(p_transform2d.columns[0].x, p_transform2d.columns[0].y, 0)); - transform.basis.set_column(1, Vector3(p_transform2d.columns[1].x, p_transform2d.columns[1].y, 0)); - transform.set_origin(Vector3(p_transform2d.get_origin().x, p_transform2d.get_origin().y, 0)); + Transform3D emit_transform; + emit_transform.basis.set_column(0, Vector3(p_transform2d.columns[0].x, p_transform2d.columns[0].y, 0)); + emit_transform.basis.set_column(1, Vector3(p_transform2d.columns[1].x, p_transform2d.columns[1].y, 0)); + emit_transform.set_origin(Vector3(p_transform2d.get_origin().x, p_transform2d.get_origin().y, 0)); Vector3 velocity = Vector3(p_velocity2d.x, p_velocity2d.y, 0); - RS::get_singleton()->particles_emit(particles, transform, velocity, p_color, p_custom, p_emit_flags); + RS::get_singleton()->particles_emit(particles, emit_transform, velocity, p_color, p_custom, p_emit_flags); } void GPUParticles2D::_attach_sub_emitter() { @@ -614,10 +610,10 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("emit_particle", "xform", "velocity", "color", "custom", "flags"), &GPUParticles2D::emit_particle); ClassDB::bind_method(D_METHOD("set_trail_enabled", "enabled"), &GPUParticles2D::set_trail_enabled); - ClassDB::bind_method(D_METHOD("set_trail_length", "secs"), &GPUParticles2D::set_trail_length); + ClassDB::bind_method(D_METHOD("set_trail_lifetime", "secs"), &GPUParticles2D::set_trail_lifetime); ClassDB::bind_method(D_METHOD("is_trail_enabled"), &GPUParticles2D::is_trail_enabled); - ClassDB::bind_method(D_METHOD("get_trail_length"), &GPUParticles2D::get_trail_length); + ClassDB::bind_method(D_METHOD("get_trail_lifetime"), &GPUParticles2D::get_trail_lifetime); ClassDB::bind_method(D_METHOD("set_trail_sections", "sections"), &GPUParticles2D::set_trail_sections); ClassDB::bind_method(D_METHOD("get_trail_sections"), &GPUParticles2D::get_trail_sections); @@ -647,7 +643,7 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime,Reverse Lifetime"), "set_draw_order", "get_draw_order"); ADD_GROUP("Trails", "trail_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled"), "set_trail_enabled", "is_trail_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_length_secs", PROPERTY_HINT_RANGE, "0.01,10,0.01,suffix:s"), "set_trail_length", "get_trail_length"); + 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", "process_"); diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index 10ae91775f..94555e0bb0 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -64,7 +64,7 @@ private: #endif Ref<Material> process_material; - DrawOrder draw_order; + DrawOrder draw_order = DRAW_ORDER_LIFETIME; Ref<Texture2D> texture; @@ -74,7 +74,7 @@ private: real_t collision_base_size = 1.0; bool trail_enabled = false; - double trail_length = 0.3; + double trail_lifetime = 0.3; int trail_sections = 8; int trail_section_subdivisions = 4; @@ -104,7 +104,7 @@ public: void set_speed_scale(double p_scale); void set_collision_base_size(real_t p_ratio); void set_trail_enabled(bool p_enabled); - void set_trail_length(double p_seconds); + void set_trail_lifetime(double p_seconds); void set_trail_sections(int p_sections); void set_trail_section_subdivisions(int p_subdivisions); @@ -126,7 +126,7 @@ public: real_t get_collision_base_size() const; bool is_trail_enabled() const; - double get_trail_length() const; + double get_trail_lifetime() const; int get_trail_sections() const; int get_trail_section_subdivisions() const; @@ -145,7 +145,7 @@ public: void set_texture(const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_sub_emitter(const NodePath &p_path); NodePath get_sub_emitter() const; diff --git a/scene/2d/joint_2d.cpp b/scene/2d/joint_2d.cpp index 89b6f3f9da..8de4c281f4 100644 --- a/scene/2d/joint_2d.cpp +++ b/scene/2d/joint_2d.cpp @@ -133,7 +133,13 @@ void Joint2D::set_node_a(const NodePath &p_node_a) { } a = p_node_a; - _update_joint(); + if (Engine::get_singleton()->is_editor_hint()) { + // When in editor, the setter may be called as a result of node rename. + // It happens before the node actually changes its name, which triggers false warning. + callable_mp(this, &Joint2D::_update_joint).call_deferred(); + } else { + _update_joint(); + } } NodePath Joint2D::get_node_a() const { @@ -150,7 +156,11 @@ void Joint2D::set_node_b(const NodePath &p_node_b) { } b = p_node_b; - _update_joint(); + if (Engine::get_singleton()->is_editor_hint()) { + callable_mp(this, &Joint2D::_update_joint).call_deferred(); + } else { + _update_joint(); + } } NodePath Joint2D::get_node_b() const { @@ -202,8 +212,8 @@ bool Joint2D::get_exclude_nodes_from_collision() const { return exclude_from_collision; } -TypedArray<String> Joint2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node2D::get_configuration_warnings(); +PackedStringArray Joint2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!warning.is_empty()) { warnings.push_back(warning); diff --git a/scene/2d/joint_2d.h b/scene/2d/joint_2d.h index e3cd600cbd..8b145be6ae 100644 --- a/scene/2d/joint_2d.h +++ b/scene/2d/joint_2d.h @@ -62,7 +62,7 @@ protected: _FORCE_INLINE_ bool is_configured() const { return configured; } public: - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_node_a(const NodePath &p_node_a); NodePath get_node_a() const; diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 7eb6b43af7..78b5199358 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -172,6 +172,7 @@ void Light2D::set_shadow_filter(ShadowFilter p_filter) { ERR_FAIL_INDEX(p_filter, SHADOW_FILTER_MAX); shadow_filter = p_filter; RS::get_singleton()->canvas_light_set_shadow_filter(canvas_light, RS::CanvasLightShadowFilter(p_filter)); + notify_property_list_changed(); } Light2D::ShadowFilter Light2D::get_shadow_filter() const { @@ -231,6 +232,10 @@ void Light2D::_validate_property(PropertyInfo &p_property) const { if (!shadow && (p_property.name == "shadow_color" || p_property.name == "shadow_filter" || p_property.name == "shadow_filter_smooth" || p_property.name == "shadow_item_cull_mask")) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; } + + if (shadow && p_property.name == "shadow_filter_smooth" && shadow_filter == SHADOW_FILTER_NONE) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } void Light2D::_bind_methods() { @@ -395,8 +400,8 @@ Vector2 PointLight2D::get_texture_offset() const { return texture_offset; } -TypedArray<String> PointLight2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray PointLight2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!texture.is_valid()) { warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property.")); @@ -454,7 +459,7 @@ void DirectionalLight2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &DirectionalLight2D::set_max_distance); ClassDB::bind_method(D_METHOD("get_max_distance"), &DirectionalLight2D::get_max_distance); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.01,suffix:px"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,16384.0,1.0,or_greater,suffix:px"), "set_max_distance", "get_max_distance"); } diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 373cfe59fd..29870923aa 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -171,7 +171,7 @@ public: void set_texture_scale(real_t p_scale); real_t get_texture_scale() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; PointLight2D(); }; diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 6c171383ca..67e82140e4 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -246,8 +246,8 @@ int LightOccluder2D::get_occluder_light_mask() const { return mask; } -TypedArray<String> LightOccluder2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray LightOccluder2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!occluder_polygon.is_valid()) { warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect.")); diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index b61e23464a..ee4d87e54b 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -105,7 +105,7 @@ public: void set_as_sdf_collision(bool p_enable); bool is_set_as_sdf_collision() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; LightOccluder2D(); ~LightOccluder2D(); diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp index d7f75c63a4..1a62c9bb6c 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -75,6 +75,7 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_location", "location"), &NavigationAgent2D::set_target_location); ClassDB::bind_method(D_METHOD("get_target_location"), &NavigationAgent2D::get_target_location); + ClassDB::bind_method(D_METHOD("get_next_location"), &NavigationAgent2D::get_next_location); ClassDB::bind_method(D_METHOD("distance_to_target"), &NavigationAgent2D::distance_to_target); ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationAgent2D::set_velocity); @@ -88,6 +89,7 @@ void NavigationAgent2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_avoidance_done", "new_velocity"), &NavigationAgent2D::_avoidance_done); ADD_GROUP("Pathfinding", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "target_location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_target_location", "get_target_location"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:px"), "set_path_desired_distance", "get_path_desired_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:px"), "set_target_desired_distance", "get_target_desired_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_max_distance", PROPERTY_HINT_RANGE, "10,100,1,suffix:px"), "set_path_max_distance", "get_path_max_distance"); @@ -104,7 +106,7 @@ void NavigationAgent2D::_bind_methods() { ADD_SIGNAL(MethodInfo("path_changed")); ADD_SIGNAL(MethodInfo("target_reached")); ADD_SIGNAL(MethodInfo("navigation_finished")); - ADD_SIGNAL(MethodInfo("velocity_computed", PropertyInfo(Variant::VECTOR3, "safe_velocity"))); + ADD_SIGNAL(MethodInfo("velocity_computed", PropertyInfo(Variant::VECTOR2, "safe_velocity"))); } void NavigationAgent2D::_notification(int p_what) { @@ -178,6 +180,13 @@ NavigationAgent2D::NavigationAgent2D() { set_time_horizon(20.0); set_radius(10.0); set_max_speed(200.0); + + // Preallocate query and result objects to improve performance. + navigation_query = Ref<NavigationPathQueryParameters2D>(); + navigation_query.instantiate(); + + navigation_result = Ref<NavigationPathQueryResult2D>(); + navigation_result.instantiate(); } NavigationAgent2D::~NavigationAgent2D() { @@ -314,6 +323,8 @@ Vector2 NavigationAgent2D::get_target_location() const { Vector2 NavigationAgent2D::get_next_location() { update_navigation(); + + const Vector<Vector2> &navigation_path = navigation_result->get_path(); if (navigation_path.size() == 0) { ERR_FAIL_COND_V_MSG(agent_parent == nullptr, Vector2(), "The agent has no parent."); return agent_parent->get_global_position(); @@ -322,6 +333,10 @@ Vector2 NavigationAgent2D::get_next_location() { } } +const Vector<Vector2> &NavigationAgent2D::get_nav_path() const { + return navigation_result->get_path(); +} + real_t NavigationAgent2D::distance_to_target() const { ERR_FAIL_COND_V_MSG(agent_parent == nullptr, 0.0, "The agent has no parent."); return agent_parent->get_global_position().distance_to(target_location); @@ -342,6 +357,8 @@ bool NavigationAgent2D::is_navigation_finished() { Vector2 NavigationAgent2D::get_final_location() { update_navigation(); + + const Vector<Vector2> &navigation_path = navigation_result->get_path(); if (navigation_path.size() == 0) { return Vector2(); } @@ -368,8 +385,8 @@ void NavigationAgent2D::_avoidance_done(Vector3 p_new_velocity) { emit_signal(SNAME("velocity_computed"), velocity); } -TypedArray<String> NavigationAgent2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationAgent2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node2D>(get_parent())) { warnings.push_back(RTR("The NavigationAgent2D can be used only under a Node2D inheriting parent node.")); @@ -391,22 +408,24 @@ void NavigationAgent2D::update_navigation() { update_frame_id = Engine::get_singleton()->get_physics_frames(); - Vector2 o = agent_parent->get_global_position(); + Vector2 origin = agent_parent->get_global_position(); bool reload_path = false; if (NavigationServer2D::get_singleton()->agent_is_map_changed(agent)) { reload_path = true; - } else if (navigation_path.size() == 0) { + } else if (navigation_result->get_path().size() == 0) { reload_path = true; } else { // Check if too far from the navigation path if (nav_path_index > 0) { + const Vector<Vector2> &navigation_path = navigation_result->get_path(); + Vector2 segment[2]; segment[0] = navigation_path[nav_path_index - 1]; segment[1] = navigation_path[nav_path_index]; - Vector2 p = Geometry2D::get_closest_point_to_segment(o, segment); - if (o.distance_to(p) >= path_max_distance) { + Vector2 p = Geometry2D::get_closest_point_to_segment(origin, segment); + if (origin.distance_to(p) >= path_max_distance) { // To faraway, reload path reload_path = true; } @@ -414,24 +433,31 @@ void NavigationAgent2D::update_navigation() { } if (reload_path) { + navigation_query->set_start_position(origin); + navigation_query->set_target_position(target_location); + navigation_query->set_navigation_layers(navigation_layers); + if (map_override.is_valid()) { - navigation_path = NavigationServer2D::get_singleton()->map_get_path(map_override, o, target_location, true, navigation_layers); + navigation_query->set_map(map_override); } else { - navigation_path = NavigationServer2D::get_singleton()->map_get_path(agent_parent->get_world_2d()->get_navigation_map(), o, target_location, true, navigation_layers); + navigation_query->set_map(agent_parent->get_world_2d()->get_navigation_map()); } + + NavigationServer2D::get_singleton()->query_path(navigation_query, navigation_result); navigation_finished = false; nav_path_index = 0; emit_signal(SNAME("path_changed")); } - if (navigation_path.size() == 0) { + if (navigation_result->get_path().size() == 0) { return; } // Check if we can advance the navigation path if (navigation_finished == false) { // Advances to the next far away location. - while (o.distance_to(navigation_path[nav_path_index]) < path_desired_distance) { + const Vector<Vector2> &navigation_path = navigation_result->get_path(); + while (origin.distance_to(navigation_path[nav_path_index]) < path_desired_distance) { nav_path_index += 1; if (nav_path_index == navigation_path.size()) { _check_distance_to_target(); @@ -445,7 +471,7 @@ void NavigationAgent2D::update_navigation() { } void NavigationAgent2D::_request_repath() { - navigation_path.clear(); + navigation_result->reset(); target_reached = false; navigation_finished = false; update_frame_id = 0; @@ -454,8 +480,8 @@ void NavigationAgent2D::_request_repath() { void NavigationAgent2D::_check_distance_to_target() { if (!target_reached) { if (distance_to_target() < target_desired_distance) { - emit_signal(SNAME("target_reached")); target_reached = true; + emit_signal(SNAME("target_reached")); } } } diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h index 11b845665d..5abd3c0317 100644 --- a/scene/2d/navigation_agent_2d.h +++ b/scene/2d/navigation_agent_2d.h @@ -34,6 +34,8 @@ #include "scene/main/node.h" class Node2D; +class NavigationPathQueryParameters2D; +class NavigationPathQueryResult2D; class NavigationAgent2D : public Node { GDCLASS(NavigationAgent2D, Node); @@ -58,7 +60,8 @@ class NavigationAgent2D : public Node { real_t path_max_distance = 3.0; Vector2 target_location; - Vector<Vector2> navigation_path; + Ref<NavigationPathQueryParameters2D> navigation_query; + Ref<NavigationPathQueryResult2D> navigation_result; int nav_path_index = 0; bool velocity_submitted = false; Vector2 prev_safe_velocity; @@ -138,9 +141,7 @@ public: Vector2 get_next_location(); - Vector<Vector2> get_nav_path() const { - return navigation_path; - } + const Vector<Vector2> &get_nav_path() const; int get_nav_path_index() const { return nav_path_index; @@ -155,7 +156,7 @@ public: void set_velocity(Vector2 p_velocity); void _avoidance_done(Vector3 p_new_velocity); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; private: void update_navigation(); diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp index 8ba51482ee..3f7e10eaea 100644 --- a/scene/2d/navigation_link_2d.cpp +++ b/scene/2d/navigation_link_2d.cpp @@ -267,8 +267,8 @@ void NavigationLink2D::set_travel_cost(real_t p_travel_cost) { NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost); } -TypedArray<String> NavigationLink2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationLink2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (start_location.is_equal_approx(end_location)) { warnings.push_back(RTR("NavigationLink2D start location should be different than the end location to be useful.")); diff --git a/scene/2d/navigation_link_2d.h b/scene/2d/navigation_link_2d.h index 5990ea082c..9d36f80dd6 100644 --- a/scene/2d/navigation_link_2d.h +++ b/scene/2d/navigation_link_2d.h @@ -37,11 +37,11 @@ class NavigationLink2D : public Node2D { GDCLASS(NavigationLink2D, Node2D); bool enabled = true; - RID link = RID(); + RID link; bool bidirectional = true; uint32_t navigation_layers = 1; - Vector2 end_location = Vector2(); - Vector2 start_location = Vector2(); + Vector2 end_location; + Vector2 start_location; real_t enter_cost = 0.0; real_t travel_cost = 1.0; @@ -79,7 +79,7 @@ public: void set_travel_cost(real_t p_travel_cost); real_t get_travel_cost() const { return travel_cost; } - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; NavigationLink2D(); ~NavigationLink2D(); diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 1850e00ecd..cbec4db4c5 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -120,8 +120,8 @@ NavigationObstacle2D::~NavigationObstacle2D() { agent = RID(); // Pointless } -TypedArray<String> NavigationObstacle2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationObstacle2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node2D>(get_parent())) { warnings.push_back(RTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object.")); @@ -153,7 +153,7 @@ void NavigationObstacle2D::reevaluate_agent_radius() { real_t NavigationObstacle2D::estimate_agent_radius() const { if (parent_node2d && parent_node2d->is_inside_tree()) { // Estimate the radius of this physics body - real_t radius = 0.0; + real_t max_radius = 0.0; for (int i(0); i < parent_node2d->get_child_count(); i++) { // For each collision shape CollisionShape2D *cs = Object::cast_to<CollisionShape2D>(parent_node2d->get_child(i)); @@ -167,17 +167,17 @@ real_t NavigationObstacle2D::estimate_agent_radius() const { Size2 s = cs->get_global_scale(); r *= MAX(s.x, s.y); // Takes the biggest radius - radius = MAX(radius, r); + max_radius = MAX(max_radius, r); } else if (cs && !cs->is_inside_tree()) { WARN_PRINT("A CollisionShape2D of the NavigationObstacle2D parent node was not inside the SceneTree when estimating the obstacle radius." "\nMove the NavigationObstacle2D to a child position below any CollisionShape2D node of the parent node so the CollisionShape2D is already inside the SceneTree."); } } Vector2 s = parent_node2d->get_global_scale(); - radius *= MAX(s.x, s.y); + max_radius *= MAX(s.x, s.y); - if (radius > 0.0) { - return radius; + if (max_radius > 0.0) { + return max_radius; } } return 1.0; // Never a 0 radius diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index 6eff95adec..d4c1df343f 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -73,7 +73,7 @@ public: return radius; } - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; private: void initialize_agent(); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index ffccb95a22..6e8ecb13b1 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -354,10 +354,13 @@ void NavigationPolygon::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines"); } +///////////////////////////// + void NavigationRegion2D::set_enabled(bool p_enabled) { if (enabled == p_enabled) { return; } + enabled = p_enabled; if (!is_inside_tree()) { @@ -384,35 +387,50 @@ bool NavigationRegion2D::is_enabled() const { } void NavigationRegion2D::set_navigation_layers(uint32_t p_navigation_layers) { - NavigationServer2D::get_singleton()->region_set_navigation_layers(region, p_navigation_layers); + if (navigation_layers == p_navigation_layers) { + return; + } + + navigation_layers = p_navigation_layers; + + NavigationServer2D::get_singleton()->region_set_navigation_layers(region, navigation_layers); } uint32_t NavigationRegion2D::get_navigation_layers() const { - return NavigationServer2D::get_singleton()->region_get_navigation_layers(region); + return navigation_layers; } void NavigationRegion2D::set_navigation_layer_value(int p_layer_number, bool p_value) { ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + uint32_t _navigation_layers = get_navigation_layers(); + if (p_value) { _navigation_layers |= 1 << (p_layer_number - 1); } else { _navigation_layers &= ~(1 << (p_layer_number - 1)); } + set_navigation_layers(_navigation_layers); } bool NavigationRegion2D::get_navigation_layer_value(int p_layer_number) const { ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + return get_navigation_layers() & (1 << (p_layer_number - 1)); } void NavigationRegion2D::set_enter_cost(real_t p_enter_cost) { ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive."); - enter_cost = MAX(p_enter_cost, 0.0); - NavigationServer2D::get_singleton()->region_set_enter_cost(region, p_enter_cost); + if (Math::is_equal_approx(enter_cost, p_enter_cost)) { + return; + } + + enter_cost = p_enter_cost; + + NavigationServer2D::get_singleton()->region_set_enter_cost(region, enter_cost); } real_t NavigationRegion2D::get_enter_cost() const { @@ -421,7 +439,12 @@ real_t NavigationRegion2D::get_enter_cost() const { void NavigationRegion2D::set_travel_cost(real_t p_travel_cost) { ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive."); - travel_cost = MAX(p_travel_cost, 0.0); + if (Math::is_equal_approx(travel_cost, p_travel_cost)) { + return; + } + + travel_cost = p_travel_cost; + NavigationServer2D::get_singleton()->region_set_travel_cost(region, travel_cost); } @@ -433,7 +456,6 @@ RID NavigationRegion2D::get_region_rid() const { return region; } -///////////////////////////// #ifdef TOOLS_ENABLED Rect2 NavigationRegion2D::_edit_get_rect() const { return navpoly.is_valid() ? navpoly->_edit_get_rect() : Rect2(); @@ -566,8 +588,8 @@ void NavigationRegion2D::_map_changed(RID p_map) { #endif // DEBUG_ENABLED } -TypedArray<String> NavigationRegion2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node2D::get_configuration_warnings(); +PackedStringArray NavigationRegion2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!navpoly.is_valid()) { diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 3c9df91fe3..c630e20780 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -96,10 +96,10 @@ class NavigationRegion2D : public Node2D { bool enabled = true; RID region; - Ref<NavigationPolygon> navpoly; - + uint32_t navigation_layers = 1; real_t enter_cost = 0.0; real_t travel_cost = 1.0; + Ref<NavigationPolygon> navpoly; void _navpoly_changed(); void _map_changed(RID p_RID); @@ -134,7 +134,7 @@ public: void set_navigation_polygon(const Ref<NavigationPolygon> &p_navpoly); Ref<NavigationPolygon> get_navigation_polygon() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; NavigationRegion2D(); ~NavigationRegion2D(); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 7765533016..2518069b78 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -437,7 +437,7 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_less,or_greater,no_slider,suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_less,or_greater,hide_slider,suffix:px"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "skew", PROPERTY_HINT_RANGE, "-89.9,89.9,0.1,radians"), "set_skew", "get_skew"); diff --git a/scene/2d/parallax_background.cpp b/scene/2d/parallax_background.cpp index f1a28b7852..b7e6eb8ea5 100644 --- a/scene/2d/parallax_background.cpp +++ b/scene/2d/parallax_background.cpp @@ -71,29 +71,29 @@ void ParallaxBackground::_update_scroll() { return; } - Vector2 ofs = base_offset + offset * base_scale; + Vector2 scroll_ofs = base_offset + offset * base_scale; Size2 vps = get_viewport_size(); - ofs = -ofs; + scroll_ofs = -scroll_ofs; if (limit_begin.x < limit_end.x) { - if (ofs.x < limit_begin.x) { - ofs.x = limit_begin.x; - } else if (ofs.x + vps.x > limit_end.x) { - ofs.x = limit_end.x - vps.x; + if (scroll_ofs.x < limit_begin.x) { + scroll_ofs.x = limit_begin.x; + } else if (scroll_ofs.x + vps.x > limit_end.x) { + scroll_ofs.x = limit_end.x - vps.x; } } if (limit_begin.y < limit_end.y) { - if (ofs.y < limit_begin.y) { - ofs.y = limit_begin.y; - } else if (ofs.y + vps.y > limit_end.y) { - ofs.y = limit_end.y - vps.y; + if (scroll_ofs.y < limit_begin.y) { + scroll_ofs.y = limit_begin.y; + } else if (scroll_ofs.y + vps.y > limit_end.y) { + scroll_ofs.y = limit_end.y - vps.y; } } - ofs = -ofs; + scroll_ofs = -scroll_ofs; - final_offset = ofs; + final_offset = scroll_ofs; for (int i = 0; i < get_child_count(); i++) { ParallaxLayer *l = Object::cast_to<ParallaxLayer>(get_child(i)); @@ -102,9 +102,9 @@ void ParallaxBackground::_update_scroll() { } if (ignore_camera_zoom) { - l->set_base_offset_and_scale((ofs + screen_offset * (scale - 1)) / scale, 1.0); + l->set_base_offset_and_scale((scroll_ofs + screen_offset * (scale - 1)) / scale, 1.0); } else { - l->set_base_offset_and_scale(ofs, scale); + l->set_base_offset_and_scale(scroll_ofs, scale); } } } diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index d4138dc516..feb1fcd90c 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -37,9 +37,9 @@ void ParallaxLayer::set_motion_scale(const Size2 &p_scale) { ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent()); if (pb && is_inside_tree()) { - Vector2 ofs = pb->get_final_offset(); - real_t scale = pb->get_scroll_scale(); - set_base_offset_and_scale(ofs, scale); + Vector2 final_ofs = pb->get_final_offset(); + real_t scroll_scale = pb->get_scroll_scale(); + set_base_offset_and_scale(final_ofs, scroll_scale); } } @@ -52,9 +52,9 @@ void ParallaxLayer::set_motion_offset(const Size2 &p_offset) { ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent()); if (pb && is_inside_tree()) { - Vector2 ofs = pb->get_final_offset(); - real_t scale = pb->get_scroll_scale(); - set_base_offset_and_scale(ofs, scale); + Vector2 final_ofs = pb->get_final_offset(); + real_t scroll_scale = pb->get_scroll_scale(); + set_base_offset_and_scale(final_ofs, scroll_scale); } } @@ -137,8 +137,8 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s _update_mirroring(); } -TypedArray<String> ParallaxLayer::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray ParallaxLayer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<ParallaxBackground>(get_parent())) { warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node.")); diff --git a/scene/2d/parallax_layer.h b/scene/2d/parallax_layer.h index c03f4cc293..6471f56c5c 100644 --- a/scene/2d/parallax_layer.h +++ b/scene/2d/parallax_layer.h @@ -59,7 +59,7 @@ public: void set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; ParallaxLayer(); }; diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 90b2e3d460..b5945a4562 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -175,51 +175,18 @@ void PathFollow2D::_update_transform() { if (path_length == 0) { return; } - Vector2 pos = c->sample_baked(progress, cubic); if (rotates) { - real_t ahead = progress + lookahead; - - if (loop && ahead >= path_length) { - // If our lookahead will loop, we need to check if the path is closed. - int point_count = c->get_point_count(); - if (point_count > 0) { - Vector2 start_point = c->get_point_position(0); - Vector2 end_point = c->get_point_position(point_count - 1); - if (start_point == end_point) { - // Since the path is closed we want to 'smooth off' - // the corner at the start/end. - // So we wrap the lookahead back round. - ahead = Math::fmod(ahead, path_length); - } - } - } - - Vector2 ahead_pos = c->sample_baked(ahead, cubic); - - Vector2 tangent_to_curve; - if (ahead_pos == pos) { - // This will happen at the end of non-looping or non-closed paths. - // We'll try a look behind instead, in order to get a meaningful angle. - tangent_to_curve = - (pos - c->sample_baked(progress - lookahead, cubic)).normalized(); - } else { - tangent_to_curve = (ahead_pos - pos).normalized(); - } - - Vector2 normal_of_curve = -tangent_to_curve.orthogonal(); - - pos += tangent_to_curve * h_offset; - pos += normal_of_curve * v_offset; - - set_rotation(tangent_to_curve.angle()); - + Transform2D xform = c->sample_baked_with_rotation(progress, cubic, loop, lookahead); + xform.translate_local(v_offset, h_offset); + set_rotation(xform[1].angle()); + set_position(xform[2]); } else { + Vector2 pos = c->sample_baked(progress, cubic); pos.x += h_offset; pos.y += v_offset; + set_position(pos); } - - set_position(pos); } void PathFollow2D::_notification(int p_what) { @@ -256,8 +223,8 @@ void PathFollow2D::_validate_property(PropertyInfo &p_property) const { } } -TypedArray<String> PathFollow2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray PathFollow2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path2D>(get_parent())) { diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index 3d66ca1fab..5e436fb9f6 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -106,7 +106,7 @@ public: void set_cubic_interpolation(bool p_enable); bool get_cubic_interpolation() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; PathFollow2D() {} }; diff --git a/scene/2d/physical_bone_2d.cpp b/scene/2d/physical_bone_2d.cpp index e6933b8a40..5ff706ebb7 100644 --- a/scene/2d/physical_bone_2d.cpp +++ b/scene/2d/physical_bone_2d.cpp @@ -106,8 +106,8 @@ void PhysicalBone2D::_find_joint_child() { } } -TypedArray<String> PhysicalBone2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray PhysicalBone2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!parent_skeleton) { warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); diff --git a/scene/2d/physical_bone_2d.h b/scene/2d/physical_bone_2d.h index 9fbfa04100..33ac0d9935 100644 --- a/scene/2d/physical_bone_2d.h +++ b/scene/2d/physical_bone_2d.h @@ -79,7 +79,7 @@ public: void set_follow_bone_when_simulating(bool p_follow); bool get_follow_bone_when_simulating() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; PhysicalBone2D(); ~PhysicalBone2D(); diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 2aa14aa11a..0f3e6c7529 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -62,7 +62,7 @@ Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_distance, bool p if (move_and_collide(parameters, result, p_test_only)) { // Create a new instance when the cached reference is invalid or still in use in script. - if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { + if (motion_cache.is_null() || motion_cache->get_reference_count() > 1) { motion_cache.instantiate(); motion_cache->owner = this; } @@ -262,21 +262,16 @@ void AnimatableBody2D::_update_kinematic_motion() { #endif if (sync_to_physics) { - PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &AnimatableBody2D::_body_state_changed)); set_only_update_transform_changes(true); set_notify_local_transform(true); } else { - PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), Callable()); set_only_update_transform_changes(false); set_notify_local_transform(false); } } -void AnimatableBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { - AnimatableBody2D *body = static_cast<AnimatableBody2D *>(p_instance); - body->_body_state_changed(p_state); -} - void AnimatableBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { if (!sync_to_physics) { return; @@ -438,11 +433,6 @@ struct _RigidBody2DInOut { int local_shape = 0; }; -void RigidBody2D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState2D *p_state) { - RigidBody2D *body = static_cast<RigidBody2D *>(p_instance); - body->_body_state_changed(p_state); -} - void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { set_block_transform_notify(true); // don't want notify (would feedback loop) if (!freeze || freeze_mode != FREEZE_MODE_KINEMATIC) { @@ -481,28 +471,28 @@ void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) { //put the ones to add for (int i = 0; i < p_state->get_contact_count(); i++) { - RID rid = p_state->get_contact_collider(i); - ObjectID obj = p_state->get_contact_collider_id(i); + RID col_rid = p_state->get_contact_collider(i); + ObjectID col_obj = p_state->get_contact_collider_id(i); int local_shape = p_state->get_contact_local_shape(i); - int shape = p_state->get_contact_collider_shape(i); + int col_shape = p_state->get_contact_collider_shape(i); - HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(obj); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(col_obj); if (!E) { - toadd[toadd_count].rid = rid; + toadd[toadd_count].rid = col_rid; toadd[toadd_count].local_shape = local_shape; - toadd[toadd_count].id = obj; - toadd[toadd_count].shape = shape; + toadd[toadd_count].id = col_obj; + toadd[toadd_count].shape = col_shape; toadd_count++; continue; } - ShapePair sp(shape, local_shape); + ShapePair sp(col_shape, local_shape); int idx = E->value.shapes.find(sp); if (idx == -1) { - toadd[toadd_count].rid = rid; + toadd[toadd_count].rid = col_rid; toadd[toadd_count].local_shape = local_shape; - toadd[toadd_count].id = obj; - toadd[toadd_count].shape = shape; + toadd[toadd_count].id = col_obj; + toadd[toadd_count].shape = col_shape; toadd_count++; continue; } @@ -921,10 +911,10 @@ void RigidBody2D::_notification(int p_what) { #endif } -TypedArray<String> RigidBody2D::get_configuration_warnings() const { +PackedStringArray RigidBody2D::get_configuration_warnings() const { Transform2D t = get_transform(); - TypedArray<String> warnings = CollisionObject2D::get_configuration_warnings(); + PackedStringArray warnings = CollisionObject2D::get_configuration_warnings(); if (ABS(t.columns[0].length() - 1.0) > 0.05 || ABS(t.columns[1].length() - 1.0) > 0.05) { warnings.push_back(RTR("Size changes to RigidBody2D will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); @@ -1079,7 +1069,7 @@ void RigidBody2D::_validate_property(PropertyInfo &p_property) const { RigidBody2D::RigidBody2D() : PhysicsBody2D(PhysicsServer2D::BODY_MODE_RIGID) { - PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + PhysicsServer2D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &RigidBody2D::_body_state_changed)); } RigidBody2D::~RigidBody2D() { @@ -1557,7 +1547,7 @@ Ref<KinematicCollision2D> CharacterBody2D::_get_slide_collision(int p_bounce) { } // Create a new instance when the cached reference is invalid or still in use in script. - if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->reference_get_count() > 1) { + if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->get_reference_count() > 1) { slide_colliders.write[p_bounce].instantiate(); slide_colliders.write[p_bounce]->owner = this; } diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 1bc24f3264..932ec1de16 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -309,7 +309,7 @@ public: TypedArray<Node2D> get_colliding_bodies() const; //function for script - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; RigidBody2D(); ~RigidBody2D(); diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index 6c4bfd58ce..f4343e4c03 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -183,8 +183,8 @@ void RemoteTransform2D::force_update_cache() { _update_cache(); } -TypedArray<String> RemoteTransform2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray RemoteTransform2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { warnings.push_back(RTR("Path property must point to a valid Node2D node to work.")); diff --git a/scene/2d/remote_transform_2d.h b/scene/2d/remote_transform_2d.h index bd352e1054..f98eec75c6 100644 --- a/scene/2d/remote_transform_2d.h +++ b/scene/2d/remote_transform_2d.h @@ -70,7 +70,7 @@ public: void force_update_cache(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; RemoteTransform2D(); }; diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp index a25d5934ee..4c2e9e4c19 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -107,6 +107,11 @@ Object *ShapeCast2D::get_collider(int p_idx) const { return ObjectDB::get_instance(result[p_idx].collider_id); } +RID ShapeCast2D::get_collider_rid(int p_idx) const { + ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), RID(), "No collider RID found."); + return result[p_idx].rid; +} + int ShapeCast2D::get_collider_shape(int p_idx) const { ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found."); return result[p_idx].shape; @@ -391,8 +396,8 @@ Array ShapeCast2D::_get_collision_result() const { return ret; } -TypedArray<String> ShapeCast2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node2D::get_configuration_warnings(); +PackedStringArray ShapeCast2D::get_configuration_warnings() const { + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (shape.is_null()) { warnings.push_back(RTR("This node cannot interact with other objects unless a Shape2D is assigned.")); @@ -422,6 +427,7 @@ void ShapeCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast2D::force_shapecast_update); ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast2D::get_collider); + ClassDB::bind_method(D_METHOD("get_collider_rid", "index"), &ShapeCast2D::get_collider_rid); ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast2D::get_collider_shape); ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast2D::get_collision_point); ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast2D::get_collision_normal); diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h index 660e52f189..134f236d3b 100644 --- a/scene/2d/shape_cast_2d.h +++ b/scene/2d/shape_cast_2d.h @@ -104,6 +104,7 @@ public: int get_collision_count() const; Object *get_collider(int p_idx) const; + RID get_collider_rid(int p_idx) const; int get_collider_shape(int p_idx) const; Vector2 get_collision_point(int p_idx) const; Vector2 get_collision_normal(int p_idx) const; @@ -117,7 +118,7 @@ public: void remove_exception(const CollisionObject2D *p_node); void clear_exceptions(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; }; #endif // SHAPE_CAST_2D_H diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 8f0bf22617..62787d4488 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -146,9 +146,9 @@ void Bone2D::_notification(int p_what) { queue_redraw(); if (get_parent()) { - Bone2D *parent_bone = Object::cast_to<Bone2D>(get_parent()); - if (parent_bone) { - parent_bone->queue_redraw(); + Bone2D *p_bone = Object::cast_to<Bone2D>(get_parent()); + if (p_bone) { + p_bone->queue_redraw(); } } #endif // TOOLS_ENABLED @@ -192,10 +192,8 @@ void Bone2D::_notification(int p_what) { cache_transform = tmp_trans; } break; - // Bone2D Editor gizmo drawing: -#ifndef _MSC_VER -#warning TODO Bone2D gizmo drawing needs to be moved to an editor plugin -#endif + // Bone2D Editor gizmo drawing. + // TODO: Bone2D gizmo drawing needs to be moved to an editor plugin. case NOTIFICATION_DRAW: { // Only draw the gizmo in the editor! if (Engine::get_singleton()->is_editor_hint() == false) { @@ -215,15 +213,15 @@ void Bone2D::_notification(int p_what) { } // Undo scaling - Transform2D editor_gizmo_trans = Transform2D(); + Transform2D editor_gizmo_trans; editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale()); RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans); - Color bone_color1 = EditorSettings::get_singleton()->get("editors/2d/bone_color1"); - Color bone_color2 = EditorSettings::get_singleton()->get("editors/2d/bone_color2"); - Color bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); - Color bone_outline_color = EditorSettings::get_singleton()->get("editors/2d/bone_outline_color"); - Color bone_selected_color = EditorSettings::get_singleton()->get("editors/2d/bone_selected_color"); + Color bone_color1 = EDITOR_GET("editors/2d/bone_color1"); + Color bone_color2 = EDITOR_GET("editors/2d/bone_color2"); + Color bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color"); + Color bone_outline_color = EDITOR_GET("editors/2d/bone_outline_color"); + Color bone_selected_color = EDITOR_GET("editors/2d/bone_selected_color"); bool Bone2D_found = false; for (int i = 0; i < get_child_count(); i++) { @@ -319,8 +317,8 @@ void Bone2D::_notification(int p_what) { #ifdef TOOLS_ENABLED bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone) { - int bone_width = EditorSettings::get_singleton()->get("editors/2d/bone_width"); - int bone_outline_width = EditorSettings::get_singleton()->get("editors/2d/bone_outline_size"); + int bone_width = EDITOR_GET("editors/2d/bone_width"); + int bone_outline_width = EDITOR_GET("editors/2d/bone_outline_size"); if (!is_inside_tree()) { return false; //may have been removed @@ -434,8 +432,8 @@ int Bone2D::get_index_in_skeleton() const { return skeleton_index; } -TypedArray<String> Bone2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray Bone2D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!skeleton) { if (parent_bone) { warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node.")); diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 98fb867d99..580aed97ce 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -78,7 +78,7 @@ public: void apply_rest(); Transform2D get_skeleton_rest() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_default_length(real_t p_length); real_t get_default_length() const; diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp index 0ecf8333a0..0784318442 100644 --- a/scene/2d/sprite_2d.cpp +++ b/scene/2d/sprite_2d.cpp @@ -309,9 +309,7 @@ bool Sprite2D::is_pixel_opaque(const Point2 &p_point) const { q.y = 1.0f - q.y; } q = q * src_rect.size + src_rect.position; -#ifndef _MSC_VER -#warning this need to be obtained from CanvasItem new repeat mode (but it needs to guess it from hierarchy, need to add a function for that) -#endif + // TODO: This need to be obtained from CanvasItem new repeat mode (but it needs to guess it from hierarchy, need to add a function for that). bool is_repeat = false; bool is_mirrored_repeat = false; if (is_repeat) { @@ -446,7 +444,7 @@ void Sprite2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 5de6d547d7..40e8818262 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -43,10 +43,10 @@ HashMap<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlap ERR_FAIL_COND_V(is_center_bit(), output); - Ref<TileSet> tile_set = tile_map->get_tileset(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND_V(!ts.is_valid(), output); - TileSet::TileShape shape = tile_set->get_tile_shape(); + TileSet::TileShape shape = ts->get_tile_shape(); if (shape == TileSet::TILE_SHAPE_SQUARE) { switch (bit) { case 1: @@ -87,7 +87,7 @@ HashMap<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlap } } else { // Half offset shapes. - TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { switch (bit) { case 1: @@ -150,8 +150,8 @@ HashMap<Vector2i, TileSet::CellNeighbor> TileMap::TerrainConstraint::get_overlap TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { tile_map = p_tile_map; - Ref<TileSet> tile_set = tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); bit = 0; base_cell_coords = p_position; @@ -162,10 +162,10 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V // The way we build the constraint make it easy to detect conflicting constraints. tile_map = p_tile_map; - Ref<TileSet> tile_set = tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); - TileSet::TileShape shape = tile_set->get_tile_shape(); + TileSet::TileShape shape = ts->get_tile_shape(); if (shape == TileSet::TILE_SHAPE_SQUARE) { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: @@ -244,7 +244,7 @@ TileMap::TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const V } } else { // Half-offset shapes - TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { switch (p_bit) { case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: @@ -758,12 +758,12 @@ void TileMap::set_y_sort_enabled(bool p_enable) { } Vector2i TileMap::_coords_to_quadrant_coords(int p_layer, const Vector2i &p_coords) const { - int quadrant_size = get_effective_quadrant_size(p_layer); + int quad_size = get_effective_quadrant_size(p_layer); // Rounding down, instead of simply rounding towards zero (truncating) return Vector2i( - p_coords.x > 0 ? p_coords.x / quadrant_size : (p_coords.x - (quadrant_size - 1)) / quadrant_size, - p_coords.y > 0 ? p_coords.y / quadrant_size : (p_coords.y - (quadrant_size - 1)) / quadrant_size); + p_coords.x > 0 ? p_coords.x / quad_size : (p_coords.x - (quad_size - 1)) / quad_size, + p_coords.y > 0 ? p_coords.y / quad_size : (p_coords.y - (quad_size - 1)) / quad_size); } HashMap<Vector2i, TileMapQuadrant>::Iterator TileMap::_create_quadrant(int p_layer, const Vector2i &p_qk) { @@ -1007,8 +1007,24 @@ void TileMap::_recompute_rect_cache() { void TileMap::_rendering_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_CANVAS: { + bool node_visible = is_visible_in_tree(); + for (int layer = 0; layer < (int)layers.size(); layer++) { + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layers[layer].quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + Transform2D xform; + xform.set_origin(map_to_local(kv.key)); + RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, get_canvas()); + RS::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform); + RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible); + } + } + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { - bool visible = is_visible_in_tree(); + bool node_visible = is_visible_in_tree(); for (int layer = 0; layer < (int)layers.size(); layer++) { for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layers[layer].quadrant_map) { TileMapQuadrant &q = E_quadrant.value; @@ -1017,8 +1033,8 @@ void TileMap::_rendering_notification(int p_what) { for (const KeyValue<Vector2i, Vector2i> &E_cell : q.local_to_map) { Transform2D xform; xform.set_origin(E_cell.key); - for (const RID &occluder : q.occluders) { - RS::get_singleton()->canvas_light_occluder_set_enabled(occluder, visible); + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + RS::get_singleton()->canvas_light_occluder_set_enabled(kv.value, node_visible); } } } @@ -1034,12 +1050,10 @@ void TileMap::_rendering_notification(int p_what) { TileMapQuadrant &q = E_quadrant.value; // Update occluders transform. - for (const KeyValue<Vector2i, Vector2i> &E_cell : q.local_to_map) { + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { Transform2D xform; - xform.set_origin(E_cell.key); - for (const RID &occluder : q.occluders) { - RS::get_singleton()->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); - } + xform.set_origin(map_to_local(kv.key)); + RenderingServer::get_singleton()->canvas_light_occluder_set_transform(kv.value, get_global_transform() * xform); } } } @@ -1050,6 +1064,17 @@ void TileMap::_rendering_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_set_sort_children_by_y(get_canvas_item(), is_y_sort_enabled()); } } break; + + case NOTIFICATION_EXIT_CANVAS: { + for (int layer = 0; layer < (int)layers.size(); layer++) { + for (KeyValue<Vector2i, TileMapQuadrant> &E_quadrant : layers[layer].quadrant_map) { + TileMapQuadrant &q = E_quadrant.value; + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + RS::get_singleton()->canvas_light_occluder_attach_to_canvas(kv.value, RID()); + } + } + } + } break; } } @@ -1072,8 +1097,8 @@ void TileMap::_rendering_update_layer(int p_layer) { rs->canvas_item_set_sort_children_by_y(ci, layers[p_layer].y_sort_enabled); rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); rs->canvas_item_set_z_index(ci, layers[p_layer].z_index); - rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter())); - rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat())); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter_in_tree())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree())); rs->canvas_item_set_light_mask(ci, get_light_mask()); } @@ -1091,7 +1116,7 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List ERR_FAIL_COND(!is_inside_tree()); ERR_FAIL_COND(!tile_set.is_valid()); - bool visible = is_visible_in_tree(); + bool node_visible = is_visible_in_tree(); SelfList<TileMapQuadrant> *q_list_element = r_dirty_quadrant_list.first(); while (q_list_element) { @@ -1106,26 +1131,26 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List q.canvas_items.clear(); // Free the occluders. - for (const RID &occluder : q.occluders) { - rs->free(occluder); + for (const KeyValue<Vector2i, RID> &kv : q.occluders) { + rs->free(kv.value); } q.occluders.clear(); // Those allow to group cell per material or z-index. Ref<Material> prev_material; int prev_z_index = 0; - RID prev_canvas_item; + RID prev_ci; - Color modulate = get_self_modulate(); - modulate *= get_layer_modulate(q.layer); + Color tile_modulate = get_self_modulate(); + tile_modulate *= get_layer_modulate(q.layer); if (selected_layer >= 0) { int z1 = get_layer_z_index(q.layer); int z2 = get_layer_z_index(selected_layer); if (z1 < z2 || (z1 == z2 && q.layer < selected_layer)) { - modulate = modulate.darkened(0.5); + tile_modulate = tile_modulate.darkened(0.5); } else if (z1 > z2 || (z1 == z2 && q.layer > selected_layer)) { - modulate = modulate.darkened(0.5); - modulate.a *= 0.3; + tile_modulate = tile_modulate.darkened(0.5); + tile_modulate.a *= 0.3; } } @@ -1152,53 +1177,53 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List } Ref<Material> mat = tile_data->get_material(); - int z_index = tile_data->get_z_index(); + int tile_z_index = tile_data->get_z_index(); // Quandrant pos. - Vector2 position = map_to_local(q.coords * get_effective_quadrant_size(q.layer)); + Vector2 tile_position = map_to_local(q.coords * get_effective_quadrant_size(q.layer)); if (is_y_sort_enabled() && layers[q.layer].y_sort_enabled) { // When Y-sorting, the quandrant size is sure to be 1, we can thus offset the CanvasItem. - position.y += layers[q.layer].y_sort_origin + tile_data->get_y_sort_origin(); + tile_position.y += layers[q.layer].y_sort_origin + tile_data->get_y_sort_origin(); } // --- CanvasItems --- // Create two canvas items, for rendering and debug. - RID canvas_item; + RID ci; // Check if the material or the z_index changed. - if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { + if (prev_ci == RID() || prev_material != mat || prev_z_index != tile_z_index) { // If so, create a new CanvasItem. - canvas_item = rs->canvas_item_create(); + ci = rs->canvas_item_create(); if (mat.is_valid()) { - rs->canvas_item_set_material(canvas_item, mat->get_rid()); + rs->canvas_item_set_material(ci, mat->get_rid()); } - rs->canvas_item_set_parent(canvas_item, layers[q.layer].canvas_item); - rs->canvas_item_set_use_parent_material(canvas_item, get_use_parent_material() || get_material().is_valid()); + rs->canvas_item_set_parent(ci, layers[q.layer].canvas_item); + rs->canvas_item_set_use_parent_material(ci, get_use_parent_material() || get_material().is_valid()); Transform2D xform; - xform.set_origin(position); - rs->canvas_item_set_transform(canvas_item, xform); + xform.set_origin(tile_position); + rs->canvas_item_set_transform(ci, xform); - rs->canvas_item_set_light_mask(canvas_item, get_light_mask()); - rs->canvas_item_set_z_as_relative_to_parent(canvas_item, true); - rs->canvas_item_set_z_index(canvas_item, z_index); + rs->canvas_item_set_light_mask(ci, get_light_mask()); + rs->canvas_item_set_z_as_relative_to_parent(ci, true); + rs->canvas_item_set_z_index(ci, tile_z_index); - rs->canvas_item_set_default_texture_filter(canvas_item, RS::CanvasItemTextureFilter(get_texture_filter())); - rs->canvas_item_set_default_texture_repeat(canvas_item, RS::CanvasItemTextureRepeat(get_texture_repeat())); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter_in_tree())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree())); - q.canvas_items.push_back(canvas_item); + q.canvas_items.push_back(ci); - prev_canvas_item = canvas_item; + prev_ci = ci; prev_material = mat; - prev_z_index = z_index; + prev_z_index = tile_z_index; } else { // Keep the same canvas_item to draw on. - canvas_item = prev_canvas_item; + ci = prev_ci; } // Drawing the tile in the canvas item. - draw_tile(canvas_item, E_cell.key - position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, modulate, tile_data); + draw_tile(ci, E_cell.key - tile_position, tile_set, c.source_id, c.get_atlas_coords(), c.alternative_tile, -1, tile_modulate, tile_data); // --- Occluders --- for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { @@ -1206,12 +1231,12 @@ void TileMap::_rendering_update_dirty_quadrants(SelfList<TileMapQuadrant>::List xform.set_origin(E_cell.key); if (tile_data->get_occluder(i).is_valid()) { RID occluder_id = rs->canvas_light_occluder_create(); - rs->canvas_light_occluder_set_enabled(occluder_id, visible); + rs->canvas_light_occluder_set_enabled(occluder_id, node_visible); rs->canvas_light_occluder_set_transform(occluder_id, get_global_transform() * xform); rs->canvas_light_occluder_set_polygon(occluder_id, tile_data->get_occluder(i)->get_rid()); rs->canvas_light_occluder_attach_to_canvas(occluder_id, get_canvas()); rs->canvas_light_occluder_set_light_mask(occluder_id, tile_set->get_occlusion_layer_light_mask(i)); - q.occluders.push_back(occluder_id); + q.occluders[E_cell.value] = occluder_id; } } } @@ -1259,8 +1284,8 @@ void TileMap::_rendering_cleanup_quadrant(TileMapQuadrant *p_quadrant) { p_quadrant->canvas_items.clear(); // Free the occluders. - for (const RID &occluder : p_quadrant->occluders) { - RenderingServer::get_singleton()->free(occluder); + for (const KeyValue<Vector2i, RID> &kv : p_quadrant->occluders) { + RenderingServer::get_singleton()->free(kv.value); } p_quadrant->occluders.clear(); } @@ -1272,7 +1297,7 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { return; } - // Draw a placeholder for scenes needing one. + // Draw a placeholder for tiles needing one. RenderingServer *rs = RenderingServer::get_singleton(); Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); for (const Vector2i &E_cell : p_quadrant->cells) { @@ -1305,9 +1330,9 @@ void TileMap::_rendering_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { 0.8); // Draw a placeholder tile. - Transform2D xform; - xform.set_origin(map_to_local(E_cell) - quadrant_pos); - rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + Transform2D cell_to_quadrant; + cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant); rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } } @@ -1420,7 +1445,7 @@ void TileMap::_physics_notification(int p_what) { #endif if (is_inside_tree() && (!collision_animatable || in_editor)) { // Update the new transform directly if we are not in animatable mode. - Transform2D global_transform = get_global_transform(); + Transform2D gl_transform = get_global_transform(); for (int layer = 0; layer < (int)layers.size(); layer++) { for (KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) { TileMapQuadrant &q = E.value; @@ -1428,7 +1453,7 @@ void TileMap::_physics_notification(int p_what) { for (RID body : q.bodies) { Transform2D xform; xform.set_origin(map_to_local(bodies_coords[body])); - xform = global_transform * xform; + xform = gl_transform * xform; PhysicsServer2D::get_singleton()->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); } } @@ -1471,9 +1496,9 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r ERR_FAIL_COND(!is_inside_tree()); ERR_FAIL_COND(!tile_set.is_valid()); - Transform2D global_transform = get_global_transform(); - last_valid_transform = global_transform; - new_transform = global_transform; + Transform2D gl_transform = get_global_transform(); + last_valid_transform = gl_transform; + new_transform = gl_transform; PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); RID space = get_world_2d()->get_space(); @@ -1521,7 +1546,7 @@ void TileMap::_physics_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r Transform2D xform; xform.set_origin(map_to_local(E_cell)); - xform = global_transform * xform; + xform = gl_transform * xform; ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); ps->body_attach_object_instance_id(body, get_instance_id()); @@ -1607,13 +1632,13 @@ void TileMap::_physics_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { color.push_back(debug_collision_color); Vector2 quadrant_pos = map_to_local(p_quadrant->coords * get_effective_quadrant_size(p_quadrant->layer)); - Transform2D qudrant_xform; - qudrant_xform.set_origin(quadrant_pos); - Transform2D global_transform_inv = (get_global_transform() * qudrant_xform).affine_inverse(); + Transform2D quadrant_to_local; + quadrant_to_local.set_origin(quadrant_pos); + Transform2D global_to_quadrant = (get_global_transform() * quadrant_to_local).affine_inverse(); for (RID body : p_quadrant->bodies) { - Transform2D xform = Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)) * global_transform_inv; - rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + Transform2D body_to_quadrant = global_to_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, body_to_quadrant); for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) { const RID &shape = ps->body_get_shape(body, shape_index); PhysicsServer2D::ShapeType type = ps->shape_get_type(shape); @@ -1790,9 +1815,9 @@ void TileMap::_navigation_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); } - Transform2D xform; - xform.set_origin(map_to_local(E_cell) - quadrant_pos); - rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + Transform2D cell_to_quadrant; + cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant); for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { Ref<NavigationPolygon> navpoly = tile_data->get_navigation_polygon(layer_index); @@ -1838,7 +1863,7 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ for (const KeyValue<Vector2i, String> &E : q.scenes) { Node *node = get_node_or_null(E.value); if (node) { - node->queue_delete(); + node->queue_free(); } } @@ -1861,7 +1886,6 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile); if (packed_scene.is_valid()) { Node *scene = packed_scene->instantiate(); - add_child(scene); Control *scene_as_control = Object::cast_to<Control>(scene); Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); if (scene_as_control) { @@ -1871,6 +1895,7 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ xform.set_origin(map_to_local(E_cell)); scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); } + add_child(scene); q.scenes[E_cell] = scene->get_name(); } } @@ -1886,7 +1911,7 @@ void TileMap::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { for (const KeyValue<Vector2i, String> &E : p_quadrant->scenes) { Node *node = get_node_or_null(E.value); if (node) { - node->queue_delete(); + node->queue_free(); } } @@ -1931,9 +1956,9 @@ void TileMap::_scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant) { 0.8); // Draw a placeholder tile. - Transform2D xform; - xform.set_origin(map_to_local(E_cell) - quadrant_pos); - rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, xform); + Transform2D cell_to_quadrant; + cell_to_quadrant.set_origin(map_to_local(E_cell) - quadrant_pos); + rs->canvas_item_add_set_transform(p_quadrant->debug_canvas_item, cell_to_quadrant); rs->canvas_item_add_circle(p_quadrant->debug_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); } } @@ -2181,11 +2206,10 @@ void TileMap::set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPat } } -TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, RBSet<TerrainConstraint> p_constraints) { +TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, RBSet<TerrainConstraint> p_constraints, TileSet::TerrainsPattern p_current_pattern) { if (!tile_set.is_valid()) { return TileSet::TerrainsPattern(); } - // Returns all tiles compatible with the given constraints. RBMap<TileSet::TerrainsPattern, int> terrain_pattern_score; RBSet<TileSet::TerrainsPattern> pattern_set = tile_set->get_terrains_pattern_set(p_terrain_set); @@ -2196,28 +2220,41 @@ TileSet::TerrainsPattern TileMap::_get_best_terrain_pattern_for_constraints(int // Check the center bit constraint TerrainConstraint terrain_constraint = TerrainConstraint(this, p_position, terrain_pattern.get_terrain()); RBSet<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_constraint); - if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_constraint.get_terrain()) { - score += in_set_constraint_element->get().get_priority(); + if (in_set_constraint_element) { + if (in_set_constraint_element->get().get_terrain() != terrain_constraint.get_terrain()) { + score += in_set_constraint_element->get().get_priority(); + } + } else if (p_current_pattern.get_terrain() != terrain_pattern.get_terrain()) { + continue; // Ignore a pattern that cannot keep bits without constraints unmodified. } // Check the surrounding bits + bool invalid_pattern = false; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { // Check if the bit is compatible with the constraints. TerrainConstraint terrain_bit_constraint = TerrainConstraint(this, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit)); in_set_constraint_element = p_constraints.find(terrain_bit_constraint); - if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { - score += in_set_constraint_element->get().get_priority(); + if (in_set_constraint_element) { + if (in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { + score += in_set_constraint_element->get().get_priority(); + } + } else if (p_current_pattern.get_terrain_peering_bit(bit) != terrain_pattern.get_terrain_peering_bit(bit)) { + invalid_pattern = true; // Ignore a pattern that cannot keep bits without constraints unmodified. + break; } } } + if (invalid_pattern) { + continue; + } terrain_pattern_score[terrain_pattern] = score; } // Compute the minimum score - TileSet::TerrainsPattern min_score_pattern; + TileSet::TerrainsPattern min_score_pattern = p_current_pattern; int min_score = INT32_MAX; for (KeyValue<TileSet::TerrainsPattern, int> E : terrain_pattern_score) { if (E.value < min_score) { @@ -2249,7 +2286,7 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_added_p return output; } -RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_cells_list(int p_layer, const RBSet<Vector2i> &p_cell_list, int p_terrain_set, bool p_ignore_empty_terrains) const { +RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { if (!tile_set.is_valid()) { return RBSet<TerrainConstraint>(); } @@ -2259,8 +2296,8 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_cells_l // Build a set of dummy constraints to get the constrained points. RBSet<TerrainConstraint> dummy_constraints; - for (const Vector2i &E : p_cell_list) { - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides. + for (const Vector2i &E : p_painted) { + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over neighbor bits. TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { dummy_constraints.insert(TerrainConstraint(this, E, bit, -1)); @@ -2317,7 +2354,7 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_cells_l } // Add the centers as constraints - for (Vector2i E_coords : p_cell_list) { + for (Vector2i E_coords : p_painted) { TileData *tile_data = nullptr; TileMapCell cell = get_cell(p_layer, E_coords); if (cell.source_id != TileSet::INVALID_SOURCE) { @@ -2337,7 +2374,7 @@ RBSet<TileMap::TerrainConstraint> TileMap::_get_terrain_constraints_from_cells_l return constraints; } -HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> p_constraints) { +HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> p_constraints) { if (!tile_set.is_valid()) { return HashMap<Vector2i, TileSet::TerrainsPattern>(); } @@ -2353,7 +2390,20 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_constraints(co const Vector2i &coords = p_to_replace[i]; // Select the best pattern for the given constraints - TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints); + TileSet::TerrainsPattern current_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = get_cell(p_layer, coords); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + current_pattern = tile_data->get_terrains_pattern(); + } + } + } + TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints, current_pattern); // Update the constraint set with the new ones RBSet<TerrainConstraint> new_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, pattern); @@ -2390,7 +2440,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ // Find the adequate neighbor for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + if (is_existing_neighbor(bit)) { Vector2i neighbor = get_neighbor_cell(coords, bit); if (!can_modify_set.has(neighbor)) { can_modify_list.push_back(neighbor); @@ -2467,12 +2517,12 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_connect(int p_ } // Fills in the constraint list from existing tiles. - for (TerrainConstraint c : _get_terrain_constraints_from_cells_list(p_layer, can_modify_set, p_terrain_set, p_ignore_empty_terrains)) { + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { constraints.insert(c); } // Fill the terrains. - output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); return output; } @@ -2488,7 +2538,7 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_lay TileSet::CellNeighbor found_bit = TileSet::CELL_NEIGHBOR_MAX; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + if (is_existing_neighbor(bit)) { if (get_neighbor_cell(p_path[i], bit) == p_path[i + 1]) { found_bit = bit; break; @@ -2502,10 +2552,12 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_lay // Build list and set of tiles that can be modified (painted and their surroundings) Vector<Vector2i> can_modify_list; RBSet<Vector2i> can_modify_set; + RBSet<Vector2i> painted_set; for (int i = p_path.size() - 1; i >= 0; i--) { const Vector2i &coords = p_path[i]; can_modify_list.push_back(coords); can_modify_set.insert(coords); + painted_set.insert(coords); } for (Vector2i coords : p_path) { // Find the adequate neighbor @@ -2538,12 +2590,12 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_path(int p_lay } // Fills in the constraint list from existing tiles. - for (TerrainConstraint c : _get_terrain_constraints_from_cells_list(p_layer, can_modify_set, p_terrain_set, p_ignore_empty_terrains)) { + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { constraints.insert(c); } // Fill the terrains. - output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); return output; } @@ -2555,10 +2607,12 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ // Build list and set of tiles that can be modified (painted and their surroundings). Vector<Vector2i> can_modify_list; RBSet<Vector2i> can_modify_set; + RBSet<Vector2i> painted_set; for (int i = p_coords_array.size() - 1; i >= 0; i--) { const Vector2i &coords = p_coords_array[i]; can_modify_list.push_back(coords); can_modify_set.insert(coords); + painted_set.insert(coords); } for (Vector2i coords : p_coords_array) { // Find the adequate neighbor @@ -2588,12 +2642,12 @@ HashMap<Vector2i, TileSet::TerrainsPattern> TileMap::terrain_fill_pattern(int p_ } // Fills in the constraint list from modified tiles border. - for (TerrainConstraint c : _get_terrain_constraints_from_cells_list(p_layer, can_modify_set, p_terrain_set, p_ignore_empty_terrains)) { + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(p_layer, painted_set, p_terrain_set, p_ignore_empty_terrains)) { constraints.insert(c); } // Fill the terrains. - output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + output = terrain_fill_constraints(p_layer, can_modify_list, p_terrain_set, constraints); return output; } @@ -2602,14 +2656,38 @@ void TileMap::set_cells_terrain_connect(int p_layer, TypedArray<Vector2i> p_cell ERR_FAIL_INDEX(p_layer, (int)layers.size()); ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); - Vector<Vector2i> vector_cells; + Vector<Vector2i> cells_vector; + HashSet<Vector2i> painted_set; for (int i = 0; i < p_cells.size(); i++) { - vector_cells.push_back(p_cells[i]); - } - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(p_layer, vector_cells, p_terrain_set, p_terrain, p_ignore_empty_terrains); - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E : terrain_fill_output) { - TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); - set_cell(p_layer, E.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + cells_vector.push_back(p_cells[i]); + painted_set.insert(p_cells[i]); + } + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(p_layer, cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains); + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { + if (painted_set.has(kv.key)) { + // Paint a random tile with the correct terrain for the painted path. + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = get_cell(p_layer, kv.key); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + in_map_terrain_pattern = tile_data->get_terrains_pattern(); + } + } + } + if (in_map_terrain_pattern != kv.value) { + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } + } } } @@ -2619,13 +2697,38 @@ void TileMap::set_cells_terrain_path(int p_layer, TypedArray<Vector2i> p_path, i ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); Vector<Vector2i> vector_path; + HashSet<Vector2i> painted_set; for (int i = 0; i < p_path.size(); i++) { vector_path.push_back(p_path[i]); + painted_set.insert(p_path[i]); } + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(p_layer, vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E : terrain_fill_output) { - TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); - set_cell(p_layer, E.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { + if (painted_set.has(kv.key)) { + // Paint a random tile with the correct terrain for the painted path. + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = get_cell(p_layer, kv.key); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + in_map_terrain_pattern = tile_data->get_terrains_pattern(); + } + } + } + if (in_map_terrain_pattern != kv.value) { + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(p_layer, kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } + } } } @@ -2794,9 +2897,9 @@ Vector<int> TileMap::_get_tile_data(int p_layer) const { // Export tile data to raw format const HashMap<Vector2i, TileMapCell> &tile_map = layers[p_layer].tile_map; - Vector<int> data; - data.resize(tile_map.size() * 3); - int *w = data.ptrw(); + Vector<int> tile_data; + tile_data.resize(tile_map.size() * 3); + int *w = tile_data.ptrw(); // Save in highest format @@ -2812,7 +2915,7 @@ Vector<int> TileMap::_get_tile_data(int p_layer) const { idx += 3; } - return data; + return tile_data; } void TileMap::_build_runtime_update_tile_data(SelfList<TileMapQuadrant>::List &r_dirty_quadrant_list) { @@ -3615,7 +3718,7 @@ TypedArray<Vector2i> TileMap::get_used_cells(int p_layer) const { return a; } -Rect2 TileMap::get_used_rect() { // Not const because of cache +Rect2i TileMap::get_used_rect() { // Not const because of cache // Return the rect of the currently used area if (used_rect_cache_dirty) { bool first = true; @@ -3692,13 +3795,14 @@ void TileMap::set_use_parent_material(bool p_use_parent_material) { } void TileMap::set_texture_filter(TextureFilter p_texture_filter) { - // Set a default texture filter for the whole tilemap + // Set a default texture filter for the whole tilemap. CanvasItem::set_texture_filter(p_texture_filter); + TextureFilter target_filter = get_texture_filter_in_tree(); for (unsigned int layer = 0; layer < layers.size(); layer++) { for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = layers[layer].quadrant_map.begin(); F; ++F) { TileMapQuadrant &q = F->value; for (const RID &ci : q.canvas_items) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(p_texture_filter)); + RenderingServer::get_singleton()->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(target_filter)); _make_quadrant_dirty(F); } } @@ -3707,13 +3811,14 @@ void TileMap::set_texture_filter(TextureFilter p_texture_filter) { } void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { - // Set a default texture repeat for the whole tilemap + // Set a default texture repeat for the whole tilemap. CanvasItem::set_texture_repeat(p_texture_repeat); + TextureRepeat target_repeat = get_texture_repeat_in_tree(); for (unsigned int layer = 0; layer < layers.size(); layer++) { for (HashMap<Vector2i, TileMapQuadrant>::Iterator F = layers[layer].quadrant_map.begin(); F; ++F) { TileMapQuadrant &q = F->value; for (const RID &ci : q.canvas_items) { - RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(p_texture_repeat)); + RenderingServer::get_singleton()->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(target_repeat)); _make_quadrant_dirty(F); } } @@ -3819,8 +3924,8 @@ void TileMap::draw_cells_outline(Control *p_control, RBSet<Vector2i> p_cells, Co #undef DRAW_SIDE_IF_NEEDED } -TypedArray<String> TileMap::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray TileMap::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); // Retrieve the set of Z index values with a Y-sorted layer. RBSet<int> y_sorted_z_index; @@ -3951,9 +4056,5 @@ TileMap::TileMap() { } TileMap::~TileMap() { - if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); - } - _clear_internals(); } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index a819eeab71..eaf100631e 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -68,7 +68,7 @@ struct TileMapQuadrant { // Rendering. List<RID> canvas_items; - List<RID> occluders; + HashMap<Vector2i, RID> occluders; // Physics. List<RID> bodies; @@ -115,7 +115,7 @@ public: class TerrainConstraint { private: const TileMap *tile_map; - Vector2i base_cell_coords = Vector2i(); + Vector2i base_cell_coords; int bit = -1; int terrain = -1; @@ -266,9 +266,9 @@ private: void _scenes_draw_quadrant_debug(TileMapQuadrant *p_quadrant); // Terrains. - TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, RBSet<TerrainConstraint> p_constraints); + TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, RBSet<TerrainConstraint> p_constraints, TileSet::TerrainsPattern p_current_pattern); RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(Vector2i p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; - RBSet<TerrainConstraint> _get_terrain_constraints_from_cells_list(int p_layer, const RBSet<Vector2i> &p_on_map, int p_terrain_set, bool p_ignore_empty_terrains) const; + RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(int p_layer, const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; // Set and get tiles from data arrays. void _set_tile_data(int p_layer, const Vector<int> &p_data); @@ -352,7 +352,7 @@ public: void set_pattern(int p_layer, Vector2i p_position, const Ref<TileMapPattern> p_pattern); // Terrains. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> p_constraints); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(int p_layer, const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> p_constraints); // Not exposed. HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(int p_layer, const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. @@ -375,7 +375,7 @@ public: Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const; TypedArray<Vector2i> get_used_cells(int p_layer) const; - Rect2 get_used_rect(); // Not const because of cache + Rect2i get_used_rect(); // Not const because of cache // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems virtual void set_light_mask(int p_light_mask) override; @@ -406,7 +406,7 @@ public: GDVIRTUAL3(_tile_data_runtime_update, int, Vector2i, TileData *); // Configuration warnings. - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; TileMap(); ~TileMap(); diff --git a/scene/3d/area_3d.cpp b/scene/3d/area_3d.cpp index cefa9eceff..c6433e773d 100644 --- a/scene/3d/area_3d.cpp +++ b/scene/3d/area_3d.cpp @@ -578,11 +578,11 @@ bool Area3D::is_using_reverb_bus() const { return use_reverb_bus; } -void Area3D::set_reverb_bus(const StringName &p_audio_bus) { +void Area3D::set_reverb_bus_name(const StringName &p_audio_bus) { reverb_bus = p_audio_bus; } -StringName Area3D::get_reverb_bus() const { +StringName Area3D::get_reverb_bus_name() const { for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { if (AudioServer::get_singleton()->get_bus_name(i) == reverb_bus) { return reverb_bus; @@ -711,8 +711,8 @@ void Area3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_reverb_bus", "enable"), &Area3D::set_use_reverb_bus); ClassDB::bind_method(D_METHOD("is_using_reverb_bus"), &Area3D::is_using_reverb_bus); - ClassDB::bind_method(D_METHOD("set_reverb_bus", "name"), &Area3D::set_reverb_bus); - ClassDB::bind_method(D_METHOD("get_reverb_bus"), &Area3D::get_reverb_bus); + ClassDB::bind_method(D_METHOD("set_reverb_bus_name", "name"), &Area3D::set_reverb_bus_name); + ClassDB::bind_method(D_METHOD("get_reverb_bus_name"), &Area3D::get_reverb_bus_name); ClassDB::bind_method(D_METHOD("set_reverb_amount", "amount"), &Area3D::set_reverb_amount); ClassDB::bind_method(D_METHOD("get_reverb_amount"), &Area3D::get_reverb_amount); @@ -760,8 +760,8 @@ void Area3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "audio_bus_name", PROPERTY_HINT_ENUM, ""), "set_audio_bus_name", "get_audio_bus_name"); ADD_GROUP("Reverb Bus", "reverb_bus_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverb_bus_enable"), "set_use_reverb_bus", "is_using_reverb_bus"); - ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "reverb_bus_name", PROPERTY_HINT_ENUM, ""), "set_reverb_bus", "get_reverb_bus"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverb_bus_enabled"), "set_use_reverb_bus", "is_using_reverb_bus"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "reverb_bus_name", PROPERTY_HINT_ENUM, ""), "set_reverb_bus_name", "get_reverb_bus_name"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "reverb_bus_amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_reverb_amount", "get_reverb_amount"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "reverb_bus_uniformity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_reverb_uniformity", "get_reverb_uniformity"); diff --git a/scene/3d/area_3d.h b/scene/3d/area_3d.h index 0f0bcc7ce0..195f9f0d9e 100644 --- a/scene/3d/area_3d.h +++ b/scene/3d/area_3d.h @@ -141,13 +141,12 @@ private: float reverb_amount = 0.0; float reverb_uniformity = 0.0; - void _validate_property(PropertyInfo &p_property) const; - void _initialize_wind(); protected: void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; public: void set_gravity_space_override_mode(SpaceOverride p_mode); @@ -216,8 +215,8 @@ public: void set_use_reverb_bus(bool p_enable); bool is_using_reverb_bus() const; - void set_reverb_bus(const StringName &p_audio_bus); - StringName get_reverb_bus() const; + void set_reverb_bus_name(const StringName &p_audio_bus); + StringName get_reverb_bus_name() const; void set_reverb_amount(float p_amount); float get_reverb_amount() const; diff --git a/scene/3d/audio_listener_3d.cpp b/scene/3d/audio_listener_3d.cpp index 4f3f403ab7..8836244f3b 100644 --- a/scene/3d/audio_listener_3d.cpp +++ b/scene/3d/audio_listener_3d.cpp @@ -138,8 +138,6 @@ bool AudioListener3D::is_current() const { } else { return current; } - - return false; } void AudioListener3D::_bind_methods() { diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 21cf3bdb3b..b9d95672fe 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -231,7 +231,7 @@ float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { } } - att += unit_db; + att += volume_db; if (att > max_db) { att = max_db; } @@ -485,7 +485,7 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() { } if (area->is_using_reverb_bus()) { - StringName reverb_bus_name = area->get_reverb_bus(); + StringName reverb_bus_name = area->get_reverb_bus_name(); Vector<AudioFrame> reverb_vol; _calc_reverb_vol(area, listener_area_pos, output_volume_vector, reverb_vol); bus_volumes[reverb_bus_name] = reverb_vol; @@ -538,12 +538,12 @@ Ref<AudioStream> AudioStreamPlayer3D::get_stream() const { return stream; } -void AudioStreamPlayer3D::set_unit_db(float p_volume) { - unit_db = p_volume; +void AudioStreamPlayer3D::set_volume_db(float p_volume) { + volume_db = p_volume; } -float AudioStreamPlayer3D::get_unit_db() const { - return unit_db; +float AudioStreamPlayer3D::get_volume_db() const { + return volume_db; } void AudioStreamPlayer3D::set_unit_size(float p_volume) { @@ -810,8 +810,8 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer3D::set_stream); ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer3D::get_stream); - ClassDB::bind_method(D_METHOD("set_unit_db", "unit_db"), &AudioStreamPlayer3D::set_unit_db); - ClassDB::bind_method(D_METHOD("get_unit_db"), &AudioStreamPlayer3D::get_unit_db); + ClassDB::bind_method(D_METHOD("set_volume_db", "volume_db"), &AudioStreamPlayer3D::set_volume_db); + ClassDB::bind_method(D_METHOD("get_volume_db"), &AudioStreamPlayer3D::get_volume_db); ClassDB::bind_method(D_METHOD("set_unit_size", "unit_size"), &AudioStreamPlayer3D::set_unit_size); ClassDB::bind_method(D_METHOD("get_unit_size"), &AudioStreamPlayer3D::get_unit_size); @@ -878,7 +878,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,Inverse Square,Logarithmic,Disabled"), "set_attenuation_model", "get_attenuation_model"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_db", PROPERTY_HINT_RANGE, "-80,80,suffix:dB"), "set_unit_db", "get_unit_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,80,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); @@ -916,7 +916,7 @@ AudioStreamPlayer3D::AudioStreamPlayer3D() { velocity_tracker.instantiate(); AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer3D::_bus_layout_changed)); set_disable_scale(true); - cached_global_panning_strength = ProjectSettings::get_singleton()->get("audio/general/3d_panning_strength"); + cached_global_panning_strength = GLOBAL_GET("audio/general/3d_panning_strength"); } AudioStreamPlayer3D::~AudioStreamPlayer3D() { diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index ef48269544..806b250ba7 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -71,7 +71,7 @@ private: SafeNumeric<float> setplay{ -1.0 }; AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE; - float unit_db = 0.0; + float volume_db = 0.0; float unit_size = 10.0; float max_db = 3.0; float pitch_scale = 1.0; @@ -117,7 +117,7 @@ private: float _get_attenuation_db(float p_distance) const; float panning_strength = 1.0f; - float cached_global_panning_strength = 1.0f; + float cached_global_panning_strength = 0.5f; protected: void _validate_property(PropertyInfo &p_property) const; @@ -128,8 +128,8 @@ public: void set_stream(Ref<AudioStream> p_stream); Ref<AudioStream> get_stream() const; - void set_unit_db(float p_volume); - float get_unit_db() const; + void set_volume_db(float p_volume); + float get_volume_db() const; void set_unit_size(float p_volume); float get_unit_size() const; diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index b3ff6497a7..d8524a7392 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -91,7 +91,7 @@ bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const { void BoneAttachment3D::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, "override_pose", PROPERTY_HINT_NONE, "")); if (override_pose) { - p_list->push_back(PropertyInfo(Variant::INT, "override_mode", PROPERTY_HINT_ENUM, "Global Pose Override, Local Pose Override, Custom Pose")); + p_list->push_back(PropertyInfo(Variant::INT, "override_mode", PROPERTY_HINT_ENUM, "Global Pose Override,Local Pose Override,Custom Pose")); } p_list->push_back(PropertyInfo(Variant::BOOL, "use_external_skeleton", PROPERTY_HINT_NONE, "")); @@ -100,8 +100,8 @@ void BoneAttachment3D::_get_property_list(List<PropertyInfo> *p_list) const { } } -TypedArray<String> BoneAttachment3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node3D::get_configuration_warnings(); +PackedStringArray BoneAttachment3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (use_external_skeleton) { if (external_skeleton_node_cache.is_null()) { diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index f85053e614..2db6ba6268 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -76,7 +76,7 @@ protected: #endif // TOOLS_ENABLED public: - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_bone_name(const String &p_name); String get_bone_name() const; diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index f5e3e8b015..ca23fe03a2 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -150,13 +150,13 @@ uint32_t CollisionObject3D::get_collision_mask() const { void CollisionObject3D::set_collision_layer_value(int p_layer_number, bool p_value) { ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); - uint32_t collision_layer = get_collision_layer(); + uint32_t collision_layer_new = get_collision_layer(); if (p_value) { - collision_layer |= 1 << (p_layer_number - 1); + collision_layer_new |= 1 << (p_layer_number - 1); } else { - collision_layer &= ~(1 << (p_layer_number - 1)); + collision_layer_new &= ~(1 << (p_layer_number - 1)); } - set_collision_layer(collision_layer); + set_collision_layer(collision_layer_new); } bool CollisionObject3D::get_collision_layer_value(int p_layer_number) const { @@ -330,7 +330,7 @@ bool CollisionObject3D::_are_collision_shapes_visible() { void CollisionObject3D::_update_shape_data(uint32_t p_owner) { if (_are_collision_shapes_visible()) { if (debug_shapes_to_update.is_empty()) { - callable_mp(this, &CollisionObject3D::_update_debug_shapes).call_deferredp({}, 0); + callable_mp(this, &CollisionObject3D::_update_debug_shapes).call_deferred(); } debug_shapes_to_update.insert(p_owner); } @@ -339,9 +339,9 @@ void CollisionObject3D::_update_shape_data(uint32_t p_owner) { void CollisionObject3D::_shape_changed(const Ref<Shape3D> &p_shape) { for (KeyValue<uint32_t, ShapeData> &E : shapes) { ShapeData &shapedata = E.value; - ShapeData::ShapeBase *shapes = shapedata.shapes.ptrw(); + ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptrw(); for (int i = 0; i < shapedata.shapes.size(); i++) { - ShapeData::ShapeBase &s = shapes[i]; + ShapeData::ShapeBase &s = shape_bases[i]; if (s.shape == p_shape && s.debug_shape.is_valid()) { Ref<Mesh> mesh = s.shape->get_debug_mesh(); RS::get_singleton()->instance_set_base(s.debug_shape, mesh->get_rid()); @@ -359,9 +359,9 @@ void CollisionObject3D::_update_debug_shapes() { for (const uint32_t &shapedata_idx : debug_shapes_to_update) { if (shapes.has(shapedata_idx)) { ShapeData &shapedata = shapes[shapedata_idx]; - ShapeData::ShapeBase *shapes = shapedata.shapes.ptrw(); + ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptrw(); for (int i = 0; i < shapedata.shapes.size(); i++) { - ShapeData::ShapeBase &s = shapes[i]; + ShapeData::ShapeBase &s = shape_bases[i]; if (s.shape.is_null() || shapedata.disabled) { if (s.debug_shape.is_valid()) { RS::get_singleton()->free(s.debug_shape); @@ -394,9 +394,9 @@ void CollisionObject3D::_update_debug_shapes() { void CollisionObject3D::_clear_debug_shapes() { for (KeyValue<uint32_t, ShapeData> &E : shapes) { ShapeData &shapedata = E.value; - ShapeData::ShapeBase *shapes = shapedata.shapes.ptrw(); + ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptrw(); for (int i = 0; i < shapedata.shapes.size(); i++) { - ShapeData::ShapeBase &s = shapes[i]; + ShapeData::ShapeBase &s = shape_bases[i]; if (s.debug_shape.is_valid()) { RS::get_singleton()->free(s.debug_shape); s.debug_shape = RID(); @@ -417,9 +417,9 @@ void CollisionObject3D::_on_transform_changed() { if (shapedata.disabled) { continue; // If disabled then there are no debug shapes to update. } - const ShapeData::ShapeBase *shapes = shapedata.shapes.ptr(); + const ShapeData::ShapeBase *shape_bases = shapedata.shapes.ptr(); for (int i = 0; i < shapedata.shapes.size(); i++) { - RS::get_singleton()->instance_set_transform(shapes[i].debug_shape, debug_shape_old_transform * shapedata.xform); + RS::get_singleton()->instance_set_transform(shape_bases[i].debug_shape, debug_shape_old_transform * shapedata.xform); } } } @@ -703,8 +703,8 @@ bool CollisionObject3D::get_capture_input_on_drag() const { return capture_input_on_drag; } -TypedArray<String> CollisionObject3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionObject3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape.")); diff --git a/scene/3d/collision_object_3d.h b/scene/3d/collision_object_3d.h index c638be9d90..1406e6c698 100644 --- a/scene/3d/collision_object_3d.h +++ b/scene/3d/collision_object_3d.h @@ -164,7 +164,7 @@ public: _FORCE_INLINE_ RID get_rid() const { return rid; } - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CollisionObject3D(); ~CollisionObject3D(); diff --git a/scene/3d/collision_polygon_3d.cpp b/scene/3d/collision_polygon_3d.cpp index 90099d787b..81b2c85de4 100644 --- a/scene/3d/collision_polygon_3d.cpp +++ b/scene/3d/collision_polygon_3d.cpp @@ -167,8 +167,8 @@ void CollisionPolygon3D::set_margin(real_t p_margin) { } } -TypedArray<String> CollisionPolygon3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionPolygon3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject3D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node. Please only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape.")); diff --git a/scene/3d/collision_polygon_3d.h b/scene/3d/collision_polygon_3d.h index 74e5867a2f..bbcea539b2 100644 --- a/scene/3d/collision_polygon_3d.h +++ b/scene/3d/collision_polygon_3d.h @@ -74,7 +74,7 @@ public: real_t get_margin() const; void set_margin(real_t p_margin); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CollisionPolygon3D(); }; diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index a9bc28b464..ef381641ab 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -62,9 +62,9 @@ void CollisionShape3D::make_convex_from_siblings() { } } - Ref<ConvexPolygonShape3D> shape = memnew(ConvexPolygonShape3D); - shape->set_points(vertices); - set_shape(shape); + Ref<ConvexPolygonShape3D> shape_new = memnew(ConvexPolygonShape3D); + shape_new->set_points(vertices); + set_shape(shape_new); } void CollisionShape3D::_update_in_shape_owner(bool p_xform_only) { @@ -114,8 +114,8 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) { update_gizmos(); } -TypedArray<String> CollisionShape3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray CollisionShape3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<CollisionObject3D>(get_parent())) { warnings.push_back(RTR("CollisionShape3D only serves to provide a collision shape to a CollisionObject3D derived node. Please only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape.")); diff --git a/scene/3d/collision_shape_3d.h b/scene/3d/collision_shape_3d.h index 124c0d166d..70653daa19 100644 --- a/scene/3d/collision_shape_3d.h +++ b/scene/3d/collision_shape_3d.h @@ -62,7 +62,7 @@ public: void set_disabled(bool p_disabled); bool is_disabled() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; CollisionShape3D(); ~CollisionShape3D(); diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index d7bf76a6f6..5ac8535bb6 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -188,8 +188,8 @@ bool CPUParticles3D::get_fractional_delta() const { return fractional_delta; } -TypedArray<String> CPUParticles3D::get_configuration_warnings() const { - TypedArray<String> warnings = GeometryInstance3D::get_configuration_warnings(); +PackedStringArray CPUParticles3D::get_configuration_warnings() const { + PackedStringArray warnings = GeometryInstance3D::get_configuration_warnings(); bool mesh_found = false; bool anim_material_found = false; @@ -867,7 +867,7 @@ void CPUParticles3D::_particles_process(double p_delta) { real_t ring_random_angle = Math::randf() * Math_TAU; real_t ring_random_radius = Math::randf() * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius; Vector3 axis = emission_ring_axis.normalized(); - Vector3 ortho_axis = Vector3(); + Vector3 ortho_axis; if (axis == Vector3(1.0, 0.0, 0.0)) { ortho_axis = Vector3(0.0, 1.0, 0.0).cross(axis); } else { @@ -1326,24 +1326,24 @@ void CPUParticles3D::_notification(int p_what) { } void CPUParticles3D::convert_from_particles(Node *p_particles) { - GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_particles); - ERR_FAIL_COND_MSG(!particles, "Only GPUParticles3D nodes can be converted to CPUParticles3D."); - - set_emitting(particles->is_emitting()); - set_amount(particles->get_amount()); - set_lifetime(particles->get_lifetime()); - set_one_shot(particles->get_one_shot()); - set_pre_process_time(particles->get_pre_process_time()); - set_explosiveness_ratio(particles->get_explosiveness_ratio()); - set_randomness_ratio(particles->get_randomness_ratio()); - set_use_local_coordinates(particles->get_use_local_coordinates()); - set_fixed_fps(particles->get_fixed_fps()); - set_fractional_delta(particles->get_fractional_delta()); - set_speed_scale(particles->get_speed_scale()); - set_draw_order(DrawOrder(particles->get_draw_order())); - set_mesh(particles->get_draw_pass_mesh(0)); - - Ref<ParticleProcessMaterial> material = particles->get_process_material(); + GPUParticles3D *gpu_particles = Object::cast_to<GPUParticles3D>(p_particles); + ERR_FAIL_COND_MSG(!gpu_particles, "Only GPUParticles3D nodes can be converted to CPUParticles3D."); + + set_emitting(gpu_particles->is_emitting()); + set_amount(gpu_particles->get_amount()); + set_lifetime(gpu_particles->get_lifetime()); + set_one_shot(gpu_particles->get_one_shot()); + set_pre_process_time(gpu_particles->get_pre_process_time()); + set_explosiveness_ratio(gpu_particles->get_explosiveness_ratio()); + set_randomness_ratio(gpu_particles->get_randomness_ratio()); + set_use_local_coordinates(gpu_particles->get_use_local_coordinates()); + set_fixed_fps(gpu_particles->get_fixed_fps()); + set_fractional_delta(gpu_particles->get_fractional_delta()); + set_speed_scale(gpu_particles->get_speed_scale()); + set_draw_order(DrawOrder(gpu_particles->get_draw_order())); + set_mesh(gpu_particles->get_draw_pass_mesh(0)); + + Ref<ParticleProcessMaterial> material = gpu_particles->get_process_material(); if (material.is_null()) { return; } diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index d84b0aedd2..6b70137736 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -156,7 +156,7 @@ private: real_t spread = 45.0; real_t flatness = 0.0; - real_t parameters_min[PARAM_MAX]; + real_t parameters_min[PARAM_MAX] = {}; real_t parameters_max[PARAM_MAX] = {}; Ref<Curve> curve_parameters[PARAM_MAX]; @@ -302,7 +302,7 @@ public: void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void restart(); diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 460402ad1d..fc442986a8 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -158,8 +158,8 @@ void Decal::_validate_property(PropertyInfo &p_property) const { } } -TypedArray<String> Decal::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray Decal::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (textures[TEXTURE_ALBEDO].is_null() && textures[TEXTURE_NORMAL].is_null() && textures[TEXTURE_ORM].is_null() && textures[TEXTURE_EMISSION].is_null()) { warnings.push_back(RTR("The decal has no textures loaded into any of its texture properties, and will therefore not be visible.")); diff --git a/scene/3d/decal.h b/scene/3d/decal.h index 1a7d55b108..ab39350b75 100644 --- a/scene/3d/decal.h +++ b/scene/3d/decal.h @@ -65,7 +65,7 @@ protected: void _validate_property(PropertyInfo &p_property) const; public: - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_extents(const Vector3 &p_extents); Vector3 get_extents() const; diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index cfee7028d4..4606e70310 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -99,8 +99,8 @@ AABB FogVolume::get_aabb() const { return AABB(); } -TypedArray<String> FogVolume::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray FogVolume::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment(); diff --git a/scene/3d/fog_volume.h b/scene/3d/fog_volume.h index fcdc1e2807..d79836be0e 100644 --- a/scene/3d/fog_volume.h +++ b/scene/3d/fog_volume.h @@ -62,7 +62,7 @@ public: Ref<Material> get_material() const; virtual AABB get_aabb() const override; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; FogVolume(); ~FogVolume(); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index bd63939d74..17dfe2610e 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -176,22 +176,22 @@ void GPUParticles3D::set_draw_order(DrawOrder p_order) { void GPUParticles3D::set_trail_enabled(bool p_enabled) { trail_enabled = p_enabled; - RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); + RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime); update_configuration_warnings(); } -void GPUParticles3D::set_trail_length(double p_seconds) { +void GPUParticles3D::set_trail_lifetime(double p_seconds) { ERR_FAIL_COND(p_seconds < 0.001); - trail_length = p_seconds; - RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_length); + trail_lifetime = p_seconds; + RS::get_singleton()->particles_set_trails(particles, trail_enabled, trail_lifetime); } bool GPUParticles3D::is_trail_enabled() const { return trail_enabled; } -double GPUParticles3D::get_trail_length() const { - return trail_length; +double GPUParticles3D::get_trail_lifetime() const { + return trail_lifetime; } GPUParticles3D::DrawOrder GPUParticles3D::get_draw_order() const { @@ -269,12 +269,8 @@ bool GPUParticles3D::get_interpolate() const { return interpolate; } -TypedArray<String> GPUParticles3D::get_configuration_warnings() const { - TypedArray<String> warnings = GeometryInstance3D::get_configuration_warnings(); - - if (RenderingServer::get_singleton()->is_low_end()) { - warnings.push_back(RTR("GPU-based particles are not supported by the OpenGL video driver.\nUse the CPUParticles3D node instead. You can use the \"Convert to CPUParticles3D\" option for this purpose.")); - } +PackedStringArray GPUParticles3D::get_configuration_warnings() const { + PackedStringArray warnings = GeometryInstance3D::get_configuration_warnings(); bool meshes_found = false; bool anim_material_found = false; @@ -552,10 +548,10 @@ void GPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("emit_particle", "xform", "velocity", "color", "custom", "flags"), &GPUParticles3D::emit_particle); ClassDB::bind_method(D_METHOD("set_trail_enabled", "enabled"), &GPUParticles3D::set_trail_enabled); - ClassDB::bind_method(D_METHOD("set_trail_length", "secs"), &GPUParticles3D::set_trail_length); + ClassDB::bind_method(D_METHOD("set_trail_lifetime", "secs"), &GPUParticles3D::set_trail_lifetime); ClassDB::bind_method(D_METHOD("is_trail_enabled"), &GPUParticles3D::is_trail_enabled); - ClassDB::bind_method(D_METHOD("get_trail_length"), &GPUParticles3D::get_trail_length); + ClassDB::bind_method(D_METHOD("get_trail_lifetime"), &GPUParticles3D::get_trail_lifetime); ClassDB::bind_method(D_METHOD("set_transform_align", "align"), &GPUParticles3D::set_transform_align); ClassDB::bind_method(D_METHOD("get_transform_align"), &GPUParticles3D::get_transform_align); @@ -583,7 +579,7 @@ void GPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_align", PROPERTY_HINT_ENUM, "Disabled,Z-Billboard,Y to Velocity,Z-Billboard + Y to Velocity"), "set_transform_align", "get_transform_align"); ADD_GROUP("Trails", "trail_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "trail_enabled"), "set_trail_enabled", "is_trail_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_length_secs", PROPERTY_HINT_RANGE, "0.01,10,0.01,suffix:s"), "set_trail_length", "get_trail_length"); + 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_GROUP("Process Material", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticleProcessMaterial"), "set_process_material", "get_process_material"); ADD_GROUP("Draw Passes", "draw_"); @@ -627,7 +623,7 @@ GPUParticles3D::GPUParticles3D() { set_pre_process_time(0); set_explosiveness_ratio(0); set_randomness_ratio(0); - set_trail_length(0.3); + set_trail_lifetime(0.3); set_visibility_aabb(AABB(Vector3(-4, -4, -4), Vector3(8, 8, 8))); set_use_local_coordinates(false); set_draw_passes(1); diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 2ad9672474..d1768436e5 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -76,13 +76,13 @@ private: real_t collision_base_size = 0.01; bool trail_enabled = false; - double trail_length = 0.3; + double trail_lifetime = 0.3; TransformAlign transform_align = TRANSFORM_ALIGN_DISABLED; Ref<Material> process_material; - DrawOrder draw_order; + DrawOrder draw_order = DRAW_ORDER_INDEX; Vector<Ref<Mesh>> draw_passes; Ref<Skin> skin; @@ -112,7 +112,7 @@ public: void set_speed_scale(double p_scale); void set_collision_base_size(real_t p_ratio); void set_trail_enabled(bool p_enabled); - void set_trail_length(double p_seconds); + void set_trail_lifetime(double p_seconds); bool is_emitting() const; int get_amount() const; @@ -127,7 +127,7 @@ public: double get_speed_scale() const; real_t get_collision_base_size() const; bool is_trail_enabled() const; - double get_trail_length() const; + double get_trail_lifetime() const; void set_fixed_fps(int p_count); int get_fixed_fps() const; @@ -147,7 +147,7 @@ public: void set_draw_pass_mesh(int p_pass, const Ref<Mesh> &p_mesh); Ref<Mesh> get_draw_pass_mesh(int p_pass) const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_sub_emitter(const NodePath &p_path); NodePath get_sub_emitter() const; diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index 24bfa7b6de..2c5df48b75 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -225,23 +225,23 @@ static _FORCE_INLINE_ real_t Vector3_dot2(const Vector3 &p_vec3) { return p_vec3.dot(p_vec3); } -void GPUParticlesCollisionSDF3D::_find_closest_distance(const Vector3 &p_pos, const BVH *bvh, uint32_t p_bvh_cell, const Face3 *triangles, float thickness, float &closest_distance) { +void GPUParticlesCollisionSDF3D::_find_closest_distance(const Vector3 &p_pos, const BVH *p_bvh, uint32_t p_bvh_cell, const Face3 *p_triangles, float p_thickness, float &r_closest_distance) { if (p_bvh_cell & BVH::LEAF_BIT) { p_bvh_cell &= BVH::LEAF_MASK; //remove bit Vector3 point = p_pos; - Plane p = triangles[p_bvh_cell].get_plane(); + Plane p = p_triangles[p_bvh_cell].get_plane(); float d = p.distance_to(point); float inside_d = 1e20; - if (d < 0 && d > -thickness) { + if (d < 0 && d > -p_thickness) { //inside planes, do this in 2D - Vector3 x_axis = (triangles[p_bvh_cell].vertex[0] - triangles[p_bvh_cell].vertex[1]).normalized(); + Vector3 x_axis = (p_triangles[p_bvh_cell].vertex[0] - p_triangles[p_bvh_cell].vertex[1]).normalized(); Vector3 y_axis = p.normal.cross(x_axis).normalized(); Vector2 points[3]; for (int i = 0; i < 3; i++) { - points[i] = Vector2(x_axis.dot(triangles[p_bvh_cell].vertex[i]), y_axis.dot(triangles[p_bvh_cell].vertex[i])); + points[i] = Vector2(x_axis.dot(p_triangles[p_bvh_cell].vertex[i]), y_axis.dot(p_triangles[p_bvh_cell].vertex[i])); } Vector2 p2d = Vector2(x_axis.dot(point), y_axis.dot(point)); @@ -270,19 +270,19 @@ void GPUParticlesCollisionSDF3D::_find_closest_distance(const Vector3 &p_pos, co //make sure distance to planes is not shorter if inside if (inside_d < 0) { inside_d = MAX(inside_d, d); - inside_d = MAX(inside_d, -(thickness + d)); + inside_d = MAX(inside_d, -(p_thickness + d)); } - closest_distance = MIN(closest_distance, inside_d); + r_closest_distance = MIN(r_closest_distance, inside_d); } else { if (d < 0) { - point -= p.normal * thickness; //flatten + point -= p.normal * p_thickness; //flatten } // https://iquilezles.org/www/articles/distfunctions/distfunctions.htm - Vector3 a = triangles[p_bvh_cell].vertex[0]; - Vector3 b = triangles[p_bvh_cell].vertex[1]; - Vector3 c = triangles[p_bvh_cell].vertex[2]; + Vector3 a = p_triangles[p_bvh_cell].vertex[0]; + Vector3 b = p_triangles[p_bvh_cell].vertex[1]; + Vector3 c = p_triangles[p_bvh_cell].vertex[2]; Vector3 ba = b - a; Vector3 pa = point - a; @@ -300,28 +300,28 @@ void GPUParticlesCollisionSDF3D::_find_closest_distance(const Vector3 &p_pos, co Vector3_dot2(ac * CLAMP(ac.dot(pc) / Vector3_dot2(ac), 0.0, 1.0) - pc)) : nor.dot(pa) * nor.dot(pa) / Vector3_dot2(nor)); - closest_distance = MIN(closest_distance, inside_d); + r_closest_distance = MIN(r_closest_distance, inside_d); } } else { bool pass = true; - if (!bvh[p_bvh_cell].bounds.has_point(p_pos)) { + if (!p_bvh[p_bvh_cell].bounds.has_point(p_pos)) { //outside, find closest point - Vector3 he = bvh[p_bvh_cell].bounds.size * 0.5; - Vector3 center = bvh[p_bvh_cell].bounds.position + he; + Vector3 he = p_bvh[p_bvh_cell].bounds.size * 0.5; + Vector3 center = p_bvh[p_bvh_cell].bounds.position + he; Vector3 rel = (p_pos - center).abs(); Vector3 closest(MIN(rel.x, he.x), MIN(rel.y, he.y), MIN(rel.z, he.z)); float d = rel.distance_to(closest); - if (d >= closest_distance) { + if (d >= r_closest_distance) { pass = false; //already closer than this aabb, discard } } if (pass) { - _find_closest_distance(p_pos, bvh, bvh[p_bvh_cell].children[0], triangles, thickness, closest_distance); - _find_closest_distance(p_pos, bvh, bvh[p_bvh_cell].children[1], triangles, thickness, closest_distance); + _find_closest_distance(p_pos, p_bvh, p_bvh[p_bvh_cell].children[0], p_triangles, p_thickness, r_closest_distance); + _find_closest_distance(p_pos, p_bvh, p_bvh[p_bvh_cell].children[1], p_triangles, p_thickness, r_closest_distance); } } } @@ -473,15 +473,15 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { _create_bvh(bvh, face_pos.ptr(), face_pos.size(), faces.ptr(), th); - Vector<uint8_t> data; - data.resize(sdf_size.z * sdf_size.y * sdf_size.x * (int)sizeof(float)); + Vector<uint8_t> cells_data; + cells_data.resize(sdf_size.z * sdf_size.y * sdf_size.x * (int)sizeof(float)); if (bake_step_function) { bake_step_function(0, "Baking SDF"); } ComputeSDFParams params; - params.cells = (float *)data.ptrw(); + params.cells = (float *)cells_data.ptrw(); params.size = sdf_size; params.cell_size = cell_size; params.cell_offset = aabb.position + Vector3(cell_size * 0.5, cell_size * 0.5, cell_size * 0.5); @@ -490,9 +490,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { params.thickness = th; _compute_sdf(¶ms); - Ref<Image> ret; - ret.instantiate(); - ret->create(sdf_size.x, sdf_size.y * sdf_size.z, false, Image::FORMAT_RF, data); + Ref<Image> ret = Image::create_from_data(sdf_size.x, sdf_size.y * sdf_size.z, false, Image::FORMAT_RF, cells_data); ret->convert(Image::FORMAT_RH); //convert to half, save space ret->set_meta("depth", sdf_size.z); //hack, make sure to add to the docs of this function @@ -503,8 +501,8 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { return ret; } -TypedArray<String> GPUParticlesCollisionSDF3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (bake_mask == 0) { warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property.")); @@ -532,7 +530,7 @@ void GPUParticlesCollisionSDF3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bake_mask_value", "layer_number"), &GPUParticlesCollisionSDF3D::get_bake_mask_value); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "16,32,64,128,256,512,suffix:px"), "set_resolution", "get_resolution"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "16,32,64,128,256,512"), "set_resolution", "get_resolution"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "thickness", PROPERTY_HINT_RANGE, "0.0,2.0,0.01,suffix:m"), "set_thickness", "get_thickness"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_bake_mask", "get_bake_mask"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_texture", "get_texture"); diff --git a/scene/3d/gpu_particles_collision_3d.h b/scene/3d/gpu_particles_collision_3d.h index 712bd015ff..25b111c8ff 100644 --- a/scene/3d/gpu_particles_collision_3d.h +++ b/scene/3d/gpu_particles_collision_3d.h @@ -154,7 +154,7 @@ private: float thickness = 0.0; }; - void _find_closest_distance(const Vector3 &p_pos, const BVH *bvh, uint32_t p_bvh_cell, const Face3 *triangles, float thickness, float &closest_distance); + void _find_closest_distance(const Vector3 &p_pos, const BVH *p_bvh, uint32_t p_bvh_cell, const Face3 *p_triangles, float p_thickness, float &r_closest_distance); void _compute_sdf_z(uint32_t p_z, ComputeSDFParams *params); void _compute_sdf(ComputeSDFParams *params); @@ -162,7 +162,7 @@ protected: static void _bind_methods(); public: - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_thickness(float p_thickness); float get_thickness() const; diff --git a/scene/3d/joint_3d.cpp b/scene/3d/joint_3d.cpp index 7dc094062b..1a18f43e7b 100644 --- a/scene/3d/joint_3d.cpp +++ b/scene/3d/joint_3d.cpp @@ -198,8 +198,8 @@ bool Joint3D::get_exclude_nodes_from_collision() const { return exclude_from_collision; } -TypedArray<String> Joint3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node3D::get_configuration_warnings(); +PackedStringArray Joint3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!warning.is_empty()) { warnings.push_back(warning); diff --git a/scene/3d/joint_3d.h b/scene/3d/joint_3d.h index cb967023e8..5af427446f 100644 --- a/scene/3d/joint_3d.h +++ b/scene/3d/joint_3d.h @@ -63,7 +63,7 @@ protected: _FORCE_INLINE_ bool is_configured() const { return configured; } public: - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_node_a(const NodePath &p_node_a); NodePath get_node_a() const; diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index d977874911..262dc0db37 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -283,14 +283,14 @@ Ref<TriangleMesh> Label3D::generate_triangle_mesh() const { return Ref<TriangleMesh>(); } - real_t pixel_size = get_pixel_size(); + real_t px_size = get_pixel_size(); Vector2 vertices[4] = { - (final_rect.position + Vector2(0, -final_rect.size.y)) * pixel_size, - (final_rect.position + Vector2(final_rect.size.x, -final_rect.size.y)) * pixel_size, - (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size, - final_rect.position * pixel_size, + (final_rect.position + Vector2(0, -final_rect.size.y)) * px_size, + (final_rect.position + Vector2(final_rect.size.x, -final_rect.size.y)) * px_size, + (final_rect.position + Vector2(final_rect.size.x, 0)) * px_size, + final_rect.position * px_size, }; @@ -436,17 +436,17 @@ void Label3D::_shape() { TS->shaped_text_clear(text_rid); TS->shaped_text_set_direction(text_rid, text_direction); - String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, font->get_opentype_features(), language); + String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language); for (int i = 0; i < TextServer::SPACING_MAX; i++) { TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); } TypedArray<Vector2i> stt; if (st_parser == TextServer::STRUCTURED_TEXT_CUSTOM) { - GDVIRTUAL_CALL(_structured_text_parser, st_args, text, stt); + GDVIRTUAL_CALL(_structured_text_parser, st_args, txt, stt); } else { - stt = TS->parse_structured_text(st_parser, st_args, text); + stt = TS->parse_structured_text(st_parser, st_args, txt); } TS->shaped_text_set_bidi_override(text_rid, stt); diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index e51f06e083..198dba7811 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -165,6 +165,16 @@ AABB Light3D::get_aabb() const { return AABB(); } +PackedStringArray Light3D::get_configuration_warnings() const { + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); + + if (!get_scale().is_equal_approx(Vector3(1, 1, 1))) { + warnings.push_back(RTR("A light's scale does not affect the visual size of the light.")); + } + + return warnings; +} + void Light3D::set_bake_mode(BakeMode p_mode) { bake_mode = p_mode; RS::get_singleton()->light_set_bake_mode(light, RS::LightBakeMode(p_mode)); @@ -578,8 +588,8 @@ OmniLight3D::ShadowMode OmniLight3D::get_shadow_mode() const { return shadow_mode; } -TypedArray<String> OmniLight3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray OmniLight3D::get_configuration_warnings() const { + PackedStringArray warnings = Light3D::get_configuration_warnings(); if (!has_shadow() && get_projector().is_valid()) { warnings.push_back(RTR("Projector texture only works with shadows active.")); @@ -608,8 +618,8 @@ OmniLight3D::OmniLight3D() : set_param(PARAM_SHADOW_BIAS, 0.2); } -TypedArray<String> SpotLight3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray SpotLight3D::get_configuration_warnings() const { + PackedStringArray warnings = Light3D::get_configuration_warnings(); if (has_shadow() && get_param(PARAM_SPOT_ANGLE) >= 90.0) { warnings.push_back(RTR("A SpotLight3D with an angle wider than 90 degrees cannot cast shadows.")); diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index e43d6f0419..84d214030b 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -147,6 +147,7 @@ public: Color get_correlated_color() const; virtual AABB get_aabb() const override; + virtual PackedStringArray get_configuration_warnings() const override; Light3D(); ~Light3D(); @@ -216,7 +217,7 @@ public: void set_shadow_mode(ShadowMode p_mode); ShadowMode get_shadow_mode() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; OmniLight3D(); }; @@ -230,7 +231,7 @@ protected: static void _bind_methods(); public: - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; SpotLight3D() : Light3D(RenderingServer::LIGHT_SPOT) {} diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 555884f445..1b5427c80d 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -145,10 +145,7 @@ Array LightmapGIData::_get_light_textures_data() const { for (int i = 0; i < texture_count; i++) { int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture; - Ref<Image> texture_image; - texture_image.instantiate(); - - texture_image->create(slice_width, slice_height * texture_slice_count, false, images[0]->get_format()); + Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format()); for (int j = 0; j < texture_slice_count; j++) { texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j)); @@ -818,7 +815,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } md.albedo_on_uv2.instantiate(); - md.albedo_on_uv2->create(lightmap_size.width, lightmap_size.height, false, Image::FORMAT_RGBA8, albedom); + md.albedo_on_uv2->set_data(lightmap_size.width, lightmap_size.height, false, Image::FORMAT_RGBA8, albedom); } md.emission_on_uv2 = images[RS::BAKE_CHANNEL_EMISSION]; @@ -1001,10 +998,16 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); } else if (Object::cast_to<OmniLight3D>(light)) { OmniLight3D *l = Object::cast_to<OmniLight3D>(light); - lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy * (1.0 / (Math_PI * 4.0)), l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + energy *= (1.0 / (Math_PI * 4.0)); + } + lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); } else if (Object::cast_to<SpotLight3D>(light)) { SpotLight3D *l = Object::cast_to<SpotLight3D>(light); - lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy * (1.0 / Math_PI), l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + energy *= (1.0 / Math_PI); + } + lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR)); } } for (int i = 0; i < probes_found.size(); i++) { @@ -1048,7 +1051,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } break; case ENVIRONMENT_MODE_CUSTOM_COLOR: { environment_image.instantiate(); - environment_image->create(128, 64, false, Image::FORMAT_RGBAF); + environment_image->initialize_data(128, 64, false, Image::FORMAT_RGBAF); Color c = environment_custom_color; c.r *= environment_custom_energy; c.g *= environment_custom_energy; @@ -1061,7 +1064,10 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa float exposure_normalization = 1.0; if (camera_attributes.is_valid()) { - exposure_normalization = camera_attributes->calculate_exposure_normalization() * camera_attributes->get_exposure_multiplier(); + exposure_normalization = camera_attributes->get_exposure_multiplier(); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + exposure_normalization = camera_attributes->calculate_exposure_normalization(); + } } Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, max_texture_size, directional, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization); @@ -1072,13 +1078,13 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa /* POSTBAKE: Save Light Data */ - Ref<LightmapGIData> data; + Ref<LightmapGIData> gi_data; if (get_light_data().is_valid()) { - data = get_light_data(); + gi_data = get_light_data(); set_light_data(Ref<LightmapGIData>()); //clear - data->clear(); + gi_data->clear(); } else { - data.instantiate(); + gi_data.instantiate(); } Ref<Texture2DArray> texture; @@ -1092,8 +1098,8 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa texture->create_from_images(images); } - data->set_light_texture(texture); - data->set_uses_spherical_harmonics(directional); + gi_data->set_light_texture(texture); + gi_data->set_uses_spherical_harmonics(directional); for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { Dictionary d = lightmapper->get_bake_mesh_userdata(i); @@ -1105,7 +1111,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa Rect2 uv_scale = lightmapper->get_bake_mesh_uv_scale(i); int slice_index = lightmapper->get_bake_mesh_texture_slice(i); - data->add_user(np, uv_scale, slice_index, subindex); + gi_data->add_user(np, uv_scale, slice_index, subindex); } { @@ -1238,18 +1244,18 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa /* Obtain the colors from the images, they will be re-created as cubemaps on the server, depending on the driver */ - data->set_capture_data(bounds, interior, points, sh, tetrahedrons, bsp_array, exposure_normalization); + gi_data->set_capture_data(bounds, interior, points, sh, tetrahedrons, bsp_array, exposure_normalization); /* Compute a BSP tree of the simplices, so it's easy to find the exact one */ } - data->set_path(p_image_data_path); - Error err = ResourceSaver::save(data); + gi_data->set_path(p_image_data_path); + Error err = ResourceSaver::save(gi_data); if (err != OK) { return BAKE_ERROR_CANT_CREATE_IMAGE; } - set_light_data(data); + set_light_data(gi_data); return BAKE_ERROR_OK; } @@ -1277,9 +1283,9 @@ void LightmapGI::_assign_lightmaps() { Node *node = get_node(light_data->get_user_path(i)); int instance_idx = light_data->get_user_sub_instance(i); if (instance_idx >= 0) { - RID instance = node->call("get_bake_mesh_instance", instance_idx); - if (instance.is_valid()) { - RS::get_singleton()->instance_geometry_set_lightmap(instance, get_instance(), light_data->get_user_lightmap_uv_scale(i), light_data->get_user_lightmap_slice_index(i)); + RID instance_id = node->call("get_bake_mesh_instance", instance_idx); + if (instance_id.is_valid()) { + RS::get_singleton()->instance_geometry_set_lightmap(instance_id, get_instance(), light_data->get_user_lightmap_uv_scale(i), light_data->get_user_lightmap_slice_index(i)); } } else { VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(node); @@ -1295,9 +1301,9 @@ void LightmapGI::_clear_lightmaps() { Node *node = get_node(light_data->get_user_path(i)); int instance_idx = light_data->get_user_sub_instance(i); if (instance_idx >= 0) { - RID instance = node->call("get_bake_mesh_instance", instance_idx); - if (instance.is_valid()) { - RS::get_singleton()->instance_geometry_set_lightmap(instance, RID(), Rect2(), 0); + RID instance_id = node->call("get_bake_mesh_instance", instance_idx); + if (instance_id.is_valid()) { + RS::get_singleton()->instance_geometry_set_lightmap(instance_id, RID(), Rect2(), 0); } } else { VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(node); diff --git a/scene/3d/marker_3d.cpp b/scene/3d/marker_3d.cpp index 3987172561..8e5998d14f 100644 --- a/scene/3d/marker_3d.cpp +++ b/scene/3d/marker_3d.cpp @@ -30,5 +30,24 @@ #include "marker_3d.h" +void Marker3D::set_gizmo_extents(real_t p_extents) { + if (Math::is_equal_approx(gizmo_extents, p_extents)) { + return; + } + gizmo_extents = p_extents; + update_gizmos(); +} + +real_t Marker3D::get_gizmo_extents() const { + return gizmo_extents; +} + +void Marker3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_gizmo_extents", "extents"), &Marker3D::set_gizmo_extents); + ClassDB::bind_method(D_METHOD("get_gizmo_extents"), &Marker3D::get_gizmo_extents); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gizmo_extents", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater,suffix:m"), "set_gizmo_extents", "get_gizmo_extents"); +} + Marker3D::Marker3D() { } diff --git a/scene/3d/marker_3d.h b/scene/3d/marker_3d.h index 95caa101c5..a9bd993476 100644 --- a/scene/3d/marker_3d.h +++ b/scene/3d/marker_3d.h @@ -36,7 +36,15 @@ class Marker3D : public Node3D { GDCLASS(Marker3D, Node3D); + real_t gizmo_extents = 0.25; + +protected: + static void _bind_methods(); + public: + void set_gizmo_extents(real_t p_extents); + real_t get_gizmo_extents() const; + Marker3D(); }; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index a76f4dd0d5..d4f60503c2 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -346,9 +346,9 @@ Ref<Material> MeshInstance3D::get_surface_override_material(int p_surface) const } Ref<Material> MeshInstance3D::get_active_material(int p_surface) const { - Ref<Material> material_override = get_material_override(); - if (material_override.is_valid()) { - return material_override; + Ref<Material> mat_override = get_material_override(); + if (mat_override.is_valid()) { + return mat_override; } Ref<Material> surface_material = get_surface_override_material(p_surface); @@ -356,9 +356,9 @@ Ref<Material> MeshInstance3D::get_active_material(int p_surface) const { return surface_material; } - Ref<Mesh> mesh = get_mesh(); - if (mesh.is_valid()) { - return mesh->surface_get_material(p_surface); + Ref<Mesh> m = get_mesh(); + if (m.is_valid()) { + return m->surface_get_material(p_surface); } return Ref<Material>(); @@ -390,17 +390,17 @@ void MeshInstance3D::_mesh_changed() { update_gizmos(); } -void MeshInstance3D::create_debug_tangents() { +MeshInstance3D *MeshInstance3D::create_debug_tangents_node() { Vector<Vector3> lines; Vector<Color> colors; - Ref<Mesh> mesh = get_mesh(); - if (!mesh.is_valid()) { - return; + Ref<Mesh> m = get_mesh(); + if (!m.is_valid()) { + return nullptr; } - for (int i = 0; i < mesh->get_surface_count(); i++) { - Array arrays = mesh->surface_get_arrays(i); + for (int i = 0; i < m->get_surface_count(); i++) { + Array arrays = m->surface_get_arrays(i); ERR_CONTINUE(arrays.size() != Mesh::ARRAY_MAX); Vector<Vector3> verts = arrays[Mesh::ARRAY_VERTEX]; @@ -457,15 +457,23 @@ void MeshInstance3D::create_debug_tangents() { MeshInstance3D *mi = memnew(MeshInstance3D); mi->set_mesh(am); mi->set_name("DebugTangents"); - add_child(mi, true); -#ifdef TOOLS_ENABLED + return mi; + } - if (is_inside_tree() && this == get_tree()->get_edited_scene_root()) { - mi->set_owner(this); - } else { - mi->set_owner(get_owner()); - } -#endif + return nullptr; +} + +void MeshInstance3D::create_debug_tangents() { + MeshInstance3D *mi = create_debug_tangents_node(); + if (!mi) { + return; + } + + add_child(mi, true); + if (is_inside_tree() && this == get_tree()->get_edited_scene_root()) { + mi->set_owner(this); + } else { + mi->set_owner(get_owner()); } } @@ -495,8 +503,6 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_blend_shape_value", "blend_shape_idx", "value"), &MeshInstance3D::set_blend_shape_value); ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents); - ClassDB::set_method_flags("MeshInstance3D", "create_debug_tangents", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_GROUP("Skeleton", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skin", PROPERTY_HINT_RESOURCE_TYPE, "Skin"), "set_skin", "get_skin"); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index 48d76b9a88..caf7d25616 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -90,6 +90,7 @@ public: Node *create_multiple_convex_collisions_node(); void create_multiple_convex_collisions(); + MeshInstance3D *create_debug_tangents_node(); void create_debug_tangents(); virtual AABB get_aabb() const override; diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp index 34e84861a2..36350d251e 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -79,6 +79,7 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_location", "location"), &NavigationAgent3D::set_target_location); ClassDB::bind_method(D_METHOD("get_target_location"), &NavigationAgent3D::get_target_location); + ClassDB::bind_method(D_METHOD("get_next_location"), &NavigationAgent3D::get_next_location); ClassDB::bind_method(D_METHOD("distance_to_target"), &NavigationAgent3D::distance_to_target); ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationAgent3D::set_velocity); @@ -92,6 +93,7 @@ void NavigationAgent3D::_bind_methods() { ClassDB::bind_method(D_METHOD("_avoidance_done", "new_velocity"), &NavigationAgent3D::_avoidance_done); ADD_GROUP("Pathfinding", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "target_location", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_target_location", "get_target_location"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:m"), "set_path_desired_distance", "get_path_desired_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_desired_distance", PROPERTY_HINT_RANGE, "0.1,100,0.01,suffix:m"), "set_target_desired_distance", "get_target_desired_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_height_offset", PROPERTY_HINT_RANGE, "-100.0,100,0.01,suffix:m"), "set_agent_height_offset", "get_agent_height_offset"); @@ -185,6 +187,13 @@ NavigationAgent3D::NavigationAgent3D() { set_radius(1.0); set_max_speed(10.0); set_ignore_y(true); + + // Preallocate query and result objects to improve performance. + navigation_query = Ref<NavigationPathQueryParameters3D>(); + navigation_query.instantiate(); + + navigation_result = Ref<NavigationPathQueryResult3D>(); + navigation_result.instantiate(); } NavigationAgent3D::~NavigationAgent3D() { @@ -330,6 +339,8 @@ Vector3 NavigationAgent3D::get_target_location() const { Vector3 NavigationAgent3D::get_next_location() { update_navigation(); + + const Vector<Vector3> &navigation_path = navigation_result->get_path(); if (navigation_path.size() == 0) { ERR_FAIL_COND_V_MSG(agent_parent == nullptr, Vector3(), "The agent has no parent."); return agent_parent->get_global_transform().origin; @@ -338,6 +349,10 @@ Vector3 NavigationAgent3D::get_next_location() { } } +const Vector<Vector3> &NavigationAgent3D::get_nav_path() const { + return navigation_result->get_path(); +} + real_t NavigationAgent3D::distance_to_target() const { ERR_FAIL_COND_V_MSG(agent_parent == nullptr, 0.0, "The agent has no parent."); return agent_parent->get_global_transform().origin.distance_to(target_location); @@ -358,6 +373,8 @@ bool NavigationAgent3D::is_navigation_finished() { Vector3 NavigationAgent3D::get_final_location() { update_navigation(); + + const Vector<Vector3> &navigation_path = navigation_result->get_path(); if (navigation_path.size() == 0) { return Vector3(); } @@ -383,8 +400,8 @@ void NavigationAgent3D::_avoidance_done(Vector3 p_new_velocity) { emit_signal(SNAME("velocity_computed"), p_new_velocity); } -TypedArray<String> NavigationAgent3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationAgent3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node3D>(get_parent())) { warnings.push_back(RTR("The NavigationAgent3D can be used only under a Node3D inheriting parent node.")); @@ -406,24 +423,26 @@ void NavigationAgent3D::update_navigation() { update_frame_id = Engine::get_singleton()->get_physics_frames(); - Vector3 o = agent_parent->get_global_transform().origin; + Vector3 origin = agent_parent->get_global_transform().origin; bool reload_path = false; if (NavigationServer3D::get_singleton()->agent_is_map_changed(agent)) { reload_path = true; - } else if (navigation_path.size() == 0) { + } else if (navigation_result->get_path().size() == 0) { reload_path = true; } else { // Check if too far from the navigation path if (nav_path_index > 0) { + const Vector<Vector3> &navigation_path = navigation_result->get_path(); + Vector3 segment[2]; segment[0] = navigation_path[nav_path_index - 1]; segment[1] = navigation_path[nav_path_index]; segment[0].y -= navigation_height_offset; segment[1].y -= navigation_height_offset; - Vector3 p = Geometry3D::get_closest_point_to_segment(o, segment); - if (o.distance_to(p) >= path_max_distance) { + Vector3 p = Geometry3D::get_closest_point_to_segment(origin, segment); + if (origin.distance_to(p) >= path_max_distance) { // To faraway, reload path reload_path = true; } @@ -431,24 +450,31 @@ void NavigationAgent3D::update_navigation() { } if (reload_path) { + navigation_query->set_start_position(origin); + navigation_query->set_target_position(target_location); + navigation_query->set_navigation_layers(navigation_layers); + if (map_override.is_valid()) { - navigation_path = NavigationServer3D::get_singleton()->map_get_path(map_override, o, target_location, true, navigation_layers); + navigation_query->set_map(map_override); } else { - navigation_path = NavigationServer3D::get_singleton()->map_get_path(agent_parent->get_world_3d()->get_navigation_map(), o, target_location, true, navigation_layers); + navigation_query->set_map(agent_parent->get_world_3d()->get_navigation_map()); } + + NavigationServer3D::get_singleton()->query_path(navigation_query, navigation_result); navigation_finished = false; nav_path_index = 0; emit_signal(SNAME("path_changed")); } - if (navigation_path.size() == 0) { + if (navigation_result->get_path().size() == 0) { return; } // Check if we can advance the navigation path if (navigation_finished == false) { // Advances to the next far away location. - while (o.distance_to(navigation_path[nav_path_index] - Vector3(0, navigation_height_offset, 0)) < path_desired_distance) { + const Vector<Vector3> &navigation_path = navigation_result->get_path(); + while (origin.distance_to(navigation_path[nav_path_index] - Vector3(0, navigation_height_offset, 0)) < path_desired_distance) { nav_path_index += 1; if (nav_path_index == navigation_path.size()) { _check_distance_to_target(); @@ -462,7 +488,7 @@ void NavigationAgent3D::update_navigation() { } void NavigationAgent3D::_request_repath() { - navigation_path.clear(); + navigation_result->reset(); target_reached = false; navigation_finished = false; update_frame_id = 0; @@ -471,8 +497,8 @@ void NavigationAgent3D::_request_repath() { void NavigationAgent3D::_check_distance_to_target() { if (!target_reached) { if (distance_to_target() < target_desired_distance) { - emit_signal(SNAME("target_reached")); target_reached = true; + emit_signal(SNAME("target_reached")); } } } diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h index 35c1b1175a..90ceab0242 100644 --- a/scene/3d/navigation_agent_3d.h +++ b/scene/3d/navigation_agent_3d.h @@ -34,6 +34,8 @@ #include "scene/main/node.h" class Node3D; +class NavigationPathQueryParameters3D; +class NavigationPathQueryResult3D; class NavigationAgent3D : public Node { GDCLASS(NavigationAgent3D, Node); @@ -60,7 +62,8 @@ class NavigationAgent3D : public Node { real_t path_max_distance = 3.0; Vector3 target_location; - Vector<Vector3> navigation_path; + Ref<NavigationPathQueryParameters3D> navigation_query; + Ref<NavigationPathQueryResult3D> navigation_result; int nav_path_index = 0; bool velocity_submitted = false; Vector3 prev_safe_velocity; @@ -150,9 +153,7 @@ public: Vector3 get_next_location(); - Vector<Vector3> get_nav_path() const { - return navigation_path; - } + const Vector<Vector3> &get_nav_path() const; int get_nav_path_index() const { return nav_path_index; @@ -167,7 +168,7 @@ public: void set_velocity(Vector3 p_velocity); void _avoidance_done(Vector3 p_new_velocity); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; private: void update_navigation(); diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp index 47b602c966..78fe4754ea 100644 --- a/scene/3d/navigation_link_3d.cpp +++ b/scene/3d/navigation_link_3d.cpp @@ -378,8 +378,8 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) { NavigationServer3D::get_singleton()->link_set_travel_cost(link, travel_cost); } -TypedArray<String> NavigationLink3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationLink3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (start_location.is_equal_approx(end_location)) { warnings.push_back(RTR("NavigationLink3D start location should be different than the end location to be useful.")); diff --git a/scene/3d/navigation_link_3d.h b/scene/3d/navigation_link_3d.h index 1f88075527..46bc6318b8 100644 --- a/scene/3d/navigation_link_3d.h +++ b/scene/3d/navigation_link_3d.h @@ -37,11 +37,11 @@ class NavigationLink3D : public Node3D { GDCLASS(NavigationLink3D, Node3D); bool enabled = true; - RID link = RID(); + RID link; bool bidirectional = true; uint32_t navigation_layers = 1; - Vector3 end_location = Vector3(); - Vector3 start_location = Vector3(); + Vector3 end_location; + Vector3 start_location; real_t enter_cost = 0.0; real_t travel_cost = 1.0; @@ -84,7 +84,7 @@ public: void set_travel_cost(real_t p_travel_cost); real_t get_travel_cost() const { return travel_cost; } - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; }; #endif // NAVIGATION_LINK_3D_H diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 9b49238333..f241d65649 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -126,8 +126,8 @@ NavigationObstacle3D::~NavigationObstacle3D() { agent = RID(); // Pointless } -TypedArray<String> NavigationObstacle3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationObstacle3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<Node3D>(get_parent())) { warnings.push_back(RTR("The NavigationObstacle3D only serves to provide collision avoidance to a Node3D inheriting parent object.")); @@ -159,7 +159,7 @@ void NavigationObstacle3D::reevaluate_agent_radius() { real_t NavigationObstacle3D::estimate_agent_radius() const { if (parent_node3d && parent_node3d->is_inside_tree()) { // Estimate the radius of this physics body - real_t radius = 0.0; + real_t max_radius = 0.0; for (int i(0); i < parent_node3d->get_child_count(); i++) { // For each collision shape CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(parent_node3d->get_child(i)); @@ -173,7 +173,7 @@ real_t NavigationObstacle3D::estimate_agent_radius() const { Vector3 s = cs->get_global_transform().basis.get_scale(); r *= MAX(s.x, MAX(s.y, s.z)); // Takes the biggest radius - radius = MAX(radius, r); + max_radius = MAX(max_radius, r); } else if (cs && !cs->is_inside_tree()) { WARN_PRINT("A CollisionShape3D of the NavigationObstacle3D parent node was not inside the SceneTree when estimating the obstacle radius." "\nMove the NavigationObstacle3D to a child position below any CollisionShape3D node of the parent node so the CollisionShape3D is already inside the SceneTree."); @@ -181,10 +181,10 @@ real_t NavigationObstacle3D::estimate_agent_radius() const { } Vector3 s = parent_node3d->get_global_transform().basis.get_scale(); - radius *= MAX(s.x, MAX(s.y, s.z)); + max_radius *= MAX(s.x, MAX(s.y, s.z)); - if (radius > 0.0) { - return radius; + if (max_radius > 0.0) { + return max_radius; } } return 1.0; // Never a 0 radius diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index 24caf50680..f242d2f99e 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -72,7 +72,7 @@ public: return radius; } - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; private: void initialize_agent(); diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 049ca4c8a0..06182d921c 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -37,6 +37,7 @@ void NavigationRegion3D::set_enabled(bool p_enabled) { if (enabled == p_enabled) { return; } + enabled = p_enabled; if (!is_inside_tree()) { @@ -81,35 +82,50 @@ bool NavigationRegion3D::is_enabled() const { } void NavigationRegion3D::set_navigation_layers(uint32_t p_navigation_layers) { - NavigationServer3D::get_singleton()->region_set_navigation_layers(region, p_navigation_layers); + if (navigation_layers == p_navigation_layers) { + return; + } + + navigation_layers = p_navigation_layers; + + NavigationServer3D::get_singleton()->region_set_navigation_layers(region, navigation_layers); } uint32_t NavigationRegion3D::get_navigation_layers() const { - return NavigationServer3D::get_singleton()->region_get_navigation_layers(region); + return navigation_layers; } void NavigationRegion3D::set_navigation_layer_value(int p_layer_number, bool p_value) { ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + uint32_t _navigation_layers = get_navigation_layers(); + if (p_value) { _navigation_layers |= 1 << (p_layer_number - 1); } else { _navigation_layers &= ~(1 << (p_layer_number - 1)); } + set_navigation_layers(_navigation_layers); } bool NavigationRegion3D::get_navigation_layer_value(int p_layer_number) const { ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + return get_navigation_layers() & (1 << (p_layer_number - 1)); } void NavigationRegion3D::set_enter_cost(real_t p_enter_cost) { ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive."); - enter_cost = MAX(p_enter_cost, 0.0); - NavigationServer3D::get_singleton()->region_set_enter_cost(region, p_enter_cost); + if (Math::is_equal_approx(enter_cost, p_enter_cost)) { + return; + } + + enter_cost = p_enter_cost; + + NavigationServer3D::get_singleton()->region_set_enter_cost(region, enter_cost); } real_t NavigationRegion3D::get_enter_cost() const { @@ -118,7 +134,12 @@ real_t NavigationRegion3D::get_enter_cost() const { void NavigationRegion3D::set_travel_cost(real_t p_travel_cost) { ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive."); - travel_cost = MAX(p_travel_cost, 0.0); + if (Math::is_equal_approx(travel_cost, p_travel_cost)) { + return; + } + + travel_cost = p_travel_cost; + NavigationServer3D::get_singleton()->region_set_travel_cost(region, travel_cost); } @@ -130,8 +151,6 @@ RID NavigationRegion3D::get_region_rid() const { return region; } -///////////////////////////// - void NavigationRegion3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -242,12 +261,7 @@ void NavigationRegion3D::bake_navigation_mesh(bool p_on_thread) { BakeThreadsArgs *args = memnew(BakeThreadsArgs); args->nav_region = this; - if (p_on_thread && !OS::get_singleton()->can_use_threads()) { - WARN_PRINT("NavigationMesh bake 'on_thread' will be disabled as the current OS does not support multiple threads." - "\nAs a fallback the navigation mesh will bake on the main thread which can cause framerate issues."); - } - - if (p_on_thread && OS::get_singleton()->can_use_threads()) { + if (p_on_thread) { bake_thread.start(_bake_navigation_mesh, args); } else { _bake_navigation_mesh(args); @@ -260,8 +274,8 @@ void NavigationRegion3D::_bake_finished(Ref<NavigationMesh> p_nav_mesh) { emit_signal(SNAME("bake_finished")); } -TypedArray<String> NavigationRegion3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray NavigationRegion3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!navmesh.is_valid()) { diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h index ba326abb46..660538d314 100644 --- a/scene/3d/navigation_region_3d.h +++ b/scene/3d/navigation_region_3d.h @@ -39,10 +39,10 @@ class NavigationRegion3D : public Node3D { bool enabled = true; RID region; - Ref<NavigationMesh> navmesh; - + uint32_t navigation_layers = 1; real_t enter_cost = 0.0; real_t travel_cost = 1.0; + Ref<NavigationMesh> navmesh; Thread bake_thread; @@ -90,7 +90,7 @@ public: void bake_navigation_mesh(bool p_on_thread); void _bake_finished(Ref<NavigationMesh> p_nav_mesh); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; NavigationRegion3D(); ~NavigationRegion3D(); diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 59ec036558..1327bdd6e9 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -253,9 +253,7 @@ Vector3 Node3D::get_global_rotation() const { void Node3D::set_global_rotation(const Vector3 &p_euler_rad) { Transform3D transform = get_global_transform(); - Basis new_basis = transform.get_basis(); - new_basis.set_euler(p_euler_rad); - transform.set_basis(new_basis); + transform.basis = Basis::from_euler(p_euler_rad); set_global_transform(transform); } @@ -394,27 +392,25 @@ Node3D::RotationEditMode Node3D::get_rotation_edit_mode() const { return data.rotation_edit_mode; } -void Node3D::set_rotation_order(RotationOrder p_order) { - Basis::EulerOrder order = Basis::EulerOrder(p_order); - - if (data.euler_rotation_order == order) { +void Node3D::set_rotation_order(EulerOrder p_order) { + if (data.euler_rotation_order == p_order) { return; } - ERR_FAIL_INDEX(int32_t(order), 6); + ERR_FAIL_INDEX(int32_t(p_order), 6); bool transform_changed = false; if (data.dirty & DIRTY_EULER_ROTATION_AND_SCALE) { _update_rotation_and_scale(); } else if (data.dirty & DIRTY_LOCAL_TRANSFORM) { - data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(order); + data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(p_order); transform_changed = true; } else { data.dirty |= DIRTY_LOCAL_TRANSFORM; transform_changed = true; } - data.euler_rotation_order = order; + data.euler_rotation_order = p_order; if (transform_changed) { _propagate_transform_changed(this); @@ -425,8 +421,8 @@ void Node3D::set_rotation_order(RotationOrder p_order) { notify_property_list_changed(); // Will change the rotation property. } -Node3D::RotationOrder Node3D::get_rotation_order() const { - return RotationOrder(data.euler_rotation_order); +EulerOrder Node3D::get_rotation_order() const { + return data.euler_rotation_order; } void Node3D::set_rotation(const Vector3 &p_euler_rad) { @@ -786,6 +782,7 @@ void Node3D::set_identity() { } void Node3D::look_at(const Vector3 &p_target, const Vector3 &p_up) { + ERR_FAIL_COND_MSG(!is_inside_tree(), "Node not inside tree. Use look_at_from_position() instead."); Vector3 origin = get_global_transform().origin; look_at_from_position(origin, p_target, p_up); } @@ -1037,22 +1034,16 @@ void Node3D::_bind_methods() { BIND_CONSTANT(NOTIFICATION_ENTER_WORLD); BIND_CONSTANT(NOTIFICATION_EXIT_WORLD); BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED); + BIND_CONSTANT(NOTIFICATION_LOCAL_TRANSFORM_CHANGED); BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_EULER); BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_QUATERNION); BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_BASIS); - BIND_ENUM_CONSTANT(ROTATION_ORDER_XYZ); - BIND_ENUM_CONSTANT(ROTATION_ORDER_XZY); - BIND_ENUM_CONSTANT(ROTATION_ORDER_YXZ); - BIND_ENUM_CONSTANT(ROTATION_ORDER_YZX); - BIND_ENUM_CONSTANT(ROTATION_ORDER_ZXY); - BIND_ENUM_CONSTANT(ROTATION_ORDER_ZYX); - ADD_GROUP("Transform", ""); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_less,no_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_less,hide_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_HIDE_QUATERNION_EDIT, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion"); ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis"); diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 90c7bc89ef..76c1e3e22c 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -61,18 +61,9 @@ public: ROTATION_EDIT_MODE_BASIS, }; - enum RotationOrder { - ROTATION_ORDER_XYZ, - ROTATION_ORDER_XZY, - ROTATION_ORDER_YXZ, - ROTATION_ORDER_YZX, - ROTATION_ORDER_ZXY, - ROTATION_ORDER_ZYX - }; - private: - // For the sake of ease of use, Node3D can operate with Transforms (Basis+Origin), Quaterinon/Scale and Euler Rotation/Scale. - // Transform and Quaterinon are stored in data.local_transform Basis (so quaternion is not really stored, but converted back/forth from 3x3 matrix on demand). + // For the sake of ease of use, Node3D can operate with Transforms (Basis+Origin), Quaternion/Scale and Euler Rotation/Scale. + // Transform and Quaternion are stored in data.local_transform Basis (so quaternion is not really stored, but converted back/forth from 3x3 matrix on demand). // Euler needs to be kept separate because converting to Basis and back may result in a different vector (which is troublesome for users // editing in the inspector, not only because of the numerical precision loss but because they expect these rotations to be consistent, or support // "redundant" rotations for animation interpolation, like going from 0 to 720 degrees). @@ -96,10 +87,11 @@ private: mutable SelfList<Node> xform_change; + // This Data struct is to avoid namespace pollution in derived classes. struct Data { mutable Transform3D global_transform; mutable Transform3D local_transform; - mutable Basis::EulerOrder euler_rotation_order = Basis::EULER_ORDER_YXZ; + mutable EulerOrder euler_rotation_order = EulerOrder::YXZ; mutable Vector3 euler_rotation; mutable Vector3 scale = Vector3(1, 1, 1); mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER; @@ -178,7 +170,7 @@ public: void set_rotation_edit_mode(RotationEditMode p_mode); RotationEditMode get_rotation_edit_mode() const; - void set_rotation_order(RotationOrder p_order); + void set_rotation_order(EulerOrder p_order); void set_rotation(const Vector3 &p_euler_rad); void set_scale(const Vector3 &p_scale); @@ -187,7 +179,7 @@ public: Vector3 get_position() const; - RotationOrder get_rotation_order() const; + EulerOrder get_rotation_order() const; Vector3 get_rotation() const; Vector3 get_scale() const; @@ -276,6 +268,5 @@ public: }; VARIANT_ENUM_CAST(Node3D::RotationEditMode) -VARIANT_ENUM_CAST(Node3D::RotationOrder) #endif // NODE_3D_H diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 015cb9a21d..4e1ed5654a 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -682,8 +682,8 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node, return BAKE_ERROR_OK; } -TypedArray<String> OccluderInstance3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray OccluderInstance3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) { warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling.")); diff --git a/scene/3d/occluder_instance_3d.h b/scene/3d/occluder_instance_3d.h index 69a80e63fc..f507fee024 100644 --- a/scene/3d/occluder_instance_3d.h +++ b/scene/3d/occluder_instance_3d.h @@ -181,7 +181,7 @@ protected: static void _bind_methods(); public: - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; enum BakeError { BAKE_ERROR_OK, diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index 2d1f4a579b..ab4cba86fb 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -341,15 +341,15 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const { } } -TypedArray<String> PathFollow3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray PathFollow3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path3D>(get_parent())) { warnings.push_back(RTR("PathFollow3D only works when set as a child of a Path3D node.")); } else { - Path3D *path = Object::cast_to<Path3D>(get_parent()); - if (path->get_curve().is_valid() && !path->get_curve()->is_up_vector_enabled() && rotation_mode == ROTATION_ORIENTED) { + Path3D *p = Object::cast_to<Path3D>(get_parent()); + if (p->get_curve().is_valid() && !p->get_curve()->is_up_vector_enabled() && rotation_mode == ROTATION_ORIENTED) { warnings.push_back(RTR("PathFollow3D's ROTATION_ORIENTED requires \"Up Vector\" to be enabled in its parent Path3D's Curve resource.")); } } diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h index 45fa2c8917..b161b12185 100644 --- a/scene/3d/path_3d.h +++ b/scene/3d/path_3d.h @@ -112,7 +112,7 @@ public: void set_cubic_interpolation(bool p_enable); bool get_cubic_interpolation() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; PathFollow3D() {} }; diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index c6c59a5c64..d9fa37ce74 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -100,7 +100,7 @@ Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_distance, bool p if (move_and_collide(parameters, result, p_test_only)) { // Create a new instance when the cached reference is invalid or still in use in script. - if (motion_cache.is_null() || motion_cache->reference_get_count() > 1) { + if (motion_cache.is_null() || motion_cache->get_reference_count() > 1) { motion_cache.instantiate(); motion_cache->owner = this; } @@ -317,11 +317,6 @@ void AnimatableBody3D::_update_kinematic_motion() { } } -void AnimatableBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { - AnimatableBody3D *body = (AnimatableBody3D *)p_instance; - body->_body_state_changed(p_state); -} - void AnimatableBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { linear_velocity = p_state->get_linear_velocity(); angular_velocity = p_state->get_angular_velocity(); @@ -373,7 +368,7 @@ void AnimatableBody3D::_bind_methods() { AnimatableBody3D::AnimatableBody3D() : StaticBody3D(PhysicsServer3D::BODY_MODE_KINEMATIC) { - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &AnimatableBody3D::_body_state_changed)); } void RigidBody3D::_body_enter_tree(ObjectID p_id) { @@ -488,11 +483,6 @@ struct _RigidBodyInOut { int local_shape = 0; }; -void RigidBody3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { - RigidBody3D *body = (RigidBody3D *)p_instance; - body->_body_state_changed(p_state); -} - void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { set_ignore_transform_notification(true); set_global_transform(p_state->get_transform()); @@ -532,28 +522,28 @@ void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { //put the ones to add for (int i = 0; i < p_state->get_contact_count(); i++) { - RID rid = p_state->get_contact_collider(i); - ObjectID obj = p_state->get_contact_collider_id(i); + RID col_rid = p_state->get_contact_collider(i); + ObjectID col_obj = p_state->get_contact_collider_id(i); int local_shape = p_state->get_contact_local_shape(i); - int shape = p_state->get_contact_collider_shape(i); + int col_shape = p_state->get_contact_collider_shape(i); - HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(obj); + HashMap<ObjectID, BodyState>::Iterator E = contact_monitor->body_map.find(col_obj); if (!E) { - toadd[toadd_count].rid = rid; + toadd[toadd_count].rid = col_rid; toadd[toadd_count].local_shape = local_shape; - toadd[toadd_count].id = obj; - toadd[toadd_count].shape = shape; + toadd[toadd_count].id = col_obj; + toadd[toadd_count].shape = col_shape; toadd_count++; continue; } - ShapePair sp(shape, local_shape); + ShapePair sp(col_shape, local_shape); int idx = E->value.shapes.find(sp); if (idx == -1) { - toadd[toadd_count].rid = rid; + toadd[toadd_count].rid = col_rid; toadd[toadd_count].local_shape = local_shape; - toadd[toadd_count].id = obj; - toadd[toadd_count].shape = shape; + toadd[toadd_count].id = col_obj; + toadd[toadd_count].shape = col_shape; toadd_count++; continue; } @@ -982,10 +972,10 @@ TypedArray<Node3D> RigidBody3D::get_colliding_bodies() const { return ret; } -TypedArray<String> RigidBody3D::get_configuration_warnings() const { +PackedStringArray RigidBody3D::get_configuration_warnings() const { Transform3D t = get_transform(); - TypedArray<String> warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node::get_configuration_warnings(); if (ABS(t.basis.get_column(0).length() - 1.0) > 0.05 || ABS(t.basis.get_column(1).length() - 1.0) > 0.05 || ABS(t.basis.get_column(2).length() - 1.0) > 0.05) { warnings.push_back(RTR("Size changes to RigidBody will be overridden by the physics engine when running.\nChange the size in children collision shapes instead.")); @@ -1139,7 +1129,7 @@ void RigidBody3D::_validate_property(PropertyInfo &p_property) const { RigidBody3D::RigidBody3D() : PhysicsBody3D(PhysicsServer3D::BODY_MODE_RIGID) { - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &RigidBody3D::_body_state_changed)); } RigidBody3D::~RigidBody3D() { @@ -1274,7 +1264,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo for (int iteration = 0; iteration < max_slides; ++iteration) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), motion, margin); - parameters.max_collisions = 4; + parameters.max_collisions = 6; // There can be 4 collisions between 2 walls + 2 more for the floor. parameters.recovery_as_collision = true; // Also report collisions generated only from recovery. PhysicsServer3D::MotionResult result; @@ -1810,7 +1800,7 @@ Ref<KinematicCollision3D> CharacterBody3D::_get_slide_collision(int p_bounce) { } // Create a new instance when the cached reference is invalid or still in use in script. - if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->reference_get_count() > 1) { + if (slide_colliders[p_bounce].is_null() || slide_colliders[p_bounce]->get_reference_count() > 1) { slide_colliders.write[p_bounce].instantiate(); slide_colliders.write[p_bounce]->owner = this; } @@ -2903,11 +2893,6 @@ void PhysicalBone3D::_notification(int p_what) { } } -void PhysicalBone3D::_body_state_changed_callback(void *p_instance, PhysicsDirectBodyState3D *p_state) { - PhysicalBone3D *bone = (PhysicalBone3D *)p_instance; - bone->_body_state_changed(p_state); -} - void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { if (!simulate_physics || !_internal_simulate_physics) { return; @@ -3425,7 +3410,7 @@ void PhysicalBone3D::_start_physics_simulation() { PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); PhysicsServer3D::get_singleton()->body_set_collision_priority(get_rid(), get_collision_priority()); - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), this, _body_state_changed_callback); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), callable_mp(this, &PhysicalBone3D::_body_state_changed)); set_as_top_level(true); _internal_simulate_physics = true; } @@ -3446,7 +3431,7 @@ void PhysicalBone3D::_stop_physics_simulation() { PhysicsServer3D::get_singleton()->body_set_collision_priority(get_rid(), 1.0); } if (_internal_simulate_physics) { - PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), nullptr, nullptr); + PhysicsServer3D::get_singleton()->body_set_state_sync_callback(get_rid(), Callable()); parent_skeleton->set_bone_global_pose_override(bone_id, Transform3D(), 0.0, false); set_as_top_level(false); _internal_simulate_physics = false; diff --git a/scene/3d/physics_body_3d.h b/scene/3d/physics_body_3d.h index 8e0296fd9b..4b874b91d9 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -325,7 +325,7 @@ public: void set_constant_torque(const Vector3 &p_torque); Vector3 get_constant_torque() const; - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; RigidBody3D(); ~RigidBody3D(); diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index a45ef52452..45ff0a4b45 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -516,7 +516,7 @@ void RayCast3D::_clear_debug_shape() { MeshInstance3D *mi = static_cast<MeshInstance3D *>(debug_shape); if (mi->is_inside_tree()) { - mi->queue_delete(); + mi->queue_free(); } else { memdelete(mi); } diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index 9979052385..817ffbfec8 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -68,7 +68,7 @@ void RemoteTransform3D::_update_remote() { Transform3D our_trans = get_global_transform(); if (update_remote_rotation) { - n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order()))); + n->set_rotation(our_trans.basis.get_euler_normalized(EulerOrder(n->get_rotation_order()))); } if (update_remote_scale) { @@ -90,7 +90,7 @@ void RemoteTransform3D::_update_remote() { Transform3D our_trans = get_transform(); if (update_remote_rotation) { - n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order()))); + n->set_rotation(our_trans.basis.get_euler_normalized(EulerOrder(n->get_rotation_order()))); } if (update_remote_scale) { @@ -178,8 +178,8 @@ void RemoteTransform3D::force_update_cache() { _update_cache(); } -TypedArray<String> RemoteTransform3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray RemoteTransform3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) { warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work.")); diff --git a/scene/3d/remote_transform_3d.h b/scene/3d/remote_transform_3d.h index ab134c1261..cc83661f26 100644 --- a/scene/3d/remote_transform_3d.h +++ b/scene/3d/remote_transform_3d.h @@ -70,7 +70,7 @@ public: void force_update_cache(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; RemoteTransform3D(); }; diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp index a2fecf9c31..267139c7d0 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -115,6 +115,7 @@ void ShapeCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("force_shapecast_update"), &ShapeCast3D::force_shapecast_update); ClassDB::bind_method(D_METHOD("get_collider", "index"), &ShapeCast3D::get_collider); + ClassDB::bind_method(D_METHOD("get_collider_rid", "index"), &ShapeCast3D::get_collider_rid); ClassDB::bind_method(D_METHOD("get_collider_shape", "index"), &ShapeCast3D::get_collider_shape); ClassDB::bind_method(D_METHOD("get_collision_point", "index"), &ShapeCast3D::get_collision_point); ClassDB::bind_method(D_METHOD("get_collision_normal", "index"), &ShapeCast3D::get_collision_normal); @@ -167,8 +168,8 @@ void ShapeCast3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_shape_custom_color"), "set_debug_shape_custom_color", "get_debug_shape_custom_color"); } -TypedArray<String> ShapeCast3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node3D::get_configuration_warnings(); +PackedStringArray ShapeCast3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (shape.is_null()) { warnings.push_back(RTR("This node cannot interact with other objects unless a Shape3D is assigned.")); @@ -282,6 +283,11 @@ Object *ShapeCast3D::get_collider(int p_idx) const { return ObjectDB::get_instance(result[p_idx].collider_id); } +RID ShapeCast3D::get_collider_rid(int p_idx) const { + ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), RID(), "No collider RID found."); + return result[p_idx].rid; +} + int ShapeCast3D::get_collider_shape(int p_idx) const { ERR_FAIL_INDEX_V_MSG(p_idx, result.size(), -1, "No collider shape found."); return result[p_idx].shape; @@ -619,7 +625,7 @@ void ShapeCast3D::_clear_debug_shape() { MeshInstance3D *mi = static_cast<MeshInstance3D *>(debug_shape); if (mi->is_inside_tree()) { - mi->queue_delete(); + mi->queue_free(); } else { memdelete(mi); } diff --git a/scene/3d/shape_cast_3d.h b/scene/3d/shape_cast_3d.h index 5bda15e4b0..abe10c9b24 100644 --- a/scene/3d/shape_cast_3d.h +++ b/scene/3d/shape_cast_3d.h @@ -120,6 +120,7 @@ public: int get_collision_count() const; Object *get_collider(int p_idx) const; + RID get_collider_rid(int p_idx) const; int get_collider_shape(int p_idx) const; Vector3 get_collision_point(int p_idx) const; Vector3 get_collision_normal(int p_idx) const; @@ -136,7 +137,7 @@ public: void remove_exception(const Object *p_object); void clear_exceptions(); - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; }; #endif // SHAPE_CAST_3D_H diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 85b2c5154b..a1f962c690 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -888,10 +888,14 @@ void _pb_start_simulation(const Skeleton3D *p_skeleton, Node *p_node, const Vect PhysicalBone3D *pb = Object::cast_to<PhysicalBone3D>(p_node); if (pb) { - for (int i = p_sim_bones.size() - 1; 0 <= i; --i) { - if (p_sim_bones[i] == pb->get_bone_id() || p_skeleton->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) { - pb->set_simulate_physics(true); - break; + if (p_sim_bones.is_empty()) { // If no bones is specified, activate ragdoll on full body. + pb->set_simulate_physics(true); + } else { + for (int i = p_sim_bones.size() - 1; 0 <= i; --i) { + if (p_sim_bones[i] == pb->get_bone_id() || p_skeleton->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) { + pb->set_simulate_physics(true); + break; + } } } } @@ -901,9 +905,7 @@ void Skeleton3D::physical_bones_start_simulation_on(const TypedArray<StringName> set_physics_process_internal(false); Vector<int> sim_bones; - if (p_bones.size() <= 0) { - sim_bones.push_back(0); // If no bones is specified, activate ragdoll on full body. - } else { + if (p_bones.size() > 0) { sim_bones.resize(p_bones.size()); int c = 0; for (int i = sim_bones.size() - 1; 0 <= i; --i) { diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 47858b372c..83e3369423 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -384,8 +384,8 @@ void SoftBody3D::_bind_methods() { BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE); } -TypedArray<String> SoftBody3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray SoftBody3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (mesh.is_null()) { warnings.push_back(RTR("This body will be ignored until you set a mesh.")); @@ -523,13 +523,13 @@ uint32_t SoftBody3D::get_collision_layer() const { void SoftBody3D::set_collision_layer_value(int p_layer_number, bool p_value) { ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); - uint32_t collision_layer = get_collision_layer(); + uint32_t collision_layer_new = get_collision_layer(); if (p_value) { - collision_layer |= 1 << (p_layer_number - 1); + collision_layer_new |= 1 << (p_layer_number - 1); } else { - collision_layer &= ~(1 << (p_layer_number - 1)); + collision_layer_new &= ~(1 << (p_layer_number - 1)); } - set_collision_layer(collision_layer); + set_collision_layer(collision_layer_new); } bool SoftBody3D::get_collision_layer_value(int p_layer_number) const { diff --git a/scene/3d/soft_body_3d.h b/scene/3d/soft_body_3d.h index 40f3d6f1f4..9ec1f18396 100644 --- a/scene/3d/soft_body_3d.h +++ b/scene/3d/soft_body_3d.h @@ -125,7 +125,7 @@ protected: void _notification(int p_what); static void _bind_methods(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; public: RID get_physics_rid() const { return physics_rid; } diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 380172f396..d69953fee5 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -87,6 +87,182 @@ void SpriteBase3D::_notification(int p_what) { } } +void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect) { + ERR_FAIL_COND(p_texture.is_null()); + + Rect2 final_rect; + Rect2 final_src_rect; + if (!p_texture->get_rect_region(p_dst_rect, p_src_rect, final_rect, final_src_rect)) { + return; + } + + if (final_rect.size.x == 0 || final_rect.size.y == 0) { + return; + } + + // 2D: 3D plane (axes match exactly when `axis == Vector3::AXIS_Z`): + // -X+ -X+ + // - + + // Y +--------+ +--------+ +--------+ Y +--------+ + // + | +--+ | | | (2) | | - | 0--1 | + // | |ab| | (1) | +--+ | (3) | 3--2 | | |ab| | + // | |cd| | --> | |ab| | --> | |cd| | <==> | |cd| | + // | +--+ | | |cd| | | |ab| | | 3--2 | + // | | | +--+ | | 0--1 | | | + // +--------+ +--------+ +--------+ +--------+ + + // (1) Y-wise shift `final_rect` within `p_dst_rect` so after inverting Y + // axis distances between top/bottom borders will be preserved (so for + // example AtlasTextures with vertical margins will look the same in 2D/3D). + final_rect.position.y = (p_dst_rect.position.y + p_dst_rect.size.y) - ((final_rect.position.y + final_rect.size.y) - p_dst_rect.position.y); + + Color color = _get_color_accum(); + + real_t px_size = get_pixel_size(); + + // (2) Order vertices (0123) bottom-top in 2D / top-bottom in 3D. + Vector2 vertices[4] = { + (final_rect.position + Vector2(0, final_rect.size.y)) * px_size, + (final_rect.position + final_rect.size) * px_size, + (final_rect.position + Vector2(final_rect.size.x, 0)) * px_size, + final_rect.position * px_size, + }; + + Vector2 src_tsize = p_texture->get_size(); + + // Properly setup UVs for impostor textures (AtlasTexture). + Ref<AtlasTexture> atlas_tex = p_texture; + if (atlas_tex != nullptr) { + src_tsize[0] = atlas_tex->get_atlas()->get_width(); + src_tsize[1] = atlas_tex->get_atlas()->get_height(); + } + + // (3) Assign UVs (abcd) according to the vertices order (bottom-top in 2D / top-bottom in 3D). + Vector2 uvs[4] = { + final_src_rect.position / src_tsize, + (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize, + (final_src_rect.position + final_src_rect.size) / src_tsize, + (final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize, + }; + + if (is_flipped_h()) { + SWAP(uvs[0], uvs[1]); + SWAP(uvs[2], uvs[3]); + } + + if (is_flipped_v()) { + SWAP(uvs[0], uvs[3]); + SWAP(uvs[1], uvs[2]); + } + + Vector3 normal; + int ax = get_axis(); + normal[ax] = 1.0; + + Plane tangent; + if (ax == Vector3::AXIS_X) { + tangent = Plane(0, 0, -1, 1); + } else { + tangent = Plane(1, 0, 0, 1); + } + + int x_axis = ((ax + 1) % 3); + int y_axis = ((ax + 2) % 3); + + if (ax != Vector3::AXIS_Z) { + SWAP(x_axis, y_axis); + + for (int i = 0; i < 4; i++) { + //uvs[i] = Vector2(1.0,1.0)-uvs[i]; + //SWAP(vertices[i].x,vertices[i].y); + if (ax == Vector3::AXIS_Y) { + vertices[i].y = -vertices[i].y; + } else if (ax == Vector3::AXIS_X) { + vertices[i].x = -vertices[i].x; + } + } + } + + AABB aabb_new; + + // Everything except position and UV is compressed. + uint8_t *vertex_write_buffer = vertex_buffer.ptrw(); + uint8_t *attribute_write_buffer = attribute_buffer.ptrw(); + + uint32_t v_normal; + { + Vector3 n = normal * Vector3(0.5, 0.5, 0.5) + Vector3(0.5, 0.5, 0.5); + + Vector2 res = n.octahedron_encode(); + uint32_t value = 0; + value |= (uint16_t)CLAMP(res.x * 65535, 0, 65535); + value |= (uint16_t)CLAMP(res.y * 65535, 0, 65535) << 16; + + v_normal = value; + } + uint32_t v_tangent; + { + Plane t = tangent; + Vector2 res = t.normal.octahedron_tangent_encode(t.d); + uint32_t value = 0; + value |= (uint16_t)CLAMP(res.x * 65535, 0, 65535); + value |= (uint16_t)CLAMP(res.y * 65535, 0, 65535) << 16; + + v_tangent = value; + } + + uint8_t v_color[4] = { + uint8_t(CLAMP(color.r * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(color.g * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(color.b * 255.0, 0.0, 255.0)), + uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0)) + }; + + for (int i = 0; i < 4; i++) { + Vector3 vtx; + vtx[x_axis] = vertices[i][0]; + vtx[y_axis] = vertices[i][1]; + if (i == 0) { + aabb_new.position = vtx; + aabb_new.size = Vector3(); + } else { + aabb_new.expand_to(vtx); + } + + float v_uv[2] = { (float)uvs[i].x, (float)uvs[i].y }; + memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_TEX_UV]], v_uv, 8); + + float v_vertex[3] = { (float)vtx.x, (float)vtx.y, (float)vtx.z }; + + memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3); + memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_NORMAL]], &v_normal, 4); + memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_TANGENT]], &v_tangent, 4); + memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_COLOR]], v_color, 4); + } + + RID mesh_new = get_mesh(); + RS::get_singleton()->mesh_surface_update_vertex_region(mesh_new, 0, 0, vertex_buffer); + RS::get_singleton()->mesh_surface_update_attribute_region(mesh_new, 0, 0, attribute_buffer); + + RS::get_singleton()->mesh_set_custom_aabb(mesh_new, aabb_new); + set_aabb(aabb_new); + + RID shader_rid; + StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), &shader_rid); + if (last_shader != shader_rid) { + RS::get_singleton()->material_set_shader(get_material(), shader_rid); + last_shader = shader_rid; + } + if (last_texture != p_texture->get_rid()) { + RS::get_singleton()->material_set_param(get_material(), "texture_albedo", p_texture->get_rid()); + last_texture = p_texture->get_rid(); + } + if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) { + RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority()); + RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material()); + } +} + void SpriteBase3D::set_centered(bool p_center) { centered = p_center; _queue_redraw(); @@ -202,14 +378,14 @@ Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const { return Ref<TriangleMesh>(); } - real_t pixel_size = get_pixel_size(); + real_t px_size = get_pixel_size(); Vector2 vertices[4] = { - (final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size, - (final_rect.position + final_rect.size) * pixel_size, - (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size, - final_rect.position * pixel_size, + (final_rect.position + Vector2(0, final_rect.size.y)) * px_size, + (final_rect.position + final_rect.size) * px_size, + (final_rect.position + Vector2(final_rect.size.x, 0)) * px_size, + final_rect.position * px_size, }; @@ -464,171 +640,17 @@ void Sprite3D::_draw() { } Size2 frame_size = base_rect.size / Size2(hframes, vframes); - Point2 frame_offset = Point2(frame % hframes, frame / hframes); - frame_offset *= frame_size; + Point2 frame_offset = Point2(frame % hframes, frame / hframes) * frame_size; - Point2 dest_offset = get_offset(); + Point2 dst_offset = get_offset(); if (is_centered()) { - dest_offset -= frame_size / 2; + dst_offset -= frame_size / 2.0f; } Rect2 src_rect(base_rect.position + frame_offset, frame_size); - Rect2 final_dst_rect(dest_offset, frame_size); - Rect2 final_rect; - Rect2 final_src_rect; - if (!texture->get_rect_region(final_dst_rect, src_rect, final_rect, final_src_rect)) { - return; - } - - if (final_rect.size.x == 0 || final_rect.size.y == 0) { - return; - } - - Color color = _get_color_accum(); - - real_t pixel_size = get_pixel_size(); - - Vector2 vertices[4] = { - - (final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size, - (final_rect.position + final_rect.size) * pixel_size, - (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size, - final_rect.position * pixel_size, - - }; - - Vector2 src_tsize = tsize; - - // Properly setup UVs for impostor textures (AtlasTexture). - Ref<AtlasTexture> atlas_tex = texture; - if (atlas_tex != nullptr) { - src_tsize[0] = atlas_tex->get_atlas()->get_width(); - src_tsize[1] = atlas_tex->get_atlas()->get_height(); - } - - Vector2 uvs[4] = { - final_src_rect.position / src_tsize, - (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize, - (final_src_rect.position + final_src_rect.size) / src_tsize, - (final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize, - }; - - if (is_flipped_h()) { - SWAP(uvs[0], uvs[1]); - SWAP(uvs[2], uvs[3]); - } - - if (is_flipped_v()) { - SWAP(uvs[0], uvs[3]); - SWAP(uvs[1], uvs[2]); - } - - Vector3 normal; - int axis = get_axis(); - normal[axis] = 1.0; - - Plane tangent; - if (axis == Vector3::AXIS_X) { - tangent = Plane(0, 0, -1, 1); - } else { - tangent = Plane(1, 0, 0, 1); - } - - int x_axis = ((axis + 1) % 3); - int y_axis = ((axis + 2) % 3); - - if (axis != Vector3::AXIS_Z) { - SWAP(x_axis, y_axis); - - for (int i = 0; i < 4; i++) { - //uvs[i] = Vector2(1.0,1.0)-uvs[i]; - //SWAP(vertices[i].x,vertices[i].y); - if (axis == Vector3::AXIS_Y) { - vertices[i].y = -vertices[i].y; - } else if (axis == Vector3::AXIS_X) { - vertices[i].x = -vertices[i].x; - } - } - } - - AABB aabb; + Rect2 dst_rect(dst_offset, frame_size); - // Everything except position and UV is compressed. - uint8_t *vertex_write_buffer = vertex_buffer.ptrw(); - uint8_t *attribute_write_buffer = attribute_buffer.ptrw(); - - uint32_t v_normal; - { - Vector3 n = normal * Vector3(0.5, 0.5, 0.5) + Vector3(0.5, 0.5, 0.5); - - Vector2 res = n.octahedron_encode(); - uint32_t value = 0; - value |= (uint16_t)CLAMP(res.x * 65535, 0, 65535); - value |= (uint16_t)CLAMP(res.y * 65535, 0, 65535) << 16; - - v_normal = value; - } - uint32_t v_tangent; - { - Plane t = tangent; - Vector2 res = t.normal.octahedron_tangent_encode(t.d); - uint32_t value = 0; - value |= (uint16_t)CLAMP(res.x * 65535, 0, 65535); - value |= (uint16_t)CLAMP(res.y * 65535, 0, 65535) << 16; - - v_tangent = value; - } - - uint8_t v_color[4] = { - uint8_t(CLAMP(color.r * 255.0, 0.0, 255.0)), - uint8_t(CLAMP(color.g * 255.0, 0.0, 255.0)), - uint8_t(CLAMP(color.b * 255.0, 0.0, 255.0)), - uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0)) - }; - - for (int i = 0; i < 4; i++) { - Vector3 vtx; - vtx[x_axis] = vertices[i][0]; - vtx[y_axis] = vertices[i][1]; - if (i == 0) { - aabb.position = vtx; - aabb.size = Vector3(); - } else { - aabb.expand_to(vtx); - } - - float v_uv[2] = { (float)uvs[i].x, (float)uvs[i].y }; - memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_TEX_UV]], v_uv, 8); - - float v_vertex[3] = { (float)vtx.x, (float)vtx.y, (float)vtx.z }; - - memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3); - memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_NORMAL]], &v_normal, 4); - memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_TANGENT]], &v_tangent, 4); - memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_COLOR]], v_color, 4); - } - - RID mesh = get_mesh(); - RS::get_singleton()->mesh_surface_update_vertex_region(mesh, 0, 0, vertex_buffer); - RS::get_singleton()->mesh_surface_update_attribute_region(mesh, 0, 0, attribute_buffer); - - RS::get_singleton()->mesh_set_custom_aabb(mesh, aabb); - set_aabb(aabb); - - RID shader_rid; - StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), &shader_rid); - if (last_shader != shader_rid) { - RS::get_singleton()->material_set_shader(get_material(), shader_rid); - last_shader = shader_rid; - } - if (last_texture != texture->get_rid()) { - RS::get_singleton()->material_set_param(get_material(), "texture_albedo", texture->get_rid()); - last_texture = texture->get_rid(); - } - if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) { - RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority()); - RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material()); - } + draw_texture_rect(texture, dst_rect, src_rect); } void Sprite3D::set_texture(const Ref<Texture2D> &p_texture) { @@ -658,6 +680,7 @@ void Sprite3D::set_region_enabled(bool p_region) { region = p_region; _queue_redraw(); + notify_property_list_changed(); } bool Sprite3D::is_region_enabled() const { @@ -759,6 +782,10 @@ void Sprite3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "frame_coords") { p_property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } + + if (!region && (p_property.name == "region_rect")) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } void Sprite3D::_bind_methods() { @@ -788,7 +815,7 @@ void Sprite3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled"); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect"); @@ -831,158 +858,7 @@ void AnimatedSprite3D::_draw() { Rect2 dst_rect(ofs, tsize); - Rect2 final_rect; - Rect2 final_src_rect; - if (!texture->get_rect_region(dst_rect, src_rect, final_rect, final_src_rect)) { - return; - } - - if (final_rect.size.x == 0 || final_rect.size.y == 0) { - return; - } - - Color color = _get_color_accum(); - - real_t pixel_size = get_pixel_size(); - - Vector2 vertices[4] = { - - (final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size, - (final_rect.position + final_rect.size) * pixel_size, - (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size, - final_rect.position * pixel_size, - - }; - - Vector2 src_tsize = tsize; - - // Properly setup UVs for impostor textures (AtlasTexture). - Ref<AtlasTexture> atlas_tex = texture; - if (atlas_tex != nullptr) { - src_tsize[0] = atlas_tex->get_atlas()->get_width(); - src_tsize[1] = atlas_tex->get_atlas()->get_height(); - } - - Vector2 uvs[4] = { - final_src_rect.position / src_tsize, - (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize, - (final_src_rect.position + final_src_rect.size) / src_tsize, - (final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize, - }; - - if (is_flipped_h()) { - SWAP(uvs[0], uvs[1]); - SWAP(uvs[2], uvs[3]); - } - if (is_flipped_v()) { - SWAP(uvs[0], uvs[3]); - SWAP(uvs[1], uvs[2]); - } - - Vector3 normal; - int axis = get_axis(); - normal[axis] = 1.0; - - Plane tangent; - if (axis == Vector3::AXIS_X) { - tangent = Plane(0, 0, -1, -1); - } else { - tangent = Plane(1, 0, 0, -1); - } - - int x_axis = ((axis + 1) % 3); - int y_axis = ((axis + 2) % 3); - - if (axis != Vector3::AXIS_Z) { - SWAP(x_axis, y_axis); - - for (int i = 0; i < 4; i++) { - //uvs[i] = Vector2(1.0,1.0)-uvs[i]; - //SWAP(vertices[i].x,vertices[i].y); - if (axis == Vector3::AXIS_Y) { - vertices[i].y = -vertices[i].y; - } else if (axis == Vector3::AXIS_X) { - vertices[i].x = -vertices[i].x; - } - } - } - - AABB aabb; - - // Everything except position and UV is compressed. - uint8_t *vertex_write_buffer = vertex_buffer.ptrw(); - uint8_t *attribute_write_buffer = attribute_buffer.ptrw(); - - uint32_t v_normal; - { - Vector3 n = normal * Vector3(0.5, 0.5, 0.5) + Vector3(0.5, 0.5, 0.5); - - Vector2 res = n.octahedron_encode(); - uint32_t value = 0; - value |= (uint16_t)CLAMP(res.x * 65535, 0, 65535); - value |= (uint16_t)CLAMP(res.y * 65535, 0, 65535) << 16; - - v_normal = value; - } - uint32_t v_tangent; - { - Plane t = tangent; - Vector2 res = t.normal.octahedron_tangent_encode(t.d); - uint32_t value = 0; - value |= (uint16_t)CLAMP(res.x * 65535, 0, 65535); - value |= (uint16_t)CLAMP(res.y * 65535, 0, 65535) << 16; - v_tangent = value; - } - - uint8_t v_color[4] = { - uint8_t(CLAMP(color.r * 255.0, 0.0, 255.0)), - uint8_t(CLAMP(color.g * 255.0, 0.0, 255.0)), - uint8_t(CLAMP(color.b * 255.0, 0.0, 255.0)), - uint8_t(CLAMP(color.a * 255.0, 0.0, 255.0)) - }; - - for (int i = 0; i < 4; i++) { - Vector3 vtx; - vtx[x_axis] = vertices[i][0]; - vtx[y_axis] = vertices[i][1]; - if (i == 0) { - aabb.position = vtx; - aabb.size = Vector3(); - } else { - aabb.expand_to(vtx); - } - - float v_uv[2] = { (float)uvs[i].x, (float)uvs[i].y }; - memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_TEX_UV]], v_uv, 8); - - float v_vertex[3] = { (float)vtx.x, (float)vtx.y, (float)vtx.z }; - memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_VERTEX]], &v_vertex, sizeof(float) * 3); - memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_NORMAL]], &v_normal, 4); - memcpy(&vertex_write_buffer[i * vertex_stride + mesh_surface_offsets[RS::ARRAY_TANGENT]], &v_tangent, 4); - memcpy(&attribute_write_buffer[i * attrib_stride + mesh_surface_offsets[RS::ARRAY_COLOR]], v_color, 4); - } - - RID mesh = get_mesh(); - RS::get_singleton()->mesh_surface_update_vertex_region(mesh, 0, 0, vertex_buffer); - RS::get_singleton()->mesh_surface_update_attribute_region(mesh, 0, 0, attribute_buffer); - - RS::get_singleton()->mesh_set_custom_aabb(mesh, aabb); - set_aabb(aabb); - - RID shader_rid; - StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), &shader_rid); - if (last_shader != shader_rid) { - RS::get_singleton()->material_set_shader(get_material(), shader_rid); - last_shader = shader_rid; - } - if (last_texture != texture->get_rid()) { - RS::get_singleton()->material_set_param(get_material(), "texture_albedo", texture->get_rid()); - last_texture = texture->get_rid(); - } - if (get_alpha_cut_mode() == ALPHA_CUT_DISABLED) { - RS::get_singleton()->material_set_render_priority(get_material(), get_render_priority()); - RS::get_singleton()->mesh_surface_set_material(mesh, 0, get_material()); - } + draw_texture_rect(texture, dst_rect, src_rect); } void AnimatedSprite3D::_validate_property(PropertyInfo &p_property) const { @@ -1285,8 +1161,8 @@ StringName AnimatedSprite3D::get_animation() const { return animation; } -TypedArray<String> AnimatedSprite3D::get_configuration_warnings() const { - TypedArray<String> warnings = SpriteBase3D::get_configuration_warnings(); +PackedStringArray AnimatedSprite3D::get_configuration_warnings() const { + PackedStringArray warnings = SpriteBase3D::get_configuration_warnings(); if (frames.is_null()) { warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames.")); } diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index f6ad1bbdb8..edc48c7b71 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -80,6 +80,9 @@ private: RID mesh; RID material; + RID last_shader; + RID last_texture; + bool flags[FLAG_MAX] = {}; AlphaCutMode alpha_cut = ALPHA_CUT_DISABLED; StandardMaterial3D::BillboardMode billboard_mode = StandardMaterial3D::BILLBOARD_DISABLED; @@ -94,6 +97,7 @@ protected: void _notification(int p_what); static void _bind_methods(); virtual void _draw() = 0; + void draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, Rect2 p_src_rect); _FORCE_INLINE_ void set_aabb(const AABB &p_aabb) { aabb = p_aabb; } _FORCE_INLINE_ RID &get_mesh() { return mesh; } _FORCE_INLINE_ RID &get_material() { return material; } @@ -167,9 +171,6 @@ class Sprite3D : public SpriteBase3D { int vframes = 1; int hframes = 1; - RID last_shader; - RID last_texture; - protected: virtual void _draw() override; static void _bind_methods(); @@ -225,9 +226,6 @@ class AnimatedSprite3D : public SpriteBase3D { double _get_frame_duration(); void _reset_timeout(); - RID last_shader; - RID last_texture; - protected: virtual void _draw() override; static void _bind_methods(); @@ -255,7 +253,7 @@ public: virtual Rect2 get_item_rect() const override; - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; AnimatedSprite3D(); diff --git a/scene/3d/vehicle_body_3d.cpp b/scene/3d/vehicle_body_3d.cpp index d61b49eaa7..36b5e61f45 100644 --- a/scene/3d/vehicle_body_3d.cpp +++ b/scene/3d/vehicle_body_3d.cpp @@ -105,8 +105,8 @@ void VehicleWheel3D::_notification(int p_what) { } } -TypedArray<String> VehicleWheel3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray VehicleWheel3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Object::cast_to<VehicleBody3D>(get_parent())) { warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D.")); diff --git a/scene/3d/vehicle_body_3d.h b/scene/3d/vehicle_body_3d.h index 5c4f4beaea..a6a49ee88a 100644 --- a/scene/3d/vehicle_body_3d.h +++ b/scene/3d/vehicle_body_3d.h @@ -147,7 +147,7 @@ public: void set_steering(real_t p_steering); real_t get_steering() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; VehicleWheel3D(); }; diff --git a/scene/3d/visible_on_screen_notifier_3d.cpp b/scene/3d/visible_on_screen_notifier_3d.cpp index bcf294e216..6d4c18a69e 100644 --- a/scene/3d/visible_on_screen_notifier_3d.cpp +++ b/scene/3d/visible_on_screen_notifier_3d.cpp @@ -95,10 +95,11 @@ VisibleOnScreenNotifier3D::VisibleOnScreenNotifier3D() { RS::get_singleton()->visibility_notifier_set_callbacks(notifier, callable_mp(this, &VisibleOnScreenNotifier3D::_visibility_enter), callable_mp(this, &VisibleOnScreenNotifier3D::_visibility_exit)); set_base(notifier); } + VisibleOnScreenNotifier3D::~VisibleOnScreenNotifier3D() { - RID base = get_base(); + RID base_old = get_base(); set_base(RID()); - RS::get_singleton()->free(base); + RS::get_singleton()->free(base_old); } ////////////////////////////////////// @@ -188,7 +189,7 @@ void VisibleOnScreenEnabler3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_enable_node_path"), &VisibleOnScreenEnabler3D::get_enable_node_path); ADD_GROUP("Enabling", "enable_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "enable_mode", PROPERTY_HINT_ENUM, "Inherit,Always,WhenPaused"), "set_enable_mode", "get_enable_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "enable_mode", PROPERTY_HINT_ENUM, "Inherit,Always,When Paused"), "set_enable_mode", "get_enable_mode"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "enable_node_path"), "set_enable_node_path", "get_enable_node_path"); BIND_ENUM_CONSTANT(ENABLE_MODE_INHERIT); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index db9f68544b..b5b37bf837 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -34,14 +34,8 @@ AABB VisualInstance3D::get_aabb() const { AABB ret; - if (GDVIRTUAL_CALL(_get_aabb, ret)) { - return ret; - } - return AABB(); -} - -AABB VisualInstance3D::get_transformed_aabb() const { - return get_global_transform().xform(get_aabb()); + GDVIRTUAL_CALL(_get_aabb, ret); + return ret; } void VisualInstance3D::_update_visibility() { @@ -80,10 +74,6 @@ RID VisualInstance3D::get_instance() const { return instance; } -RID VisualInstance3D::_get_visual_instance_rid() const { - return instance; -} - void VisualInstance3D::set_layer_mask(uint32_t p_mask) { layers = p_mask; RenderingServer::get_singleton()->instance_set_layer_mask(instance, p_mask); @@ -112,7 +102,6 @@ bool VisualInstance3D::get_layer_mask_value(int p_layer_number) const { } void VisualInstance3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("_get_visual_instance_rid"), &VisualInstance3D::_get_visual_instance_rid); ClassDB::bind_method(D_METHOD("set_base", "base"), &VisualInstance3D::set_base); ClassDB::bind_method(D_METHOD("get_base"), &VisualInstance3D::get_base); ClassDB::bind_method(D_METHOD("get_instance"), &VisualInstance3D::get_instance); @@ -121,8 +110,6 @@ void VisualInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer_mask_value", "layer_number", "value"), &VisualInstance3D::set_layer_mask_value); ClassDB::bind_method(D_METHOD("get_layer_mask_value", "layer_number"), &VisualInstance3D::get_layer_mask_value); - ClassDB::bind_method(D_METHOD("get_transformed_aabb"), &VisualInstance3D::get_transformed_aabb); - GDVIRTUAL_BIND(_get_aabb); ADD_PROPERTY(PropertyInfo(Variant::INT, "layers", PROPERTY_HINT_LAYERS_3D_RENDER), "set_layer_mask", "get_layer_mask"); } @@ -164,7 +151,7 @@ Ref<Material> GeometryInstance3D::get_material_overlay() const { return material_overlay; } -void GeometryInstance3D::set_transparecy(float p_transparency) { +void GeometryInstance3D::set_transparency(float p_transparency) { transparency = CLAMP(p_transparency, 0.0f, 1.0f); RS::get_singleton()->instance_geometry_set_transparency(get_instance(), transparency); } @@ -224,15 +211,20 @@ GeometryInstance3D::VisibilityRangeFadeMode GeometryInstance3D::get_visibility_r } const StringName *GeometryInstance3D::_instance_uniform_get_remap(const StringName p_name) const { - StringName *r = instance_uniform_property_remap.getptr(p_name); + StringName *r = instance_shader_parameter_property_remap.getptr(p_name); if (!r) { String s = p_name; +#ifndef DISABLE_DEPRECATED if (s.begins_with("shader_uniforms/")) { - StringName name = s.replace("shader_uniforms/", ""); - instance_uniform_property_remap[p_name] = name; - return instance_uniform_property_remap.getptr(p_name); + s = s.replace("shader_uniforms/", "instance_shader_parameters/"); + } +#endif // DISABLE_DEPRECATED + if (s.begins_with("instance_shader_parameters/")) { + StringName pname = StringName(s); + StringName name = s.replace("instance_shader_parameters/", ""); + instance_shader_parameter_property_remap[pname] = name; + return instance_shader_parameter_property_remap.getptr(pname); } - return nullptr; } @@ -255,7 +247,7 @@ bool GeometryInstance3D::_set(const StringName &p_name, const Variant &p_value) set_gi_mode(GI_MODE_DYNAMIC); return true; } -#endif +#endif // DISABLE_DEPRECATED return false; } @@ -278,13 +270,13 @@ void GeometryInstance3D::_get_property_list(List<PropertyInfo> *p_list) const { if (def_value.get_type() != Variant::NIL) { has_def_value = true; } - if (instance_uniforms.has(pi.name)) { + if (instance_shader_parameters.has(pi.name)) { pi.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE | (has_def_value ? (PROPERTY_USAGE_CHECKABLE | PROPERTY_USAGE_CHECKED) : PROPERTY_USAGE_NONE); } else { pi.usage = PROPERTY_USAGE_EDITOR | (has_def_value ? PROPERTY_USAGE_CHECKABLE : PROPERTY_USAGE_NONE); //do not save if not changed } - pi.name = "shader_uniforms/" + pi.name; + pi.name = "instance_shader_parameters/" + pi.name; p_list->push_back(pi); } } @@ -323,9 +315,9 @@ void GeometryInstance3D::set_instance_shader_parameter(const StringName &p_name, if (p_value.get_type() == Variant::NIL) { Variant def_value = RS::get_singleton()->instance_geometry_get_shader_parameter_default_value(get_instance(), p_name); RS::get_singleton()->instance_geometry_set_shader_parameter(get_instance(), p_name, def_value); - instance_uniforms.erase(p_value); + instance_shader_parameters.erase(p_value); } else { - instance_uniforms[p_name] = p_value; + instance_shader_parameters[p_name] = p_value; if (p_value.get_type() == Variant::OBJECT) { RID tex_id = p_value; RS::get_singleton()->instance_geometry_set_shader_parameter(get_instance(), p_name, tex_id); @@ -385,8 +377,8 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() { return ignore_occlusion_culling; } -TypedArray<String> GeometryInstance3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray GeometryInstance3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) { warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance.")); @@ -416,7 +408,7 @@ void GeometryInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_lod_bias", "bias"), &GeometryInstance3D::set_lod_bias); ClassDB::bind_method(D_METHOD("get_lod_bias"), &GeometryInstance3D::get_lod_bias); - ClassDB::bind_method(D_METHOD("set_transparency", "transparency"), &GeometryInstance3D::set_transparecy); + ClassDB::bind_method(D_METHOD("set_transparency", "transparency"), &GeometryInstance3D::set_transparency); ClassDB::bind_method(D_METHOD("get_transparency"), &GeometryInstance3D::get_transparency); ClassDB::bind_method(D_METHOD("set_visibility_range_end_margin", "distance"), &GeometryInstance3D::set_visibility_range_end_margin); diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index 100d8d8836..f18bff2ddc 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -40,8 +40,6 @@ class VisualInstance3D : public Node3D { RID instance; uint32_t layers = 1; - RID _get_visual_instance_rid() const; - protected: void _update_visibility(); @@ -60,8 +58,6 @@ public: RID get_instance() const; virtual AABB get_aabb() const; - virtual AABB get_transformed_aabb() const; // helper - void set_base(const RID &p_base); RID get_base() const; @@ -121,8 +117,8 @@ private: float lod_bias = 1.0; - mutable HashMap<StringName, Variant> instance_uniforms; - mutable HashMap<StringName, StringName> instance_uniform_property_remap; + mutable HashMap<StringName, Variant> instance_shader_parameters; + mutable HashMap<StringName, StringName> instance_shader_parameter_property_remap; float extra_cull_margin = 0.0; LightmapScale lightmap_scale = LIGHTMAP_SCALE_1X; @@ -142,7 +138,7 @@ public: void set_cast_shadows_setting(ShadowCastingSetting p_shadow_casting_setting); ShadowCastingSetting get_cast_shadows_setting() const; - void set_transparecy(float p_transparency); + void set_transparency(float p_transparency); float get_transparency() const; void set_visibility_range_begin(float p_dist); @@ -186,7 +182,7 @@ public: void set_ignore_occlusion_culling(bool p_enabled); bool is_ignoring_occlusion_culling(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; GeometryInstance3D(); virtual ~GeometryInstance3D(); }; diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index c97af087bf..7caf2f4874 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -30,6 +30,7 @@ #include "voxel_gi.h" +#include "core/config/project_settings.h" #include "core/core_string_names.h" #include "mesh_instance_3d.h" #include "multimesh_instance_3d.h" @@ -45,8 +46,8 @@ void VoxelGIData::_set_data(const Dictionary &p_data) { ERR_FAIL_COND(!p_data.has("level_counts")); ERR_FAIL_COND(!p_data.has("to_cell_xform")); - AABB bounds = p_data["bounds"]; - Vector3 octree_size = p_data["octree_size"]; + AABB bounds_new = p_data["bounds"]; + Vector3 octree_size_new = p_data["octree_size"]; Vector<uint8_t> octree_cells = p_data["octree_cells"]; Vector<uint8_t> octree_data = p_data["octree_data"]; @@ -63,9 +64,9 @@ void VoxelGIData::_set_data(const Dictionary &p_data) { octree_df = img->get_data(); } Vector<int> octree_levels = p_data["level_counts"]; - Transform3D to_cell_xform = p_data["to_cell_xform"]; + Transform3D to_cell_xform_new = p_data["to_cell_xform"]; - allocate(to_cell_xform, bounds, octree_size, octree_cells, octree_data, octree_df, octree_levels); + allocate(to_cell_xform_new, bounds_new, octree_size_new, octree_cells, octree_data, octree_df, octree_levels); } Dictionary VoxelGIData::_get_data() const { @@ -76,9 +77,7 @@ Dictionary VoxelGIData::_get_data() const { d["octree_cells"] = get_octree_cells(); d["octree_data"] = get_data_cells(); if (otsize != Vector3i()) { - Ref<Image> img; - img.instantiate(); - img->create(otsize.x * otsize.y, otsize.z, false, Image::FORMAT_L8, get_distance_field()); + Ref<Image> img = Image::create_from_data(otsize.x * otsize.y, otsize.z, false, Image::FORMAT_L8, get_distance_field()); Vector<uint8_t> df_png = img->save_png_to_buffer(); ERR_FAIL_COND_V(df_png.size() == 0, Dictionary()); d["octree_df_png"] = df_png; @@ -382,7 +381,10 @@ void VoxelGI::bake(Node *p_from_node, bool p_create_visual_debug) { float exposure_normalization = 1.0; if (camera_attributes.is_valid()) { - exposure_normalization = camera_attributes->calculate_exposure_normalization() * camera_attributes->get_exposure_multiplier(); + exposure_normalization = camera_attributes->get_exposure_multiplier(); + if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + exposure_normalization = camera_attributes->calculate_exposure_normalization(); + } } Voxelizer baker; @@ -431,10 +433,10 @@ void VoxelGI::bake(Node *p_from_node, bool p_create_visual_debug) { #endif } else { - Ref<VoxelGIData> probe_data = get_probe_data(); + Ref<VoxelGIData> probe_data_new = get_probe_data(); - if (probe_data.is_null()) { - probe_data.instantiate(); + if (probe_data_new.is_null()) { + probe_data_new.instantiate(); } if (bake_step_function) { @@ -443,13 +445,13 @@ void VoxelGI::bake(Node *p_from_node, bool p_create_visual_debug) { Vector<uint8_t> df = baker.get_sdf_3d_image(); - RS::get_singleton()->voxel_gi_set_baked_exposure_normalization(probe_data->get_rid(), exposure_normalization); + RS::get_singleton()->voxel_gi_set_baked_exposure_normalization(probe_data_new->get_rid(), exposure_normalization); - probe_data->allocate(baker.get_to_cell_space_xform(), AABB(-extents, extents * 2.0), baker.get_voxel_gi_octree_size(), baker.get_voxel_gi_octree_cells(), baker.get_voxel_gi_data_cells(), df, baker.get_voxel_gi_level_cell_count()); + probe_data_new->allocate(baker.get_to_cell_space_xform(), AABB(-extents, extents * 2.0), baker.get_voxel_gi_octree_size(), baker.get_voxel_gi_octree_cells(), baker.get_voxel_gi_data_cells(), df, baker.get_voxel_gi_level_cell_count()); - set_probe_data(probe_data); + set_probe_data(probe_data_new); #ifdef TOOLS_ENABLED - probe_data->set_edited(true); //so it gets saved + probe_data_new->set_edited(true); //so it gets saved #endif } @@ -468,8 +470,8 @@ AABB VoxelGI::get_aabb() const { return AABB(-extents, extents * 2); } -TypedArray<String> VoxelGI::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray VoxelGI::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (RenderingServer::get_singleton()->is_low_end()) { warnings.push_back(RTR("VoxelGIs are not supported by the OpenGL video driver.\nUse a LightmapGI instead.")); diff --git a/scene/3d/voxel_gi.h b/scene/3d/voxel_gi.h index b31ae4cd95..fc10091d4f 100644 --- a/scene/3d/voxel_gi.h +++ b/scene/3d/voxel_gi.h @@ -157,7 +157,7 @@ public: virtual AABB get_aabb() const override; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; VoxelGI(); ~VoxelGI(); diff --git a/scene/3d/world_environment.cpp b/scene/3d/world_environment.cpp index ae7d79e8b0..6cc5e9ef20 100644 --- a/scene/3d/world_environment.cpp +++ b/scene/3d/world_environment.cpp @@ -135,8 +135,8 @@ Ref<CameraAttributes> WorldEnvironment::get_camera_attributes() const { return camera_attributes; } -TypedArray<String> WorldEnvironment::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray WorldEnvironment::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!environment.is_valid() && !camera_attributes.is_valid()) { warnings.push_back(RTR("To have any visible effect, WorldEnvironment requires its \"Environment\" property to contain an Environment, its \"Camera Attributes\" property to contain a CameraAttributes resource, or both.")); diff --git a/scene/3d/world_environment.h b/scene/3d/world_environment.h index 07f243c750..cc46a06b4c 100644 --- a/scene/3d/world_environment.h +++ b/scene/3d/world_environment.h @@ -55,7 +55,7 @@ public: void set_camera_attributes(const Ref<CameraAttributes> &p_camera_attributes); Ref<CameraAttributes> get_camera_attributes() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; WorldEnvironment(); }; diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index de765d7ccb..ca7d1dfc1d 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -36,49 +36,37 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// -void XRCamera3D::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - // need to find our XROrigin3D parent and let it know we're its camera! - XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); - if (origin != nullptr) { - origin->set_tracked_camera(this); - } - } break; +void XRCamera3D::_bind_tracker() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); - case NOTIFICATION_EXIT_TREE: { - // need to find our XROrigin3D parent and let it know we're no longer its camera! - XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent()); - if (origin != nullptr && origin->get_tracked_camera() == this) { - origin->set_tracked_camera(nullptr); - } - } break; + tracker = xr_server->get_tracker(tracker_name); + if (tracker.is_valid()) { + tracker->connect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed)); + + Ref<XRPose> pose = tracker->get_pose(pose_name); + if (pose.is_valid()) { + set_transform(pose->get_adjusted_transform()); + } + } +} + +void XRCamera3D::_unbind_tracker() { + if (tracker.is_valid()) { + tracker->disconnect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed)); } + tracker.unref(); } void XRCamera3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) { if (p_tracker_name == tracker_name) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL(xr_server); - - tracker = xr_server->get_tracker(p_tracker_name); - if (tracker.is_valid()) { - tracker->connect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed)); - - Ref<XRPose> pose = tracker->get_pose(pose_name); - if (pose.is_valid()) { - set_transform(pose->get_adjusted_transform()); - } - } + _bind_tracker(); } } void XRCamera3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) { if (p_tracker_name == tracker_name) { - if (tracker.is_valid()) { - tracker->disconnect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed)); - } - tracker.unref(); + _unbind_tracker(); } } @@ -88,8 +76,8 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) { } } -TypedArray<String> XRCamera3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray XRCamera3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // must be child node of XROrigin3D! @@ -213,6 +201,9 @@ XRCamera3D::XRCamera3D() { xr_server->connect("tracker_added", callable_mp(this, &XRCamera3D::_changed_tracker)); xr_server->connect("tracker_updated", callable_mp(this, &XRCamera3D::_changed_tracker)); xr_server->connect("tracker_removed", callable_mp(this, &XRCamera3D::_removed_tracker)); + + // check if our tracker already exists and if so, bind it... + _bind_tracker(); } XRCamera3D::~XRCamera3D() { @@ -372,7 +363,7 @@ void XRNode3D::_unbind_tracker() { } void XRNode3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) { - if (p_tracker_name == p_tracker_name) { + if (tracker_name == p_tracker_name) { // just in case unref our current tracker _unbind_tracker(); @@ -382,7 +373,7 @@ void XRNode3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_t } void XRNode3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) { - if (p_tracker_name == p_tracker_name) { + if (tracker_name == p_tracker_name) { // unref our tracker, it's no longer available _unbind_tracker(); } @@ -414,8 +405,8 @@ XRNode3D::~XRNode3D() { xr_server->disconnect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker)); } -TypedArray<String> XRNode3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray XRNode3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // must be child node of XROrigin! @@ -582,11 +573,22 @@ Plane XRAnchor3D::get_plane() const { //////////////////////////////////////////////////////////////////////////////////////////////////// -TypedArray<String> XROrigin3D::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +Vector<XROrigin3D *> XROrigin3D::origin_nodes; + +PackedStringArray XROrigin3D::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { - if (tracked_camera == nullptr) { + bool has_camera = false; + for (int i = 0; !has_camera && i < get_child_count(); i++) { + XRCamera3D *camera = Object::cast_to<XRCamera3D>(get_child(i)); + if (camera) { + // found it! + has_camera = true; + } + } + + if (!has_camera) { warnings.push_back(RTR("XROrigin3D requires an XRCamera3D child node.")); } } @@ -603,14 +605,10 @@ void XROrigin3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_world_scale", "world_scale"), &XROrigin3D::set_world_scale); ClassDB::bind_method(D_METHOD("get_world_scale"), &XROrigin3D::get_world_scale); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "world_scale"), "set_world_scale", "get_world_scale"); -} - -void XROrigin3D::set_tracked_camera(XRCamera3D *p_tracked_camera) { - tracked_camera = p_tracked_camera; -} -XRCamera3D *XROrigin3D::get_tracked_camera() const { - return tracked_camera; + ClassDB::bind_method(D_METHOD("set_current", "enabled"), &XROrigin3D::set_current); + ClassDB::bind_method(D_METHOD("is_current"), &XROrigin3D::is_current); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current"); } real_t XROrigin3D::get_world_scale() const { @@ -629,6 +627,50 @@ void XROrigin3D::set_world_scale(real_t p_world_scale) { xr_server->set_world_scale(p_world_scale); } +void XROrigin3D::set_current(bool p_enabled) { + current = p_enabled; + + if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) { + return; + } + + // Notify us of any transform changes + set_notify_local_transform(current); + set_notify_transform(current); + + if (current) { + for (int i = 0; i < origin_nodes.size(); i++) { + if (origin_nodes[i] != this) { + origin_nodes[i]->set_current(false); + } + } + + // update XRServer with our current position + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + + xr_server->set_world_origin(get_global_transform()); + } else { + bool found = false; + // We no longer have a current origin so find the first one we can make current + for (int i = 0; !found && i < origin_nodes.size(); i++) { + if (origin_nodes[i] != this) { + origin_nodes[i]->set_current(true); + found = true; + } + } + } +} + +bool XROrigin3D::is_current() const { + if (Engine::get_singleton()->is_editor_hint()) { + // return as is + return current; + } else { + return current && is_inside_tree(); + } +} + void XROrigin3D::_notification(int p_what) { // get our XRServer XRServer *xr_server = XRServer::get_singleton(); @@ -636,34 +678,47 @@ void XROrigin3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - set_process_internal(true); + if (!Engine::get_singleton()->is_editor_hint()) { + if (origin_nodes.is_empty()) { + // first entry always becomes current + current = true; + } + + origin_nodes.push_back(this); + + if (current) { + // set this again so we do whatever setup is needed. + set_current(true); + } + } } break; case NOTIFICATION_EXIT_TREE: { - set_process_internal(false); - } break; + if (!Engine::get_singleton()->is_editor_hint()) { + origin_nodes.erase(this); - case NOTIFICATION_INTERNAL_PROCESS: { - // set our world origin to our node transform - xr_server->set_world_origin(get_global_transform()); - - // check if we have a primary interface - Ref<XRInterface> xr_interface = xr_server->get_primary_interface(); - if (xr_interface.is_valid() && tracked_camera != nullptr) { - // get our positioning transform for our headset - Transform3D t = xr_interface->get_camera_transform(); + if (current) { + // We are no longer current + set_current(false); + } + } + } break; - // now apply this to our camera - tracked_camera->set_transform(t); + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: + case NOTIFICATION_TRANSFORM_CHANGED: { + if (current && !Engine::get_singleton()->is_editor_hint()) { + xr_server->set_world_origin(get_global_transform()); } } break; } - // send our notification to all active XE interfaces, they may need to react to it also - for (int i = 0; i < xr_server->get_interface_count(); i++) { - Ref<XRInterface> interface = xr_server->get_interface(i); - if (interface.is_valid() && interface->is_initialized()) { - interface->notification(p_what); + if (current) { + // send our notification to all active XE interfaces, they may need to react to it also + for (int i = 0; i < xr_server->get_interface_count(); i++) { + Ref<XRInterface> interface = xr_server->get_interface(i); + if (interface.is_valid() && interface->is_initialized()) { + interface->notification(p_what); + } } } } diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index 312bef7856..990fb61983 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -48,14 +48,14 @@ protected: StringName pose_name = "default"; Ref<XRPositionalTracker> tracker; - void _notification(int p_what); - + void _bind_tracker(); + void _unbind_tracker(); void _changed_tracker(const StringName p_tracker_name, int p_tracker_type); void _removed_tracker(const StringName p_tracker_name, int p_tracker_type); void _pose_changed(const Ref<XRPose> &p_pose); public: - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const override; virtual Point2 unproject_position(const Vector3 &p_pos) const override; @@ -107,7 +107,7 @@ public: Ref<XRPose> get_pose(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; XRNode3D(); ~XRNode3D(); @@ -180,21 +180,22 @@ class XROrigin3D : public Node3D { GDCLASS(XROrigin3D, Node3D); private: - XRCamera3D *tracked_camera = nullptr; + bool current = false; + static Vector<XROrigin3D *> origin_nodes; // all origin nodes in tree protected: void _notification(int p_what); static void _bind_methods(); public: - TypedArray<String> get_configuration_warnings() const override; - - void set_tracked_camera(XRCamera3D *p_tracked_camera); - XRCamera3D *get_tracked_camera() const; + PackedStringArray get_configuration_warnings() const override; real_t get_world_scale() const; void set_world_scale(real_t p_world_scale); + void set_current(bool p_enabled); + bool is_current() const; + XROrigin3D() {} ~XROrigin3D() {} }; diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index 2dc61efb94..0d8b7ba8f7 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -343,10 +343,10 @@ void AnimationNodeBlendSpace2D::_update_triangles() { points.write[i] = blend_points[i].position; } - Vector<Delaunay2D::Triangle> triangles = Delaunay2D::triangulate(points); + Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(points); - for (int i = 0; i < triangles.size(); i++) { - add_triangle(triangles[i].points[0], triangles[i].points[1], triangles[i].points[2]); + for (int i = 0; i < tr.size(); i++) { + add_triangle(tr[i].points[0], tr[i].points[1], tr[i].points[2]); } emit_signal(SNAME("triangles_updated")); } @@ -376,9 +376,9 @@ Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) { points[j], points[(j + 1) % 3] }; - Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, s); - if (first || closest.distance_to(p_point) < best_point.distance_to(p_point)) { - best_point = closest; + Vector2 closest_point = Geometry2D::get_closest_point_to_segment(p_point, s); + if (first || closest_point.distance_to(p_point) < best_point.distance_to(p_point)) { + best_point = closest_point; first = false; } } @@ -436,8 +436,8 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_see _update_triangles(); Vector2 blend_pos = get_parameter(blend_position); - int closest = get_parameter(this->closest); - double length_internal = get_parameter(this->length_internal); + int cur_closest = get_parameter(closest); + double cur_length_internal = get_parameter(length_internal); double mind = 0.0; //time of min distance point if (blend_mode == BLEND_MODE_INTERPOLATED) { @@ -528,37 +528,37 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek, bool p_see } } - if (new_closest != closest && new_closest != -1) { + if (new_closest != cur_closest && new_closest != -1) { double from = 0.0; - if (blend_mode == BLEND_MODE_DISCRETE_CARRY && closest != -1) { + if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) { //for ping-pong loop - Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[closest].node); + Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node); Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node); if (!na_c.is_null() && !na_n.is_null()) { na_n->set_backward(na_c->is_backward()); } //see how much animation remains - from = length_internal - blend_node(blend_points[closest].name, blend_points[closest].node, p_time, false, p_seek_root, 0.0, FILTER_IGNORE, true); + from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_seek_root, 0.0, FILTER_IGNORE, true); } mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_seek_root, 1.0, FILTER_IGNORE, true); - length_internal = from + mind; + cur_length_internal = from + mind; - closest = new_closest; + cur_closest = new_closest; } else { - mind = blend_node(blend_points[closest].name, blend_points[closest].node, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); + mind = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); } for (int i = 0; i < blend_points_used; i++) { - if (i != closest) { + if (i != cur_closest) { blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_seek_root, 0, FILTER_IGNORE, sync); } } } - set_parameter(this->closest, closest); - set_parameter(this->length_internal, length_internal); + set_parameter(this->closest, cur_closest); + set_parameter(this->length_internal, cur_length_internal); return mind; } diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 1efbaacb3b..0c91729a6f 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -68,13 +68,13 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_seek_r AnimationPlayer *ap = state->player; ERR_FAIL_COND_V(!ap, 0); - double time = get_parameter(this->time); + double cur_time = get_parameter(time); if (!ap->has_animation(animation)) { AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(parent); if (tree) { - String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); - make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), name, animation)); + String node_name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), node_name, animation)); } else { make_invalid(vformat(RTR("Animation not found: '%s'"), animation)); @@ -86,58 +86,58 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_seek_r Ref<Animation> anim = ap->get_animation(animation); double anim_size = (double)anim->get_length(); double step = 0.0; - double prev_time = time; + double prev_time = cur_time; int pingponged = 0; bool current_backward = signbit(p_time); if (p_seek) { - step = p_time - time; - time = p_time; + step = p_time - cur_time; + cur_time = p_time; } else { p_time *= backward ? -1.0 : 1.0; - if (!(time == anim_size && !current_backward) && !(time == 0 && current_backward)) { - time = time + p_time; + if (!(cur_time == anim_size && !current_backward) && !(cur_time == 0 && current_backward)) { + cur_time = cur_time + p_time; step = p_time; } } if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { if (!Math::is_zero_approx(anim_size)) { - if ((int)Math::floor(abs(time - prev_time) / anim_size) % 2 == 0) { - if (prev_time >= 0 && time < 0) { + if ((int)Math::floor(abs(cur_time - prev_time) / anim_size) % 2 == 0) { + if (prev_time >= 0 && cur_time < 0) { backward = !backward; pingponged = -1; } - if (prev_time <= anim_size && time > anim_size) { + if (prev_time <= anim_size && cur_time > anim_size) { backward = !backward; pingponged = 1; } } - time = Math::pingpong(time, anim_size); + cur_time = Math::pingpong(cur_time, anim_size); } } else { if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { if (!Math::is_zero_approx(anim_size)) { - time = Math::fposmod(time, anim_size); + cur_time = Math::fposmod(cur_time, anim_size); } - } else if (time < 0) { - step += time; - time = 0; - } else if (time > anim_size) { - step += anim_size - time; - time = anim_size; + } else if (cur_time < 0) { + step += cur_time; + cur_time = 0; + } else if (cur_time > anim_size) { + step += anim_size - cur_time; + cur_time = anim_size; } backward = false; } if (play_mode == PLAY_MODE_FORWARD) { - blend_animation(animation, time, step, p_seek, p_seek_root, 1.0, pingponged); + blend_animation(animation, cur_time, step, p_seek, p_seek_root, 1.0, pingponged); } else { - blend_animation(animation, anim_size - time, -step, p_seek, p_seek_root, 1.0, pingponged); + blend_animation(animation, anim_size - cur_time, -step, p_seek, p_seek_root, 1.0, pingponged); } - set_parameter(this->time, time); + set_parameter(time, cur_time); - return anim_size - time; + return anim_size - cur_time; } String AnimationNodeAnimation::get_caption() const { @@ -217,19 +217,19 @@ Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_pa } } -void AnimationNodeOneShot::set_fadein_time(float p_time) { +void AnimationNodeOneShot::set_fadein_time(double p_time) { fade_in = p_time; } -void AnimationNodeOneShot::set_fadeout_time(float p_time) { +void AnimationNodeOneShot::set_fadeout_time(double p_time) { fade_out = p_time; } -float AnimationNodeOneShot::get_fadein_time() const { +double AnimationNodeOneShot::get_fadein_time() const { return fade_in; } -float AnimationNodeOneShot::get_fadeout_time() const { +double AnimationNodeOneShot::get_fadeout_time() const { return fade_out; } @@ -237,11 +237,11 @@ void AnimationNodeOneShot::set_autorestart(bool p_active) { autorestart = p_active; } -void AnimationNodeOneShot::set_autorestart_delay(float p_time) { +void AnimationNodeOneShot::set_autorestart_delay(double p_time) { autorestart_delay = p_time; } -void AnimationNodeOneShot::set_autorestart_random_delay(float p_time) { +void AnimationNodeOneShot::set_autorestart_random_delay(double p_time) { autorestart_random_delay = p_time; } @@ -249,11 +249,11 @@ bool AnimationNodeOneShot::has_autorestart() const { return autorestart; } -float AnimationNodeOneShot::get_autorestart_delay() const { +double AnimationNodeOneShot::get_autorestart_delay() const { return autorestart_delay; } -float AnimationNodeOneShot::get_autorestart_random_delay() const { +double AnimationNodeOneShot::get_autorestart_random_delay() const { return autorestart_random_delay; } @@ -274,28 +274,28 @@ bool AnimationNodeOneShot::has_filter() const { } double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_seek_root) { - bool active = get_parameter(this->active); - bool prev_active = get_parameter(this->prev_active); - double time = get_parameter(this->time); - double remaining = get_parameter(this->remaining); - double time_to_restart = get_parameter(this->time_to_restart); + bool cur_active = get_parameter(active); + bool cur_prev_active = get_parameter(prev_active); + double cur_time = get_parameter(time); + double cur_remaining = get_parameter(remaining); + double cur_time_to_restart = get_parameter(time_to_restart); - if (!active) { + if (!cur_active) { //make it as if this node doesn't exist, pass input 0 by. - if (prev_active) { - set_parameter(this->prev_active, false); + if (cur_prev_active) { + set_parameter(prev_active, false); } - if (time_to_restart >= 0.0 && !p_seek) { - time_to_restart -= p_time; - if (time_to_restart < 0) { + if (cur_time_to_restart >= 0.0 && !p_seek) { + cur_time_to_restart -= p_time; + if (cur_time_to_restart < 0) { //restart - set_parameter(this->active, true); - active = true; + set_parameter(active, true); + cur_active = true; } - set_parameter(this->time_to_restart, time_to_restart); + set_parameter(time_to_restart, cur_time_to_restart); } - if (!active) { + if (!cur_active) { return blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); } } @@ -303,27 +303,27 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_seek_roo bool os_seek = p_seek; if (p_seek) { - time = p_time; + cur_time = p_time; } - bool do_start = !prev_active; + bool do_start = !cur_prev_active; if (do_start) { - time = 0; + cur_time = 0; os_seek = true; - set_parameter(this->prev_active, true); + set_parameter(prev_active, true); } - float blend; + real_t blend; - if (time < fade_in) { + if (cur_time < fade_in) { if (fade_in > 0) { - blend = time / fade_in; + blend = cur_time / fade_in; } else { blend = 0; } - } else if (!do_start && remaining <= fade_out) { + } else if (!do_start && cur_remaining <= fade_out) { if (fade_out > 0) { - blend = (remaining / fade_out); + blend = (cur_remaining / fade_out); } else { blend = 0; } @@ -338,29 +338,29 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_seek_roo main_rem = blend_input(0, p_time, p_seek, p_seek_root, 1.0 - blend, FILTER_BLEND, sync); } - double os_rem = blend_input(1, os_seek ? time : p_time, os_seek, p_seek_root, blend, FILTER_PASS, true); + double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_seek_root, blend, FILTER_PASS, true); if (do_start) { - remaining = os_rem; + cur_remaining = os_rem; } if (!p_seek) { - time += p_time; - remaining = os_rem; - if (remaining <= 0) { - set_parameter(this->active, false); - set_parameter(this->prev_active, false); + cur_time += p_time; + cur_remaining = os_rem; + if (cur_remaining <= 0) { + set_parameter(active, false); + set_parameter(prev_active, false); if (autorestart) { - float restart_sec = autorestart_delay + Math::randf() * autorestart_random_delay; - set_parameter(this->time_to_restart, restart_sec); + double restart_sec = autorestart_delay + Math::randd() * autorestart_random_delay; + set_parameter(time_to_restart, restart_sec); } } } - set_parameter(this->time, time); - set_parameter(this->remaining, remaining); + set_parameter(time, cur_time); + set_parameter(remaining, cur_remaining); - return MAX(main_rem, remaining); + return MAX(main_rem, cur_remaining); } void AnimationNodeOneShot::_bind_methods() { @@ -554,11 +554,11 @@ String AnimationNodeTimeScale::get_caption() const { } double AnimationNodeTimeScale::process(double p_time, bool p_seek, bool p_seek_root) { - double scale = get_parameter(this->scale); + double cur_scale = get_parameter(scale); if (p_seek) { return blend_input(0, p_time, true, p_seek_root, 1.0, FILTER_IGNORE, true); } else { - return blend_input(0, p_time * scale, false, p_seek_root, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time * cur_scale, false, p_seek_root, 1.0, FILTER_IGNORE, true); } } @@ -584,12 +584,12 @@ String AnimationNodeTimeSeek::get_caption() const { } double AnimationNodeTimeSeek::process(double p_time, bool p_seek, bool p_seek_root) { - double seek_pos = get_parameter(this->seek_pos); + double cur_seek_pos = get_parameter(seek_pos); if (p_seek) { return blend_input(0, p_time, true, p_seek_root, 1.0, FILTER_IGNORE, true); - } else if (seek_pos >= 0) { - double ret = blend_input(0, seek_pos, true, true, 1.0, FILTER_IGNORE, true); - set_parameter(this->seek_pos, -1.0); //reset + } else if (cur_seek_pos >= 0) { + double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true); + set_parameter(seek_pos, -1.0); //reset return ret; } else { return blend_input(0, p_time, false, p_seek_root, 1.0, FILTER_IGNORE, true); @@ -676,11 +676,11 @@ String AnimationNodeTransition::get_input_caption(int p_input) const { return inputs[p_input].name; } -void AnimationNodeTransition::set_xfade_time(float p_fade) { +void AnimationNodeTransition::set_xfade_time(double p_fade) { xfade_time = p_fade; } -float AnimationNodeTransition::get_xfade_time() const { +double AnimationNodeTransition::get_xfade_time() const { return xfade_time; } @@ -701,80 +701,80 @@ bool AnimationNodeTransition::is_from_start() const { } double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_seek_root) { - int current = get_parameter(this->current); - int prev = get_parameter(this->prev); - int prev_current = get_parameter(this->prev_current); + int cur_current = get_parameter(current); + int cur_prev = get_parameter(prev); + int cur_prev_current = get_parameter(prev_current); - double time = get_parameter(this->time); - double prev_xfading = get_parameter(this->prev_xfading); + double cur_time = get_parameter(time); + double cur_prev_xfading = get_parameter(prev_xfading); - bool switched = current != prev_current; + bool switched = cur_current != cur_prev_current; if (switched) { - set_parameter(this->prev_current, current); - set_parameter(this->prev, prev_current); + set_parameter(prev_current, cur_current); + set_parameter(prev, cur_prev_current); - prev = prev_current; - prev_xfading = xfade_time; - time = 0; + cur_prev = cur_prev_current; + cur_prev_xfading = xfade_time; + cur_time = 0; switched = true; } - if (current < 0 || current >= enabled_inputs || prev >= enabled_inputs) { + if (cur_current < 0 || cur_current >= enabled_inputs || cur_prev >= enabled_inputs) { return 0; } double rem = 0.0; for (int i = 0; i < enabled_inputs; i++) { - if (i != current && i != prev) { + if (i != cur_current && i != cur_prev) { blend_input(i, p_time, p_seek, p_seek_root, 0, FILTER_IGNORE, sync); } } - if (prev < 0) { // process current animation, check for transition + if (cur_prev < 0) { // process current animation, check for transition - rem = blend_input(current, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); + rem = blend_input(cur_current, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); if (p_seek) { - time = p_time; + cur_time = p_time; } else { - time += p_time; + cur_time += p_time; } - if (inputs[current].auto_advance && rem <= xfade_time) { - set_parameter(this->current, (current + 1) % enabled_inputs); + if (inputs[cur_current].auto_advance && rem <= xfade_time) { + set_parameter(current, (cur_current + 1) % enabled_inputs); } } else { // cross-fading from prev to current - float blend = xfade_time == 0 ? 0 : (prev_xfading / xfade_time); + real_t blend = xfade_time == 0 ? 0 : (cur_prev_xfading / xfade_time); if (xfade_curve.is_valid()) { blend = xfade_curve->sample(blend); } if (from_start && !p_seek && switched) { //just switched, seek to start of current - rem = blend_input(current, 0, true, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); + rem = blend_input(cur_current, 0, true, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); } else { - rem = blend_input(current, p_time, p_seek, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); + rem = blend_input(cur_current, p_time, p_seek, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); } if (p_seek) { - blend_input(prev, p_time, true, p_seek_root, blend, FILTER_IGNORE, true); - time = p_time; + blend_input(cur_prev, p_time, true, p_seek_root, blend, FILTER_IGNORE, true); + cur_time = p_time; } else { - blend_input(prev, p_time, false, p_seek_root, blend, FILTER_IGNORE, true); - time += p_time; - prev_xfading -= p_time; - if (prev_xfading < 0) { - set_parameter(this->prev, -1); + blend_input(cur_prev, p_time, false, p_seek_root, blend, FILTER_IGNORE, true); + cur_time += p_time; + cur_prev_xfading -= p_time; + if (cur_prev_xfading < 0) { + set_parameter(prev, -1); } } } - set_parameter(this->time, time); - set_parameter(this->prev_xfading, prev_xfading); + set_parameter(time, cur_time); + set_parameter(prev_xfading, cur_prev_xfading); return rem; } @@ -1070,10 +1070,10 @@ Ref<AnimationNode> AnimationNodeBlendTree::get_child_by_name(const StringName &p } bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - if (name.begins_with("nodes/")) { - String node_name = name.get_slicec('/', 1); - String what = name.get_slicec('/', 2); + String prop_name = p_name; + if (prop_name.begins_with("nodes/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); if (what == "node") { Ref<AnimationNode> anode = p_value; @@ -1089,7 +1089,7 @@ bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_val } return true; } - } else if (name == "node_connections") { + } else if (prop_name == "node_connections") { Array conns = p_value; ERR_FAIL_COND_V(conns.size() % 3 != 0, false); @@ -1103,10 +1103,10 @@ bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_val } bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - if (name.begins_with("nodes/")) { - String node_name = name.get_slicec('/', 1); - String what = name.get_slicec('/', 2); + String prop_name = p_name; + if (prop_name.begins_with("nodes/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); if (what == "node") { if (nodes.has(node_name)) { @@ -1121,7 +1121,7 @@ bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) cons return true; } } - } else if (name == "node_connections") { + } else if (prop_name == "node_connections") { List<NodeConnection> nc; get_node_connections(&nc); Array conns; @@ -1150,11 +1150,11 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons names.sort_custom<StringName::AlphCompare>(); for (const StringName &E : names) { - String name = E; - if (name != "output") { - p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); + String prop_name = E; + if (prop_name != "output") { + p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); } - p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 59c074cc80..1c31718259 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -102,18 +102,18 @@ public: }; private: - float fade_in = 0.0; - float fade_out = 0.0; + double fade_in = 0.0; + double fade_out = 0.0; bool autorestart = false; - float autorestart_delay = 1.0; - float autorestart_random_delay = 0.0; + double autorestart_delay = 1.0; + double autorestart_random_delay = 0.0; MixMode mix = MIX_MODE_BLEND; /* bool active; bool do_start; - float time; - float remaining;*/ + double time; + double remaining;*/ StringName active = PNAME("active"); StringName prev_active = "prev_active"; @@ -130,19 +130,19 @@ public: virtual String get_caption() const override; - void set_fadein_time(float p_time); - void set_fadeout_time(float p_time); + void set_fadein_time(double p_time); + void set_fadeout_time(double p_time); - float get_fadein_time() const; - float get_fadeout_time() const; + double get_fadein_time() const; + double get_fadeout_time() const; void set_autorestart(bool p_active); - void set_autorestart_delay(float p_time); - void set_autorestart_random_delay(float p_time); + void set_autorestart_delay(double p_time); + void set_autorestart_random_delay(double p_time); bool has_autorestart() const; - float get_autorestart_delay() const; - float get_autorestart_random_delay() const; + double get_autorestart_delay() const; + double get_autorestart_random_delay() const; void set_mix_mode(MixMode p_mix); MixMode get_mix_mode() const; @@ -285,9 +285,9 @@ class AnimationNodeTransition : public AnimationNodeSync { int enabled_inputs = 0; /* - float prev_xfading; + double prev_xfading; int prev; - float time; + double time; int current; int prev_current; */ @@ -297,7 +297,7 @@ class AnimationNodeTransition : public AnimationNodeSync { StringName current = PNAME("current"); StringName prev_current = "prev_current"; - float xfade_time = 0.0; + double xfade_time = 0.0; Ref<Curve> xfade_curve; bool from_start = true; @@ -322,8 +322,8 @@ public: void set_input_caption(int p_input, const String &p_name); String get_input_caption(int p_input) const; - void set_xfade_time(float p_fade); - float get_xfade_time() const; + void set_xfade_time(double p_fade); + double get_xfade_time() const; void set_xfade_curve(const Ref<Curve> &p_curve); Ref<Curve> get_xfade_curve() const; diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 49a59de9b2..8291df8036 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "animation_node_state_machine.h" +#include "scene/main/window.h" ///////////////////////////////////////////////// @@ -88,14 +89,6 @@ String AnimationNodeStateMachineTransition::get_advance_expression() const { return advance_expression; } -void AnimationNodeStateMachineTransition::set_advance_expression_base_node(const NodePath &p_expression_base_node) { - advance_expression_base_node = p_expression_base_node; -} - -NodePath AnimationNodeStateMachineTransition::get_advance_expression_base_node() const { - return advance_expression_base_node; -} - void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) { ERR_FAIL_COND(p_xfade < 0); xfade_time = p_xfade; @@ -157,9 +150,6 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_advance_expression", "text"), &AnimationNodeStateMachineTransition::set_advance_expression); ClassDB::bind_method(D_METHOD("get_advance_expression"), &AnimationNodeStateMachineTransition::get_advance_expression); - ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "path"), &AnimationNodeStateMachineTransition::set_advance_expression_base_node); - ClassDB::bind_method(D_METHOD("get_advance_expression_base_node"), &AnimationNodeStateMachineTransition::get_advance_expression_base_node); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve"); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); @@ -169,7 +159,6 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ADD_GROUP("Advance", "advance_"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "advance_condition"), "set_advance_condition", "get_advance_condition"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "advance_expression", PROPERTY_HINT_EXPRESSION, ""), "set_advance_expression", "get_advance_expression"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); ADD_GROUP("Disabling", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); @@ -655,14 +644,11 @@ bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref<Anima AnimationTree *tree_base = state_machine->get_animation_tree(); ERR_FAIL_COND_V(tree_base == nullptr, false); - NodePath advance_expression_base_node_path; - if (!transition->advance_expression_base_node.is_empty()) { - advance_expression_base_node_path = transition->advance_expression_base_node; - } else { - advance_expression_base_node_path = tree_base->get_advance_expression_base_node(); - } - + NodePath advance_expression_base_node_path = tree_base->get_advance_expression_base_node(); Node *expression_base = tree_base->get_node_or_null(advance_expression_base_node_path); + + WARN_PRINT_ONCE("Animation transition has a valid expression, but no expression base node was set on its AnimationTree."); + if (expression_base) { Ref<Expression> exp = transition->expression; bool ret = exp->execute(Array(), expression_base, false, Engine::get_singleton()->is_editor_hint()); // Avoids allowing the user to crash the system with an expression by only allowing const calls. @@ -725,11 +711,11 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<Animation ERR_FAIL_COND(p_node.is_null()); ERR_FAIL_COND(String(p_name).contains("/")); - State state; - state.node = p_node; - state.position = p_position; + State state_new; + state_new.node = p_node; + state_new.position = p_position; - states[p_name] = state; + states[p_name] = state_new; Ref<AnimationNodeStateMachine> anodesm = p_node; @@ -957,8 +943,8 @@ bool AnimationNodeStateMachine::_can_connect(const StringName &p_name, Vector<An return true; } - String name = p_name; - Vector<String> path = name.split("/"); + String node_name = p_name; + Vector<String> path = node_name.split("/"); if (path.size() < 2) { return false; @@ -966,12 +952,12 @@ bool AnimationNodeStateMachine::_can_connect(const StringName &p_name, Vector<An if (path[0] == "..") { if (prev_state_machine != nullptr) { - return prev_state_machine->_can_connect(name.replace_first("../", ""), p_parents); + return prev_state_machine->_can_connect(node_name.replace_first("../", ""), p_parents); } } else if (states.has(path[0])) { Ref<AnimationNodeStateMachine> anodesm = states[path[0]].node; if (anodesm.is_valid()) { - return anodesm->_can_connect(name.replace_first(path[0] + "/", ""), p_parents); + return anodesm->_can_connect(node_name.replace_first(path[0] + "/", ""), p_parents); } } @@ -1148,10 +1134,10 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { } double AnimationNodeStateMachine::process(double p_time, bool p_seek, bool p_seek_root) { - Ref<AnimationNodeStateMachinePlayback> playback = get_parameter(this->playback); - ERR_FAIL_COND_V(playback.is_null(), 0.0); + Ref<AnimationNodeStateMachinePlayback> playback_new = get_parameter(playback); + ERR_FAIL_COND_V(playback_new.is_null(), 0.0); - return playback->process(this, p_time, p_seek, p_seek_root); + return playback_new->process(this, p_time, p_seek, p_seek_root); } String AnimationNodeStateMachine::get_caption() const { @@ -1175,10 +1161,10 @@ Ref<AnimationNode> AnimationNodeStateMachine::get_child_by_name(const StringName } bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - if (name.begins_with("states/")) { - String node_name = name.get_slicec('/', 1); - String what = name.get_slicec('/', 2); + String prop_name = p_name; + if (prop_name.begins_with("states/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); if (what == "node") { Ref<AnimationNode> anode = p_value; @@ -1194,7 +1180,7 @@ bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_ } return true; } - } else if (name == "transitions") { + } else if (prop_name == "transitions") { Array trans = p_value; ERR_FAIL_COND_V(trans.size() % 3 != 0, false); @@ -1202,7 +1188,7 @@ bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_ add_transition(trans[i], trans[i + 1], trans[i + 2]); } return true; - } else if (name == "graph_offset") { + } else if (prop_name == "graph_offset") { set_graph_offset(p_value); return true; } @@ -1211,10 +1197,10 @@ bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_ } bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - if (name.begins_with("states/")) { - String node_name = name.get_slicec('/', 1); - String what = name.get_slicec('/', 2); + String prop_name = p_name; + if (prop_name.begins_with("states/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); if (what == "node") { if (states.has(node_name) && can_edit_node(node_name)) { @@ -1229,7 +1215,7 @@ bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) c return true; } } - } else if (name == "transitions") { + } else if (prop_name == "transitions") { Array trans; for (int i = 0; i < transitions.size(); i++) { String from = transitions[i].from; @@ -1246,7 +1232,7 @@ bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) c r_ret = trans; return true; - } else if (name == "graph_offset") { + } else if (prop_name == "graph_offset") { r_ret = get_graph_offset(); return true; } @@ -1261,9 +1247,9 @@ void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) c } names.sort_custom<StringName::AlphCompare>(); - for (const StringName &name : names) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + for (const StringName &prop_name : names) { + p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index ab78b1afe8..cdb4c7528a 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -54,7 +54,6 @@ private: bool disabled = false; int priority = 1; String advance_expression; - NodePath advance_expression_base_node; friend class AnimationNodeStateMachinePlayback; Ref<Expression> expression; @@ -77,9 +76,6 @@ public: void set_advance_expression(const String &p_expression); String get_advance_expression() const; - void set_advance_expression_base_node(const NodePath &p_expression_base_node); - NodePath get_advance_expression_base_node() const; - void set_xfade_time(float p_xfade); float get_xfade_time() const; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index ce9406883d..59930a3fbb 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -109,7 +109,7 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { Ref<AnimationLibrary> lib = d[lib_name]; add_animation_library(lib_name, lib); } - + emit_signal("animation_libraries_updated"); } else if (name.begins_with("next/")) { String which = name.get_slicec('/', 1); animation_set_next(which, p_value); @@ -156,7 +156,7 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { } else if (name == "blend_times") { Vector<BlendKey> keys; - for (const KeyValue<BlendKey, float> &E : blend_times) { + for (const KeyValue<BlendKey, double> &E : blend_times) { keys.ordered_insert(E.key); } @@ -216,7 +216,7 @@ void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::ARRAY, "blend_times", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } -void AnimationPlayer::advance(float p_time) { +void AnimationPlayer::advance(double p_time) { _animation_process(p_time); } @@ -1274,6 +1274,23 @@ void AnimationPlayer::_animation_set_cache_update() { } void AnimationPlayer::_animation_added(const StringName &p_name, const StringName &p_library) { + { + int at_pos = -1; + + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + if (animation_libraries[i].name == p_library) { + at_pos = i; + break; + } + } + + ERR_FAIL_COND(at_pos == -1); + + ERR_FAIL_COND(!animation_libraries[at_pos].library->animations.has(p_name)); + + _ref_anim(animation_libraries[at_pos].library->animations[p_name]); + } + _animation_set_cache_update(); } @@ -1283,11 +1300,17 @@ void AnimationPlayer::_animation_removed(const StringName &p_name, const StringN if (!animation_set.has(name)) { return; // No need to update because not the one from the library being used. } + + AnimationData animation_data = animation_set[name]; + if (animation_data.animation_library == p_library) { + _unref_anim(animation_data.animation); + } + _animation_set_cache_update(); // Erase blends if needed List<BlendKey> to_erase; - for (const KeyValue<BlendKey, float> &E : blend_times) { + for (const KeyValue<BlendKey, double> &E : blend_times) { BlendKey bk = E.key; if (bk.from == name || bk.to == name) { to_erase.push_back(bk); @@ -1303,8 +1326,8 @@ void AnimationPlayer::_animation_removed(const StringName &p_name, const StringN void AnimationPlayer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) { // Rename autoplay or blends if needed. List<BlendKey> to_erase; - HashMap<BlendKey, float, BlendKey> to_insert; - for (const KeyValue<BlendKey, float> &E : blend_times) { + HashMap<BlendKey, double, BlendKey> to_insert; + for (const KeyValue<BlendKey, double> &E : blend_times) { BlendKey bk = E.key; BlendKey new_bk = bk; bool erase = false; @@ -1376,9 +1399,13 @@ Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref animation_libraries.insert(insert_pos, ald); ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_name)); - ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_name)); + ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed).bind(p_name)); ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed).bind(p_name)); + for (const KeyValue<StringName, Ref<Animation>> &K : ald.library->animations) { + _ref_anim(K.value); + } + _animation_set_cache_update(); notify_property_list_changed(); @@ -1399,7 +1426,7 @@ void AnimationPlayer::remove_animation_library(const StringName &p_name) { ERR_FAIL_COND(at_pos == -1); animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); - animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed)); animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); stop(); @@ -1438,11 +1465,11 @@ void AnimationPlayer::rename_animation_library(const StringName &p_name, const S animation_libraries[i].name = p_new_name; // rename connections animation_libraries[i].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); - animation_libraries[i].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[i].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed)); animation_libraries[i].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); animation_libraries[i].library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_new_name)); - animation_libraries[i].library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_new_name)); + animation_libraries[i].library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed).bind(p_new_name)); animation_libraries[i].library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed).bind(p_new_name)); for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { @@ -1504,9 +1531,9 @@ bool AnimationPlayer::has_animation(const StringName &p_name) const { Ref<Animation> AnimationPlayer::get_animation(const StringName &p_name) const { ERR_FAIL_COND_V_MSG(!animation_set.has(p_name), Ref<Animation>(), vformat("Animation not found: \"%s\".", p_name)); - const AnimationData &data = animation_set[p_name]; + const AnimationData &anim_data = animation_set[p_name]; - return data.animation; + return anim_data.animation; } void AnimationPlayer::get_animation_list(List<StringName> *p_animations) const { @@ -1523,7 +1550,7 @@ void AnimationPlayer::get_animation_list(List<StringName> *p_animations) const { } } -void AnimationPlayer::set_blend_time(const StringName &p_animation1, const StringName &p_animation2, float p_time) { +void AnimationPlayer::set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time) { ERR_FAIL_COND_MSG(!animation_set.has(p_animation1), vformat("Animation not found: %s.", p_animation1)); ERR_FAIL_COND_MSG(!animation_set.has(p_animation2), vformat("Animation not found: %s.", p_animation2)); ERR_FAIL_COND_MSG(p_time < 0, "Blend time cannot be smaller than 0."); @@ -1538,7 +1565,7 @@ void AnimationPlayer::set_blend_time(const StringName &p_animation1, const Strin } } -float AnimationPlayer::get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const { +double AnimationPlayer::get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const { BlendKey bk; bk.from = p_animation1; bk.to = p_animation2; @@ -1571,11 +1598,11 @@ void AnimationPlayer::clear_queue() { queued.clear(); } -void AnimationPlayer::play_backwards(const StringName &p_name, float p_custom_blend) { +void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_blend) { play(p_name, p_custom_blend, -1, true); } -void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float p_custom_scale, bool p_from_end) { +void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { StringName name = p_name; if (String(name) == "") { @@ -1587,7 +1614,7 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float Playback &c = playback; if (c.current.from) { - float blend_time = 0.0; + double blend_time = 0.0; // find if it can blend BlendKey bk; bk.from = c.current.from->name; @@ -1741,7 +1768,7 @@ void AnimationPlayer::seek(double p_time, bool p_update) { } } -void AnimationPlayer::seek_delta(double p_time, float p_delta) { +void AnimationPlayer::seek_delta(double p_time, double p_delta) { if (!playback.current.from) { if (playback.assigned) { ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); @@ -1762,12 +1789,12 @@ bool AnimationPlayer::is_valid() const { return (playback.current.from); } -float AnimationPlayer::get_current_animation_position() const { +double AnimationPlayer::get_current_animation_position() const { ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation"); return playback.current.pos; } -float AnimationPlayer::get_current_animation_length() const { +double AnimationPlayer::get_current_animation_length() const { ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation"); return playback.current.from->animation->get_length(); } @@ -1933,11 +1960,11 @@ StringName AnimationPlayer::animation_get_next(const StringName &p_animation) co return animation_set[p_animation].next; } -void AnimationPlayer::set_default_blend_time(float p_default) { +void AnimationPlayer::set_default_blend_time(double p_default) { default_blend_time = p_default; } -float AnimationPlayer::get_default_blend_time() const { +double AnimationPlayer::get_default_blend_time() const { return default_blend_time; } @@ -2037,10 +2064,9 @@ Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) { aux_player->add_animation_library("", al); aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET); // Forcing the use of the original root because the scene where original player belongs may be not the active one - Node *root = get_node(get_root()); - Ref<AnimatedValuesBackup> old_values = aux_player->backup_animated_values(root); + Ref<AnimatedValuesBackup> old_values = aux_player->backup_animated_values(get_node(get_root())); aux_player->seek(0.0f, true); - aux_player->queue_delete(); + aux_player->queue_free(); if (p_user_initiated) { Ref<AnimatedValuesBackup> new_values = aux_player->backup_animated_values(); @@ -2152,6 +2178,7 @@ void AnimationPlayer::_bind_methods() { ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING_NAME, "old_name"), PropertyInfo(Variant::STRING_NAME, "new_name"))); ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING_NAME, "anim_name"))); ADD_SIGNAL(MethodInfo("animation_list_changed")); + ADD_SIGNAL(MethodInfo("animation_libraries_updated")); ADD_SIGNAL(MethodInfo("caches_cleared")); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index caf1387ff0..4f32927d25 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -191,7 +191,7 @@ private: uint64_t accum_pass = 1; float speed_scale = 1.0; - float default_blend_time = 0.0; + double default_blend_time = 0.0; struct AnimationData { String name; @@ -230,7 +230,7 @@ private: } }; - HashMap<BlendKey, float, BlendKey> blend_times; + HashMap<BlendKey, double, BlendKey> blend_times; struct PlaybackData { AnimationData *from = nullptr; @@ -241,8 +241,8 @@ private: struct Blend { PlaybackData data; - float blend_time = 0.0; - float blend_left = 0.0; + double blend_time = 0.0; + double blend_left = 0.0; }; struct Playback { @@ -334,17 +334,17 @@ public: void get_animation_list(List<StringName> *p_animations) const; bool has_animation(const StringName &p_name) const; - void set_blend_time(const StringName &p_animation1, const StringName &p_animation2, float p_time); - float get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const; + void set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time); + double get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const; void animation_set_next(const StringName &p_animation, const StringName &p_next); StringName animation_get_next(const StringName &p_animation) const; - void set_default_blend_time(float p_default); - float get_default_blend_time() const; + void set_default_blend_time(double p_default); + double get_default_blend_time() const; - void play(const StringName &p_name = StringName(), float p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); - void play_backwards(const StringName &p_name = StringName(), float p_custom_blend = -1); + void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false); + void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1); void queue(const StringName &p_name); Vector<String> get_queue(); void clear_queue(); @@ -378,11 +378,11 @@ public: bool is_movie_quit_on_finish_enabled() const; void seek(double p_time, bool p_update = false); - void seek_delta(double p_time, float p_delta); - float get_current_animation_position() const; - float get_current_animation_length() const; + void seek_delta(double p_time, double p_delta); + double get_current_animation_position() const; + double get_current_animation_length() const; - void advance(float p_time); + void advance(double p_time); void set_root(const NodePath &p_root); NodePath get_root() const; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 776706d7f3..517908077d 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -50,10 +50,8 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { Variant ret; - if (GDVIRTUAL_CALL(_get_parameter_default_value, p_parameter, ret)) { - return ret; - } - return Variant(); + GDVIRTUAL_CALL(_get_parameter_default_value, p_parameter, ret); + return ret; } void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { @@ -97,8 +95,8 @@ void AnimationNode::blend_animation(const StringName &p_animation, double p_time if (animation.is_null()) { AnimationNodeBlendTree *btree = Object::cast_to<AnimationNodeBlendTree>(parent); if (btree) { - String name = btree->get_node_name(Ref<AnimationNodeAnimation>(this)); - make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), name, p_animation)); + String node_name = btree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), node_name, p_animation)); } else { make_invalid(vformat(RTR("Invalid animation: '%s'."), p_animation)); } @@ -160,8 +158,8 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool StringName node_name = connections[p_input]; if (!blend_tree->has_node(node_name)) { - String name = blend_tree->get_node_name(Ref<AnimationNode>(this)); - make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), name)); + String node_name2 = blend_tree->get_node_name(Ref<AnimationNode>(this)); + make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), node_name2)); return 0; } @@ -312,12 +310,9 @@ String AnimationNode::get_input_name(int p_input) { } String AnimationNode::get_caption() const { - String ret; - if (GDVIRTUAL_CALL(_get_caption, ret)) { - return ret; - } - - return "Node"; + String ret = "Node"; + GDVIRTUAL_CALL(_get_caption, ret); + return ret; } void AnimationNode::add_input(const String &p_name) { @@ -344,12 +339,9 @@ void AnimationNode::remove_input(int p_index) { } double AnimationNode::process(double p_time, bool p_seek, bool p_seek_root) { - double ret; - if (GDVIRTUAL_CALL(_process, p_time, p_seek, p_seek_root, ret)) { - return ret; - } - - return 0; + double ret = 0; + GDVIRTUAL_CALL(_process, p_time, p_seek, p_seek_root, ret); + return ret; } void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { @@ -373,12 +365,9 @@ bool AnimationNode::is_path_filtered(const NodePath &p_path) const { } bool AnimationNode::has_filter() const { - bool ret; - if (GDVIRTUAL_CALL(_has_filter, ret)) { - return ret; - } - - return false; + bool ret = false; + GDVIRTUAL_CALL(_has_filter, ret); + return ret; } Array AnimationNode::_get_filters() const { @@ -407,10 +396,8 @@ void AnimationNode::_validate_property(PropertyInfo &p_property) const { Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) { Ref<AnimationNode> ret; - if (GDVIRTUAL_CALL(_get_child_by_name, p_name, ret)) { - return ret; - } - return Ref<AnimationNode>(); + GDVIRTUAL_CALL(_get_child_by_name, p_name, ret); + return ret; } void AnimationNode::_bind_methods() { @@ -1565,11 +1552,7 @@ void AnimationTree::_process_graph(double p_delta) { } real_t db = Math::linear_to_db(MAX(blend, 0.00001)); - if (t->object->has_method(SNAME("set_unit_db"))) { - t->object->call(SNAME("set_unit_db"), db); - } else { - t->object->call(SNAME("set_volume_db"), db); - } + t->object->call(SNAME("set_volume_db"), db); } break; case Animation::TYPE_ANIMATION: { if (blend < CMP_EPSILON) { @@ -1745,7 +1728,7 @@ Variant AnimationTree::_post_process_key_value(const Ref<Animation> &p_anim, int return p_value; } -void AnimationTree::advance(real_t p_time) { +void AnimationTree::advance(double p_time) { _process_graph(p_time); } @@ -1849,8 +1832,8 @@ uint64_t AnimationTree::get_last_process_pass() const { return process_pass; } -TypedArray<String> AnimationTree::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray AnimationTree::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!root.is_valid()) { warnings.push_back(RTR("No root AnimationNode for the graph is set.")); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 573448be93..96c1279f82 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -342,7 +342,7 @@ public: void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node); NodePath get_advance_expression_base_node() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; bool is_state_invalid() const; String get_invalid_state_reason() const; @@ -353,7 +353,7 @@ public: Transform3D get_root_motion_transform() const; real_t get_connection_activity(const StringName &p_path, int p_connection) const; - void advance(real_t p_time); + void advance(double p_time); void rename_parameter(const String &p_base, const String &p_new_base); diff --git a/scene/animation/easing_equations.h b/scene/animation/easing_equations.h index 094829e406..03d9e16454 100644 --- a/scene/animation/easing_equations.h +++ b/scene/animation/easing_equations.h @@ -28,6 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef EASING_EQUATIONS_H +#define EASING_EQUATIONS_H + /* * Derived from Robert Penner's easing equations: http://robertpenner.com/easing/ * @@ -52,9 +55,6 @@ * SOFTWARE. */ -#ifndef EASING_EQUATIONS_H -#define EASING_EQUATIONS_H - namespace linear { static real_t in(real_t t, real_t b, real_t c, real_t d) { return c * t / d + b; diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index b02f1959b7..aa58e1044a 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -71,16 +71,16 @@ void Tween::start_tweeners() { } } -Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property, Variant p_to, float p_duration) { +Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property, Variant p_to, double p_duration) { ERR_FAIL_NULL_V(p_target, nullptr); ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Variant::Type property_type = p_target->get_indexed(p_property.get_as_property_path().get_subnames()).get_type(); if (property_type != p_to.get_type()) { - // Cast p_to between floats and ints to avoid minor annoyances. + // Cast p_to between double and int to avoid minor annoyances. if (property_type == Variant::FLOAT && p_to.get_type() == Variant::INT) { - p_to = float(p_to); + p_to = double(p_to); } else if (property_type == Variant::INT && p_to.get_type() == Variant::FLOAT) { p_to = int(p_to); } else { @@ -93,7 +93,7 @@ Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property return tweener; } -Ref<IntervalTweener> Tween::tween_interval(float p_time) { +Ref<IntervalTweener> Tween::tween_interval(double p_time) { ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); @@ -111,7 +111,7 @@ Ref<CallbackTweener> Tween::tween_callback(Callable p_callback) { return tweener; } -Ref<MethodTweener> Tween::tween_method(Callable p_callback, Variant p_from, Variant p_to, float p_duration) { +Ref<MethodTweener> Tween::tween_method(Callable p_callback, Variant p_from, Variant p_to, double p_duration) { ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); @@ -245,7 +245,7 @@ Ref<Tween> Tween::chain() { return this; } -bool Tween::custom_step(float p_delta) { +bool Tween::custom_step(double p_delta) { bool r = running; running = true; bool ret = step(p_delta); @@ -253,7 +253,7 @@ bool Tween::custom_step(float p_delta) { return ret; } -bool Tween::step(float p_delta) { +bool Tween::step(double p_delta) { if (dead) { return false; } @@ -263,9 +263,9 @@ bool Tween::step(float p_delta) { } if (is_bound) { - Node *bound_node = get_bound_node(); - if (bound_node) { - if (!bound_node->is_inside_tree()) { + Node *node = get_bound_node(); + if (node) { + if (!node->is_inside_tree()) { return true; } } else { @@ -282,22 +282,22 @@ bool Tween::step(float p_delta) { started = true; } - float rem_delta = p_delta * speed_scale; + double rem_delta = p_delta * speed_scale; bool step_active = false; total_time += rem_delta; #ifdef DEBUG_ENABLED - float initial_delta = rem_delta; + double initial_delta = rem_delta; bool potential_infinite = false; #endif while (rem_delta > 0 && running) { - float step_delta = rem_delta; + double step_delta = rem_delta; step_active = false; for (Ref<Tweener> &tweener : tweeners.write[current_step]) { // Modified inside Tweener.step(). - float temp_delta = rem_delta; + double temp_delta = rem_delta; // Turns to true if any Tweener returns true (i.e. is still not finished). step_active = tweener->step(temp_delta) || step_active; step_delta = MIN(temp_delta, step_delta); @@ -342,9 +342,9 @@ bool Tween::step(float p_delta) { bool Tween::can_process(bool p_tree_paused) const { if (is_bound && pause_mode == TWEEN_PAUSE_BOUND) { - Node *bound_node = get_bound_node(); - if (bound_node) { - return bound_node->is_inside_tree() && bound_node->can_process(); + Node *node = get_bound_node(); + if (node) { + return node->is_inside_tree() && node->can_process(); } } @@ -359,7 +359,7 @@ Node *Tween::get_bound_node() const { } } -float Tween::get_total_time() const { +double Tween::get_total_time() const { return total_time; } @@ -373,7 +373,7 @@ real_t Tween::run_equation(TransitionType p_trans_type, EaseType p_ease_type, re return func(p_time, p_initial, p_delta, p_duration); } -Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, TransitionType p_trans, EaseType p_ease) { +Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, double p_time, double p_duration, TransitionType p_trans, EaseType p_ease) { ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant()); ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant()); @@ -480,7 +480,7 @@ Ref<PropertyTweener> PropertyTweener::set_ease(Tween::EaseType p_ease) { return this; } -Ref<PropertyTweener> PropertyTweener::set_delay(float p_delay) { +Ref<PropertyTweener> PropertyTweener::set_delay(double p_delay) { delay = p_delay; return this; } @@ -506,7 +506,7 @@ void PropertyTweener::start() { delta_val = Animation::subtract_variant(final_val, initial_val); } -bool PropertyTweener::step(float &r_delta) { +bool PropertyTweener::step(double &r_delta) { if (finished) { // This is needed in case there's a parallel Tweener with longer duration. return false; @@ -523,7 +523,7 @@ bool PropertyTweener::step(float &r_delta) { return true; } - float time = MIN(elapsed_time - delay, duration); + double time = MIN(elapsed_time - delay, duration); if (time < duration) { target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type)); r_delta = 0; @@ -556,7 +556,7 @@ void PropertyTweener::_bind_methods() { ClassDB::bind_method(D_METHOD("set_delay", "delay"), &PropertyTweener::set_delay); } -PropertyTweener::PropertyTweener(Object *p_target, NodePath p_property, Variant p_to, float p_duration) { +PropertyTweener::PropertyTweener(Object *p_target, NodePath p_property, Variant p_to, double p_duration) { target = p_target->get_instance_id(); property = p_property.get_as_property_path().get_subnames(); initial_val = p_target->get_indexed(property); @@ -574,7 +574,7 @@ void IntervalTweener::start() { finished = false; } -bool IntervalTweener::step(float &r_delta) { +bool IntervalTweener::step(double &r_delta) { if (finished) { return false; } @@ -592,7 +592,7 @@ bool IntervalTweener::step(float &r_delta) { } } -IntervalTweener::IntervalTweener(float p_time) { +IntervalTweener::IntervalTweener(double p_time) { duration = p_time; } @@ -600,7 +600,7 @@ IntervalTweener::IntervalTweener() { ERR_FAIL_MSG("Can't create empty IntervalTweener. Use get_tree().tween_interval() instead."); } -Ref<CallbackTweener> CallbackTweener::set_delay(float p_delay) { +Ref<CallbackTweener> CallbackTweener::set_delay(double p_delay) { delay = p_delay; return this; } @@ -610,7 +610,7 @@ void CallbackTweener::start() { finished = false; } -bool CallbackTweener::step(float &r_delta) { +bool CallbackTweener::step(double &r_delta) { if (finished) { return false; } @@ -646,7 +646,7 @@ CallbackTweener::CallbackTweener() { ERR_FAIL_MSG("Can't create empty CallbackTweener. Use get_tree().tween_callback() instead."); } -Ref<MethodTweener> MethodTweener::set_delay(float p_delay) { +Ref<MethodTweener> MethodTweener::set_delay(double p_delay) { delay = p_delay; return this; } @@ -666,7 +666,7 @@ void MethodTweener::start() { finished = false; } -bool MethodTweener::step(float &r_delta) { +bool MethodTweener::step(double &r_delta) { if (finished) { return false; } @@ -679,7 +679,7 @@ bool MethodTweener::step(float &r_delta) { } Variant current_val; - float time = MIN(elapsed_time - delay, duration); + double time = MIN(elapsed_time - delay, duration); if (time < duration) { current_val = tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type); } else { @@ -722,7 +722,7 @@ void MethodTweener::_bind_methods() { ClassDB::bind_method(D_METHOD("set_ease", "ease"), &MethodTweener::set_ease); } -MethodTweener::MethodTweener(Callable p_callback, Variant p_from, Variant p_to, float p_duration) { +MethodTweener::MethodTweener(Callable p_callback, Variant p_from, Variant p_to, double p_duration) { callback = p_callback; initial_val = p_from; delta_val = Animation::subtract_variant(p_to, p_from); diff --git a/scene/animation/tween.h b/scene/animation/tween.h index da7a8b5d71..345974ecc5 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -42,13 +42,13 @@ class Tweener : public RefCounted { public: virtual void set_tween(Ref<Tween> p_tween); virtual void start() = 0; - virtual bool step(float &r_delta) = 0; + virtual bool step(double &r_delta) = 0; void clear_tween(); protected: static void _bind_methods(); Ref<Tween> tween; - float elapsed_time = 0; + double elapsed_time = 0; bool finished = false; }; @@ -103,7 +103,7 @@ private: ObjectID bound_node; Vector<List<Ref<Tweener>>> tweeners; - float total_time = 0; + double total_time = 0; int current_step = -1; int loops = 1; int loops_done = 0; @@ -129,13 +129,13 @@ protected: static void _bind_methods(); public: - Ref<PropertyTweener> tween_property(Object *p_target, NodePath p_property, Variant p_to, float p_duration); - Ref<IntervalTweener> tween_interval(float p_time); + Ref<PropertyTweener> tween_property(Object *p_target, NodePath p_property, Variant p_to, double p_duration); + Ref<IntervalTweener> tween_interval(double p_time); Ref<CallbackTweener> tween_callback(Callable p_callback); - Ref<MethodTweener> tween_method(Callable p_callback, Variant p_from, Variant p_to, float p_duration); + Ref<MethodTweener> tween_method(Callable p_callback, Variant p_from, Variant p_to, double p_duration); void append(Ref<Tweener> p_tweener); - bool custom_step(float p_delta); + bool custom_step(double p_delta); void stop(); void pause(); void play(); @@ -163,12 +163,12 @@ public: Ref<Tween> chain(); static real_t run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t t, real_t b, real_t c, real_t d); - static Variant interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease); + static Variant interpolate_variant(Variant p_initial_val, Variant p_delta_val, double p_time, double p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease); - bool step(float p_delta); + bool step(double p_delta); bool can_process(bool p_tree_paused) const; Node *get_bound_node() const; - float get_total_time() const; + double get_total_time() const; Tween(); Tween(bool p_valid); @@ -188,13 +188,13 @@ public: Ref<PropertyTweener> as_relative(); Ref<PropertyTweener> set_trans(Tween::TransitionType p_trans); Ref<PropertyTweener> set_ease(Tween::EaseType p_ease); - Ref<PropertyTweener> set_delay(float p_delay); + Ref<PropertyTweener> set_delay(double p_delay); void set_tween(Ref<Tween> p_tween) override; void start() override; - bool step(float &r_delta) override; + bool step(double &r_delta) override; - PropertyTweener(Object *p_target, NodePath p_property, Variant p_to, float p_duration); + PropertyTweener(Object *p_target, NodePath p_property, Variant p_to, double p_duration); PropertyTweener(); protected: @@ -208,11 +208,11 @@ private: Variant final_val; Variant delta_val; - float duration = 0; + double duration = 0; Tween::TransitionType trans_type = Tween::TRANS_MAX; // This is set inside set_tween(); Tween::EaseType ease_type = Tween::EASE_MAX; - float delay = 0; + double delay = 0; bool do_continue = true; bool relative = false; }; @@ -222,23 +222,23 @@ class IntervalTweener : public Tweener { public: void start() override; - bool step(float &r_delta) override; + bool step(double &r_delta) override; - IntervalTweener(float p_time); + IntervalTweener(double p_time); IntervalTweener(); private: - float duration = 0; + double duration = 0; }; class CallbackTweener : public Tweener { GDCLASS(CallbackTweener, Tweener); public: - Ref<CallbackTweener> set_delay(float p_delay); + Ref<CallbackTweener> set_delay(double p_delay); void start() override; - bool step(float &r_delta) override; + bool step(double &r_delta) override; CallbackTweener(Callable p_callback); CallbackTweener(); @@ -248,7 +248,7 @@ protected: private: Callable callback; - float delay = 0; + double delay = 0; }; class MethodTweener : public Tweener { @@ -257,21 +257,21 @@ class MethodTweener : public Tweener { public: Ref<MethodTweener> set_trans(Tween::TransitionType p_trans); Ref<MethodTweener> set_ease(Tween::EaseType p_ease); - Ref<MethodTweener> set_delay(float p_delay); + Ref<MethodTweener> set_delay(double p_delay); void set_tween(Ref<Tween> p_tween) override; void start() override; - bool step(float &r_delta) override; + bool step(double &r_delta) override; - MethodTweener(Callable p_callback, Variant p_from, Variant p_to, float p_duration); + MethodTweener(Callable p_callback, Variant p_from, Variant p_to, double p_duration); MethodTweener(); protected: static void _bind_methods(); private: - float duration = 0; - float delay = 0; + double duration = 0; + double delay = 0; Tween::TransitionType trans_type = Tween::TRANS_MAX; Tween::EaseType ease_type = Tween::EASE_MAX; diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 4c5a63e52c..df27eecce7 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -39,87 +39,10 @@ #include "scene/main/window.h" #include "scene/resources/packed_scene.h" -Array SceneDebugger::RPCProfilerFrame::serialize() { - Array arr; - arr.push_back(infos.size() * 4); - for (int i = 0; i < infos.size(); ++i) { - arr.push_back(uint64_t(infos[i].node)); - arr.push_back(infos[i].node_path); - arr.push_back(infos[i].incoming_rpc); - arr.push_back(infos[i].outgoing_rpc); - } - return arr; -} - -bool SceneDebugger::RPCProfilerFrame::deserialize(const Array &p_arr) { - ERR_FAIL_COND_V(p_arr.size() < 1, false); - uint32_t size = p_arr[0]; - ERR_FAIL_COND_V(size % 4, false); - ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false); - infos.resize(size / 4); - int idx = 1; - for (uint32_t i = 0; i < size / 4; ++i) { - infos.write[i].node = uint64_t(p_arr[idx]); - infos.write[i].node_path = p_arr[idx + 1]; - infos.write[i].incoming_rpc = p_arr[idx + 2]; - infos.write[i].outgoing_rpc = p_arr[idx + 3]; - } - return true; -} - -class SceneDebugger::RPCProfiler : public EngineProfiler { - HashMap<ObjectID, RPCNodeInfo> rpc_node_data; - uint64_t last_profile_time = 0; - - void init_node(const ObjectID p_node) { - if (rpc_node_data.has(p_node)) { - return; - } - rpc_node_data.insert(p_node, RPCNodeInfo()); - rpc_node_data[p_node].node = p_node; - rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path(); - rpc_node_data[p_node].incoming_rpc = 0; - rpc_node_data[p_node].outgoing_rpc = 0; - } - -public: - void toggle(bool p_enable, const Array &p_opts) { - rpc_node_data.clear(); - } - - void add(const Array &p_data) { - ERR_FAIL_COND(p_data.size() < 2); - const ObjectID id = p_data[0]; - const String what = p_data[1]; - init_node(id); - RPCNodeInfo &info = rpc_node_data[id]; - if (what == "rpc_in") { - info.incoming_rpc++; - } else if (what == "rpc_out") { - info.outgoing_rpc++; - } - } - - void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { - uint64_t pt = OS::get_singleton()->get_ticks_msec(); - if (pt - last_profile_time > 100) { - last_profile_time = pt; - RPCProfilerFrame frame; - for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) { - frame.infos.push_back(E.value); - } - rpc_node_data.clear(); - EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize()); - } - } -}; - SceneDebugger *SceneDebugger::singleton = nullptr; SceneDebugger::SceneDebugger() { singleton = this; - rpc_profiler.instantiate(); - rpc_profiler->bind("rpc"); #ifdef DEBUG_ENABLED LiveEditor::singleton = memnew(LiveEditor); EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(nullptr, SceneDebugger::parse_message)); @@ -450,9 +373,9 @@ void SceneDebuggerObject::_parse_script_properties(Script *p_script, ScriptInsta for (const KeyValue<StringName, Variant> &E : sc.value) { String script_path = sc.key == p_script ? "" : sc.key->get_path().get_file() + "/"; if (E.value.get_type() == Variant::OBJECT) { - Variant id = ((Object *)E.value)->get_instance_id(); - PropertyInfo pi(id.get_type(), "Constants/" + E.key, PROPERTY_HINT_OBJECT_ID, "Object"); - properties.push_back(SceneDebuggerProperty(pi, id)); + Variant inst_id = ((Object *)E.value)->get_instance_id(); + PropertyInfo pi(inst_id.get_type(), "Constants/" + E.key, PROPERTY_HINT_OBJECT_ID, "Object"); + properties.push_back(SceneDebuggerProperty(pi, inst_id)); } else { PropertyInfo pi(E.value.get_type(), "Constants/" + script_path + E.key); properties.push_back(SceneDebuggerProperty(pi, E.value)); @@ -548,14 +471,36 @@ SceneDebuggerTree::SceneDebuggerTree(Node *p_root) { // Flatten tree into list, depth first, use stack to avoid recursion. List<Node *> stack; stack.push_back(p_root); + bool is_root = true; + const StringName &is_visible_sn = SNAME("is_visible"); + const StringName &is_visible_in_tree_sn = SNAME("is_visible_in_tree"); while (stack.size()) { Node *n = stack[0]; stack.pop_front(); + int count = n->get_child_count(); - nodes.push_back(RemoteNode(count, n->get_name(), n->get_class(), n->get_instance_id())); for (int i = 0; i < count; i++) { stack.push_front(n->get_child(count - i - 1)); } + + int view_flags = 0; + if (is_root) { + // Prevent root window visibility from being changed. + is_root = false; + } else if (n->has_method(is_visible_sn)) { + const Variant visible = n->call(is_visible_sn); + if (visible.get_type() == Variant::BOOL) { + view_flags = RemoteNode::VIEW_HAS_VISIBLE_METHOD; + view_flags |= uint8_t(visible) * RemoteNode::VIEW_VISIBLE; + } + if (n->has_method(is_visible_in_tree_sn)) { + const Variant visible_in_tree = n->call(is_visible_in_tree_sn); + if (visible_in_tree.get_type() == Variant::BOOL) { + view_flags |= uint8_t(visible_in_tree) * RemoteNode::VIEW_VISIBLE_IN_TREE; + } + } + } + nodes.push_back(RemoteNode(count, n->get_name(), n->get_class(), n->get_instance_id(), n->get_scene_file_path(), view_flags)); } } @@ -565,19 +510,23 @@ void SceneDebuggerTree::serialize(Array &p_arr) { p_arr.push_back(n.name); p_arr.push_back(n.type_name); p_arr.push_back(n.id); + p_arr.push_back(n.scene_file_path); + p_arr.push_back(n.view_flags); } } void SceneDebuggerTree::deserialize(const Array &p_arr) { int idx = 0; while (p_arr.size() > idx) { - ERR_FAIL_COND(p_arr.size() < 4); - CHECK_TYPE(p_arr[idx], INT); - CHECK_TYPE(p_arr[idx + 1], STRING); - CHECK_TYPE(p_arr[idx + 2], STRING); - CHECK_TYPE(p_arr[idx + 3], INT); - nodes.push_back(RemoteNode(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3])); - idx += 4; + ERR_FAIL_COND(p_arr.size() < 6); + CHECK_TYPE(p_arr[idx], INT); // child_count. + CHECK_TYPE(p_arr[idx + 1], STRING); // name. + CHECK_TYPE(p_arr[idx + 2], STRING); // type_name. + CHECK_TYPE(p_arr[idx + 3], INT); // id. + CHECK_TYPE(p_arr[idx + 4], STRING); // scene_file_path. + CHECK_TYPE(p_arr[idx + 5], INT); // view_flags. + nodes.push_back(RemoteNode(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3], p_arr[idx + 4], p_arr[idx + 5])); + idx += 6; } } diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 911363f45d..0428bfcc2e 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -42,28 +42,9 @@ class Node; class SceneDebugger { public: - // RPC profiler - struct RPCNodeInfo { - ObjectID node; - String node_path; - int incoming_rpc = 0; - int outgoing_rpc = 0; - }; - - struct RPCProfilerFrame { - Vector<RPCNodeInfo> infos; - - Array serialize(); - bool deserialize(const Array &p_arr); - }; - private: - class RPCProfiler; - static SceneDebugger *singleton; - Ref<RPCProfiler> rpc_profiler; - SceneDebugger(); public: @@ -110,12 +91,23 @@ public: String name; String type_name; ObjectID id; + String scene_file_path; + uint8_t view_flags = 0; - RemoteNode(int p_child, const String &p_name, const String &p_type, ObjectID p_id) { + enum ViewFlags { + VIEW_HAS_VISIBLE_METHOD = 1 << 1, + VIEW_VISIBLE = 1 << 2, + VIEW_VISIBLE_IN_TREE = 1 << 3, + }; + + RemoteNode(int p_child, const String &p_name, const String &p_type, ObjectID p_id, const String p_scene_file_path, int p_view_flags) { child_count = p_child; name = p_name; type_name = p_type; id = p_id; + + scene_file_path = p_scene_file_path; + view_flags = p_view_flags; } RemoteNode() {} diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index cf467ceafb..ac9034c6fd 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -60,7 +60,7 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseButton> mouse_button = p_event; - bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo(); + bool ui_accept = p_event->is_action("ui_accept", true) && !p_event->is_echo(); bool button_masked = mouse_button.is_valid() && (mouse_button_to_mask(mouse_button->get_button_index()) & button_mask) != MouseButton::NONE; if (button_masked || ui_accept) { @@ -127,6 +127,7 @@ void BaseButton::_notification(int p_what) { status.hovering = false; status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } break; } } @@ -160,6 +161,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { if (action_mode == ACTION_MODE_BUTTON_PRESS) { status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } status.pressed = !status.pressed; _unpress_group(); @@ -185,6 +187,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; emit_signal(SNAME("button_up")); } @@ -209,6 +212,7 @@ void BaseButton::set_disabled(bool p_disabled) { } status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } queue_redraw(); } @@ -218,13 +222,12 @@ bool BaseButton::is_disabled() const { } void BaseButton::set_pressed(bool p_pressed) { - if (!toggle_mode) { - return; - } - if (status.pressed == p_pressed) { + bool prev_pressed = status.pressed; + set_pressed_no_signal(p_pressed); + + if (status.pressed == prev_pressed) { return; } - status.pressed = p_pressed; if (p_pressed) { _unpress_group(); @@ -233,8 +236,6 @@ void BaseButton::set_pressed(bool p_pressed) { } } _toggled(status.pressed); - - queue_redraw(); } void BaseButton::set_pressed_no_signal(bool p_pressed) { @@ -264,7 +265,7 @@ bool BaseButton::is_hovered() const { BaseButton::DrawMode BaseButton::get_draw_mode() const { if (status.disabled) { return DRAW_DISABLED; - }; + } if (!status.press_attempt && status.hovering) { if (status.pressed) { @@ -273,8 +274,7 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const { return DRAW_HOVER; } else { - /* determine if pressed or not */ - + // Determine if pressed or not. bool pressing; if (status.press_attempt) { pressing = (status.pressing_inside || keep_pressed_outside); @@ -285,14 +285,12 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const { pressing = status.pressed; } - if (pressing) { + if ((shortcut_feedback || !status.shortcut_press) && pressing) { return DRAW_PRESSED; } else { return DRAW_NORMAL; } } - - return DRAW_NORMAL; } void BaseButton::set_toggle_mode(bool p_on) { @@ -352,11 +350,8 @@ Ref<Shortcut> BaseButton::get_shortcut() const { void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) { + status.shortcut_press = true; on_action_event(p_event); accept_event(); } @@ -392,36 +387,16 @@ Ref<ButtonGroup> BaseButton::get_button_group() const { return button_group; } -void BaseButton::set_shortcut_context(Node *p_node) { - if (p_node != nullptr) { - shortcut_context = p_node->get_instance_id(); - } else { - shortcut_context = ObjectID(); - } -} - -Node *BaseButton::get_shortcut_context() const { - Object *ctx_obj = ObjectDB::get_instance(shortcut_context); - Node *ctx_node = Object::cast_to<Node>(ctx_obj); - - return ctx_node; +bool BaseButton::_was_pressed_by_mouse() const { + return was_mouse_pressed; } -bool BaseButton::_is_focus_owner_in_shortcut_context() const { - if (shortcut_context == ObjectID()) { - // No context, therefore global - always "in" context. - return true; - } - - Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; - - // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. - return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); +void BaseButton::set_shortcut_feedback(bool p_feedback) { + shortcut_feedback = p_feedback; } -bool BaseButton::_was_pressed_by_mouse() const { - return was_mouse_pressed; +bool BaseButton::is_shortcut_feedback() const { + return shortcut_feedback; } void BaseButton::_bind_methods() { @@ -449,8 +424,8 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button_group", "button_group"), &BaseButton::set_button_group); ClassDB::bind_method(D_METHOD("get_button_group"), &BaseButton::get_button_group); - ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &BaseButton::set_shortcut_context); - ClassDB::bind_method(D_METHOD("get_shortcut_context"), &BaseButton::get_shortcut_context); + ClassDB::bind_method(D_METHOD("set_shortcut_feedback", "enabled"), &BaseButton::set_shortcut_feedback); + ClassDB::bind_method(D_METHOD("is_shortcut_feedback"), &BaseButton::is_shortcut_feedback); GDVIRTUAL_BIND(_pressed); GDVIRTUAL_BIND(_toggled, "button_pressed"); @@ -468,8 +443,8 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_feedback"), "set_shortcut_feedback", "is_shortcut_feedback"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); BIND_ENUM_CONSTANT(DRAW_NORMAL); BIND_ENUM_CONSTANT(DRAW_PRESSED); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index c83b08aadf..3acf535f54 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -53,6 +53,7 @@ private: bool keep_pressed_outside = false; Ref<Shortcut> shortcut; ObjectID shortcut_context; + bool shortcut_feedback = true; ActionMode action_mode = ACTION_MODE_BUTTON_RELEASE; struct Status { @@ -60,6 +61,7 @@ private: bool hovering = false; bool press_attempt = false; bool pressing_inside = false; + bool shortcut_press = false; bool disabled = false; @@ -81,7 +83,6 @@ protected: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); - bool _is_focus_owner_in_shortcut_context() const; bool _was_pressed_by_mouse() const; GDVIRTUAL0(_pressed) @@ -132,8 +133,8 @@ public: void set_button_group(const Ref<ButtonGroup> &p_group); Ref<ButtonGroup> get_button_group() const; - void set_shortcut_context(Node *p_node); - Node *get_shortcut_context() const; + void set_shortcut_feedback(bool p_feedback); + bool is_shortcut_feedback() const; BaseButton(); ~BaseButton(); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index c2b82e01d1..0e7bc5c306 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -231,7 +231,7 @@ void Button::_notification(int p_what) { _icon = icon; } - Rect2 icon_region = Rect2(); + Rect2 icon_region; HorizontalAlignment icon_align_rtl_checked = icon_alignment; HorizontalAlignment align_rtl_checked = alignment; // Swap icon and text alignment sides if right-to-left layout is set. diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index f6e0e4216d..ea310f5a12 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -138,7 +138,7 @@ void CodeEdit::_notification(int p_what) { code_completion_scroll_rect.position = code_completion_rect.position + Vector2(code_completion_rect.size.width, 0); code_completion_scroll_rect.size = Vector2(scroll_width, code_completion_rect.size.height); - code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines); + code_completion_line_ofs = CLAMP((code_completion_force_item_center < 0 ? code_completion_current_selected : code_completion_force_item_center) - lines / 2, 0, code_completion_options_count - lines); RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color); for (int i = 0; i < lines; i++) { @@ -201,7 +201,7 @@ void CodeEdit::_notification(int p_what) { if (caret_visible && !code_hint.is_empty() && (!code_completion_active || (code_completion_below != code_hint_draw_below))) { const int font_height = font->get_height(font_size); Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel"), SNAME("TooltipPanel")); - Color font_color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel")); + Color color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel")); Vector<String> code_hint_lines = code_hint.split("\n"); int line_count = code_hint_lines.size(); @@ -238,17 +238,17 @@ void CodeEdit::_notification(int p_what) { Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs); round_ofs = round_ofs.round(); - draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color); if (end > 0) { // Draw an underline for the currently edited function parameter. const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + yofs); - draw_line(b, b + Vector2(end - begin, 0), font_color, 2); + draw_line(b, b + Vector2(end - begin, 0), color, 2); // Draw a translucent text highlight as well. const Rect2 highlight_rect = Rect2( b - Vector2(0, font_height), Vector2(end - begin, font_height)); - draw_rect(highlight_rect, font_color * Color(1, 1, 1, 0.2)); + draw_rect(highlight_rect, color * Color(1, 1, 1, 0.2)); } yofs += line_spacing; } @@ -281,16 +281,22 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { case MouseButton::WHEEL_UP: { if (code_completion_current_selected > 0) { code_completion_current_selected--; + code_completion_force_item_center = -1; queue_redraw(); } } break; case MouseButton::WHEEL_DOWN: { if (code_completion_current_selected < code_completion_options.size() - 1) { code_completion_current_selected++; + code_completion_force_item_center = -1; queue_redraw(); } } break; case MouseButton::LEFT: { + if (code_completion_force_item_center == -1) { + code_completion_force_item_center = code_completion_current_selected; + } + code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1); if (mb->is_double_click()) { confirm_code_completion(); @@ -300,6 +306,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { default: break; } + return; } else if (code_completion_active && code_completion_scroll_rect.has_point(mb->get_position())) { if (mb->get_button_index() != MouseButton::LEFT) { @@ -448,6 +455,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = code_completion_options.size() - 1; } + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; @@ -458,30 +466,35 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = 0; } + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_up", true)) { code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines); + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_down", true)) { code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines); + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_home", true)) { code_completion_current_selected = 0; + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_end", true)) { code_completion_current_selected = code_completion_options.size() - 1; + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; @@ -609,126 +622,142 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { /* Text manipulation */ // Overridable actions -void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { - bool had_selection = has_selection(); - String selection_text = (had_selection ? get_selected_text() : ""); +void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) { + start_action(EditAction::ACTION_TYPING); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - if (had_selection) { - begin_complex_operation(); - delete_selection(); - } + bool had_selection = has_selection(i); + String selection_text = (had_selection ? get_selected_text(i) : ""); - // Remove the old character if in overtype mode and no selection. - if (is_overtype_mode_enabled() && !had_selection) { - begin_complex_operation(); + if (had_selection) { + delete_selection(i); + } - /* Make sure we don't try and remove empty space. */ - if (get_caret_column() < get_line(get_caret_line()).length()) { - remove_text(get_caret_line(), get_caret_column(), get_caret_line(), get_caret_column() + 1); + // Remove the old character if in overtype mode and no selection. + if (is_overtype_mode_enabled() && !had_selection) { + // Make sure we don't try and remove empty space. + if (get_caret_column(i) < get_line(get_caret_line(i)).length()) { + remove_text(get_caret_line(i), get_caret_column(i), get_caret_line(i), get_caret_column(i) + 1); + } } - } - const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; - if (auto_brace_completion_enabled) { - int cl = get_caret_line(); - int cc = get_caret_column(); + if (auto_brace_completion_enabled) { + int cl = get_caret_line(i); + int cc = get_caret_column(i); - if (had_selection) { - insert_text_at_caret(chr); + if (had_selection) { + insert_text_at_caret(chr, i); - String close_key = get_auto_brace_completion_close_key(chr); - if (!close_key.is_empty()) { - insert_text_at_caret(selection_text + close_key); - set_caret_column(get_caret_column() - 1); - } - } else { - int caret_move_offset = 1; - - int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; - - if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) { - insert_text_at_caret(chr); - } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) { - insert_text_at_caret(chr); - } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { - caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); - } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { - insert_text_at_caret(chr); + String close_key = get_auto_brace_completion_close_key(chr); + if (!close_key.is_empty()) { + insert_text_at_caret(selection_text + close_key, i); + set_caret_column(get_caret_column(i) - 1, i == 0, i); + } } else { - insert_text_at_caret(chr); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_caret(chr, i); + } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) { + insert_text_at_caret(chr, i); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_caret(chr, i); + } else { + insert_text_at_caret(chr, i); - int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); - if (pre_brace_pair != -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i); + } } + set_caret_column(cc + caret_move_offset, i == 0, i); } - set_caret_column(cc + caret_move_offset); + } else { + insert_text_at_caret(chr, i); } - } else { - insert_text_at_caret(chr); - } - - if ((is_overtype_mode_enabled() && !had_selection) || (had_selection)) { - end_complex_operation(); } + end_action(); } -void CodeEdit::_backspace_internal() { +void CodeEdit::_backspace_internal(int p_caret) { if (!is_editable()) { return; } - if (has_selection()) { - delete_selection(); + if (has_selection(p_caret)) { + delete_selection(p_caret); return; } - int cc = get_caret_column(); - int cl = get_caret_line(); + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - if (cc == 0 && cl == 0) { - return; - } + int cc = get_caret_column(i); + int cl = get_caret_line(i); - if (cl > 0 && _is_line_hidden(cl - 1)) { - unfold_line(get_caret_line() - 1); - } + if (cc == 0 && cl == 0) { + continue; + } - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); + if (cl > 0 && _is_line_hidden(cl - 1)) { + unfold_line(get_caret_line(i) - 1); + } - merge_gutters(prev_line, cl); + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); - if (auto_brace_completion_enabled && cc > 0) { - int idx = _get_auto_brace_pair_open_at_pos(cl, cc); - if (idx != -1) { - prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + merge_gutters(prev_line, cl); - if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { - remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); - } else { - remove_text(prev_line, prev_column, cl, cc); + if (auto_brace_completion_enabled && cc > 0) { + int idx = _get_auto_brace_pair_open_at_pos(cl, cc); + if (idx != -1) { + prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + + if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { + remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); + } else { + remove_text(prev_line, prev_column, cl, cc); + } + set_caret_line(prev_line, false, true, 0, i); + set_caret_column(prev_column, i == 0, i); + + adjust_carets_after_edit(i, prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); + continue; } - set_caret_line(prev_line, false, true); - set_caret_column(prev_column); - return; } - } - // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the - // same way as tabs. - if (indent_using_spaces && cc != 0) { - if (get_first_non_whitespace_column(cl) >= cc) { - prev_column = cc - _calculate_spaces_till_next_left_indent(cc); - prev_line = cl; + // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the + // same way as tabs. + if (indent_using_spaces && cc != 0) { + if (get_first_non_whitespace_column(cl) >= cc) { + prev_column = cc - _calculate_spaces_till_next_left_indent(cc); + prev_line = cl; + } } - } - remove_text(prev_line, prev_column, cl, cc); + remove_text(prev_line, prev_column, cl, cc); - set_caret_line(prev_line, false, true); - set_caret_column(prev_column); + set_caret_line(prev_line, false, true, 0, i); + set_caret_column(prev_column, i == 0, i); + + adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); + } + merge_overlapping_carets(); + end_complex_operation(); } /* Indent management */ @@ -803,10 +832,15 @@ void CodeEdit::do_indent() { return; } - int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column()); - if (spaces_to_add > 0) { - insert_text_at_caret(String(" ").repeat(spaces_to_add)); + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column(i)); + if (spaces_to_add > 0) { + insert_text_at_caret(String(" ").repeat(spaces_to_add), i); + } } + end_complex_operation(); } void CodeEdit::indent_lines() { @@ -815,48 +849,49 @@ void CodeEdit::indent_lines() { } begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + // This value informs us by how much we changed selection position by indenting right. + // Default is 1 for tab indentation. + int selection_offset = 1; + + int start_line = get_caret_line(c); + int end_line = start_line; + if (has_selection(c)) { + start_line = get_selection_from_line(c); + end_line = get_selection_to_line(c); + + // Ignore the last line if the selection is not past the first column. + if (get_selection_to_column(c) == 0) { + selection_offset = 0; + end_line--; + } + } - /* This value informs us by how much we changed selection position by indenting right. */ - /* Default is 1 for tab indentation. */ - int selection_offset = 1; + for (int i = start_line; i <= end_line; i++) { + const String line_text = get_line(i); + if (line_text.size() == 0 && has_selection(c)) { + continue; + } - int start_line = get_caret_line(); - int end_line = start_line; - if (has_selection()) { - start_line = get_selection_from_line(); - end_line = get_selection_to_line(); + if (!indent_using_spaces) { + set_line(i, '\t' + line_text); + continue; + } - /* Ignore the last line if the selection is not past the first column. */ - if (get_selection_to_column() == 0) { - selection_offset = 0; - end_line--; + // We don't really care where selection is - we just need to know indentation level at the beginning of the line. + // Since we will add this many spaces, we want to move the whole selection and caret by this much. + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); + set_line(i, String(" ").repeat(spaces_to_add) + line_text); + selection_offset = spaces_to_add; } - } - for (int i = start_line; i <= end_line; i++) { - const String line_text = get_line(i); - if (line_text.size() == 0 && has_selection()) { - continue; + // Fix selection and caret being off after shifting selection right. + if (has_selection(c)) { + select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c); } - - if (!indent_using_spaces) { - set_line(i, '\t' + line_text); - continue; - } - - /* We don't really care where selection is - we just need to know indentation level at the beginning of the line. */ - /* Since we will add this many spaces, we want to move the whole selection and caret by this much. */ - int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); - set_line(i, String(" ").repeat(spaces_to_add) + line_text); - selection_offset = spaces_to_add; - } - - /* Fix selection and caret being off after shifting selection right.*/ - if (has_selection()) { - select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset); + set_caret_column(get_caret_column(c) + selection_offset, false, c); } - set_caret_column(get_caret_column() + selection_offset, false); - end_complex_operation(); } @@ -872,30 +907,36 @@ void CodeEdit::do_unindent() { return; } - int cl = get_caret_line(); - const String &line = get_line(cl); - - if (line[cc - 1] == '\t') { - remove_text(cl, cc - 1, cl, cc); - set_caret_column(MAX(0, cc - 1)); - return; - } + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + int cl = get_caret_line(c); + const String &line = get_line(cl); + + if (line[cc - 1] == '\t') { + remove_text(cl, cc - 1, cl, cc); + set_caret_column(MAX(0, cc - 1), c == 0, c); + adjust_carets_after_edit(c, cl, cc, cl, cc - 1); + continue; + } - if (line[cc - 1] != ' ') { - return; - } + if (line[cc - 1] != ' ') { + continue; + } - int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); - if (spaces_to_remove > 0) { - for (int i = 1; i <= spaces_to_remove; i++) { - if (line[cc - i] != ' ') { - spaces_to_remove = i - 1; - break; + int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); + if (spaces_to_remove > 0) { + for (int i = 1; i <= spaces_to_remove; i++) { + if (line[cc - i] != ' ') { + spaces_to_remove = i - 1; + break; + } } + remove_text(cl, cc - spaces_to_remove, cl, cc); + set_caret_column(MAX(0, cc - spaces_to_remove), c == 0, c); } - remove_text(cl, cc - spaces_to_remove, cl, cc); - set_caret_column(MAX(0, cc - spaces_to_remove)); } + end_complex_operation(); } void CodeEdit::unindent_lines() { @@ -905,71 +946,73 @@ void CodeEdit::unindent_lines() { begin_complex_operation(); - /* Moving caret and selection after unindenting can get tricky because */ - /* changing content of line can move caret and selection on its own (if new line ends before previous position of either), */ - /* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */ - int removed_characters = 0; - int initial_selection_end_column = 0; - int initial_cursor_column = get_caret_column(); - - int start_line = get_caret_line(); - int end_line = start_line; - if (has_selection()) { - start_line = get_selection_from_line(); - end_line = get_selection_to_line(); - - /* Ignore the last line if the selection is not past the first column. */ - initial_selection_end_column = get_selection_to_column(); - if (initial_selection_end_column == 0) { - end_line--; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + // Moving caret and selection after unindenting can get tricky because + // changing content of line can move caret and selection on its own (if new line ends before previous position of either) + // therefore we just remember initial values and at the end of the operation offset them by number of removed characters. + int removed_characters = 0; + int initial_selection_end_column = 0; + int initial_cursor_column = get_caret_column(c); + + int start_line = get_caret_line(c); + int end_line = start_line; + if (has_selection(c)) { + start_line = get_selection_from_line(c); + end_line = get_selection_to_line(c); + + // Ignore the last line if the selection is not past the first column. + initial_selection_end_column = get_selection_to_column(c); + if (initial_selection_end_column == 0) { + end_line--; + } } - } - bool first_line_edited = false; - bool last_line_edited = false; + bool first_line_edited = false; + bool last_line_edited = false; - for (int i = start_line; i <= end_line; i++) { - String line_text = get_line(i); + for (int i = start_line; i <= end_line; i++) { + String line_text = get_line(i); - if (line_text.begins_with("\t")) { - line_text = line_text.substr(1, line_text.length()); + if (line_text.begins_with("\t")) { + line_text = line_text.substr(1, line_text.length()); - set_line(i, line_text); - removed_characters = 1; + set_line(i, line_text); + removed_characters = 1; - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; - continue; - } + first_line_edited = (i == start_line) ? true : first_line_edited; + last_line_edited = (i == end_line) ? true : last_line_edited; + continue; + } - if (line_text.begins_with(" ")) { - /* When unindenting we aim to remove spaces before line that has selection no matter what is selected, */ - /* Here we remove only enough spaces to align text to nearest full multiple of indentation_size. */ - /* In case where selection begins at the start of indentation_size multiple we remove whole indentation level. */ - int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i)); - line_text = line_text.substr(spaces_to_remove, line_text.length()); + if (line_text.begins_with(" ")) { + // When unindenting we aim to remove spaces before line that has selection no matter what is selected. + // Here we remove only enough spaces to align text to nearest full multiple of indentation_size. + // In case where selection begins at the start of indentation_size multiple we remove whole indentation level. + int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i)); + line_text = line_text.substr(spaces_to_remove, line_text.length()); - set_line(i, line_text); - removed_characters = spaces_to_remove; + set_line(i, line_text); + removed_characters = spaces_to_remove; - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; + first_line_edited = (i == start_line) ? true : first_line_edited; + last_line_edited = (i == end_line) ? true : last_line_edited; + } } - } - if (has_selection()) { - /* Fix selection being off by one on the first line. */ - if (first_line_edited) { - select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column); - } + if (has_selection(c)) { + // Fix selection being off by one on the first line. + if (first_line_edited) { + select(get_selection_from_line(c), get_selection_from_column(c) - removed_characters, get_selection_to_line(c), initial_selection_end_column, c); + } - /* Fix selection being off by one on the last line. */ - if (last_line_edited) { - select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters); + // Fix selection being off by one on the last line. + if (last_line_edited) { + select(get_selection_from_line(c), get_selection_from_column(c), get_selection_to_line(c), initial_selection_end_column - removed_characters, c); + } } + set_caret_column(initial_cursor_column - removed_characters, false, c); } - set_caret_column(initial_cursor_column - removed_characters, false); - end_complex_operation(); } @@ -990,106 +1033,108 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { return; } - /* When not splitting the line, we need to factor in indentation from the end of the current line. */ - const int cc = p_split_current_line ? get_caret_column() : get_line(get_caret_line()).length(); - const int cl = get_caret_line(); - - const String line = get_line(cl); - - String ins = "\n"; + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + // When not splitting the line, we need to factor in indentation from the end of the current line. + const int cc = p_split_current_line ? get_caret_column(i) : get_line(get_caret_line(i)).length(); + const int cl = get_caret_line(i); - /* Append current indentation. */ - int space_count = 0; - int line_col = 0; - for (; line_col < cc; line_col++) { - if (line[line_col] == '\t') { - ins += indent_text; - space_count = 0; - continue; - } + const String line = get_line(cl); - if (line[line_col] == ' ') { - space_count++; + String ins = "\n"; - if (space_count == indent_size) { + // Append current indentation. + int space_count = 0; + int line_col = 0; + for (; line_col < cc; line_col++) { + if (line[line_col] == '\t') { ins += indent_text; space_count = 0; + continue; } - continue; - } - break; - } - if (is_line_folded(cl)) { - unfold_line(cl); - } - - /* Indent once again if the previous line needs it, ie ':'. */ - /* Then add an addition new line for any closing pairs aka '()'. */ - /* Skip this in comments or if we are going above. */ - bool brace_indent = false; - if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) { - bool should_indent = false; - char32_t indent_char = ' '; + if (line[line_col] == ' ') { + space_count++; - for (; line_col < cc; line_col++) { - char32_t c = line[line_col]; - if (auto_indent_prefixes.has(c)) { - should_indent = true; - indent_char = c; + if (space_count == indent_size) { + ins += indent_text; + space_count = 0; + } continue; } + break; + } - /* Make sure this is the last char, trailing whitespace or comments are okay. */ - /* Increment column for comments because the delimiter (#) should be ignored. */ - if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) { - should_indent = false; - } + if (is_line_folded(cl)) { + unfold_line(cl); } - if (should_indent) { - ins += indent_text; + // Indent once again if the previous line needs it, ie ':'. + // Then add an addition new line for any closing pairs aka '()'. + // Skip this in comments or if we are going above. + bool brace_indent = false; + if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) { + bool should_indent = false; + char32_t indent_char = ' '; - String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char)); - if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) { - /* No need to move the brace below if we are not taking the text with us. */ - if (p_split_current_line) { - brace_indent = true; - ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); - } else { - brace_indent = false; - ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); + for (; line_col < cc; line_col++) { + char32_t c = line[line_col]; + if (auto_indent_prefixes.has(c)) { + should_indent = true; + indent_char = c; + continue; + } + + // Make sure this is the last char, trailing whitespace or comments are okay. + // Increment column for comments because the delimiter (#) should be ignored. + if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) { + should_indent = false; } } - } - } - begin_complex_operation(); + if (should_indent) { + ins += indent_text; - bool first_line = false; - if (!p_split_current_line) { - deselect(); + String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char)); + if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) { + // No need to move the brace below if we are not taking the text with us. + if (p_split_current_line) { + brace_indent = true; + ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); + } else { + brace_indent = false; + ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); + } + } + } + } - if (p_above) { - if (cl > 0) { - set_caret_line(cl - 1, false); - set_caret_column(get_line(get_caret_line()).length()); + bool first_line = false; + if (!p_split_current_line) { + deselect(i); + + if (p_above) { + if (cl > 0) { + set_caret_line(cl - 1, false, true, 0, i); + set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); + } else { + set_caret_column(0, i == 0, i); + first_line = true; + } } else { - set_caret_column(0); - first_line = true; + set_caret_column(line.length(), i == 0, i); } - } else { - set_caret_column(line.length()); } - } - insert_text_at_caret(ins); + insert_text_at_caret(ins, i); - if (first_line) { - set_caret_line(0); - } else if (brace_indent) { - set_caret_line(get_caret_line() - 1, false); - set_caret_column(get_line(get_caret_line()).length()); + if (first_line) { + set_caret_line(0, i == 0, true, 0, i); + } else if (brace_indent) { + set_caret_line(get_caret_line(i) - 1, false, true, 0, i); + set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); + } } end_complex_operation(); @@ -1217,39 +1262,52 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const { void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { if (draw_breakpoints && breakpoint_icon.is_valid()) { - bool hovering = p_region.has_point(get_local_mouse_pos()); bool breakpointed = is_line_breakpointed(p_line); + bool hovering = p_region.has_point(get_local_mouse_pos()); + bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); - if (breakpointed || (hovering && !is_dragging_cursor())) { + if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) { int padding = p_region.size.x / 6; + + Color use_color = breakpoint_color; + if (hovering && !shift_pressed) { + use_color = breakpointed ? use_color.lightened(0.3) : use_color.darkened(0.5); + } Rect2 icon_region = p_region; icon_region.position += Point2(padding, padding); icon_region.size -= Point2(padding, padding) * 2; - - // Darken icon when hovering & not yet breakpointed. - Color use_color = hovering && !breakpointed ? breakpoint_color.darkened(0.4) : breakpoint_color; breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color); } } - if (draw_bookmarks && is_line_bookmarked(p_line) && bookmark_icon.is_valid()) { - int horizontal_padding = p_region.size.x / 2; - int vertical_padding = p_region.size.y / 4; + if (draw_bookmarks && bookmark_icon.is_valid()) { + bool bookmarked = is_line_bookmarked(p_line); + bool hovering = p_region.has_point(get_local_mouse_pos()); + bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); + + if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) { + int horizontal_padding = p_region.size.x / 2; + int vertical_padding = p_region.size.y / 4; - Rect2 bookmark_region = p_region; - bookmark_region.position += Point2(horizontal_padding, 0); - bookmark_region.size -= Point2(horizontal_padding * 1.1, vertical_padding); - bookmark_icon->draw_rect(get_canvas_item(), bookmark_region, false, bookmark_color); + Color use_color = bookmark_color; + if (hovering && shift_pressed) { + use_color = bookmarked ? use_color.lightened(0.3) : use_color.darkened(0.5); + } + Rect2 icon_region = p_region; + icon_region.position += Point2(horizontal_padding, 0); + icon_region.size -= Point2(horizontal_padding * 1.1, vertical_padding); + bookmark_icon->draw_rect(get_canvas_item(), icon_region, false, use_color); + } } if (draw_executing_lines && is_line_executing(p_line) && executing_line_icon.is_valid()) { int horizontal_padding = p_region.size.x / 10; int vertical_padding = p_region.size.y / 4; - Rect2 executing_line_region = p_region; - executing_line_region.position += Point2(horizontal_padding, vertical_padding); - executing_line_region.size -= Point2(horizontal_padding, vertical_padding) * 2; - executing_line_icon->draw_rect(get_canvas_item(), executing_line_region, false, executing_line_color); + Rect2 icon_region = p_region; + icon_region.position += Point2(horizontal_padding, vertical_padding); + icon_region.size -= Point2(horizontal_padding, vertical_padding) * 2; + executing_line_icon->draw_rect(get_canvas_item(), icon_region, false, executing_line_color); } } @@ -1513,22 +1571,26 @@ void CodeEdit::fold_line(int p_line) { _set_line_as_hidden(i, true); } - /* Fix selection. */ - if (has_selection()) { - if (_is_line_hidden(get_selection_from_line()) && _is_line_hidden(get_selection_to_line())) { - deselect(); - } else if (_is_line_hidden(get_selection_from_line())) { - select(p_line, 9999, get_selection_to_line(), get_selection_to_column()); - } else if (_is_line_hidden(get_selection_to_line())) { - select(get_selection_from_line(), get_selection_from_column(), p_line, 9999); + for (int i = 0; i < get_caret_count(); i++) { + // Fix selection. + if (has_selection(i)) { + if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) { + deselect(i); + } else if (_is_line_hidden(get_selection_from_line(i))) { + select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i); + } else if (_is_line_hidden(get_selection_to_line(i))) { + select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i); + } } - } - /* Reset caret. */ - if (_is_line_hidden(get_caret_line())) { - set_caret_line(p_line, false, false); - set_caret_column(get_line(p_line).length(), false); + // Reset caret. + if (_is_line_hidden(get_caret_line(i))) { + set_caret_line(p_line, false, false, 0, i); + set_caret_column(get_line(p_line).length(), false, i); + } } + + merge_overlapping_carets(); queue_redraw(); } @@ -1929,6 +1991,7 @@ void CodeEdit::set_code_completion_selected_index(int p_index) { } ERR_FAIL_INDEX(p_index, code_completion_options.size()); code_completion_current_selected = p_index; + code_completion_force_item_center = -1; queue_redraw(); } @@ -1941,98 +2004,108 @@ void CodeEdit::confirm_code_completion(bool p_replace) { return; } + char32_t caret_last_completion_char; begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + int caret_line = get_caret_line(i); + + const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; + const String &display_text = code_completion_options[code_completion_current_selected].display; + + if (p_replace) { + // Find end of current section. + const String line = get_line(caret_line); + int caret_col = get_caret_column(i); + int caret_remove_line = caret_line; + + bool merge_text = true; + int in_string = is_in_string(caret_line, caret_col); + if (in_string != -1) { + Point2 string_end = get_delimiter_end_position(caret_line, caret_col); + if (string_end.x != -1) { + merge_text = false; + caret_remove_line = string_end.y; + caret_col = string_end.x - 1; + } + } - int caret_line = get_caret_line(); - - const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; - const String &display_text = code_completion_options[code_completion_current_selected].display; - - if (p_replace) { - /* Find end of current section */ - const String line = get_line(caret_line); - int caret_col = get_caret_column(); - int caret_remove_line = caret_line; - - bool merge_text = true; - int in_string = is_in_string(caret_line, caret_col); - if (in_string != -1) { - Point2 string_end = get_delimiter_end_position(caret_line, caret_col); - if (string_end.x != -1) { - merge_text = false; - caret_remove_line = string_end.y; - caret_col = string_end.x - 1; + if (merge_text) { + for (; caret_col < line.length(); caret_col++) { + if (is_symbol(line[caret_col])) { + break; + } + } } - } - if (merge_text) { - for (; caret_col < line.length(); caret_col++) { - if (is_symbol(line[caret_col])) { + // Replace. + remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_remove_line, caret_col); + adjust_carets_after_edit(i, caret_line, caret_col - code_completion_base.length(), caret_remove_line, caret_col); + set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); + insert_text_at_caret(insert_text, i); + } else { + // Get first non-matching char. + const String line = get_line(caret_line); + int caret_col = get_caret_column(i); + int matching_chars = code_completion_base.length(); + for (; matching_chars <= insert_text.length(); matching_chars++) { + if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) { break; } + caret_col++; } + + // Remove base completion text. + remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); + adjust_carets_after_edit(i, caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); + set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); + + // Merge with text. + insert_text_at_caret(insert_text.substr(0, code_completion_base.length()), i); + set_caret_column(caret_col, false, i); + insert_text_at_caret(insert_text.substr(matching_chars), i); } - /* Replace. */ - remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_remove_line, caret_col); - set_caret_column(get_caret_column() - code_completion_base.length(), false); - insert_text_at_caret(insert_text); - } else { - /* Get first non-matching char. */ + //* Handle merging of symbols eg strings, brackets. const String line = get_line(caret_line); - int caret_col = get_caret_column(); - int matching_chars = code_completion_base.length(); - for (; matching_chars <= insert_text.length(); matching_chars++) { - if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) { - break; - } - caret_col++; + char32_t next_char = line[get_caret_column(i)]; + char32_t last_completion_char = insert_text[insert_text.length() - 1]; + if (i == 0) { + caret_last_completion_char = last_completion_char; } + char32_t last_completion_char_display = display_text[display_text.length() - 1]; - /* Remove base completion text. */ - remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_line, get_caret_column()); - set_caret_column(get_caret_column() - code_completion_base.length(), false); + int pre_brace_pair = get_caret_column(i) > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i)) : -1; + int post_brace_pair = get_caret_column(i) < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i)) : -1; - /* Merge with text. */ - insert_text_at_caret(insert_text.substr(0, code_completion_base.length())); - set_caret_column(caret_col, false); - insert_text_at_caret(insert_text.substr(matching_chars)); - } - - /* Handle merging of symbols eg strings, brackets. */ - const String line = get_line(caret_line); - char32_t next_char = line[get_caret_column()]; - char32_t last_completion_char = insert_text[insert_text.length() - 1]; - char32_t last_completion_char_display = display_text[display_text.length() - 1]; - - int pre_brace_pair = get_caret_column() > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column()) : -1; - int post_brace_pair = get_caret_column() < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column()) : -1; - - if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) { - remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1); - } + if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) { + remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + } - if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) { - remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1); - } else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); - set_caret_column(get_caret_column() - auto_brace_completion_pairs[pre_brace_pair].close_key.length()); - } + if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) { + remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); + } else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i); + set_caret_column(get_caret_column(i) - auto_brace_completion_pairs[pre_brace_pair].close_key.length(), i == 0, i); + } - if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) { - pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1); - if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) { - remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column()); - if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) { - set_caret_column(get_caret_column() - 1); + if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column(i) > 0 && get_caret_column(i) < get_line(caret_line).length()) { + pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i) + 1); + if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1)) { + remove_text(caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); + adjust_carets_after_edit(i, caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); + if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1) != pre_brace_pair) { + set_caret_column(get_caret_column(i) - 1, i == 0, i); + } } } } - end_complex_operation(); cancel_code_completion(); - if (code_completion_prefixes.has(last_completion_char)) { + if (code_completion_prefixes.has(caret_last_completion_char)) { request_code_completion(); } } @@ -2080,15 +2153,15 @@ String CodeEdit::get_text_for_symbol_lookup() { StringBuilder lookup_text; const int text_size = get_line_count(); for (int i = 0; i < text_size; i++) { - String text = get_line(i); + String line_text = get_line(i); if (i == line) { - lookup_text += text.substr(0, col); + lookup_text += line_text.substr(0, col); /* Not unicode, represents the cursor. */ lookup_text += String::chr(0xFFFF); - lookup_text += text.substr(col, text.size()); + lookup_text += line_text.substr(col, line_text.size()); } else { - lookup_text += text; + lookup_text += line_text; } if (i != text_size - 1) { @@ -2378,14 +2451,19 @@ int CodeEdit::_get_auto_brace_pair_close_at_pos(int p_line, int p_col) { /* Gutters */ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { + bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); + if (p_gutter == main_gutter) { - if (draw_breakpoints) { + if (draw_breakpoints && !shift_pressed) { set_line_as_breakpoint(p_line, !is_line_breakpointed(p_line)); + } else if (draw_bookmarks && shift_pressed) { + set_line_as_bookmarked(p_line, !is_line_bookmarked(p_line)); } return; } if (p_gutter == line_number_gutter) { + remove_secondary_carets(); set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0); select(p_line, 0, p_line + 1, 0); set_caret_line(p_line + 1); @@ -2744,6 +2822,7 @@ void CodeEdit::_update_scroll_selected_line(float p_mouse_y) { percent = CLAMP(percent, 0.0f, 1.0f); code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1)); + code_completion_force_item_center = -1; } void CodeEdit::_filter_code_completion_candidates_impl() { @@ -2795,12 +2874,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() { offset = line_height; } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } code_completion_options.push_back(option); } code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; + code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); return; @@ -2906,7 +2988,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { if (string_to_complete.length() == 0) { code_completion_options.push_back(option); - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } continue; } @@ -3012,7 +3096,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_matches); completion_options_subseq.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower. option.matches.clear(); @@ -3029,7 +3115,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_lower_matches); completion_options_subseq_casei.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } } } @@ -3051,6 +3139,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; + code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); } diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 2065f3e681..cbbc13480e 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -214,6 +214,7 @@ private: Vector<ScriptLanguage::CodeCompletionOption> code_completion_options; int code_completion_line_ofs = 0; int code_completion_current_selected = 0; + int code_completion_force_item_center = -1; int code_completion_longest_line = 0; Rect2i code_completion_rect; Rect2i code_completion_scroll_rect; @@ -261,8 +262,8 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input_internal(const uint32_t p_unicode) override; - virtual void _backspace_internal() override; + virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override; + virtual void _backspace_internal(int p_caret) override; GDVIRTUAL1(_confirm_code_completion, bool) GDVIRTUAL1(_request_code_completion, bool) diff --git a/scene/gui/color_mode.cpp b/scene/gui/color_mode.cpp index ebd86e0937..308fe057c5 100644 --- a/scene/gui/color_mode.cpp +++ b/scene/gui/color_mode.cpp @@ -73,10 +73,10 @@ void ColorModeRGB::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -97,10 +97,10 @@ void ColorModeRGB::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -147,10 +147,10 @@ void ColorModeHSV::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -158,8 +158,7 @@ void ColorModeHSV::slider_draw(int p_which) { right_color.a = 1; } else if (p_which == 0) { Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); - slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(margin, size.x)), false); + slider->draw_texture_rect(hue, Rect2(Vector2(), Vector2(size.x, margin)), false); return; } else { Color s_col; @@ -174,10 +173,10 @@ void ColorModeHSV::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -216,10 +215,10 @@ void ColorModeRAW::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -230,10 +229,10 @@ void ColorModeRAW::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -245,8 +244,7 @@ bool ColorModeRAW::apply_theme() const { slider->remove_theme_icon_override("grabber"); slider->remove_theme_icon_override("grabber_highlight"); slider->remove_theme_style_override("slider"); - slider->remove_theme_style_override("grabber_area"); - slider->remove_theme_style_override("grabber_area_highlight"); + slider->remove_theme_constant_override("grabber_offset"); } return true; @@ -285,46 +283,67 @@ Color ColorModeOKHSL::get_color() const { } void ColorModeOKHSL::slider_draw(int p_which) { - Vector<Vector2> pos; - pos.resize(4); - Vector<Color> col; - col.resize(4); HSlider *slider = color_picker->get_slider(p_which); Size2 size = slider->get_size(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); + + if (p_which == 0) { // H + Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_okhsl_hue"), SNAME("ColorPicker")); + slider->draw_texture_rect(hue, Rect2(Vector2(), Vector2(size.x, margin)), false); + return; + } + + Vector<Vector2> pos; + Vector<Color> col; Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); - if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + if (p_which == 2) { // L + pos.resize(6); + col.resize(6); + left_color = Color(0, 0, 0); + Color middle_color; + middle_color.set_ok_hsl(color.get_ok_hsl_h(), color.get_ok_hsl_s(), 0.5); + right_color.set_ok_hsl(color.get_ok_hsl_h(), color.get_ok_hsl_s(), 1); - left_color = color; - left_color.a = 0; - right_color = color; - right_color.a = 1; - } else if (p_which == 0) { - Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); - slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(margin, size.x)), false); - return; - } else { - Color s_col; - Color v_col; - s_col.set_ok_hsl(color.get_h(), 0, color.get_v()); - left_color = (p_which == 1) ? s_col : Color(0, 0, 0); - s_col.set_ok_hsl(color.get_h(), 1, color.get_v()); - v_col.set_ok_hsl(color.get_h(), color.get_s(), 1); - right_color = (p_which == 1) ? s_col : v_col; + col.set(0, left_color); + col.set(1, middle_color); + col.set(2, right_color); + col.set(3, right_color); + col.set(4, middle_color); + col.set(5, left_color); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x * 0.5, 0)); + pos.set(2, Vector2(size.x, 0)); + pos.set(3, Vector2(size.x, margin)); + pos.set(4, Vector2(size.x * 0.5, margin)); + pos.set(5, Vector2(0, margin)); + } else { // A / S + pos.resize(4); + col.resize(4); + + if (p_which == ColorPicker::SLIDER_COUNT) { + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + } else { + left_color.set_ok_hsl(color.get_ok_hsl_h(), 0, color.get_ok_hsl_l()); + right_color.set_ok_hsl(color.get_ok_hsl_h(), 1, color.get_ok_hsl_l()); + } + + col.set(0, left_color); + col.set(1, right_color); + col.set(2, right_color); + col.set(3, left_color); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); } - col.set(0, left_color); - col.set(1, right_color); - col.set(2, right_color); - col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); slider->draw_polygon(pos, col); } diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 4a1f2ab7c6..724e5bcaf6 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -44,6 +44,7 @@ #include "thirdparty/misc/ok_color_shader.h" List<Color> ColorPicker::preset_cache; +List<Color> ColorPicker::recent_preset_cache; void ColorPicker::_notification(int p_what) { switch (p_what) { @@ -61,14 +62,31 @@ void ColorPicker::_notification(int p_what) { for (int i = 0; i < preset_cache.size(); i++) { presets.push_back(preset_cache[i]); } + + if (recent_preset_cache.is_empty()) { + PackedColorArray saved_recent_presets = EditorSettings::get_singleton()->get_project_metadata("color_picker", "recent_presets", PackedColorArray()); + for (int i = 0; i < saved_recent_presets.size(); i++) { + recent_preset_cache.push_back(saved_recent_presets[i]); + } + } + + for (int i = 0; i < recent_preset_cache.size(); i++) { + recent_presets.push_back(recent_preset_cache[i]); + } } #endif [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); + _update_drop_down_arrow(btn_preset->is_pressed(), btn_preset); + _update_drop_down_arrow(btn_recent_preset->is_pressed(), btn_recent_preset); btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + btn_pick->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + btn_shape->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + btn_mode->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + uv_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height")))); w_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("h_width")), 0)); @@ -90,12 +108,13 @@ void ColorPicker::_notification(int p_what) { } _update_presets(); + _update_recent_presets(); _update_controls(); } break; case NOTIFICATION_VISIBILITY_CHANGED: { Popup *p = Object::cast_to<Popup>(get_parent()); - if (p) { + if (p && is_visible_in_tree()) { p->set_size(Size2(get_combined_minimum_size().width + get_theme_constant(SNAME("margin")) * 2, get_combined_minimum_size().height + get_theme_constant(SNAME("margin")) * 2)); } } break; @@ -234,18 +253,20 @@ void ColorPicker::_update_controls() { wheel_edit->hide(); w_edit->show(); uv_edit->show(); + btn_shape->show(); break; case SHAPE_HSV_WHEEL: wheel_edit->show(); w_edit->hide(); uv_edit->hide(); - + btn_shape->show(); wheel->set_material(wheel_mat); break; case SHAPE_VHS_CIRCLE: wheel_edit->show(); w_edit->show(); uv_edit->hide(); + btn_shape->show(); wheel->set_material(circle_mat); circle_mat->set_shader(circle_shader); break; @@ -253,15 +274,27 @@ void ColorPicker::_update_controls() { wheel_edit->show(); w_edit->show(); uv_edit->hide(); + btn_shape->show(); wheel->set_material(circle_mat); circle_mat->set_shader(circle_ok_color_shader); break; + case SHAPE_NONE: + wheel_edit->hide(); + w_edit->hide(); + uv_edit->hide(); + btn_shape->hide(); + break; default: { } } } void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { + if (text_changed) { + add_recent_preset(color); + text_changed = false; + } + color = p_color; if (color != last_color) { _copy_color_to_hsv(); @@ -330,55 +363,61 @@ void ColorPicker::_value_changed(double) { void ColorPicker::add_mode(ColorMode *p_mode) { modes.push_back(p_mode); - mode_option_button->add_item(RTR(p_mode->get_name())); } void ColorPicker::create_slider(GridContainer *gc, int idx) { - Label *l = memnew(Label()); - l->set_v_size_flags(SIZE_SHRINK_CENTER); - gc->add_child(l); + Label *lbl = memnew(Label()); + lbl->set_v_size_flags(SIZE_SHRINK_CENTER); + gc->add_child(lbl); + + HSlider *slider = memnew(HSlider); + slider->set_v_size_flags(SIZE_SHRINK_CENTER); + slider->set_focus_mode(FOCUS_NONE); + gc->add_child(slider); - HSlider *s = memnew(HSlider); - s->set_v_size_flags(SIZE_SHRINK_CENTER); - s->set_focus_mode(FOCUS_NONE); - gc->add_child(s); + SpinBox *val = memnew(SpinBox); + slider->share(val); + val->set_select_all_on_focus(true); + gc->add_child(val); - SpinBox *v = memnew(SpinBox); - s->share(v); - gc->add_child(v); - v->get_line_edit()->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); - v->get_line_edit()->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); + LineEdit *vle = val->get_line_edit(); + vle->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); + vle->connect("gui_input", callable_mp(this, &ColorPicker::_line_edit_input)); + vle->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - s->set_h_size_flags(SIZE_EXPAND_FILL); + val->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); - s->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); - s->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + slider->set_h_size_flags(SIZE_EXPAND_FILL); + + slider->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); + slider->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + slider->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); if (idx < SLIDER_COUNT) { - sliders[idx] = s; - values[idx] = v; - labels[idx] = l; + sliders[idx] = slider; + values[idx] = val; + labels[idx] = lbl; } else { - alpha_slider = s; - alpha_value = v; - alpha_label = l; + alpha_slider = slider; + alpha_value = val; + alpha_label = lbl; } } -HSlider *ColorPicker::get_slider(int idx) { - if (idx < SLIDER_COUNT) { - return sliders[idx]; +HSlider *ColorPicker::get_slider(int p_idx) { + if (p_idx < SLIDER_COUNT) { + return sliders[p_idx]; } return alpha_slider; } Vector<float> ColorPicker::get_active_slider_values() { - Vector<float> values; + Vector<float> cur_values; for (int i = 0; i < current_slider_count; i++) { - values.push_back(sliders[i]->get_value()); + cur_values.push_back(sliders[i]->get_value()); } - values.push_back(alpha_slider->get_value()); - return values; + cur_values.push_back(alpha_slider->get_value()); + return cur_values; } void ColorPicker::_copy_color_to_hsv() { @@ -401,25 +440,53 @@ void ColorPicker::_copy_hsv_to_color() { } } +void ColorPicker::_select_from_preset_container(const Color &p_color) { + if (preset_group->get_pressed_button()) { + preset_group->get_pressed_button()->set_pressed(false); + } + + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->set_pressed(true); + break; + } + } +} + +bool ColorPicker::_select_from_recent_preset_hbc(const Color &p_color) { + for (int i = 0; i < recent_preset_hbc->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(recent_preset_hbc->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->set_pressed(true); + return true; + } + } + return false; +} + ColorPicker::PickerShapeType ColorPicker::_get_actual_shape() const { return modes[current_mode]->get_shape_override() != SHAPE_MAX ? modes[current_mode]->get_shape_override() : current_shape; } void ColorPicker::_reset_theme() { - Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); - + Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat)); + style_box_flat->set_default_margin(SIDE_TOP, 16 * get_theme_default_base_scale()); + style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp()); for (int i = 0; i < SLIDER_COUNT; i++) { sliders[i]->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); sliders[i]->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); - sliders[i]->add_theme_style_override("slider", style_box_empty); - sliders[i]->add_theme_style_override("grabber_area", style_box_empty); - sliders[i]->add_theme_style_override("grabber_area_highlight", style_box_empty); + sliders[i]->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale()); + if (!colorize_sliders) { + sliders[i]->add_theme_style_override("slider", style_box_flat); + } } alpha_slider->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); alpha_slider->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); - alpha_slider->add_theme_style_override("slider", style_box_empty); - alpha_slider->add_theme_style_override("grabber_area", style_box_empty); - alpha_slider->add_theme_style_override("grabber_area_highlight", style_box_empty); + alpha_slider->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale()); + if (!colorize_sliders) { + alpha_slider->add_theme_style_override("slider", style_box_flat); + } } void ColorPicker::_html_submitted(const String &p_html) { @@ -427,12 +494,15 @@ void ColorPicker::_html_submitted(const String &p_html) { return; } - float last_alpha = color.a; + Color previous_color = color; color = Color::html(p_html); if (!is_editing_alpha()) { - color.a = last_alpha; + color.a = previous_color.a; } + if (color == previous_color) { + return; + } if (!is_inside_tree()) { return; } @@ -481,13 +551,41 @@ void ColorPicker::_update_presets() { cpb->set_custom_minimum_size(Size2(preset_size, preset_size)); } } - // Only load preset buttons when the only child is the add-preset button. - if (preset_container->get_child_count() == 1) { - for (int i = 0; i < preset_cache.size(); i++) { - _add_preset_button(preset_size, preset_cache[i]); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + // Only load preset buttons when the only child is the add-preset button. + if (preset_container->get_child_count() == 1) { + for (int i = 0; i < preset_cache.size(); i++) { + _add_preset_button(preset_size, preset_cache[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); + } + } +#endif +} + +void ColorPicker::_update_recent_presets() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + int recent_preset_count = recent_preset_hbc->get_child_count(); + for (int i = 0; i < recent_preset_count; i++) { + memdelete(recent_preset_hbc->get_child(0)); + } + + recent_presets.clear(); + for (int i = 0; i < recent_preset_cache.size(); i++) { + recent_presets.push_back(recent_preset_cache[i]); + } + + int preset_size = _get_preset_size(); + for (int i = 0; i < recent_presets.size(); i++) { + _add_recent_preset_button(preset_size, recent_presets[i]); } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } +#endif } void ColorPicker::_text_type_toggled() { @@ -497,11 +595,13 @@ void ColorPicker::_text_type_toggled() { text_type->set_icon(get_theme_icon(SNAME("Script"), SNAME("EditorIcons"))); c_text->set_editable(false); + c_text->set_h_size_flags(SIZE_EXPAND_FILL); } else { text_type->set_text("#"); text_type->set_icon(nullptr); c_text->set_editable(true); + c_text->set_h_size_flags(SIZE_FILL); } _update_color(); } @@ -512,9 +612,17 @@ Color ColorPicker::get_pick_color() const { void ColorPicker::set_picker_shape(PickerShapeType p_shape) { ERR_FAIL_INDEX(p_shape, SHAPE_MAX); - if (current_shape == p_shape) { + if (p_shape == current_shape) { return; } + if (current_shape != SHAPE_NONE) { + shape_popup->set_item_checked(current_shape, false); + } + if (p_shape != SHAPE_NONE) { + shape_popup->set_item_checked(p_shape, true); + btn_shape->set_icon(shape_popup->get_item_icon(p_shape)); + } + current_shape = p_shape; _copy_color_to_hsv(); @@ -528,45 +636,105 @@ ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { } inline int ColorPicker::_get_preset_size() { - return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("h_separation")) * (preset_column_count - 1))) / preset_column_count; + return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("h_separation")) * (PRESET_COLUMN_COUNT - 1))) / PRESET_COLUMN_COUNT; } void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { - ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color)); - btn_preset->set_preset_color(p_color); - btn_preset->set_custom_minimum_size(Size2(p_size, p_size)); - btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); - btn_preset->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); - preset_container->add_child(btn_preset); + ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size)); + btn_preset_new->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + btn_preset_new->set_drag_forwarding(this); + btn_preset_new->set_button_group(preset_group); + preset_container->add_child(btn_preset_new); + btn_preset_new->set_pressed(true); + btn_preset_new->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); +} + +void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { + ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size)); + btn_preset_new->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color"), p_color.to_html(p_color.a < 1))); + btn_preset_new->set_button_group(recent_preset_group); + recent_preset_hbc->add_child(btn_preset_new); + recent_preset_hbc->move_child(btn_preset_new, 0); + btn_preset_new->set_pressed(true); + btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); +} + +void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) { + if (p_is_btn_pressed) { + p_preset_container->show(); + } else { + p_preset_container->hide(); + } + _update_drop_down_arrow(p_is_btn_pressed, p_btn_preset); } -void ColorPicker::_set_color_mode(ColorModeType p_mode) { - if (slider_theme_modified) { - _reset_theme(); +void ColorPicker::_update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset) { + if (p_is_btn_pressed) { + p_btn_preset->set_icon(get_theme_icon(SNAME("expanded_arrow"), SNAME("ColorPicker"))); + } else { + p_btn_preset->set_icon(get_theme_icon(SNAME("folded_arrow"), SNAME("ColorPicker"))); } +} - current_mode = p_mode; +void ColorPicker::_set_mode_popup_value(ColorModeType p_mode) { + ERR_FAIL_INDEX(p_mode, MODE_MAX + 1); - if (!is_inside_tree()) { + if (p_mode == MODE_MAX) { + set_colorize_sliders(!colorize_sliders); + } else { + set_color_mode(p_mode); + } +} + +Variant ColorPicker::_get_drag_data_fw(const Point2 &p_point, Control *p_from_control) { + ColorPresetButton *dragged_preset_button = Object::cast_to<ColorPresetButton>(p_from_control); + + if (!dragged_preset_button) { + return Variant(); + } + + ColorPresetButton *drag_preview = memnew(ColorPresetButton(dragged_preset_button->get_preset_color(), _get_preset_size())); + set_drag_preview(drag_preview); + + Dictionary drag_data; + drag_data["type"] = "color_preset"; + drag_data["color_preset"] = dragged_preset_button->get_index(); + + return drag_data; +} + +bool ColorPicker::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const { + Dictionary d = p_data; + if (!d.has("type") || String(d["type"]) != "color_preset") { + return false; + } + return true; +} + +void ColorPicker::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) { + Dictionary d = p_data; + if (!d.has("type")) { return; } - _update_controls(); - _update_color(); + if (String(d["type"]) == "color_preset") { + int preset_from_id = d["color_preset"]; + int hover_now = p_from_control->get_index(); + + if (preset_from_id == hover_now || hover_now == -1) { + return; + } + preset_container->move_child(preset_container->get_child(preset_from_id), hover_now); + } } void ColorPicker::add_preset(const Color &p_color) { - if (presets.find(p_color)) { - presets.move_to_back(presets.find(p_color)); + List<Color>::Element *e = presets.find(p_color); + if (e) { + presets.move_to_back(e); + preset_cache.move_to_back(preset_cache.find(p_color)); - // Find button to move to the end. - for (int i = 1; i < preset_container->get_child_count(); i++) { - ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); - if (current_btn && p_color == current_btn->get_preset_color()) { - preset_container->move_child(current_btn, preset_container->get_child_count() - 1); - break; - } - } + preset_container->move_child(preset_group->get_pressed_button(), preset_container->get_child_count() - 1); } else { presets.push_back(p_color); preset_cache.push_back(p_color); @@ -582,16 +750,38 @@ void ColorPicker::add_preset(const Color &p_color) { #endif } +void ColorPicker::add_recent_preset(const Color &p_color) { + if (!_select_from_recent_preset_hbc(p_color)) { + if (recent_preset_hbc->get_child_count() >= PRESET_COLUMN_COUNT) { + recent_preset_cache.pop_front(); + recent_presets.pop_front(); + recent_preset_hbc->get_child(PRESET_COLUMN_COUNT - 1)->queue_free(); + } + recent_presets.push_back(p_color); + recent_preset_cache.push_back(p_color); + _add_recent_preset_button(_get_preset_size(), p_color); + } + _select_from_preset_container(p_color); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + PackedColorArray arr_to_save = get_recent_presets(); + EditorSettings::get_singleton()->set_project_metadata("color_picker", "recent_presets", arr_to_save); + } +#endif +} + void ColorPicker::erase_preset(const Color &p_color) { - if (presets.find(p_color)) { - presets.erase(presets.find(p_color)); + List<Color>::Element *e = presets.find(p_color); + if (e) { + presets.erase(e); preset_cache.erase(preset_cache.find(p_color)); // Find preset button to remove. for (int i = 1; i < preset_container->get_child_count(); i++) { ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); if (current_btn && p_color == current_btn->get_preset_color()) { - current_btn->queue_delete(); + current_btn->queue_free(); break; } } @@ -605,6 +795,30 @@ void ColorPicker::erase_preset(const Color &p_color) { } } +void ColorPicker::erase_recent_preset(const Color &p_color) { + List<Color>::Element *e = recent_presets.find(p_color); + if (e) { + recent_presets.erase(e); + recent_preset_cache.erase(recent_preset_cache.find(p_color)); + + // Find recent preset button to remove. + for (int i = 1; i < recent_preset_hbc->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(recent_preset_hbc->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->queue_free(); + break; + } + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + PackedColorArray arr_to_save = get_recent_presets(); + EditorSettings::get_singleton()->set_project_metadata("color_picker", "recent_presets", arr_to_save); + } +#endif + } +} + PackedColorArray ColorPicker::get_presets() const { PackedColorArray arr; arr.resize(presets.size()); @@ -614,16 +828,84 @@ PackedColorArray ColorPicker::get_presets() const { return arr; } +PackedColorArray ColorPicker::get_recent_presets() const { + PackedColorArray arr; + arr.resize(recent_presets.size()); + for (int i = 0; i < recent_presets.size(); i++) { + arr.set(i, recent_presets[i]); + } + return arr; +} + void ColorPicker::set_color_mode(ColorModeType p_mode) { ERR_FAIL_INDEX(p_mode, MODE_MAX); - mode_option_button->select(p_mode); - _set_color_mode(p_mode); + + if (current_mode == p_mode) { + return; + } + + if (slider_theme_modified) { + _reset_theme(); + } + + mode_popup->set_item_checked(current_mode, false); + mode_popup->set_item_checked(p_mode, true); + + if (p_mode < MODE_BUTTON_COUNT) { + mode_btns[p_mode]->set_pressed(true); + } else if (current_mode < MODE_BUTTON_COUNT) { + mode_btns[current_mode]->set_pressed(false); + } + + current_mode = p_mode; + + if (!is_inside_tree()) { + return; + } + + _update_controls(); + _update_color(); } ColorPicker::ColorModeType ColorPicker::get_color_mode() const { return current_mode; } +void ColorPicker::set_colorize_sliders(bool p_colorize_sliders) { + if (colorize_sliders == p_colorize_sliders) { + return; + } + + colorize_sliders = p_colorize_sliders; + mode_popup->set_item_checked(MODE_MAX + 1, colorize_sliders); + + if (colorize_sliders) { + Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); + + if (!slider_theme_modified) { + for (int i = 0; i < SLIDER_COUNT; i++) { + sliders[i]->add_theme_style_override("slider", style_box_empty); + } + } + alpha_slider->add_theme_style_override("slider", style_box_empty); + } else { + Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat)); + style_box_flat->set_default_margin(SIDE_TOP, 16 * get_theme_default_base_scale()); + style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp()); + + if (!slider_theme_modified) { + for (int i = 0; i < SLIDER_COUNT; i++) { + sliders[i]->add_theme_style_override("slider", style_box_flat); + } + } + alpha_slider->add_theme_style_override("slider", style_box_flat); + } +} + +bool ColorPicker::is_colorizing_sliders() const { + return colorize_sliders; +} + void ColorPicker::set_deferred_mode(bool p_enabled) { deferred_mode_enabled = p_enabled; } @@ -633,7 +915,7 @@ bool ColorPicker::is_deferred_mode() const { } void ColorPicker::_update_text_value() { - bool visible = true; + bool text_visible = true; if (text_is_constructor) { String t = "Color(" + String::num(color.r) + ", " + String::num(color.g) + ", " + String::num(color.b); if (edit_alpha && color.a < 1) { @@ -645,13 +927,13 @@ void ColorPicker::_update_text_value() { } if (color.r > 1 || color.g > 1 || color.b > 1 || color.r < 0 || color.g < 0 || color.b < 0) { - visible = false; + text_visible = false; } else if (!text_is_constructor) { c_text->set_text(color.to_html(edit_alpha && color.a < 1)); } - text_type->set_visible(visible); - c_text->set_visible(visible); + text_type->set_visible(text_visible); + c_text->set_visible(text_visible); } void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) { @@ -816,16 +1098,24 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Vector<Color> colors; Color col; col.set_ok_hsl(h, s, 1); - points.resize(4); - colors.resize(4); - points.set(0, Vector2()); - points.set(1, Vector2(c->get_size().x, 0)); + Color col2; + col2.set_ok_hsl(h, s, 0.5); + Color col3; + col3.set_ok_hsl(h, s, 0); + points.resize(6); + colors.resize(6); + points.set(0, Vector2(c->get_size().x, 0)); + points.set(1, Vector2(c->get_size().x, c->get_size().y * 0.5)); points.set(2, c->get_size()); points.set(3, Vector2(0, c->get_size().y)); + points.set(4, Vector2(0, c->get_size().y * 0.5)); + points.set(5, Vector2()); colors.set(0, col); - colors.set(1, col); - colors.set(2, Color(0, 0, 0)); - colors.set(3, Color(0, 0, 0)); + colors.set(1, col2); + colors.set(2, col3); + colors.set(3, col3); + colors.set(4, col2); + colors.set(5, col); c->draw_polygon(points, colors); int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); col.set_ok_hsl(h, 1, v); @@ -859,17 +1149,19 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } void ColorPicker::_slider_draw(int p_which) { - modes[current_mode]->slider_draw(p_which); + if (colorize_sliders) { + modes[current_mode]->slider_draw(p_which); + } } void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Ref<InputEventMouseButton> bev = p_event; - PickerShapeType current_picker = _get_actual_shape(); + PickerShapeType actual_shape = _get_actual_shape(); if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; - if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(bev->get_position()); if (dist <= center.x) { real_t rad = center.angle_to_point(bev->get_position()); @@ -917,8 +1209,11 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } - } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { - emit_signal(SNAME("color_changed"), color); + } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + if (deferred_mode_enabled) { + emit_signal(SNAME("color_changed"), color); + } + add_recent_preset(color); changing_color = false; spinning = false; } else { @@ -935,7 +1230,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } Vector2 center = c->get_size() / 2.0; - if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; @@ -990,9 +1285,10 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { set_pick_color(color); _update_color(); - if (!deferred_mode_enabled) { + if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + add_recent_preset(color); emit_signal(SNAME("color_changed"), color); - } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + } else if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } } @@ -1021,20 +1317,55 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } } +void ColorPicker::_slider_or_spin_input(const Ref<InputEvent> &p_event) { + if (line_edit_mouse_release) { + line_edit_mouse_release = false; + return; + } + Ref<InputEventMouseButton> bev = p_event; + if (bev.is_valid() && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + add_recent_preset(color); + } +} + +void ColorPicker::_line_edit_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> bev = p_event; + if (bev.is_valid() && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + line_edit_mouse_release = true; + } +} + void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_color) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { set_pick_color(p_color); + add_recent_preset(color); emit_signal(SNAME("color_changed"), p_color); - } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && presets_enabled) { + } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && can_add_swatches) { erase_preset(p_color); emit_signal(SNAME("preset_removed"), p_color); } } } +void ColorPicker::_recent_preset_pressed(const bool p_pressed, ColorPresetButton *p_preset) { + if (!p_pressed) { + return; + } + set_pick_color(p_preset->get_preset_color()); + + recent_presets.move_to_back(recent_presets.find(p_preset->get_preset_color())); + List<Color>::Element *e = recent_preset_cache.find(p_preset->get_preset_color()); + if (e) { + recent_preset_cache.move_to_back(e); + } + + recent_preset_hbc->move_child(p_preset, 0); + emit_signal(SNAME("color_changed"), p_preset->get_preset_color()); +} + void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { if (!is_inside_tree()) { return; @@ -1055,7 +1386,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<Image> img = r->get_texture()->get_image(); if (img.is_valid() && !img->is_empty()) { - Vector2 ofs = mev->get_global_position() - r->get_visible_rect().get_position(); + Vector2 ofs = mev->get_global_position(); Color c = img->get_pixel(ofs.x, ofs.y); set_pick_color(c); @@ -1063,6 +1394,10 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { } } +void ColorPicker::_text_changed(const String &) { + text_changed = true; +} + void ColorPicker::_add_preset_pressed() { add_preset(color); emit_signal(SNAME("preset_added"), color); @@ -1087,60 +1422,22 @@ void ColorPicker::_screen_pick_pressed() { screen->show(); } screen->move_to_front(); -#ifndef _MSC_VER -#warning show modal no longer works, needs to be converted to a popup -#endif + // TODO: show modal no longer works, needs to be converted to a popup. //screen->show_modal(); } -void ColorPicker::_focus_enter() { - bool has_ctext_focus = c_text->has_focus(); - if (has_ctext_focus) { - c_text->select_all(); - } else { - c_text->select(0, 0); - } - - for (int i = 0; i < current_slider_count; i++) { - if (values[i]->get_line_edit()->has_focus() && !has_ctext_focus) { - values[i]->get_line_edit()->select_all(); - } else { - values[i]->get_line_edit()->select(0, 0); - } - } - if (alpha_value->get_line_edit()->has_focus() && !has_ctext_focus) { - alpha_value->get_line_edit()->select_all(); - } else { - alpha_value->get_line_edit()->select(0, 0); - } -} - -void ColorPicker::_focus_exit() { - for (int i = 0; i < current_slider_count; i++) { - if (!values[i]->get_line_edit()->get_menu()->is_visible()) { - values[i]->get_line_edit()->select(0, 0); - } - } - if (!alpha_value->get_line_edit()->get_menu()->is_visible()) { - alpha_value->get_line_edit()->select(0, 0); - } - - c_text->select(0, 0); -} - void ColorPicker::_html_focus_exit() { if (c_text->is_menu_visible()) { return; } _html_submitted(c_text->get_text()); - _focus_exit(); } -void ColorPicker::set_presets_enabled(bool p_enabled) { - if (presets_enabled == p_enabled) { +void ColorPicker::set_can_add_swatches(bool p_enabled) { + if (can_add_swatches == p_enabled) { return; } - presets_enabled = p_enabled; + can_add_swatches = p_enabled; if (!p_enabled) { btn_add_preset->set_disabled(true); btn_add_preset->set_focus_mode(FOCUS_NONE); @@ -1150,8 +1447,8 @@ void ColorPicker::set_presets_enabled(bool p_enabled) { } } -bool ColorPicker::are_presets_enabled() const { - return presets_enabled; +bool ColorPicker::are_swatches_enabled() const { + return can_add_swatches; } void ColorPicker::set_presets_visible(bool p_visible) { @@ -1159,14 +1456,62 @@ void ColorPicker::set_presets_visible(bool p_visible) { return; } presets_visible = p_visible; - preset_separator->set_visible(p_visible); - preset_container->set_visible(p_visible); + btn_preset->set_visible(p_visible); + btn_recent_preset->set_visible(p_visible); } bool ColorPicker::are_presets_visible() const { return presets_visible; } +void ColorPicker::set_modes_visible(bool p_visible) { + if (color_modes_visible == p_visible) { + return; + } + color_modes_visible = p_visible; + mode_hbc->set_visible(p_visible); +} + +bool ColorPicker::are_modes_visible() const { + return color_modes_visible; +} + +void ColorPicker::set_sampler_visible(bool p_visible) { + if (sampler_visible == p_visible) { + return; + } + sampler_visible = p_visible; + sample_hbc->set_visible(p_visible); +} + +bool ColorPicker::is_sampler_visible() const { + return sampler_visible; +} + +void ColorPicker::set_sliders_visible(bool p_visible) { + if (sliders_visible == p_visible) { + return; + } + sliders_visible = p_visible; + slider_gc->set_visible(p_visible); +} + +bool ColorPicker::are_sliders_visible() const { + return sliders_visible; +} + +void ColorPicker::set_hex_visible(bool p_visible) { + if (hex_visible == p_visible) { + return; + } + hex_visible = p_visible; + hex_hbc->set_visible(p_visible); +} + +bool ColorPicker::is_hex_visible() const { + return hex_visible; +} + void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPicker::set_pick_color); ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); @@ -1176,22 +1521,42 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("get_color_mode"), &ColorPicker::get_color_mode); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPicker::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPicker::is_editing_alpha); - ClassDB::bind_method(D_METHOD("set_presets_enabled", "enabled"), &ColorPicker::set_presets_enabled); - ClassDB::bind_method(D_METHOD("are_presets_enabled"), &ColorPicker::are_presets_enabled); + ClassDB::bind_method(D_METHOD("set_can_add_swatches", "enabled"), &ColorPicker::set_can_add_swatches); + ClassDB::bind_method(D_METHOD("are_swatches_enabled"), &ColorPicker::are_swatches_enabled); ClassDB::bind_method(D_METHOD("set_presets_visible", "visible"), &ColorPicker::set_presets_visible); ClassDB::bind_method(D_METHOD("are_presets_visible"), &ColorPicker::are_presets_visible); + ClassDB::bind_method(D_METHOD("set_modes_visible", "visible"), &ColorPicker::set_modes_visible); + ClassDB::bind_method(D_METHOD("are_modes_visible"), &ColorPicker::are_modes_visible); + ClassDB::bind_method(D_METHOD("set_sampler_visible", "visible"), &ColorPicker::set_sampler_visible); + ClassDB::bind_method(D_METHOD("is_sampler_visible"), &ColorPicker::is_sampler_visible); + ClassDB::bind_method(D_METHOD("set_sliders_visible", "visible"), &ColorPicker::set_sliders_visible); + ClassDB::bind_method(D_METHOD("are_sliders_visible"), &ColorPicker::are_sliders_visible); + ClassDB::bind_method(D_METHOD("set_hex_visible", "visible"), &ColorPicker::set_hex_visible); + ClassDB::bind_method(D_METHOD("is_hex_visible"), &ColorPicker::is_hex_visible); ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); ClassDB::bind_method(D_METHOD("erase_preset", "color"), &ColorPicker::erase_preset); ClassDB::bind_method(D_METHOD("get_presets"), &ColorPicker::get_presets); + ClassDB::bind_method(D_METHOD("add_recent_preset", "color"), &ColorPicker::add_recent_preset); + ClassDB::bind_method(D_METHOD("erase_recent_preset", "color"), &ColorPicker::erase_recent_preset); + ClassDB::bind_method(D_METHOD("get_recent_presets"), &ColorPicker::get_recent_presets); ClassDB::bind_method(D_METHOD("set_picker_shape", "shape"), &ColorPicker::set_picker_shape); ClassDB::bind_method(D_METHOD("get_picker_shape"), &ColorPicker::get_picker_shape); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &ColorPicker::_get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &ColorPicker::_can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &ColorPicker::_drop_data_fw); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); ADD_PROPERTY(PropertyInfo(Variant::INT, "color_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW,OKHSL"), "set_color_mode", "get_color_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle"), "set_picker_shape", "get_picker_shape"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle,None"), "set_picker_shape", "get_picker_shape"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_add_swatches"), "set_can_add_swatches", "are_swatches_enabled"); + ADD_GROUP("Customization", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sampler_visible"), "set_sampler_visible", "is_sampler_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "color_modes_visible"), "set_modes_visible", "are_modes_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sliders_visible"), "set_sliders_visible", "are_sliders_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hex_visible"), "set_hex_visible", "is_hex_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible"); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); @@ -1207,13 +1572,14 @@ void ColorPicker::_bind_methods() { BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL); BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE); BIND_ENUM_CONSTANT(SHAPE_OKHSL_CIRCLE); + BIND_ENUM_CONSTANT(SHAPE_NONE); } ColorPicker::ColorPicker() : BoxContainer(true) { HBoxContainer *hb_edit = memnew(HBoxContainer); add_child(hb_edit, false, INTERNAL_MODE_FRONT); - hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); + hb_edit->set_v_size_flags(SIZE_SHRINK_BEGIN); uv_edit = memnew(Control); hb_edit->add_child(uv_edit); @@ -1223,58 +1589,110 @@ ColorPicker::ColorPicker() : uv_edit->set_v_size_flags(SIZE_EXPAND_FILL); uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(0, uv_edit)); - HBoxContainer *hb_smpl = memnew(HBoxContainer); - add_child(hb_smpl, false, INTERNAL_MODE_FRONT); + sample_hbc = memnew(HBoxContainer); + add_child(sample_hbc, false, INTERNAL_MODE_FRONT); + + btn_pick = memnew(Button); + sample_hbc->add_child(btn_pick); + btn_pick->set_toggle_mode(true); + btn_pick->set_tooltip_text(RTR("Pick a color from the editor window.")); + btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); sample = memnew(TextureRect); - hb_smpl->add_child(sample); + sample_hbc->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input)); sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw)); - btn_pick = memnew(Button); - btn_pick->set_flat(true); - hb_smpl->add_child(btn_pick); - btn_pick->set_toggle_mode(true); - btn_pick->set_tooltip_text(RTR("Pick a color from the editor window.")); - btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); + btn_shape = memnew(MenuButton); + btn_shape->set_flat(false); + sample_hbc->add_child(btn_shape); + btn_shape->set_toggle_mode(true); + btn_shape->set_tooltip_text(RTR("Select a picker shape.")); + current_shape = SHAPE_HSV_RECTANGLE; + + shape_popup = btn_shape->get_popup(); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect"), SNAME("ColorPicker")), "HSV Rectangle", SHAPE_HSV_RECTANGLE); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect_wheel"), SNAME("ColorPicker")), "HSV Wheel", SHAPE_HSV_WHEEL); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "VHS Circle", SHAPE_VHS_CIRCLE); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "OKHSL Circle", SHAPE_OKHSL_CIRCLE); + shape_popup->set_item_checked(current_shape, true); + shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape)); + + btn_shape->set_icon(shape_popup->get_item_icon(current_shape)); + + add_mode(new ColorModeRGB(this)); + add_mode(new ColorModeHSV(this)); + add_mode(new ColorModeRAW(this)); + add_mode(new ColorModeOKHSL(this)); + + mode_hbc = memnew(HBoxContainer); + add_child(mode_hbc, false, INTERNAL_MODE_FRONT); + + mode_group.instantiate(); + + for (int i = 0; i < MODE_BUTTON_COUNT; i++) { + mode_btns[i] = memnew(Button); + mode_hbc->add_child(mode_btns[i]); + mode_btns[i]->set_focus_mode(FOCUS_NONE); + mode_btns[i]->set_h_size_flags(SIZE_EXPAND_FILL); + mode_btns[i]->add_theme_style_override("pressed", get_theme_stylebox("tab_selected", "TabContainer")); + mode_btns[i]->add_theme_style_override("normal", get_theme_stylebox("tab_unselected", "TabContainer")); + mode_btns[i]->add_theme_style_override("hover", get_theme_stylebox("tab_selected", "TabContainer")); + mode_btns[i]->set_toggle_mode(true); + mode_btns[i]->set_text(modes[i]->get_name()); + mode_btns[i]->set_button_group(mode_group); + mode_btns[i]->connect("pressed", callable_mp(this, &ColorPicker::set_color_mode).bind((ColorModeType)i)); + } + mode_btns[0]->set_pressed(true); + + btn_mode = memnew(MenuButton); + btn_mode->set_text("..."); + btn_mode->set_flat(false); + mode_hbc->add_child(btn_mode); + btn_mode->set_toggle_mode(true); + btn_mode->set_tooltip_text(RTR("Select a picker mode.")); + + current_mode = MODE_RGB; + + mode_popup = btn_mode->get_popup(); + for (int i = 0; i < modes.size(); i++) { + 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->set_item_checked(current_mode, true); + mode_popup->set_item_checked(MODE_MAX + 1, true); + mode_popup->connect("id_pressed", callable_mp(this, &ColorPicker::_set_mode_popup_value)); VBoxContainer *vbl = memnew(VBoxContainer); add_child(vbl, false, INTERNAL_MODE_FRONT); - add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT); - VBoxContainer *vbr = memnew(VBoxContainer); add_child(vbr, false, INTERNAL_MODE_FRONT); vbr->set_h_size_flags(SIZE_EXPAND_FILL); - GridContainer *gc = memnew(GridContainer); + slider_gc = memnew(GridContainer); - vbr->add_child(gc); - gc->set_h_size_flags(SIZE_EXPAND_FILL); - gc->set_columns(3); + vbr->add_child(slider_gc); + slider_gc->set_h_size_flags(SIZE_EXPAND_FILL); + slider_gc->set_columns(3); for (int i = 0; i < SLIDER_COUNT + 1; i++) { - create_slider(gc, i); + create_slider(slider_gc, i); } alpha_label->set_text("A"); - HBoxContainer *hhb = memnew(HBoxContainer); - vbr->add_child(hhb); + hex_hbc = memnew(HBoxContainer); + hex_hbc->set_alignment(ALIGNMENT_BEGIN); + vbr->add_child(hex_hbc); - mode_option_button = memnew(OptionButton); - - hhb->add_child(mode_option_button); - add_mode(new ColorModeRGB(this)); - add_mode(new ColorModeHSV(this)); - add_mode(new ColorModeRAW(this)); - add_mode(new ColorModeOKHSL(this)); - mode_option_button->connect("item_selected", callable_mp(this, &ColorPicker::_set_color_mode)); + hex_hbc->add_child(memnew(Label("Hex"))); text_type = memnew(Button); - hhb->add_child(text_type); + hex_hbc->add_child(text_type); text_type->set_text("#"); text_type->set_tooltip_text(RTR("Switch between hexadecimal and code values.")); if (Engine::get_singleton()->is_editor_hint()) { @@ -1285,10 +1703,10 @@ ColorPicker::ColorPicker() : } c_text = memnew(LineEdit); - hhb->add_child(c_text); - c_text->set_h_size_flags(SIZE_EXPAND_FILL); + hex_hbc->add_child(c_text); + c_text->set_select_all_on_focus(true); c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted)); - c_text->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); + c_text->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); wheel_edit = memnew(AspectRatioContainer); @@ -1325,16 +1743,43 @@ ColorPicker::ColorPicker() : _update_controls(); updating = false; - set_pick_color(Color(1, 1, 1)); - - preset_separator = memnew(HSeparator); - add_child(preset_separator, false, INTERNAL_MODE_FRONT); - preset_container = memnew(GridContainer); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); - preset_container->set_columns(preset_column_count); + preset_container->set_columns(PRESET_COLUMN_COUNT); + preset_container->hide(); + + preset_group.instantiate(); + + btn_preset = memnew(Button); + btn_preset->set_text("Swatches"); + btn_preset->set_flat(true); + btn_preset->set_toggle_mode(true); + btn_preset->set_focus_mode(FOCUS_NONE); + btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); + add_child(btn_preset, false, INTERNAL_MODE_FRONT); + add_child(preset_container, false, INTERNAL_MODE_FRONT); + recent_preset_hbc = memnew(HBoxContainer); + recent_preset_hbc->set_v_size_flags(SIZE_SHRINK_BEGIN); + recent_preset_hbc->hide(); + + recent_preset_group.instantiate(); + + btn_recent_preset = memnew(Button); + btn_recent_preset->set_text("Recent Colors"); + btn_recent_preset->set_flat(true); + btn_recent_preset->set_toggle_mode(true); + btn_recent_preset->set_focus_mode(FOCUS_NONE); + btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); + add_child(btn_recent_preset, false, INTERNAL_MODE_FRONT); + + add_child(recent_preset_hbc, false, INTERNAL_MODE_FRONT); + + set_pick_color(Color(1, 1, 1)); + btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_add_preset->set_tooltip_text(RTR("Add current color as a preset.")); @@ -1375,6 +1820,7 @@ void ColorPickerButton::pressed() { popup->reset_size(); picker->_update_presets(); + picker->_update_recent_presets(); Rect2i usable_rect = popup->get_usable_parent_rect(); //let's try different positions to see which one we can use @@ -1481,6 +1927,7 @@ void ColorPickerButton::_update_picker() { picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup)); popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed)); + picker->connect("minimum_size_changed", callable_mp((Window *)popup, &Window::reset_size)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); picker->set_display_old_color(true); @@ -1520,6 +1967,13 @@ void ColorPresetButton::_notification(int p_what) { Ref<StyleBoxTexture> sb_texture = sb_raw; if (sb_flat.is_valid()) { + sb_flat->set_border_width(SIDE_BOTTOM, 2); + if (get_draw_mode() == DRAW_PRESSED || get_draw_mode() == DRAW_HOVER_PRESSED) { + sb_flat->set_border_color(Color(1, 1, 1, 1)); + } else { + sb_flat->set_border_color(Color(0, 0, 0, 1)); + } + if (preset_color.a < 1) { // Draw a background pattern when the color is transparent. sb_flat->set_bg_color(Color(1, 1, 1)); @@ -1563,8 +2017,10 @@ Color ColorPresetButton::get_preset_color() const { return preset_color; } -ColorPresetButton::ColorPresetButton(Color p_color) { +ColorPresetButton::ColorPresetButton(Color p_color, int p_size) { preset_color = p_color; + set_toggle_mode(true); + set_custom_minimum_size(Size2(p_size, p_size)); } ColorPresetButton::~ColorPresetButton() { diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 05b760b109..3208676539 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -38,6 +38,7 @@ #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" #include "scene/gui/popup.h" #include "scene/gui/separator.h" @@ -63,7 +64,7 @@ public: void set_preset_color(const Color &p_color); Color get_preset_color() const; - ColorPresetButton(Color p_color); + ColorPresetButton(Color p_color, int p_size); ~ColorPresetButton(); }; @@ -85,6 +86,7 @@ public: SHAPE_HSV_WHEEL, SHAPE_VHS_CIRCLE, SHAPE_OKHSL_CIRCLE, + SHAPE_NONE, SHAPE_MAX }; @@ -96,8 +98,10 @@ private: static Ref<Shader> circle_shader; static Ref<Shader> circle_ok_color_shader; static List<Color> preset_cache; + static List<Color> recent_preset_cache; int current_slider_count = SLIDER_COUNT; + static const int MODE_BUTTON_COUNT = 3; bool slider_theme_modified = true; @@ -114,9 +118,24 @@ private: Control *wheel_uv = nullptr; TextureRect *sample = nullptr; GridContainer *preset_container = nullptr; - HSeparator *preset_separator = nullptr; + HBoxContainer *recent_preset_hbc = nullptr; Button *btn_add_preset = nullptr; Button *btn_pick = nullptr; + Button *btn_preset = nullptr; + Button *btn_recent_preset = nullptr; + PopupMenu *shape_popup = nullptr; + PopupMenu *mode_popup = nullptr; + MenuButton *btn_shape = nullptr; + HBoxContainer *mode_hbc = nullptr; + HBoxContainer *sample_hbc = nullptr; + GridContainer *slider_gc = nullptr; + HBoxContainer *hex_hbc = nullptr; + MenuButton *btn_mode = nullptr; + Button *mode_btns[MODE_BUTTON_COUNT]; + Ref<ButtonGroup> mode_group = nullptr; + ColorPresetButton *selected_recent_preset = nullptr; + Ref<ButtonGroup> preset_group; + Ref<ButtonGroup> recent_preset_group; OptionButton *mode_option_button = nullptr; @@ -135,10 +154,13 @@ private: bool text_is_constructor = false; PickerShapeType current_shape = SHAPE_HSV_RECTANGLE; ColorModeType current_mode = MODE_RGB; + bool colorize_sliders = true; - const int preset_column_count = 9; + const int PRESET_COLUMN_COUNT = 9; int prev_preset_size = 0; + int prev_rencet_preset_size = 0; List<Color> presets; + List<Color> recent_presets; Color color; Color old_color; @@ -148,8 +170,14 @@ private: bool updating = true; bool changing_color = false; bool spinning = false; - bool presets_enabled = true; + bool can_add_swatches = true; bool presets_visible = true; + bool color_modes_visible = true; + bool sampler_visible = true; + bool sliders_visible = true; + bool hex_visible = true; + bool line_edit_mouse_release = false; + bool text_changed = false; float h = 0.0; float s = 0.0; @@ -175,18 +203,28 @@ private: void _uv_input(const Ref<InputEvent> &p_event, Control *c); void _w_input(const Ref<InputEvent> &p_event); + void _slider_or_spin_input(const Ref<InputEvent> &p_event); + void _line_edit_input(const Ref<InputEvent> &p_event); void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color); + void _recent_preset_pressed(const bool pressed, ColorPresetButton *p_preset); void _screen_input(const Ref<InputEvent> &p_event); + void _text_changed(const String &p_new_text); void _add_preset_pressed(); void _screen_pick_pressed(); - void _focus_enter(); - void _focus_exit(); void _html_focus_exit(); inline int _get_preset_size(); void _add_preset_button(int p_size, const Color &p_color); + void _add_recent_preset_button(int p_size, const Color &p_color); - void _set_color_mode(ColorModeType p_mode); + void _show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container); + void _update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset); + + void _set_mode_popup_value(ColorModeType p_mode); + + Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from_control); + bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const; + void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control); protected: void _notification(int); @@ -218,22 +256,44 @@ public: PickerShapeType get_picker_shape() const; void add_preset(const Color &p_color); + void add_recent_preset(const Color &p_color); void erase_preset(const Color &p_color); + void erase_recent_preset(const Color &p_color); PackedColorArray get_presets() const; + PackedColorArray get_recent_presets() const; void _update_presets(); + void _update_recent_presets(); + + void _select_from_preset_container(const Color &p_color); + bool _select_from_recent_preset_hbc(const Color &p_color); void set_color_mode(ColorModeType p_mode); ColorModeType get_color_mode() const; + void set_colorize_sliders(bool p_colorize_sliders); + bool is_colorizing_sliders() const; + void set_deferred_mode(bool p_enabled); bool is_deferred_mode() const; - void set_presets_enabled(bool p_enabled); - bool are_presets_enabled() const; + void set_can_add_swatches(bool p_enabled); + bool are_swatches_enabled() const; void set_presets_visible(bool p_visible); bool are_presets_visible() const; + void set_modes_visible(bool p_visible); + bool are_modes_visible() const; + + void set_sampler_visible(bool p_visible); + bool is_sampler_visible() const; + + void set_sliders_visible(bool p_visible); + bool are_sliders_visible() const; + + void set_hex_visible(bool p_visible); + bool is_hex_visible() const; + void set_focus_on_line_edit(); ColorPicker(); diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 5512c0f1fd..3c29c37479 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -192,8 +192,8 @@ void Container::_notification(int p_what) { } } -TypedArray<String> Container::get_configuration_warnings() const { - TypedArray<String> warnings = Control::get_configuration_warnings(); +PackedStringArray Container::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); if (get_class() == "Container" && get_script().is_null()) { warnings.push_back(RTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead.")); diff --git a/scene/gui/container.h b/scene/gui/container.h index 9ec4ad3200..21bdb95186 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -63,7 +63,7 @@ public: virtual Vector<int> get_allowed_size_flags_horizontal() const; virtual Vector<int> get_allowed_size_flags_vertical() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; Container(); }; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 347fe9aa11..c5cb7157e8 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -213,8 +213,8 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List } } -TypedArray<String> Control::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray Control::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); @@ -633,7 +633,7 @@ Rect2 Control::get_parent_anchorable_rect() const { #ifdef TOOLS_ENABLED Node *edited_root = get_tree()->get_edited_scene_root(); if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) { - parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); + parent_rect.size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); } else { parent_rect = get_viewport()->get_visible_rect(); } @@ -1436,13 +1436,6 @@ Rect2 Control::get_screen_rect() const { return r; } -Rect2 Control::get_window_rect() const { - ERR_FAIL_COND_V(!is_inside_tree(), Rect2()); - Rect2 gr = get_global_rect(); - gr.position += get_viewport()->get_visible_rect().position; - return gr; -} - Rect2 Control::get_anchorable_rect() const { return Rect2(Point2(), get_size()); } @@ -1558,13 +1551,11 @@ bool Control::is_minimum_size_adjust_blocked() const { Size2 Control::get_minimum_size() const { Vector2 ms; - if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { - return ms; - } - return Vector2(); + GDVIRTUAL_CALL(_get_minimum_size, ms); + return ms; } -void Control::set_custom_minimum_size(const Size2i &p_custom) { +void Control::set_custom_minimum_size(const Size2 &p_custom) { if (p_custom == data.custom_minimum_size) { return; } @@ -1572,7 +1563,7 @@ void Control::set_custom_minimum_size(const Size2i &p_custom) { update_minimum_size(); } -Size2i Control::get_custom_minimum_size() const { +Size2 Control::get_custom_minimum_size() const { return data.custom_minimum_size; } @@ -1759,6 +1750,34 @@ void Control::warp_mouse(const Point2 &p_position) { get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position)); } +void Control::set_shortcut_context(const Node *p_node) { + if (p_node != nullptr) { + data.shortcut_context = p_node->get_instance_id(); + } else { + data.shortcut_context = ObjectID(); + } +} + +Node *Control::get_shortcut_context() const { + Object *ctx_obj = ObjectDB::get_instance(data.shortcut_context); + Node *ctx_node = Object::cast_to<Node>(ctx_obj); + + return ctx_node; +} + +bool Control::is_focus_owner_in_shortcut_context() const { + if (data.shortcut_context == ObjectID()) { + // No context, therefore global - always "in" context. + return true; + } + + const Node *ctx_node = get_shortcut_context(); + const Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; + + // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. + return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); +} + // Drag and drop handling. void Control::set_drag_forwarding(Object *p_target) { @@ -1778,11 +1797,8 @@ Variant Control::get_drag_data(const Point2 &p_point) { } Variant dd; - if (GDVIRTUAL_CALL(_get_drag_data, p_point, dd)) { - return dd; - } - - return Variant(); + GDVIRTUAL_CALL(_get_drag_data, p_point, dd); + return dd; } bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const { @@ -1793,11 +1809,9 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const } } - bool ret; - if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { - return ret; - } - return false; + bool ret = false; + GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret); + return ret; } void Control::drop_data(const Point2 &p_point, const Variant &p_data) { @@ -2225,7 +2239,19 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons void Control::set_default_cursor_shape(CursorShape p_shape) { ERR_FAIL_INDEX(int(p_shape), CURSOR_MAX); + if (data.default_cursor == p_shape) { + return; + } data.default_cursor = p_shape; + + if (!is_inside_tree()) { + return; + } + if (!get_global_rect().has_point(get_global_mouse_position())) { + return; + } + + get_viewport()->get_base_window()->update_mouse_cursor_shape(); } Control::CursorShape Control::get_default_cursor_shape() const { @@ -2692,11 +2718,8 @@ void Control::end_bulk_theme_override() { TypedArray<Vector2i> Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const { if (p_parser_type == TextServer::STRUCTURED_TEXT_CUSTOM) { TypedArray<Vector2i> ret; - if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) { - return ret; - } else { - return TypedArray<Vector2i>(); - } + GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret); + return ret; } else { return TS->parse_structured_text(p_parser_type, p_args, p_text); } @@ -2781,10 +2804,8 @@ String Control::get_tooltip(const Point2 &p_pos) const { Control *Control::make_custom_tooltip(const String &p_text) const { Object *ret = nullptr; - if (GDVIRTUAL_CALL(_make_custom_tooltip, p_text, ret)) { - return Object::cast_to<Control>(ret); - } - return nullptr; + GDVIRTUAL_CALL(_make_custom_tooltip, p_text, ret); + return Object::cast_to<Control>(ret); } // Base object overrides. @@ -2873,8 +2894,8 @@ void Control::_notification(int p_notification) { if (data.parent_canvas_item) { data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed)); data.parent_canvas_item = nullptr; - } else if (!is_set_as_top_level()) { - //disconnect viewport + } else { + // Disconnect viewport. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); viewport->disconnect("size_changed", callable_mp(this, &Control::_size_changed)); @@ -3121,6 +3142,9 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Control::warp_mouse); + ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &Control::set_shortcut_context); + ClassDB::bind_method(D_METHOD("get_shortcut_context"), &Control::get_shortcut_context); + ClassDB::bind_method(D_METHOD("update_minimum_size"), &Control::update_minimum_size); ClassDB::bind_method(D_METHOD("set_layout_direction", "direction"), &Control::set_layout_direction); @@ -3132,7 +3156,7 @@ void Control::_bind_methods() { ADD_GROUP("Layout", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); @@ -3194,6 +3218,9 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); + ADD_GROUP("Input", ""); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); + ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); diff --git a/scene/gui/control.h b/scene/gui/control.h index 38cafd835a..72e870930d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -159,6 +159,7 @@ private: } }; + // This Data struct is to avoid namespace pollution in derived classes. struct Data { // Global relations. @@ -200,7 +201,7 @@ private: int h_size_flags = SIZE_FILL; int v_size_flags = SIZE_FILL; real_t expand = 1.0; - Point2i custom_minimum_size; + Point2 custom_minimum_size; // Input events and rendering. @@ -218,6 +219,8 @@ private: NodePath focus_next; NodePath focus_prev; + ObjectID shortcut_context; + // Theming. ThemeOwner *theme_owner = nullptr; @@ -387,7 +390,7 @@ public: // Editor integration. virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; virtual bool is_text_field() const; @@ -442,7 +445,6 @@ public: Rect2 get_rect() const; Rect2 get_global_rect() const; Rect2 get_screen_rect() const; - Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the rendering server Rect2 get_anchorable_rect() const override; void set_scale(const Vector2 &p_scale); @@ -460,8 +462,8 @@ public: virtual Size2 get_minimum_size() const; virtual Size2 get_combined_minimum_size() const; - void set_custom_minimum_size(const Size2i &p_custom); - Size2i get_custom_minimum_size() const; + void set_custom_minimum_size(const Size2 &p_custom); + Size2 get_custom_minimum_size() const; // Container sizing. @@ -487,6 +489,10 @@ public: void warp_mouse(const Point2 &p_position); + bool is_focus_owner_in_shortcut_context() const; + void set_shortcut_context(const Node *p_node); + Node *get_shortcut_context() const; + // Drag and drop handling. virtual void set_drag_forwarding(Object *p_target); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index f5edaf02d8..bf4dd3d245 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -208,24 +208,24 @@ void AcceptDialog::register_text_enter(Control *p_line_edit) { } void AcceptDialog::_update_child_rects() { - Size2 size = get_size(); + Size2 dlg_size = get_size(); float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT); float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); // Fill the entire size of the window with the background. bg_panel->set_position(Point2()); - bg_panel->set_size(size); + bg_panel->set_size(dlg_size); // Place the buttons from the bottom edge to their minimum required size. Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); - Size2 buttons_size = Size2(size.x - h_margins, buttons_minsize.y); - Point2 buttons_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), size.y - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - buttons_size.y); + Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y); + Point2 buttons_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), dlg_size.y - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - buttons_size.y); buttons_hbox->set_position(buttons_position); buttons_hbox->set_size(buttons_size); // Place the content from the top to fill the rest of the space (minus the separation). Point2 content_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), theme_cache.panel_style->get_margin(SIDE_TOP)); - Size2 content_size = Size2(size.x - h_margins, size.y - v_margins - buttons_size.y - theme_cache.buttons_separation); + Size2 content_size = Size2(dlg_size.x - h_margins, dlg_size.y - v_margins - buttons_size.y - theme_cache.buttons_separation); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 57f27e299f..cade65108c 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -172,18 +172,20 @@ void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) { void FileDialog::set_enable_multiple_selection(bool p_enable) { tree->set_select_mode(p_enable ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE); -}; +} Vector<String> FileDialog::get_selected_files() const { Vector<String> list; TreeItem *item = tree->get_root(); - while ((item = tree->get_next_selected(item))) { + item = tree->get_next_selected(item); + while (item) { list.push_back(dir_access->get_current_dir().path_join(item->get_text(0))); - }; + item = tree->get_next_selected(item); + } return list; -}; +} void FileDialog::update_dir() { if (root_prefix.is_empty()) { @@ -741,10 +743,10 @@ void FileDialog::set_current_path(const String &p_path) { if (pos == -1) { set_current_file(p_path); } else { - String dir = p_path.substr(0, pos); - String file = p_path.substr(pos + 1, p_path.length()); - set_current_dir(dir); - set_current_file(file); + String path_dir = p_path.substr(0, pos); + String path_file = p_path.substr(pos + 1, p_path.length()); + set_current_dir(path_dir); + set_current_file(path_file); } } diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index b0d15aa7f4..44c5ec62f8 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -152,6 +152,28 @@ void FlowContainer::_resort() { line_data = lines_data[current_line_idx]; } + // The first child of each line adds the offset caused by the alignment, + // but only if the line doesn't contain a child that expands. + if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) { + int alignment_ofs = 0; + switch (alignment) { + case ALIGNMENT_CENTER: + alignment_ofs = line_data.stretch_avail / 2; + break; + case ALIGNMENT_END: + alignment_ofs = line_data.stretch_avail; + break; + default: + break; + } + + if (vertical) { /* VERTICAL */ + ofs.y += alignment_ofs; + } else { /* HORIZONTAL */ + ofs.x += alignment_ofs; + } + } + if (vertical) { /* VERTICAL */ if (child->get_h_size_flags() & (SIZE_FILL | SIZE_SHRINK_CENTER | SIZE_SHRINK_END)) { child_size.width = line_data.min_line_height; @@ -282,6 +304,18 @@ int FlowContainer::get_line_count() const { return cached_line_count; } +void FlowContainer::set_alignment(AlignmentMode p_alignment) { + if (alignment == p_alignment) { + return; + } + alignment = p_alignment; + _resort(); +} + +FlowContainer::AlignmentMode FlowContainer::get_alignment() const { + return alignment; +} + void FlowContainer::set_vertical(bool p_vertical) { ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + "."); vertical = p_vertical; @@ -300,8 +334,15 @@ FlowContainer::FlowContainer(bool p_vertical) { void FlowContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count); + ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment); + ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment); ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical); + BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN); + BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); + BIND_ENUM_CONSTANT(ALIGNMENT_END); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); } diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index 536df27ad6..6a61e9b904 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -36,11 +36,19 @@ class FlowContainer : public Container { GDCLASS(FlowContainer, Container); +public: + enum AlignmentMode { + ALIGNMENT_BEGIN, + ALIGNMENT_CENTER, + ALIGNMENT_END + }; + private: int cached_size = 0; int cached_line_count = 0; bool vertical = false; + AlignmentMode alignment = ALIGNMENT_BEGIN; struct ThemeCache { int h_separation = 0; @@ -61,6 +69,9 @@ protected: public: int get_line_count() const; + void set_alignment(AlignmentMode p_alignment); + AlignmentMode get_alignment() const; + void set_vertical(bool p_vertical); bool is_vertical() const; @@ -88,4 +99,6 @@ public: FlowContainer(true) { is_fixed = true; } }; +VARIANT_ENUM_CAST(FlowContainer::AlignmentMode); + #endif // FLOW_CONTAINER_H diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 5a0236268b..46b712379d 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -92,8 +92,8 @@ void GraphEditMinimap::update_minimap() { Rect2 GraphEditMinimap::get_camera_rect() { Vector2 camera_center = _convert_from_graph_position(camera_position + camera_size / 2) + minimap_offset; Vector2 camera_viewport = _convert_from_graph_position(camera_size); - Vector2 camera_position = (camera_center - camera_viewport / 2); - return Rect2(camera_position, camera_viewport); + Vector2 camera_pos = (camera_center - camera_viewport / 2); + return Rect2(camera_pos, camera_viewport); } Vector2 GraphEditMinimap::_get_render_size() { @@ -190,8 +190,8 @@ void GraphEditMinimap::_adjust_graph_scroll(const Vector2 &p_offset) { ge->set_scroll_ofs(p_offset + graph_offset - camera_size / 2); } -TypedArray<String> GraphEdit::get_configuration_warnings() const { - TypedArray<String> warnings = Control::get_configuration_warnings(); +PackedStringArray GraphEdit::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); warnings.push_back(RTR("Please be aware that GraphEdit and GraphNode will undergo extensive refactoring in a future beta version involving compatibility-breaking API changes.")); @@ -1385,16 +1385,16 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (p_ev->is_pressed()) { - if (p_ev->is_action("ui_graph_duplicate")) { + if (p_ev->is_action("ui_graph_duplicate", true)) { emit_signal(SNAME("duplicate_nodes_request")); accept_event(); - } else if (p_ev->is_action("ui_copy")) { + } else if (p_ev->is_action("ui_copy", true)) { emit_signal(SNAME("copy_nodes_request")); accept_event(); - } else if (p_ev->is_action("ui_paste")) { + } else if (p_ev->is_action("ui_paste", true)) { emit_signal(SNAME("paste_nodes_request")); accept_event(); - } else if (p_ev->is_action("ui_graph_delete")) { + } else if (p_ev->is_action("ui_graph_delete", true)) { TypedArray<StringName> nodes; for (int i = 0; i < get_child_count(); i++) { @@ -1475,11 +1475,9 @@ void GraphEdit::force_connection_drag_end() { } bool GraphEdit::is_node_hover_valid(const StringName &p_from, const int p_from_port, const StringName &p_to, const int p_to_port) { - bool valid; - if (GDVIRTUAL_CALL(_is_node_hover_valid, p_from, p_from_port, p_to, p_to_port, valid)) { - return valid; - } - return true; + bool valid = true; + GDVIRTUAL_CALL(_is_node_hover_valid, p_from, p_from_port, p_to, p_to_port, valid); + return valid; } void GraphEdit::set_panning_scheme(PanningScheme p_scheme) { diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 0fe9e7c555..101087bdbd 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -287,7 +287,7 @@ protected: GDVIRTUAL4R(bool, _is_node_hover_valid, StringName, int, StringName, int); public: - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 21c0b5a842..5df4c066e4 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -294,14 +294,14 @@ void GraphNode::_resort() { bool GraphNode::has_point(const Point2 &p_point) const { if (comment) { - Ref<StyleBox> comment = get_theme_stylebox(SNAME("comment")); + Ref<StyleBox> comment_sb = get_theme_stylebox(SNAME("comment")); Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); if (Rect2(get_size() - resizer->get_size(), resizer->get_size()).has_point(p_point)) { return true; } - if (Rect2(0, 0, get_size().width, comment->get_margin(SIDE_TOP)).has_point(p_point)) { + if (Rect2(0, 0, get_size().width, comment_sb->get_margin(SIDE_TOP)).has_point(p_point)) { return true; } diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 008109da65..82f089735d 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -662,19 +662,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { pos.x = get_size().width - pos.x; } - int closest = -1; - - for (int i = 0; i < items.size(); i++) { - Rect2 rc = items[i].rect_cache; - if (i % current_columns == current_columns - 1) { - rc.size.width = get_size().width; //not right but works - } - - if (rc.has_point(pos)) { - closest = i; - break; - } - } + int closest = get_item_at_position(mb->get_position(), true); if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) { int i = closest; @@ -740,12 +728,12 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } if (p_event->is_pressed() && items.size() > 0) { - if (p_event->is_action("ui_up")) { + if (p_event->is_action("ui_up", true)) { if (!search_string.is_empty()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { + if (diff < uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current - 1; i >= 0; i--) { if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); @@ -778,12 +766,12 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_down")) { + } else if (p_event->is_action("ui_down", true)) { if (!search_string.is_empty()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { + if (diff < uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current + 1; i < items.size(); i++) { if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); @@ -815,7 +803,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_page_up")) { + } else if (p_event->is_action("ui_page_up", true)) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { @@ -829,7 +817,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { break; } } - } else if (p_event->is_action("ui_page_down")) { + } else if (p_event->is_action("ui_page_down", true)) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { @@ -844,7 +832,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { break; } } - } else if (p_event->is_action("ui_left")) { + } else if (p_event->is_action("ui_left", true)) { search_string = ""; //any mousepress cancels if (current % current_columns != 0) { @@ -864,7 +852,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_right")) { + } else if (p_event->is_action("ui_right", true)) { search_string = ""; //any mousepress cancels if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) { @@ -884,9 +872,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_cancel")) { + } else if (p_event->is_action("ui_cancel", true)) { search_string = ""; - } else if (p_event->is_action("ui_select") && select_mode == SELECT_MULTI) { + } else if (p_event->is_action("ui_select", true) && select_mode == SELECT_MULTI) { if (current >= 0 && current < items.size()) { if (items[current].selectable && !items[current].disabled && !items[current].selected) { select(current, false); @@ -896,7 +884,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { emit_signal(SNAME("multi_selected"), current, false); } } - } else if (p_event->is_action("ui_accept")) { + } else if (p_event->is_action("ui_accept", true)) { search_string = ""; //any mousepress cancels if (current >= 0 && current < items.size()) { @@ -908,7 +896,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (k.is_valid() && k->get_unicode()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000)); + uint64_t max_interval = uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")); search_time_msec = now; if (diff > max_interval) { @@ -1308,9 +1296,9 @@ void ItemList::_notification(int p_what) { draw_rect.size = adj.size; } - Color modulate = items[i].icon_modulate; + Color icon_modulate = items[i].icon_modulate; if (items[i].disabled) { - modulate.a *= 0.5; + icon_modulate.a *= 0.5; } // If the icon is transposed, we have to switch the size so that it is drawn correctly @@ -1325,7 +1313,7 @@ void ItemList::_notification(int p_what) { if (rtl) { draw_rect.position.x = size.width - draw_rect.position.x - draw_rect.size.x; } - draw_texture_rect_region(items[i].icon, draw_rect, region, modulate, items[i].icon_transposed); + draw_texture_rect_region(items[i].icon, draw_rect, region, icon_modulate, items[i].icon_transposed); } if (items[i].tag_icon.is_valid()) { @@ -1348,9 +1336,9 @@ void ItemList::_notification(int p_what) { max_len = size2.x; } - Color modulate = items[i].selected ? theme_cache.font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : theme_cache.font_color); + Color txt_modulate = items[i].selected ? theme_cache.font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : theme_cache.font_color); if (items[i].disabled) { - modulate.a *= 0.5; + txt_modulate.a *= 0.5; } if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { @@ -1367,7 +1355,7 @@ void ItemList::_notification(int p_what) { items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); + items[i].text_buf->draw(get_canvas_item(), text_ofs, txt_modulate); } else { if (fixed_column_width > 0) { size2.x = MIN(size2.x, fixed_column_width); @@ -1399,7 +1387,7 @@ void ItemList::_notification(int p_what) { } if (width - text_ofs.x > 0) { - items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); + items[i].text_buf->draw(get_canvas_item(), text_ofs, txt_modulate); } } } @@ -1467,7 +1455,7 @@ int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { for (int i = 0; i < items.size(); i++) { Rect2 rc = items[i].rect_cache; if (i % current_columns == current_columns - 1) { - rc.size.width = get_size().width - rc.position.x; //make sure you can still select the last item when clicking past the column + rc.size.width = get_size().width - rc.position.x; // Make sure you can still select the last item when clicking past the column. } if (rc.has_point(pos)) { @@ -1843,9 +1831,6 @@ void ItemList::_bind_methods() { ADD_SIGNAL(MethodInfo("item_clicked", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "selected"))); ADD_SIGNAL(MethodInfo("item_activated", PropertyInfo(Variant::INT, "index"))); - - GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000); - ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/incremental_search_max_interval_msec", PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater")); // No negative numbers } ItemList::ItemList() { diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 306ca3d340..2203573bbc 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -102,12 +102,12 @@ void Label::_shape() { const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; ERR_FAIL_COND(font.is_null()); - String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - text = text.substr(0, visible_chars); + txt = txt.substr(0, visible_chars); } if (dirty) { - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, font->get_opentype_features(), language); + TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language); } else { int spans = TS->shaped_get_span_count(text_rid); for (int i = 0; i < spans; i++) { @@ -117,7 +117,7 @@ void Label::_shape() { for (int i = 0; i < TextServer::SPACING_MAX; i++) { TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); } - TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text)); + TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, txt)); dirty = false; font_dirty = false; lines_dirty = true; @@ -288,6 +288,36 @@ void Label::_update_theme_item_cache() { theme_cache.font_shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); } +PackedStringArray Label::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); + + // Ensure that the font can render all of the required glyphs. + Ref<Font> font; + if (settings.is_valid()) { + font = settings->get_font(); + } + if (font.is_null()) { + font = theme_cache.font; + } + + if (font.is_valid()) { + if (dirty || font_dirty || lines_dirty) { + const_cast<Label *>(this)->_shape(); + } + + const Glyph *glyph = TS->shaped_text_get_glyphs(text_rid); + int64_t glyph_count = TS->shaped_text_get_glyph_count(text_rid); + for (int64_t i = 0; i < glyph_count; i++) { + if (glyph[i].font_rid == RID()) { + warnings.push_back(RTR("The current font does not support rendering one or more characters used in this Label's text.")); + break; + } + } + } + + return warnings; +} + void Label::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { @@ -302,6 +332,7 @@ void Label::_notification(int p_what) { dirty = true; queue_redraw(); + update_configuration_warnings(); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { @@ -674,6 +705,7 @@ void Label::set_text(const String &p_string) { } queue_redraw(); update_minimum_size(); + update_configuration_warnings(); } void Label::_invalidate() { diff --git a/scene/gui/label.h b/scene/gui/label.h index b79c94a5ba..41d7049b22 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -93,6 +93,7 @@ protected: public: virtual Size2 get_minimum_size() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_horizontal_alignment(HorizontalAlignment p_alignment); HorizontalAlignment get_horizontal_alignment() const; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index be94337c89..8a77c39487 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -372,6 +372,11 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { selection.drag_attempt = false; } + if (pending_select_all_on_focus) { + select_all(); + pending_select_all_on_focus = false; + } + show_virtual_keyboard(); } @@ -955,23 +960,47 @@ void LineEdit::_notification(int p_what) { if (ime_text.length() == 0) { // Normal caret. CaretInfo caret = TS->shaped_text_get_carets(text_rid, caret_column); - - if (caret.l_caret == Rect2() && caret.t_caret == Rect2()) { + if (using_placeholder || (caret.l_caret == Rect2() && caret.t_caret == Rect2())) { // No carets, add one at the start. int h = theme_cache.font->get_height(theme_cache.font_size); int y = style->get_offset().y + (y_area - h) / 2; - if (rtl) { - caret.l_dir = TextServer::DIRECTION_RTL; - caret.l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h)); - } else { - caret.l_dir = TextServer::DIRECTION_LTR; - caret.l_caret = Rect2(Vector2(x_ofs, y), Size2(caret_width, h)); + caret.l_dir = (rtl) ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { + if (rtl) { + caret.l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h)); + } else { + caret.l_caret = Rect2(Vector2(style->get_offset().x, y), Size2(caret_width, h)); + } + } break; + case HORIZONTAL_ALIGNMENT_CENTER: { + caret.l_caret = Rect2(Vector2(size.x / 2, y), Size2(caret_width, h)); + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + if (rtl) { + caret.l_caret = Rect2(Vector2(style->get_offset().x, y), Size2(caret_width, h)); + } else { + caret.l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h)); + } + } break; } RenderingServer::get_singleton()->canvas_item_add_rect(ci, caret.l_caret, caret_color); } else { if (caret.l_caret != Rect2() && caret.l_dir == TextServer::DIRECTION_AUTO) { // Draw extra marker on top of mid caret. - Rect2 trect = Rect2(caret.l_caret.position.x - 3 * caret_width, caret.l_caret.position.y, 6 * caret_width, caret_width); + Rect2 trect = Rect2(caret.l_caret.position.x - 2.5 * caret_width, caret.l_caret.position.y, 6 * caret_width, caret_width); + trect.position += ofs; + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + } else if (caret.l_caret != Rect2() && caret.t_caret != Rect2() && caret.l_dir != caret.t_dir) { + // Draw extra direction marker on top of split caret. + float d = (caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + Rect2 trect = Rect2(caret.l_caret.position.x + d * caret_width, caret.l_caret.position.y + caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); + trect.position += ofs; + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + + d = (caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + trect = Rect2(caret.t_caret.position.x + d * caret_width, caret.t_caret.position.y, 3 * caret_width, caret_width); trect.position += ofs; RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } @@ -1045,10 +1074,19 @@ void LineEdit::_notification(int p_what) { } } + 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(); + } + } + if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - Point2 caret_column = Point2(get_caret_column(), 1) * get_minimum_size().height; - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret_column, get_viewport()->get_window_id()); + Point2 column = Point2(get_caret_column(), 1) * get_minimum_size().height; + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + column, get_viewport()->get_window_id()); } show_virtual_keyboard(); @@ -2153,6 +2191,18 @@ bool LineEdit::is_flat() const { return flat; } +void LineEdit::set_select_all_on_focus(bool p_enabled) { + select_all_on_focus = p_enabled; +} + +bool LineEdit::is_select_all_on_focus() const { + return select_all_on_focus; +} + +void LineEdit::clear_pending_select_all_on_focus() { + pending_select_all_on_focus = false; +} + void LineEdit::_text_changed() { _emit_text_change(); _clear_redo(); @@ -2164,6 +2214,12 @@ void LineEdit::_emit_text_change() { } void LineEdit::_shape() { + const Ref<Font> &font = theme_cache.font; + int font_size = theme_cache.font_size; + if (font.is_null()) { + return; + } + Size2 old_size = TS->shaped_text_get_size(text_rid); TS->shaped_text_clear(text_rid); @@ -2186,9 +2242,6 @@ void LineEdit::_shape() { } TS->shaped_text_set_preserve_control(text_rid, draw_control_chars); - const Ref<Font> &font = theme_cache.font; - int font_size = theme_cache.font_size; - ERR_FAIL_COND(font.is_null()); TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, font->get_opentype_features(), language); for (int i = 0; i < TextServer::SPACING_MAX; i++) { TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); @@ -2353,6 +2406,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon); ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &LineEdit::set_flat); ClassDB::bind_method(D_METHOD("is_flat"), &LineEdit::is_flat); + ClassDB::bind_method(D_METHOD("set_select_all_on_focus", "enabled"), &LineEdit::set_select_all_on_focus); + ClassDB::bind_method(D_METHOD("is_select_all_on_focus"), &LineEdit::is_select_all_on_focus); ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text"))); ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring"))); @@ -2416,6 +2471,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index a4d5205f81..e0a079b623 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -174,6 +174,9 @@ private: double caret_blink_timer = 0.0; bool caret_blinking = false; + bool pending_select_all_on_focus = false; + bool select_all_on_focus = false; + struct ThemeCache { Ref<StyleBox> normal; Ref<StyleBox> read_only; @@ -199,8 +202,6 @@ private: float base_scale = 1.0; } theme_cache; - bool _is_over_clear_button(const Point2 &p_pos) const; - void _clear_undo_stack(); void _clear_redo(); void _create_undo_state(); @@ -240,6 +241,7 @@ private: void _ensure_menu(); protected: + bool _is_over_clear_button(const Point2 &p_pos) const; virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); @@ -366,6 +368,10 @@ public: void set_flat(bool p_enabled); bool is_flat() const; + void set_select_all_on_focus(bool p_enabled); + bool is_select_all_on_focus() const; + void clear_pending_select_all_on_focus(); // For other controls, e.g. SpinBox. + virtual bool is_text_field() const override; void show_virtual_keyboard(); diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index d6bf84ea5a..82ef53e317 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -41,7 +41,7 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) { } MutexLock lock(mutex); - if (p_event->is_action("ui_left") && p_event->is_pressed()) { + if (p_event->is_action("ui_left", true) && p_event->is_pressed()) { int new_sel = selected_menu; int old_sel = (selected_menu < 0) ? 0 : selected_menu; do { @@ -63,7 +63,7 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) { _open_popup(selected_menu, true); } return; - } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_right", true) && p_event->is_pressed()) { int new_sel = selected_menu; int old_sel = (selected_menu < 0) ? menu_cache.size() - 1 : selected_menu; do { @@ -149,10 +149,6 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) { void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (disable_shortcuts) { return; } @@ -175,34 +171,6 @@ void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) { } } -void MenuBar::set_shortcut_context(Node *p_node) { - if (p_node != nullptr) { - shortcut_context = p_node->get_instance_id(); - } else { - shortcut_context = ObjectID(); - } -} - -Node *MenuBar::get_shortcut_context() const { - Object *ctx_obj = ObjectDB::get_instance(shortcut_context); - Node *ctx_node = Object::cast_to<Node>(ctx_obj); - - return ctx_node; -} - -bool MenuBar::_is_focus_owner_in_shortcut_context() const { - if (shortcut_context == ObjectID()) { - // No context, therefore global - always "in" context. - return true; - } - - Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; - - // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. - return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); -} - void MenuBar::_popup_visibility_changed(bool p_visible) { if (!p_visible) { active_menu = -1; @@ -213,19 +181,19 @@ void MenuBar::_popup_visibility_changed(bool p_visible) { } if (switch_on_hover) { - Window *window = Object::cast_to<Window>(get_viewport()); - if (window) { - mouse_pos_adjusted = window->get_position(); - - if (window->is_embedded()) { - Window *window_parent = Object::cast_to<Window>(window->get_parent()->get_viewport()); - while (window_parent) { - if (!window_parent->is_embedded()) { - mouse_pos_adjusted += window_parent->get_position(); + Window *wnd = Object::cast_to<Window>(get_viewport()); + if (wnd) { + mouse_pos_adjusted = wnd->get_position(); + + if (wnd->is_embedded()) { + Window *wnd_parent = Object::cast_to<Window>(wnd->get_parent()->get_viewport()); + while (wnd_parent) { + if (!wnd_parent->is_embedded()) { + mouse_pos_adjusted += wnd_parent->get_position(); break; } - window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport()); + wnd_parent = Object::cast_to<Window>(wnd_parent->get_parent()->get_viewport()); } } @@ -694,16 +662,12 @@ void MenuBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); - ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &MenuBar::set_shortcut_context); - ClassDB::bind_method(D_METHOD("get_shortcut_context"), &MenuBar::get_shortcut_context); - ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index f7ef19e98b..c057a7c96f 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -118,8 +118,6 @@ class MenuBar : public Control { void _clear_menu(); void _update_menu(); - bool _is_focus_owner_in_shortcut_context() const; - protected: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; @@ -170,9 +168,6 @@ public: void set_menu_hidden(int p_menu, bool p_hidden); bool is_menu_hidden(int p_menu) const; - void set_shortcut_context(Node *p_node); - Node *get_shortcut_context() const; - PopupMenu *get_menu_popup(int p_menu) const; virtual void get_translatable_strings(List<String> *p_strings) const override; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 67a36240a2..786f23873e 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -36,23 +36,16 @@ void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { + if (disable_shortcuts) { return; } - if (disable_shortcuts) { + if (p_event->is_pressed() && !p_event->is_echo() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) { + accept_event(); return; } - if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) { - if (!get_parent() || !is_visible_in_tree() || is_disabled()) { - return; - } - - if (popup->activate_item_by_event(p_event, false)) { - accept_event(); - } - } + Button::shortcut_input(p_event); } void MenuButton::_popup_visibility_changed(bool p_visible) { @@ -64,19 +57,19 @@ void MenuButton::_popup_visibility_changed(bool p_visible) { } if (switch_on_hover) { - Window *window = Object::cast_to<Window>(get_viewport()); - if (window) { - mouse_pos_adjusted = window->get_position(); - - if (window->is_embedded()) { - Window *window_parent = Object::cast_to<Window>(window->get_parent()->get_viewport()); - while (window_parent) { - if (!window_parent->is_embedded()) { - mouse_pos_adjusted += window_parent->get_position(); + Window *wnd = Object::cast_to<Window>(get_viewport()); + if (wnd) { + mouse_pos_adjusted = wnd->get_position(); + + if (wnd->is_embedded()) { + Window *wnd_parent = Object::cast_to<Window>(wnd->get_parent()->get_viewport()); + while (wnd_parent) { + if (!wnd_parent->is_embedded()) { + mouse_pos_adjusted += wnd_parent->get_position(); break; } - window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport()); + wnd_parent = Object::cast_to<Window>(wnd_parent->get_parent()->get_viewport()); } } @@ -91,6 +84,18 @@ void MenuButton::pressed() { return; } + show_popup(); +} + +PopupMenu *MenuButton::get_popup() const { + return popup; +} + +void MenuButton::show_popup() { + if (!get_viewport()) { + return; + } + emit_signal(SNAME("about_to_popup")); Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); @@ -116,14 +121,6 @@ void MenuButton::pressed() { popup->popup(); } -void MenuButton::gui_input(const Ref<InputEvent> &p_event) { - BaseButton::gui_input(p_event); -} - -PopupMenu *MenuButton::get_popup() const { - return popup; -} - void MenuButton::set_switch_on_hover(bool p_enabled) { switch_on_hover = p_enabled; } @@ -226,6 +223,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); + ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover); ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover); ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuButton::set_disable_shortcuts); diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index 97c0d21f1e..3aacbca3a8 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -44,8 +44,6 @@ class MenuButton : public Button { Vector2i mouse_pos_adjusted; - virtual void gui_input(const Ref<InputEvent> &p_event) override; - void _popup_visibility_changed(bool p_visible); protected: @@ -60,6 +58,8 @@ public: virtual void pressed() override; PopupMenu *get_popup() const; + void show_popup(); + void set_switch_on_hover(bool p_enabled); bool is_switch_on_hover(); void set_disable_shortcuts(bool p_disabled); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 08f5e0bbfb..0940b4c07b 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -231,32 +231,7 @@ void OptionButton::pressed() { return; } - Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); - popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); - popup->set_size(Size2(size.width, 0)); - - // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused. - if (current != NONE_SELECTED && !popup->is_item_disabled(current)) { - if (!_was_pressed_by_mouse()) { - popup->set_focused_item(current); - } else { - popup->scroll_to_item(current); - } - } else { - for (int i = 0; i < popup->get_item_count(); i++) { - if (!popup->is_item_disabled(i)) { - if (!_was_pressed_by_mouse()) { - popup->set_focused_item(i); - } else { - popup->scroll_to_item(i); - } - - break; - } - } - } - - popup->popup(); + show_popup(); } void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id) { @@ -476,7 +451,7 @@ void OptionButton::_queue_refresh_cache() { } cache_refresh_pending = true; - callable_mp(this, &OptionButton::_refresh_size_cache).call_deferredp(nullptr, 0); + callable_mp(this, &OptionButton::_refresh_size_cache).call_deferred(); } void OptionButton::select(int p_idx) { @@ -511,6 +486,39 @@ PopupMenu *OptionButton::get_popup() const { return popup; } +void OptionButton::show_popup() { + if (!get_viewport()) { + return; + } + + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); + popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); + popup->set_size(Size2(size.width, 0)); + + // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused. + if (current != NONE_SELECTED && !popup->is_item_disabled(current)) { + if (!_was_pressed_by_mouse()) { + popup->set_focused_item(current); + } else { + popup->scroll_to_item(current); + } + } else { + for (int i = 0; i < popup->get_item_count(); i++) { + if (!popup->is_item_disabled(i)) { + if (!_was_pressed_by_mouse()) { + popup->set_focused_item(i); + } else { + popup->scroll_to_item(i); + } + + break; + } + } + } + + popup->popup(); +} + void OptionButton::get_translatable_strings(List<String> *p_strings) const { popup->get_translatable_strings(p_strings); } @@ -548,6 +556,7 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("_select_int", "idx"), &OptionButton::_select_int); ClassDB::bind_method(D_METHOD("get_popup"), &OptionButton::get_popup); + ClassDB::bind_method(D_METHOD("show_popup"), &OptionButton::show_popup); ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 2c7e0510f5..b76a31d37e 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -123,6 +123,7 @@ public: void remove_item(int p_idx); PopupMenu *get_popup() const; + void show_popup(); virtual void get_translatable_strings(List<String> *p_strings) const override; diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index ceae3791f3..434eb87e7a 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -77,28 +77,36 @@ void Popup::_update_theme_item_cache() { void Popup::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { - if (is_visible()) { - _initialize_visible_parents(); - } else { - _deinitialize_visible_parents(); - emit_signal(SNAME("popup_hide")); - popped_up = false; + if (!is_in_edited_scene_root()) { + if (is_visible()) { + _initialize_visible_parents(); + } else { + _deinitialize_visible_parents(); + emit_signal(SNAME("popup_hide")); + popped_up = false; + } } } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { - if (has_focus()) { - popped_up = true; + if (!is_in_edited_scene_root()) { + if (has_focus()) { + popped_up = true; + } } } break; case NOTIFICATION_EXIT_TREE: { - _deinitialize_visible_parents(); + if (!is_in_edited_scene_root()) { + _deinitialize_visible_parents(); + } } break; case NOTIFICATION_WM_CLOSE_REQUEST: case NOTIFICATION_APPLICATION_FOCUS_OUT: { - _close_pressed(); + if (!is_in_edited_scene_root()) { + _close_pressed(); + } } break; } } @@ -126,52 +134,62 @@ void Popup::_bind_methods() { ADD_SIGNAL(MethodInfo("popup_hide")); } +void Popup::_validate_property(PropertyInfo &p_property) const { + if ( + p_property.name == "transient" || + p_property.name == "exclusive" || + p_property.name == "popup_window" || + p_property.name == "unfocusable") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + Rect2i Popup::_popup_adjust_rect() const { ERR_FAIL_COND_V(!is_inside_tree(), Rect2()); - Rect2i parent = get_usable_parent_rect(); + Rect2i parent_rect = get_usable_parent_rect(); - if (parent == Rect2i()) { + if (parent_rect == Rect2i()) { return Rect2i(); } Rect2i current(get_position(), get_size()); - if (current.position.x + current.size.x > parent.position.x + parent.size.x) { - current.position.x = parent.position.x + parent.size.x - current.size.x; + if (current.position.x + current.size.x > parent_rect.position.x + parent_rect.size.x) { + current.position.x = parent_rect.position.x + parent_rect.size.x - current.size.x; } - if (current.position.x < parent.position.x) { - current.position.x = parent.position.x; + if (current.position.x < parent_rect.position.x) { + current.position.x = parent_rect.position.x; } - if (current.position.y + current.size.y > parent.position.y + parent.size.y) { - current.position.y = parent.position.y + parent.size.y - current.size.y; + if (current.position.y + current.size.y > parent_rect.position.y + parent_rect.size.y) { + current.position.y = parent_rect.position.y + parent_rect.size.y - current.size.y; } - if (current.position.y < parent.position.y) { - current.position.y = parent.position.y; + if (current.position.y < parent_rect.position.y) { + current.position.y = parent_rect.position.y; } - if (current.size.y > parent.size.y) { - current.size.y = parent.size.y; + if (current.size.y > parent_rect.size.y) { + current.size.y = parent_rect.size.y; } - if (current.size.x > parent.size.x) { - current.size.x = parent.size.x; + if (current.size.x > parent_rect.size.x) { + current.size.x = parent_rect.size.x; } // Early out if max size not set. - Size2i max_size = get_max_size(); - if (max_size <= Size2()) { + Size2i popup_max_size = get_max_size(); + if (popup_max_size <= Size2()) { return current; } - if (current.size.x > max_size.x) { - current.size.x = max_size.x; + if (current.size.x > popup_max_size.x) { + current.size.x = popup_max_size.x; } - if (current.size.y > max_size.y) { - current.size.y = max_size.y; + if (current.size.y > popup_max_size.y) { + current.size.y = popup_max_size.y; } return current; diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 0d6ca25c18..57b811cadb 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -59,6 +59,7 @@ protected: virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; virtual void _parent_focused(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index d4a4efd578..ab74979777 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -58,20 +58,20 @@ Size2 PopupMenu::_get_contents_minimum_size() const { bool has_check = false; for (int i = 0; i < items.size(); i++) { - Size2 size; + Size2 item_size; Size2 icon_size = items[i].get_icon_size(); - size.height = _get_item_height(i); + item_size.height = _get_item_height(i); icon_w = MAX(icon_size.width, icon_w); - size.width += items[i].indent * theme_cache.indent; + item_size.width += items[i].indent * theme_cache.indent; if (items[i].checkable_type && !items[i].separator) { has_check = true; } - size.width += items[i].text_buf->get_size().x; - size.height += theme_cache.v_separation; + item_size.width += items[i].text_buf->get_size().x; + item_size.height += theme_cache.v_separation; if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { int accel_w = theme_cache.h_separation * 2; @@ -80,12 +80,12 @@ Size2 PopupMenu::_get_contents_minimum_size() const { } if (!items[i].submenu.is_empty()) { - size.width += theme_cache.submenu->get_width(); + item_size.width += theme_cache.submenu->get_width(); } - max_w = MAX(max_w, size.width); + max_w = MAX(max_w, item_size.width); - minsize.height += size.height; + minsize.height += item_size.height; } int item_side_padding = theme_cache.item_start_padding + theme_cache.item_end_padding; @@ -273,7 +273,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!items.is_empty()) { - if (p_event->is_action("ui_down") && p_event->is_pressed()) { + if (p_event->is_action("ui_down", true) && p_event->is_pressed()) { int search_from = mouse_over + 1; if (search_from >= items.size()) { search_from = 0; @@ -305,7 +305,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { } } } - } else if (p_event->is_action("ui_up") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) { int search_from = mouse_over - 1; if (search_from < 0) { search_from = items.size() - 1; @@ -337,7 +337,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { } } } - } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) { Node *n = get_parent(); if (n) { if (Object::cast_to<PopupMenu>(n)) { @@ -349,7 +349,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } } - } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_right", true) && p_event->is_pressed()) { if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { _activate_submenu(mouse_over, true); set_input_as_handled(); @@ -361,7 +361,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } } - } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) { if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) { if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { _activate_submenu(mouse_over, true); @@ -472,7 +472,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000)); + uint64_t max_interval = uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")); search_time_msec = now; if (diff > max_interval) { @@ -558,7 +558,7 @@ void PopupMenu::_draw_items() { check_ofs += theme_cache.h_separation; } - Point2 ofs = Point2(); + Point2 ofs; // Loop through all items and draw each. for (int i = 0; i < items.size(); i++) { @@ -1762,7 +1762,7 @@ void PopupMenu::clear() { void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) { if (!shortcut_refcount.has(p_sc)) { shortcut_refcount[p_sc] = 1; - p_sc->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + p_sc->connect("changed", callable_mp(this, &PopupMenu::_shortcut_changed)); } else { shortcut_refcount[p_sc] += 1; } @@ -1772,11 +1772,18 @@ void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) { ERR_FAIL_COND(!shortcut_refcount.has(p_sc)); shortcut_refcount[p_sc]--; if (shortcut_refcount[p_sc] == 0) { - p_sc->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); + p_sc->disconnect("changed", callable_mp(this, &PopupMenu::_shortcut_changed)); shortcut_refcount.erase(p_sc); } } +void PopupMenu::_shortcut_changed() { + for (int i = 0; i < items.size(); i++) { + items.write[i].dirty = true; + } + control->queue_redraw(); +} + // Hide on item selection determines whether or not the popup will close after item selection void PopupMenu::set_hide_on_item_selection(bool p_enabled) { hide_on_item_selection = p_enabled; diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index ad7909842e..89d3904456 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -121,6 +121,8 @@ class PopupMenu : public Popup { void _ref_shortcut(Ref<Shortcut> p_sc); void _unref_shortcut(Ref<Shortcut> p_sc); + void _shortcut_changed(); + bool allow_search = true; uint64_t search_time_msec = 0; String search_string = ""; diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 1eb412abaf..27002fad38 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -30,8 +30,8 @@ #include "range.h" -TypedArray<String> Range::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray Range::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (shared->exp_ratio && shared->min <= 0) { warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.")); @@ -80,6 +80,15 @@ void Range::Shared::emit_changed(const char *p_what) { } void Range::set_value(double p_val) { + double prev_val = shared->val; + set_value_no_signal(p_val); + + if (shared->val != prev_val) { + shared->emit_value_changed(); + } +} + +void Range::set_value_no_signal(double p_val) { if (shared->step > 0) { p_val = Math::round(p_val / shared->step) * shared->step; } @@ -101,8 +110,6 @@ void Range::set_value(double p_val) { } shared->val = p_val; - - shared->emit_value_changed(); } void Range::set_min(double p_min) { @@ -267,6 +274,7 @@ void Range::_bind_methods() { ClassDB::bind_method(D_METHOD("get_page"), &Range::get_page); ClassDB::bind_method(D_METHOD("get_as_ratio"), &Range::get_as_ratio); ClassDB::bind_method(D_METHOD("set_value", "value"), &Range::set_value); + ClassDB::bind_method(D_METHOD("set_value_no_signal", "value"), &Range::set_value_no_signal); ClassDB::bind_method(D_METHOD("set_min", "minimum"), &Range::set_min); ClassDB::bind_method(D_METHOD("set_max", "maximum"), &Range::set_max); ClassDB::bind_method(D_METHOD("set_step", "step"), &Range::set_step); diff --git a/scene/gui/range.h b/scene/gui/range.h index 87bd0d88af..f804155dec 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -72,6 +72,7 @@ protected: public: void set_value(double p_val); + void set_value_no_signal(double p_val); void set_min(double p_min); void set_max(double p_max); void set_step(double p_step); @@ -100,7 +101,7 @@ public: void share(Range *p_range); void unshare(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; Range(); ~Range(); diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index c9516ed6b9..0dece1c287 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -56,10 +56,8 @@ Variant RichTextEffect::get_bbcode() const { bool RichTextEffect::_process_effect_impl(Ref<CharFXTransform> p_cfx) { bool return_value = false; - if (GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value)) { - return return_value; - } - return false; + GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value); + return return_value; } RichTextEffect::RichTextEffect() { diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 3a238e9edd..87cc26187a 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -52,7 +52,7 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co } else if (p_item->E->next()) { return p_item->E->next()->get(); } else { - //go up until something with a next is found + // Go up until something with a next is found. while (p_item->parent && !p_item->E->next()) { p_item = p_item->parent; } @@ -72,7 +72,7 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co } else if (p_item->E->next()) { return p_item->E->next()->get(); } else { - //go up until something with a next is found + // Go up until something with a next is found. while (p_item->type != ITEM_FRAME && !p_item->E->next()) { p_item = p_item->parent; } @@ -84,8 +84,6 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co } } } - - return nullptr; } RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) const { @@ -97,7 +95,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } else if (p_item->E->prev()) { return p_item->E->prev()->get(); } else { - //go back until something with a prev is found + // Go back until something with a prev is found. while (p_item->parent && !p_item->E->prev()) { p_item = p_item->parent; } @@ -117,7 +115,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } else if (p_item->E->prev()) { return p_item->E->prev()->get(); } else { - //go back until something with a prev is found + // Go back until something with a prev is found. while (p_item->type != ITEM_FRAME && !p_item->E->prev()) { p_item = p_item->parent; } @@ -129,8 +127,6 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } } } - - return nullptr; } Rect2 RichTextLabel::_get_text_rect() { @@ -171,17 +167,17 @@ String RichTextLabel::_roman(int p_num, bool p_capitalize) const { }; String s; if (p_capitalize) { - const String M[] = { "", "M", "MM", "MMM" }; - const String C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; - const String X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; - const String I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; - s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10]; + const String roman_M[] = { "", "M", "MM", "MMM" }; + const String roman_C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; + const String roman_X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; + const String roman_I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; + s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10]; } else { - const String M[] = { "", "m", "mm", "mmm" }; - const String C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }; - const String X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" }; - const String I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" }; - s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10]; + const String roman_M[] = { "", "m", "mm", "mmm" }; + const String roman_C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }; + const String roman_X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" }; + const String roman_I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" }; + s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10]; } return s; } @@ -472,7 +468,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } // Shape current paragraph. - String text; + String txt; Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; int remaining_characters = visible_characters - l.char_offset; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { @@ -506,7 +502,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> font_size = font_size_it->font_size; } l.text_buf->add_string("\n", font, font_size); - text += "\n"; + txt += "\n"; l.char_count++; remaining_characters--; } break; @@ -536,13 +532,13 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> remaining_characters -= tx.length(); l.text_buf->add_string(tx, font, font_size, lang, (uint64_t)it); - text += tx; + txt += tx; l.char_count += tx.length(); } break; case ITEM_IMAGE: { ItemImage *img = static_cast<ItemImage *>(it); l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1); - text += String::chr(0xfffc); + txt += String::chr(0xfffc); l.char_count++; remaining_characters--; } break; @@ -697,15 +693,15 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } l.text_buf->add_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, t_char_count); - text += String::chr(0xfffc).repeat(t_char_count); + txt += String::chr(0xfffc).repeat(t_char_count); } break; default: break; } } - //Apply BiDi override. - l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, text)); + // Apply BiDi override. + l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, txt)); *r_char_offset = l.char_offset + l.char_count; @@ -965,17 +961,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o uint32_t gl = glyphs[i].index; uint16_t gl_fl = glyphs[i].flags; uint8_t gl_cn = glyphs[i].count; - bool cprev = false; + bool cprev_cluster = false; + bool cprev_conn = false; if (gl_cn == 0) { // Parts of the same cluster, always connected. - cprev = true; + cprev_cluster = true; } if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { - cprev = true; + cprev_conn = true; } } else { if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { - cprev = true; + cprev_conn = true; } } @@ -990,10 +987,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_shadow_color.a = faded_visibility; } - bool visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); + bool txt_visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; + bool cn = cprev_cluster || (cprev_conn && item_fx->connected); + if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) { ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx); @@ -1003,7 +1002,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!custom_effect.is_null()) { charfx->elapsed_time = item_custom->elapsed_time; charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->visibility = visible; + charfx->visibility = txt_visible; charfx->outline = true; charfx->font = frid; charfx->glyph_index = gl; @@ -1019,12 +1018,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color = charfx->color; frid = charfx->font; gl = charfx->glyph_index; - visible &= charfx->visibility; + txt_visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - if (!cprev) { + if (!cn) { uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); uint64_t max_rand = 2147483647; @@ -1038,7 +1037,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - if (!cprev) { + if (!cn) { double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f); item_wave->prev_off = Point2(0, 1) * value; } @@ -1046,7 +1045,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - if (!cprev) { + if (!cn) { double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); item_tornado->prev_off = Point2(torn_x, torn_y); @@ -1063,7 +1062,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a); const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a); for (int j = 0; j < glyphs[i].repeat; j++) { - if (visible) { + if (txt_visible) { bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); if (!skip && frid != RID()) { if (modulated_shadow_color.a > 0) { @@ -1088,7 +1087,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); // Draw main text. - Color selection_fg = theme_cache.font_selected_color; Color selection_bg = theme_cache.selection_color; int sel_start = -1; @@ -1181,17 +1179,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o uint32_t gl = glyphs[i].index; uint16_t gl_fl = glyphs[i].flags; uint8_t gl_cn = glyphs[i].count; - bool cprev = false; + bool cprev_cluster = false; + bool cprev_conn = false; if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected. - cprev = true; + cprev_cluster = true; } if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { - cprev = true; + cprev_conn = true; } } else { if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { - cprev = true; + cprev_conn = true; } } @@ -1205,10 +1204,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color.a = faded_visibility; } - bool visible = (font_color.a != 0); + bool txt_visible = (font_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; + bool cn = cprev_cluster || (cprev_conn && item_fx->connected); + if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) { ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx); @@ -1218,7 +1219,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!custom_effect.is_null()) { charfx->elapsed_time = item_custom->elapsed_time; charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->visibility = visible; + charfx->visibility = txt_visible; charfx->outline = false; charfx->font = frid; charfx->glyph_index = gl; @@ -1234,12 +1235,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color = charfx->color; frid = charfx->font; gl = charfx->glyph_index; - visible &= charfx->visibility; + txt_visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - if (!cprev) { + if (!cn) { uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); uint64_t max_rand = 2147483647; @@ -1253,7 +1254,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - if (!cprev) { + if (!cn) { double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); item_wave->prev_off = Point2(0, 1) * value; } @@ -1261,7 +1262,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - if (!cprev) { + if (!cn) { double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); item_tornado->prev_off = Point2(torn_x, torn_y); @@ -1274,19 +1275,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - if (selected) { - font_color = override_selected_font_color ? selection_fg : font_color; + if (selected && use_selected_font_color) { + font_color = theme_cache.font_selected_color; } // Draw glyphs. for (int j = 0; j < glyphs[i].repeat; j++) { bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); - if (visible) { + if (txt_visible) { if (!skip) { if (frid != RID()) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } } r_processed_glyphs++; @@ -1686,6 +1687,7 @@ void RichTextLabel::_update_theme_item_cache() { theme_cache.default_color = get_theme_color(SNAME("default_color")); theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + use_selected_font_color = theme_cache.font_selected_color != Color(0, 0, 0, 0); theme_cache.selection_color = get_theme_color(SNAME("selection_color")); theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); theme_cache.font_shadow_color = get_theme_color(SNAME("font_shadow_color")); @@ -2017,36 +2019,36 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (k->is_pressed()) { bool handled = false; - if (k->is_action("ui_page_up") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() - vscroll->get_page()); handled = true; } - if (k->is_action("ui_page_down") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() + vscroll->get_page()); handled = true; } - if (k->is_action("ui_up") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } - if (k->is_action("ui_down") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } - if (k->is_action("ui_home") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(0); handled = true; } - if (k->is_action("ui_end") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_max()); handled = true; } if (is_shortcut_keys_enabled()) { - if (k->is_action("ui_text_select_all")) { + if (k->is_action("ui_text_select_all", true)) { select_all(); handled = true; } - if (k->is_action("ui_copy")) { + if (k->is_action("ui_copy", true)) { selection_copy(); handled = true; } @@ -2190,24 +2192,69 @@ RichTextLabel::ItemFont *RichTextLabel::_find_font(Item *p_item) { ItemFont *fi = static_cast<ItemFont *>(fontitem); switch (fi->def_font) { case NORMAL_FONT: { - fi->font = theme_cache.normal_font; - fi->font_size = theme_cache.normal_font_size; + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.normal_font); + } + } else { + fi->font = theme_cache.normal_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.normal_font_size; + } } break; case BOLD_FONT: { - fi->font = theme_cache.bold_font; - fi->font_size = theme_cache.bold_font_size; + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.bold_font); + } + } else { + fi->font = theme_cache.bold_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.bold_font_size; + } } break; case ITALICS_FONT: { - fi->font = theme_cache.italics_font; - fi->font_size = theme_cache.italics_font_size; + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.italics_font); + } + } else { + fi->font = theme_cache.italics_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.italics_font_size; + } } break; case BOLD_ITALICS_FONT: { - fi->font = theme_cache.bold_italics_font; - fi->font_size = theme_cache.bold_italics_font_size; + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.bold_italics_font); + } + } else { + fi->font = theme_cache.bold_italics_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.bold_italics_font_size; + } } break; case MONO_FONT: { - fi->font = theme_cache.mono_font; - fi->font_size = theme_cache.mono_font_size; + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.mono_font); + } + } else { + fi->font = theme_cache.mono_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.mono_font_size; + } } break; default: { } break; @@ -2909,7 +2956,7 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub memdelete(p_item); } -void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment) { +void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -2922,7 +2969,15 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, ERR_FAIL_COND(p_image->get_height() == 0); ItemImage *item = memnew(ItemImage); - item->image = p_image; + if (p_region.has_area()) { + Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); + atlas_tex->set_atlas(p_image); + atlas_tex->set_region(p_region); + item->image = atlas_tex; + } else { + item->image = p_image; + } + item->color = p_color; item->inline_align = p_alignment; @@ -2934,17 +2989,30 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, item->size.height = p_height; } else { // calculate height to keep aspect ratio - item->size.height = p_image->get_height() * p_width / p_image->get_width(); + if (p_region.has_area()) { + item->size.height = p_region.get_size().height * p_width / p_region.get_size().width; + } else { + item->size.height = p_image->get_height() * p_width / p_image->get_width(); + } } } else { if (p_height > 0) { // custom height item->size.height = p_height; // calculate width to keep aspect ratio - item->size.width = p_image->get_width() * p_height / p_image->get_height(); + if (p_region.has_area()) { + item->size.width = p_region.get_size().width * p_height / p_region.get_size().height; + } else { + item->size.width = p_image->get_width() * p_height / p_image->get_height(); + } } else { - // keep original width and height - item->size = p_image->get_size(); + if (p_region.has_area()) { + // if the image has a region, keep the region size + item->size = p_region.get_size(); + } else { + // keep original width and height + item->size = p_image->get_size(); + } } } @@ -3028,14 +3096,30 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font _add_item(item, false); } -void RichTextLabel::_push_def_font(DefaultFont p_font) { +void RichTextLabel::_push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size) { _stop_thread(); MutexLock data_lock(data_mutex); ERR_FAIL_COND(current->type == ITEM_TABLE); ItemFont *item = memnew(ItemFont); - item->def_font = p_font; + item->def_font = p_def_font; + item->variation = true; + item->font = p_font; + item->font_size = p_size; + item->def_size = (p_size <= 0); + _add_item(item, true); +} + +void RichTextLabel::_push_def_font(DefaultFont p_def_font) { + _stop_thread(); + MutexLock data_lock(data_mutex); + + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemFont *item = memnew(ItemFont); + + item->def_font = p_def_font; + item->def_size = true; _add_item(item, true); } @@ -3236,33 +3320,36 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) { _add_item(item, true); } -void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) { +void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f, bool p_connected = true) { _stop_thread(); MutexLock data_lock(data_mutex); ItemShake *item = memnew(ItemShake); item->strength = p_strength; item->rate = p_rate; + item->connected = p_connected; _add_item(item, true); } -void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f) { +void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f, bool p_connected = true) { _stop_thread(); MutexLock data_lock(data_mutex); ItemWave *item = memnew(ItemWave); item->frequency = p_frequency; item->amplitude = p_amplitude; + item->connected = p_connected; _add_item(item, true); } -void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f) { +void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f, bool p_connected = true) { _stop_thread(); MutexLock data_lock(data_mutex); ItemTornado *item = memnew(ItemTornado); item->frequency = p_frequency; item->radius = p_radius; + item->connected = p_connected; _add_item(item, true); } @@ -3477,14 +3564,6 @@ bool RichTextLabel::is_hint_underlined() const { return underline_hint; } -void RichTextLabel::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; -} - -bool RichTextLabel::is_overriding_selected_font_color() const { - return override_selected_font_color; -} - void RichTextLabel::set_offset(int p_pixel) { vscroll->set_value(p_pixel); } @@ -3543,21 +3622,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { brk_pos = p_bbcode.length(); } - String text = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; + String txt = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; // Trim the first newline character, it may be added later as needed. if (after_list_close_tag || after_list_open_tag) { - text = text.trim_prefix("\n"); + txt = txt.trim_prefix("\n"); } if (brk_pos == p_bbcode.length()) { // For tags that are not properly closed. - if (text.is_empty() && after_list_open_tag) { - text = "\n"; + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } break; //nothing else to add } @@ -3566,8 +3645,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (brk_end == -1) { //no close, add the rest - text += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); - add_text(text); + txt += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); + add_text(txt); break; } @@ -3613,36 +3692,36 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (!tag_ok) { - text += "[" + tag; - add_text(text); + txt += "[" + tag; + add_text(txt); after_list_open_tag = false; after_list_close_tag = false; pos = brk_end; continue; } - if (text.is_empty() && after_list_open_tag) { - text = "\n"; // Make empty list have at least one item. + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; // Make empty list have at least one item. } after_list_open_tag = false; if (tag == "/ol" || tag == "/ul") { - if (!text.is_empty()) { + if (!txt.is_empty()) { // Make sure text ends with a newline character, that is, the last item // will wrap at the end of block. - if (!text.ends_with("\n")) { - text += "\n"; + if (!txt.ends_with("\n")) { + txt += "\n"; } } else if (!after_list_close_tag) { - text = "\n"; // Make the innermost list item wrap at the end of lists. + txt = "\n"; // Make the innermost list item wrap at the end of lists. } after_list_close_tag = true; } else { after_list_close_tag = false; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } tag_stack.pop_front(); @@ -3654,15 +3733,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (tag == "ol" || tag.begins_with("ol ") || tag == "ul" || tag.begins_with("ul ")) { - if (text.is_empty() && after_list_open_tag) { - text = "\n"; // Make each list have at least one item at the beginning. + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; // Make each list have at least one item at the beginning. } after_list_open_tag = true; } else { after_list_open_tag = false; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } after_list_close_tag = false; @@ -3904,7 +3983,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang; - TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); if (subtag_a.size() == 2) { @@ -3930,24 +4009,24 @@ void RichTextLabel::append_text(const String &p_bbcode) { lang = subtag_a[1]; } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") { if (subtag_a[1] == "d" || subtag_a[1] == "default") { - st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") { - st_parser = TextServer::STRUCTURED_TEXT_URI; + st_parser_type = TextServer::STRUCTURED_TEXT_URI; } else if (subtag_a[1] == "f" || subtag_a[1] == "file") { - st_parser = TextServer::STRUCTURED_TEXT_FILE; + st_parser_type = TextServer::STRUCTURED_TEXT_FILE; } else if (subtag_a[1] == "e" || subtag_a[1] == "email") { - st_parser = TextServer::STRUCTURED_TEXT_EMAIL; + st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; } else if (subtag_a[1] == "l" || subtag_a[1] == "list") { - st_parser = TextServer::STRUCTURED_TEXT_LIST; + st_parser_type = TextServer::STRUCTURED_TEXT_LIST; } else if (subtag_a[1] == "n" || subtag_a[1] == "none") { - st_parser = TextServer::STRUCTURED_TEXT_NONE; + st_parser_type = TextServer::STRUCTURED_TEXT_NONE; } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") { - st_parser = TextServer::STRUCTURED_TEXT_CUSTOM; + st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; } } } } - push_paragraph(alignment, dir, lang, st_parser); + push_paragraph(alignment, dir, lang, st_parser_type); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { @@ -3978,7 +4057,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { Color color = theme_cache.default_color; Color outline_color = theme_cache.font_outline_color; int outline_size = theme_cache.outline_size; - Rect2 dropcap_margins = Rect2(); + Rect2 dropcap_margins; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); @@ -4013,9 +4092,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { end = p_bbcode.length(); } - String txt = p_bbcode.substr(brk_end + 1, end - brk_end - 1); + String dc_txt = p_bbcode.substr(brk_end + 1, end - brk_end - 1); - push_dropcap(txt, f, fs, dropcap_margins, color, outline_size, outline_color); + push_dropcap(dc_txt, f, fs, dropcap_margins, color, outline_size, outline_color); pos = end; tag_stack.push_front(bbcode_name); @@ -4060,6 +4139,18 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D"); if (texture.is_valid()) { + Rect2 region; + OptionMap::Iterator region_option = bbcode_options.find("region"); + if (region_option) { + Vector<String> region_values = region_option->value.split(",", false); + if (region_values.size() == 4) { + region.position.x = region_values[0].to_float(); + region.position.y = region_values[1].to_float(); + region.size.x = region_values[2].to_float(); + region.size.y = region_values[3].to_float(); + } + } + Color color = Color(1.0, 1.0, 1.0); OptionMap::Iterator color_option = bbcode_options.find("color"); if (color_option) { @@ -4088,7 +4179,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } - add_image(texture, width, height, color, (InlineAlignment)alignment); + add_image(texture, width, height, color, (InlineAlignment)alignment, region); } pos = end; @@ -4113,24 +4204,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("font_size"); - } else if (tag.begins_with("opentype_features=")) { - String fnt_ftr = tag.substr(18, tag.length()); + } else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) { + int value_pos = tag.find("="); + String fnt_ftr = tag.substr(value_pos + 1); Vector<String> subtag = fnt_ftr.split(","); if (subtag.size() > 0) { Ref<Font> font = theme_cache.normal_font; - int font_size = 0; + DefaultFont def_font = NORMAL_FONT; + ItemFont *font_it = _find_font(current); if (font_it) { if (font_it->font.is_valid()) { font = font_it->font; - } - if (font_it->font_size > 0) { - font_size = font_it->font_size; + def_font = font_it->def_font; } } - Ref<FontVariation> fc; - fc.instantiate(); - fc->set_base_font(font); Dictionary features; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); @@ -4140,11 +4228,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { features[TS->name_to_tag(subtag_a[0])] = 1; } } + + Ref<FontVariation> fc; + fc.instantiate(); + + fc->set_base_font(font); fc->set_opentype_features(features); - push_font(fc, font_size); + + if (def_font != CUSTOM_FONT) { + _push_def_font_var(def_font, fc); + } else { + push_font(fc); + } } pos = brk_end + 1; - tag_stack.push_front("opentype_features"); + tag_stack.push_front(tag.substr(0, value_pos)); } else if (tag.begins_with("font=")) { String fnt = tag.substr(5, tag.length()); @@ -4160,9 +4258,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("font ")) { Vector<String> subtag = tag.substr(2, tag.length()).split(" "); + Ref<Font> font = theme_cache.normal_font; + DefaultFont def_font = NORMAL_FONT; + + ItemFont *font_it = _find_font(current); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + def_font = font_it->def_font; + } + } + Ref<FontVariation> fc; fc.instantiate(); - int fnt_size = 0; + + int fnt_size = -1; for (int i = 1; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("=", true, 2); if (subtag_a.size() == 2) { @@ -4170,7 +4280,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { String fnt = subtag_a[1]; Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); if (font_data.is_valid()) { - fc->set_base_font(font_data); + font = font_data; + def_font = CUSTOM_FONT; } } else if (subtag_a[0] == "size" || subtag_a[0] == "s") { fnt_size = subtag_a[1].to_int(); @@ -4224,7 +4335,14 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - push_font(fc, fnt_size); + fc->set_base_font(font); + + if (def_font != CUSTOM_FONT) { + _push_def_font_var(def_font, fc, fnt_size); + } else { + push_font(fc, fnt_size); + } + pos = brk_end + 1; tag_stack.push_front("font"); @@ -4265,7 +4383,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { rate = rate_option->value.to_float(); } - push_shake(strength, rate); + bool connected = true; + OptionMap::Iterator connected_option = bbcode_options.find("connected"); + if (connected_option) { + connected = connected_option->value.to_int(); + } + + push_shake(strength, rate, connected); pos = brk_end + 1; tag_stack.push_front("shake"); set_process_internal(true); @@ -4282,7 +4406,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { period = period_option->value.to_float(); } - push_wave(period, amplitude); + bool connected = true; + OptionMap::Iterator connected_option = bbcode_options.find("connected"); + if (connected_option) { + connected = connected_option->value.to_int(); + } + + push_wave(period, amplitude, connected); pos = brk_end + 1; tag_stack.push_front("wave"); set_process_internal(true); @@ -4299,7 +4429,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { frequency = frequency_option->value.to_float(); } - push_tornado(frequency, radius); + bool connected = true; + OptionMap::Iterator connected_option = bbcode_options.find("connected"); + if (connected_option) { + connected = connected_option->value.to_int(); + } + + push_tornado(frequency, radius, connected); pos = brk_end + 1; tag_stack.push_front("tornado"); set_process_internal(true); @@ -4557,19 +4693,19 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p Line &l = p_frame->lines[p_line]; - String text; + String txt; Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { switch (it->type) { case ITEM_NEWLINE: { - text += "\n"; + txt += "\n"; } break; case ITEM_TEXT: { ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } break; case ITEM_IMAGE: { - text += " "; + txt += " "; } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); @@ -4585,9 +4721,9 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p int sp = -1; if (p_reverse_search) { - sp = text.rfindn(p_string, p_char_idx); + sp = txt.rfindn(p_string, p_char_idx); } else { - sp = text.findn(p_string, p_char_idx); + sp = txt.findn(p_string, p_char_idx); } if (sp != -1) { @@ -4676,7 +4812,10 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p queue_redraw(); return true; } - p_search_previous ? current_line-- : current_line++; + + if (current_line != ending_line) { + p_search_previous ? current_line-- : current_line++; + } } if (p_from_selection && selection.active) { @@ -4688,10 +4827,10 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p } String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const { - String text; + String txt; - ERR_FAIL_COND_V(p_frame == nullptr, text); - ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), text); + ERR_FAIL_COND_V(p_frame == nullptr, txt); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), txt); Line &l = p_frame->lines[p_line]; @@ -4711,7 +4850,7 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. ItemFrame *frame = static_cast<ItemFrame *>(E); for (int i = 0; i < (int)frame->lines.size(); i++) { - text += _get_line_text(frame, i, p_selection); + txt += _get_line_text(frame, i, p_selection); } } } @@ -4723,23 +4862,23 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p } if (it->type == ITEM_DROPCAP) { const ItemDropcap *dc = static_cast<ItemDropcap *>(it); - text += dc->text; + txt += dc->text; } else if (it->type == ITEM_TEXT) { const ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } else if (it->type == ITEM_NEWLINE) { - text += "\n"; + txt += "\n"; } else if (it->type == ITEM_IMAGE) { - text += " "; + txt += " "; } } if ((l.from != nullptr) && (p_frame == p_selection.to_frame) && (p_selection.to_item != nullptr) && (p_selection.to_item->index >= l.from->index) && (p_selection.to_item->index < end_idx)) { - text = text.substr(0, p_selection.to_char); + txt = txt.substr(0, p_selection.to_char); } if ((l.from != nullptr) && (p_frame == p_selection.from_frame) && (p_selection.from_item != nullptr) && (p_selection.from_item->index >= l.from->index) && (p_selection.from_item->index < end_idx)) { - text = text.substr(p_selection.from_char, -1); + txt = txt.substr(p_selection.from_char, -1); } - return text; + return txt; } void RichTextLabel::set_context_menu_enabled(bool p_enabled) { @@ -4773,12 +4912,12 @@ String RichTextLabel::get_selected_text() const { return ""; } - String text; + String txt; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { - text += _get_line_text(main, i, selection); + txt += _get_line_text(main, i, selection); } - return text; + return txt; } void RichTextLabel::deselect() { @@ -4787,10 +4926,10 @@ void RichTextLabel::deselect() { } void RichTextLabel::selection_copy() { - String text = get_selected_text(); + String txt = get_selected_text(); - if (!text.is_empty()) { - DisplayServer::get_singleton()->clipboard_set(text); + if (!txt.is_empty()) { + DisplayServer::get_singleton()->clipboard_set(txt); } } @@ -4904,25 +5043,25 @@ bool RichTextLabel::is_using_bbcode() const { } String RichTextLabel::get_parsed_text() const { - String text = ""; + String txt = ""; Item *it = main; while (it) { if (it->type == ITEM_DROPCAP) { ItemDropcap *dc = static_cast<ItemDropcap *>(it); - text += dc->text; + txt += dc->text; } else if (it->type == ITEM_TEXT) { ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } else if (it->type == ITEM_NEWLINE) { - text += "\n"; + txt += "\n"; } else if (it->type == ITEM_IMAGE) { - text += " "; + txt += " "; } else if (it->type == ITEM_INDENT || it->type == ITEM_LIST) { - text += "\t"; + txt += "\t"; } it = _get_next_item(it, true); } - return text; + return txt; } void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) { @@ -5095,7 +5234,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); - ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER)); + ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2(0, 0, 0, 0))); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font); @@ -5147,9 +5286,6 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hint_underline", "enable"), &RichTextLabel::set_hint_underline); ClassDB::bind_method(D_METHOD("is_hint_underlined"), &RichTextLabel::is_hint_underlined); - ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &RichTextLabel::set_override_selected_font_color); - ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &RichTextLabel::is_overriding_selected_font_color); - ClassDB::bind_method(D_METHOD("set_scroll_active", "active"), &RichTextLabel::set_scroll_active); ClassDB::bind_method(D_METHOD("is_scroll_active"), &RichTextLabel::is_scroll_active); @@ -5259,7 +5395,6 @@ void RichTextLabel::_bind_methods() { ADD_GROUP("Text Selection", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_GROUP("Displayed Text", ""); @@ -5508,6 +5643,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item Vector2i fbg_index = Vector2i(end, start); Color last_color = Color(0, 0, 0, 0); bool draw_box = false; + int hpad = get_theme_constant(SNAME("text_highlight_h_padding")); + int vpad = get_theme_constant(SNAME("text_highlight_v_padding")); // Draw a box based on color tags associated with glyphs for (int i = start; i < end; i++) { Item *it = _get_item_at_pos(it_from, it_to, i); @@ -5537,8 +5674,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item if (draw_box) { Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y); for (int j = 0; j < sel.size(); j++) { - Vector2 rect_off = line_off + Vector2(sel[j].x, -TS->shaped_text_get_ascent(p_rid)); - Vector2 rect_size = Vector2(sel[j].y - sel[j].x, TS->shaped_text_get_size(p_rid).y); + Vector2 rect_off = line_off + Vector2(sel[j].x - hpad, -TS->shaped_text_get_ascent(p_rid) - vpad); + Vector2 rect_size = Vector2(sel[j].y - sel[j].x + 2 * hpad, TS->shaped_text_get_size(p_rid).y + 2 * vpad); RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); } fbg_index = Vector2i(end, start); @@ -5556,8 +5693,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item if (last_color.a > 0) { Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end); for (int i = 0; i < sel.size(); i++) { - Vector2 rect_off = line_off + Vector2(sel[i].x, -TS->shaped_text_get_ascent(p_rid)); - Vector2 rect_size = Vector2(sel[i].y - sel[i].x, TS->shaped_text_get_size(p_rid).y); + Vector2 rect_off = line_off + Vector2(sel[i].x - hpad, -TS->shaped_text_get_ascent(p_rid) - vpad); + Vector2 rect_size = Vector2(sel[i].y - sel[i].x + 2 * hpad, TS->shaped_text_get_size(p_rid).y + 2 * vpad); RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); } } @@ -5579,11 +5716,11 @@ Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_id } Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) { - Dictionary d = Dictionary(); + Dictionary d; for (int i = 0; i < p_expressions.size(); i++) { String expression = p_expressions[i]; - Array a = Array(); + Array a; Vector<String> parts = expression.split("=", true); String key = parts[0]; if (parts.size() != 2) { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 2a5ec4b5d5..d30baaa8d3 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -189,6 +189,8 @@ private: struct ItemFont : public Item { DefaultFont def_font = CUSTOM_FONT; Ref<Font> font; + bool variation = false; + bool def_size = false; int font_size = 0; ItemFont() { type = ITEM_FONT; } }; @@ -282,6 +284,7 @@ private: struct ItemFX : public Item { double elapsed_time = 0.f; + bool connected = true; }; struct ItemShake : public ItemFX { @@ -298,14 +301,14 @@ private: _current_rng = Math::rand(); } - uint64_t offset_random(int index) { - return (_current_rng >> (index % 64)) | - (_current_rng << (64 - (index % 64))); + uint64_t offset_random(int p_index) { + return (_current_rng >> (p_index % 64)) | + (_current_rng << (64 - (p_index % 64))); } - uint64_t offset_previous_random(int index) { - return (_previous_rng >> (index % 64)) | - (_previous_rng << (64 - (index % 64))); + uint64_t offset_previous_random(int p_index) { + return (_previous_rng >> (p_index % 64)) | + (_previous_rng << (64 - (p_index % 64))); } }; @@ -393,7 +396,7 @@ private: int tab_size = 4; bool underline_meta = true; bool underline_hint = true; - bool override_selected_font_color = false; + bool use_selected_font_color = false; HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; @@ -566,11 +569,12 @@ private: public: String get_parsed_text() const; void add_text(const String &p_text); - void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER); + void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(0, 0, 0, 0)); void add_newline(); bool remove_line(const int p_line); void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0)); - void _push_def_font(DefaultFont p_font); + void _push_def_font(DefaultFont p_def_font); + void _push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size = -1); void push_font(const Ref<Font> &p_font, int p_size = 0); void push_font_size(int p_font_size); void push_outline_size(int p_font_size); @@ -590,9 +594,9 @@ public: void push_hint(const String &p_string); void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP); void push_fade(int p_start_index, int p_length); - void push_shake(int p_strength, float p_rate); - void push_wave(float p_frequency, float p_amplitude); - void push_tornado(float p_frequency, float p_radius); + void push_shake(int p_strength, float p_rate, bool p_connected); + void push_wave(float p_frequency, float p_amplitude, bool p_connected); + void push_tornado(float p_frequency, float p_radius, bool p_connected); void push_rainbow(float p_saturation, float p_value, float p_frequency); void push_bgcolor(const Color &p_color); void push_fgcolor(const Color &p_color); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 6c05b171e3..6899178885 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -183,35 +183,35 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } if (p_event->is_pressed()) { - if (p_event->is_action("ui_left")) { + if (p_event->is_action("ui_left", true)) { if (orientation != HORIZONTAL) { return; } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_right")) { + } else if (p_event->is_action("ui_right", true)) { if (orientation != HORIZONTAL) { return; } set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_up")) { + } else if (p_event->is_action("ui_up", true)) { if (orientation != VERTICAL) { return; } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_down")) { + } else if (p_event->is_action("ui_down", true)) { if (orientation != VERTICAL) { return; } set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_home")) { + } else if (p_event->is_action("ui_home", true)) { set_value(get_min()); - } else if (p_event->is_action("ui_end")) { + } else if (p_event->is_action("ui_end", true)) { set_value(get_max()); } } @@ -338,7 +338,7 @@ void ScrollBar::_notification(int p_what) { if (scrolling) { if (get_value() != target_scroll) { double target = target_scroll - get_value(); - double dist = sqrt(target * target); + double dist = abs(target); double vel = ((target / dist) * 500) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 13ca62d7ff..d62acd52af 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -72,7 +72,7 @@ class ScrollBar : public Range { NodePath drag_node_path; bool drag_node_enabled = true; - Vector2 drag_node_speed = Vector2(); + Vector2 drag_node_speed; Vector2 drag_node_accum; Vector2 drag_node_from; Vector2 last_drag_node_accum; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index c12ac115b7..531226f938 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -107,45 +107,65 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { double prev_v_scroll = v_scroll->get_value(); double prev_h_scroll = h_scroll->get_value(); + bool h_scroll_enabled = horizontal_scroll_mode != SCROLL_MODE_DISABLED; + bool v_scroll_enabled = vertical_scroll_mode != SCROLL_MODE_DISABLED; Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { - // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { - h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor()); - } else if (v_scroll->is_visible_in_tree()) { - v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8 * mb->get_factor()); + if (mb->is_pressed()) { + bool scroll_value_modified = false; + + bool v_scroll_hidden = !v_scroll->is_visible() && vertical_scroll_mode != SCROLL_MODE_SHOW_NEVER; + if (mb->get_button_index() == MouseButton::WHEEL_UP) { + // By default, the vertical orientation takes precedence. This is an exception. + if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { + h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - - if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { - // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor()); - } else if (v_scroll->is_visible()) { - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8 * mb->get_factor()); + if (mb->get_button_index() == MouseButton::WHEEL_DOWN) { + if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - if (mb->get_button_index() == MouseButton::WHEEL_LEFT && mb->is_pressed()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8); + bool h_scroll_hidden = !h_scroll->is_visible() && horizontal_scroll_mode != SCROLL_MODE_SHOW_NEVER; + if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { + // By default, the horizontal orientation takes precedence. This is an exception. + if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { + v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - - if (mb->get_button_index() == MouseButton::WHEEL_RIGHT && mb->is_pressed()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8); + if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { + if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { - accept_event(); //accept event if scroll changed + if (scroll_value_modified && (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll)) { + accept_event(); // Accept event if scroll changed. + return; + } } - if (!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()))) { + bool screen_is_touchscreen = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + if (!screen_is_touchscreen) { return; } @@ -161,15 +181,13 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { drag_speed = Vector2(); drag_accum = Vector2(); last_drag_accum = Vector2(); - drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value()); - drag_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_from = Vector2(prev_h_scroll, prev_v_scroll); + drag_touching = true; drag_touching_deaccel = false; beyond_deadzone = false; time_since_motion = 0; - if (drag_touching) { - set_physics_process_internal(true); - time_since_motion = 0; - } + set_physics_process_internal(true); + time_since_motion = 0; } else { if (drag_touching) { @@ -180,6 +198,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } } + return; } Ref<InputEventMouseMotion> mm = p_gui_input; @@ -189,22 +208,22 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Vector2 motion = mm->get_relative(); drag_accum -= motion; - if (beyond_deadzone || (horizontal_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.x) > deadzone) || (vertical_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.y) > deadzone)) { + if (beyond_deadzone || (h_scroll_enabled && Math::abs(drag_accum.x) > deadzone) || (v_scroll_enabled && Math::abs(drag_accum.y) > deadzone)) { if (!beyond_deadzone) { propagate_notification(NOTIFICATION_SCROLL_BEGIN); emit_signal(SNAME("scroll_started")); beyond_deadzone = true; - // resetting drag_accum here ensures smooth scrolling after reaching deadzone + // Resetting drag_accum here ensures smooth scrolling after reaching deadzone. drag_accum = -motion; } Vector2 diff = drag_from + drag_accum; - if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { + if (h_scroll_enabled) { h_scroll->set_value(diff.x); } else { drag_accum.x = 0; } - if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { + if (v_scroll_enabled) { v_scroll->set_value(diff.y); } else { drag_accum.y = 0; @@ -212,20 +231,26 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { time_since_motion = 0; } } + + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { + accept_event(); // Accept event if scroll changed. + } + return; } Ref<InputEventPanGesture> pan_gesture = p_gui_input; if (pan_gesture.is_valid()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8); + if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8); } - if (v_scroll->is_visible_in_tree()) { - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8); + if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8); } - } - if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { - accept_event(); //accept event if scroll changed + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { + accept_event(); // Accept event if scroll changed. + } + return; } } @@ -501,8 +526,8 @@ void ScrollContainer::set_follow_focus(bool p_follow) { follow_focus = p_follow; } -TypedArray<String> ScrollContainer::get_configuration_warnings() const { - TypedArray<String> warnings = Container::get_configuration_warnings(); +PackedStringArray ScrollContainer::get_configuration_warnings() const { + PackedStringArray warnings = Container::get_configuration_warnings(); int found = 0; diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index f4899846f4..0079358ef7 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -114,7 +114,7 @@ public: VScrollBar *get_v_scroll_bar(); void ensure_control_visible(Control *p_control); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; ScrollContainer(); }; diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index ff3adfb9ac..6cbacf0dd3 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -138,10 +138,10 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); accept_event(); - } else if (p_event->is_action("ui_home") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_home", true) && p_event->is_pressed()) { set_value(get_min()); accept_event(); - } else if (p_event->is_action("ui_end") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_end", true) && p_event->is_pressed()) { set_value(get_max()); accept_event(); } @@ -227,7 +227,7 @@ void Slider::_notification(int p_what) { tick->draw(ci, Point2i((size.width - widget_width) / 2, ofs)); } } - grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2, size.height - ratio * areasize - grabber->get_size().height)); + grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2 + get_theme_constant(SNAME("grabber_offset")), size.height - ratio * areasize - grabber->get_size().height)); } else { int widget_height = style->get_minimum_size().height + style->get_center_size().height; double areasize = size.width - grabber->get_size().width; @@ -245,7 +245,7 @@ void Slider::_notification(int p_what) { tick->draw(ci, Point2i(ofs, (size.height - widget_height) / 2)); } } - grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2)); + grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2 + get_theme_constant(SNAME("grabber_offset")))); } } break; } diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index fe14049d93..c4000120c8 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -168,6 +168,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { range_click_timer->stop(); _release_mouse(); drag.allowed = false; + line_edit->clear_pending_select_all_on_focus(); } Ref<InputEventMouseMotion> mm = p_event; @@ -190,6 +191,11 @@ void SpinBox::_line_edit_focus_enter() { int col = line_edit->get_caret_column(); _value_changed(0); // Update the LineEdit's text. line_edit->set_caret_column(col); + + // LineEdit text might change and it clears any selection. Have to re-select here. + if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + line_edit->select_all(); + } } void SpinBox::_line_edit_focus_exit() { @@ -308,6 +314,14 @@ bool SpinBox::get_update_on_text_changed() const { return update_on_text_changed; } +void SpinBox::set_select_all_on_focus(bool p_enabled) { + line_edit->set_select_all_on_focus(p_enabled); +} + +bool SpinBox::is_select_all_on_focus() const { + return line_edit->is_select_all_on_focus(); +} + void SpinBox::set_editable(bool p_enabled) { line_edit->set_editable(p_enabled); } @@ -341,6 +355,8 @@ void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("is_editable"), &SpinBox::is_editable); ClassDB::bind_method(D_METHOD("set_update_on_text_changed", "enabled"), &SpinBox::set_update_on_text_changed); ClassDB::bind_method(D_METHOD("get_update_on_text_changed"), &SpinBox::get_update_on_text_changed); + ClassDB::bind_method(D_METHOD("set_select_all_on_focus", "enabled"), &SpinBox::set_select_all_on_focus); + ClassDB::bind_method(D_METHOD("is_select_all_on_focus"), &SpinBox::is_select_all_on_focus); ClassDB::bind_method(D_METHOD("apply"), &SpinBox::apply); ClassDB::bind_method(D_METHOD("get_line_edit"), &SpinBox::get_line_edit); @@ -350,6 +366,7 @@ void SpinBox::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "prefix"), "set_prefix", "get_prefix"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_arrow_step"), "set_custom_arrow_step", "get_custom_arrow_step"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus"); } SpinBox::SpinBox() { diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index c2f2ac3f5a..00f5a95699 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -101,6 +101,9 @@ public: void set_update_on_text_changed(bool p_enabled); bool get_update_on_text_changed() const; + void set_select_all_on_focus(bool p_enabled); + bool is_select_all_on_focus() const; + void apply(); void set_custom_arrow_step(const double p_custom_arrow_step); double get_custom_arrow_step() const; diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 88e68ec763..3ad84cbc6d 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -227,8 +227,8 @@ void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) { } } -TypedArray<String> SubViewportContainer::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray SubViewportContainer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); bool has_viewport = false; for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 5b488fb79e..63a58b5f07 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -58,7 +58,7 @@ public: virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; SubViewportContainer(); }; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index ab4808d312..9105267ad3 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -345,7 +345,7 @@ Vector<Control *> TabContainer::_get_tab_controls() const { Vector<Control *> controls; for (int i = 0; i < get_child_count(); i++) { Control *control = Object::cast_to<Control>(get_child(i)); - if (!control || control->is_set_as_top_level() || control == tab_bar || control == child_removing) { + if (!control || control->is_set_as_top_level() || control == tab_bar || children_removing.has(control)) { continue; } @@ -584,10 +584,10 @@ void TabContainer::remove_child_notify(Node *p_child) { int idx = get_tab_idx_from_control(c); - // Before this, the tab control has not changed; after this, the tab control has changed. - child_removing = p_child; + // As the child hasn't been removed yet, keep track of it so when the "tab_changed" signal is fired it can be ignored. + children_removing.push_back(c); tab_bar->remove_tab(idx); - child_removing = nullptr; + children_removing.erase(c); _update_margins(); if (get_tab_count() == 0) { diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index b552aa459b..60961e02d3 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -46,7 +46,7 @@ class TabContainer : public Container { bool drag_to_rearrange_enabled = false; bool use_hidden_tabs_for_min_size = false; bool theme_changing = false; - Node *child_removing = nullptr; + Vector<Control *> children_removing; struct ThemeCache { int side_margin = 0; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 318447ecd8..56f7281721 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -151,7 +151,7 @@ void TextEdit::Text::_calculate_line_height() { } void TextEdit::Text::_calculate_max_line_width() { - int width = 0; + int line_width = 0; for (const Line &l : text) { if (l.hidden) { continue; @@ -159,12 +159,12 @@ void TextEdit::Text::_calculate_max_line_width() { // Found another line with the same width...nothing to update. if (l.width == max_width) { - width = max_width; + line_width = max_width; break; } - width = MAX(width, l.width); + line_width = MAX(line_width, l.width); } - max_width = width; + max_width = line_width; } void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { @@ -233,14 +233,14 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan // Update width. const int old_width = text.write[p_line].width; - int width = get_line_width(p_line); - text.write[p_line].width = width; + int line_width = get_line_width(p_line); + text.write[p_line].width = line_width; // If this line has shrunk, this may no longer the the longest line. - if (old_width == max_width && width < max_width) { + if (old_width == max_width && line_width < max_width) { _calculate_max_line_width(); } else if (!is_hidden(p_line)) { - max_width = MAX(width, max_width); + max_width = MAX(line_width, max_width); } } @@ -464,7 +464,7 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (scrolling && get_v_scroll() != target_v_scroll) { double target_y = target_v_scroll - get_v_scroll(); - double dist = sqrt(target_y * target_y); + double dist = abs(target_y); // To ensure minimap is responsive override the speed setting. double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time(); @@ -536,145 +536,145 @@ void TextEdit::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), background_color); } - int brace_open_match_line = -1; - int brace_open_match_column = -1; - bool brace_open_matching = false; - bool brace_open_mismatch = false; - int brace_close_match_line = -1; - int brace_close_match_column = -1; - bool brace_close_matching = false; - bool brace_close_mismatch = false; - - if (highlight_matching_braces_enabled && caret.line >= 0 && caret.line < text.size() && caret.column >= 0) { - if (caret.column < text[caret.line].length()) { - // Check for open. - char32_t c = text[caret.line][caret.column]; - char32_t closec = 0; - - if (c == '[') { - closec = ']'; - } else if (c == '{') { - closec = '}'; - } else if (c == '(') { - closec = ')'; + Vector<BraceMatchingData> brace_matching; + if (highlight_matching_braces_enabled) { + brace_matching.resize(carets.size()); + + for (int caret = 0; caret < carets.size(); caret++) { + if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) { + continue; } - if (closec != 0) { - int stack = 1; - - for (int i = caret.line; i < text.size(); i++) { - int from = i == caret.line ? caret.column + 1 : 0; - for (int j = from; j < text[i].length(); j++) { - char32_t cc = text[i][j]; - // Ignore any brackets inside a string. - if (cc == '"' || cc == '\'') { - char32_t quotation = cc; - do { - j++; - if (!(j < text[i].length())) { - break; - } - cc = text[i][j]; - // Skip over escaped quotation marks inside strings. - if (cc == '\\') { - bool escaped = true; - while (j + 1 < text[i].length() && text[i][j + 1] == '\\') { - escaped = !escaped; - j++; + if (get_caret_column(caret) < text[get_caret_line(caret)].length()) { + // Check for open. + char32_t c = text[get_caret_line(caret)][get_caret_column(caret)]; + char32_t closec = 0; + + if (c == '[') { + closec = ']'; + } else if (c == '{') { + closec = '}'; + } else if (c == '(') { + closec = ')'; + } + + if (closec != 0) { + int stack = 1; + + for (int i = get_caret_line(caret); i < text.size(); i++) { + int from = i == get_caret_line(caret) ? get_caret_column(caret) + 1 : 0; + for (int j = from; j < text[i].length(); j++) { + char32_t cc = text[i][j]; + // Ignore any brackets inside a string. + if (cc == '"' || cc == '\'') { + char32_t quotation = cc; + do { + j++; + if (!(j < text[i].length())) { + break; } - if (escaped) { - j++; - continue; + cc = text[i][j]; + // Skip over escaped quotation marks inside strings. + if (cc == '\\') { + bool escaped = true; + while (j + 1 < text[i].length() && text[i][j + 1] == '\\') { + escaped = !escaped; + j++; + } + if (escaped) { + j++; + continue; + } } - } - } while (cc != quotation); - } else if (cc == c) { - stack++; - } else if (cc == closec) { - stack--; - } + } while (cc != quotation); + } else if (cc == c) { + stack++; + } else if (cc == closec) { + stack--; + } - if (stack == 0) { - brace_open_match_line = i; - brace_open_match_column = j; - brace_open_matching = true; + if (stack == 0) { + brace_matching.write[caret].open_match_line = i; + brace_matching.write[caret].open_match_column = j; + brace_matching.write[caret].open_matching = true; + break; + } + } + if (brace_matching.write[caret].open_match_line != -1) { break; } } - if (brace_open_match_line != -1) { - break; - } - } - if (!brace_open_matching) { - brace_open_mismatch = true; + if (!brace_matching.write[caret].open_matching) { + brace_matching.write[caret].open_mismatch = true; + } } } - } - if (caret.column > 0) { - char32_t c = text[caret.line][caret.column - 1]; - char32_t closec = 0; + if (get_caret_column(caret) > 0) { + char32_t c = text[get_caret_line(caret)][get_caret_column(caret) - 1]; + char32_t closec = 0; - if (c == ']') { - closec = '['; - } else if (c == '}') { - closec = '{'; - } else if (c == ')') { - closec = '('; - } + if (c == ']') { + closec = '['; + } else if (c == '}') { + closec = '{'; + } else if (c == ')') { + closec = '('; + } - if (closec != 0) { - int stack = 1; - - for (int i = caret.line; i >= 0; i--) { - int from = i == caret.line ? caret.column - 2 : text[i].length() - 1; - for (int j = from; j >= 0; j--) { - char32_t cc = text[i][j]; - // Ignore any brackets inside a string. - if (cc == '"' || cc == '\'') { - char32_t quotation = cc; - do { - j--; - if (!(j >= 0)) { - break; - } - cc = text[i][j]; - // Skip over escaped quotation marks inside strings. - if (cc == quotation) { - bool escaped = false; - while (j - 1 >= 0 && text[i][j - 1] == '\\') { - escaped = !escaped; - j--; + if (closec != 0) { + int stack = 1; + + for (int i = get_caret_line(caret); i >= 0; i--) { + int from = i == get_caret_line(caret) ? get_caret_column(caret) - 2 : text[i].length() - 1; + for (int j = from; j >= 0; j--) { + char32_t cc = text[i][j]; + // Ignore any brackets inside a string. + if (cc == '"' || cc == '\'') { + char32_t quotation = cc; + do { + j--; + if (!(j >= 0)) { + break; } - if (escaped) { - cc = '\\'; - continue; + cc = text[i][j]; + // Skip over escaped quotation marks inside strings. + if (cc == quotation) { + bool escaped = false; + while (j - 1 >= 0 && text[i][j - 1] == '\\') { + escaped = !escaped; + j--; + } + if (escaped) { + cc = '\\'; + continue; + } } - } - } while (cc != quotation); - } else if (cc == c) { - stack++; - } else if (cc == closec) { - stack--; - } + } while (cc != quotation); + } else if (cc == c) { + stack++; + } else if (cc == closec) { + stack--; + } - if (stack == 0) { - brace_close_match_line = i; - brace_close_match_column = j; - brace_close_matching = true; + if (stack == 0) { + brace_matching.write[caret].close_match_line = i; + brace_matching.write[caret].close_match_column = j; + brace_matching.write[caret].close_matching = true; + break; + } + } + if (brace_matching.write[caret].close_match_line != -1) { break; } } - if (brace_close_match_line != -1) { - break; - } - } - if (!brace_close_matching) { - brace_close_mismatch = true; + if (!brace_matching.write[caret].close_matching) { + brace_matching.write[caret].close_mismatch = true; + } } } } @@ -683,16 +683,24 @@ void TextEdit::_notification(int p_what) { bool draw_placeholder = text.size() == 1 && text[0].length() == 0; // Get the highlighted words. - String highlighted_text = get_selected_text(); + String highlighted_text = get_selected_text(0); // Check if highlighted words contain only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); - const int caret_wrap_index = get_caret_wrap_index(); + HashMap<int, HashSet<int>> caret_line_wrap_index_map; + Vector<int> carets_wrap_index; + carets_wrap_index.resize(carets.size()); + for (int i = 0; i < carets.size(); i++) { + carets.write[i].visible = false; + int wrap_index = get_caret_wrap_index(i); + caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index); + carets_wrap_index.write[i] = wrap_index; + } - int first_visible_line = get_first_visible_line() - 1; + int first_vis_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(first_visible_line + 1); + draw_amount += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(first_vis_line + 1); // Draw minimap. if (draw_minimap) { @@ -703,13 +711,13 @@ void TextEdit::_notification(int p_what) { // calculate viewport size and y offset int viewport_height = (draw_amount - 1) * minimap_line_height; int control_height = _get_control_height() - viewport_height; - int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + int viewport_offset_y = round(get_scroll_pos_for_line(first_vis_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); // calculate the first line. int num_lines_before = round((viewport_offset_y) / minimap_line_height); - int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_vis_line; if (minimap_line >= 0) { - minimap_line -= get_next_visible_line_index_offset_from(first_visible_line, 0, -num_lines_before).x; + minimap_line -= get_next_visible_line_index_offset_from(first_vis_line, 0, -num_lines_before).x; minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0); } int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1); @@ -783,7 +791,7 @@ void TextEdit::_notification(int p_what) { last_wrap_column += wrap_rows[line_wrap_index - 1].length(); } - if (minimap_line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { + if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), current_line_color); } else { @@ -825,7 +833,7 @@ void TextEdit::_notification(int p_what) { continue; } - // If we've changed colour we are at the start of a new section, therefore we need to go back to the end + // If we've changed color we are at the start of a new section, therefore we need to go back to the end // of the previous section to draw it, we'll also add the character back on. if (color != previous_color) { characters--; @@ -875,10 +883,9 @@ void TextEdit::_notification(int p_what) { } // Draw main text. - caret.visible = false; line_drawing_cache.clear(); int row_height = draw_placeholder ? placeholder_line_height + line_spacing : get_line_height(); - int line = first_visible_line; + int line = first_vis_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -921,7 +928,7 @@ void TextEdit::_notification(int p_what) { } const String &str = wrap_rows[line_wrap_index]; - int char_margin = xmargin_beg - caret.x_ofs; + int char_margin = xmargin_beg - first_visible_col; int ofs_x = 0; int ofs_y = 0; @@ -934,7 +941,7 @@ void TextEdit::_notification(int p_what) { } ofs_y += i * row_height + line_spacing / 2; - ofs_y -= caret.wrap_ofs * row_height; + ofs_y -= first_visible_line_wrap_ofs * row_height; ofs_y -= _get_v_scroll_offset() * row_height; bool clipped = false; @@ -960,7 +967,7 @@ void TextEdit::_notification(int p_what) { if (str.length() == 0) { // Draw line background if empty as we won't loop at all. - if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { + if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color); } else { @@ -969,17 +976,19 @@ void TextEdit::_notification(int p_what) { } // Give visual indication of empty selected line. - if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - float char_w = font->get_char_size(' ', font_size).width; - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color); + for (int c = 0; c < carets.size(); c++) { + if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c) && char_margin >= xmargin_beg) { + float char_w = font->get_char_size(' ', font_size).width; + if (rtl) { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color); + } else { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), selection_color); + } } } } else { // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. - if (line == caret.line && caret_wrap_index == line_wrap_index && highlight_current_line) { + if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), current_line_color); } else { @@ -1003,14 +1012,14 @@ void TextEdit::_notification(int p_what) { switch (gutter.type) { case GUTTER_TYPE_STRING: { - const String &text = get_line_gutter_text(line, g); - if (text.is_empty()) { + const String &txt = get_line_gutter_text(line, g); + if (txt.is_empty()) { break; } Ref<TextLine> tl; tl.instantiate(); - tl->add_string(text, font, font_size); + tl->add_string(txt, font, font_size); int yofs = ofs_y + (row_height - tl->get_size().y) / 2; if (outline_size > 0 && outline_color.a > 0) { @@ -1074,23 +1083,25 @@ void TextEdit::_notification(int p_what) { char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x; } - if (!clipped && selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection - int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column; - int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column; - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to); - for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); - if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { - continue; - } - if (rect.position.x < xmargin_beg) { - rect.size.x -= (xmargin_beg - rect.position.x); - rect.position.x = xmargin_beg; - } - if (rect.position.x + rect.size.x > xmargin_end) { - rect.size.x = xmargin_end - rect.position.x; + for (int c = 0; c < carets.size(); c++) { + if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection + int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); + int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to); + for (int j = 0; j < sel.size(); j++) { + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); + if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { + continue; + } + if (rect.position.x < xmargin_beg) { + rect.size.x -= (xmargin_beg - rect.position.x); + rect.position.x = xmargin_beg; + } + if (rect.position.x + rect.size.x > xmargin_end) { + rect.size.x = xmargin_end - rect.position.x; + } + draw_rect(rect, selection_color, true); } - draw_rect(rect, selection_color, true); } } @@ -1201,45 +1212,50 @@ void TextEdit::_notification(int p_what) { current_color.a = font_readonly_color.a; } } + Color gl_color = current_color; - if (selection.active && line >= selection.from_line && line <= selection.to_line) { // Selection - int sel_from = (line > selection.from_line) ? TS->shaped_text_get_range(rid).x : selection.from_column; - int sel_to = (line < selection.to_line) ? TS->shaped_text_get_range(rid).y : selection.to_column; + for (int c = 0; c < carets.size(); c++) { + if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection + int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); + int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); - if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) { - current_color = font_selected_color; + if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && use_selected_font_color) { + gl_color = font_selected_color; + } } } float char_pos = char_ofs + char_margin + ofs_x; if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { - if ((brace_open_match_line == line && brace_open_match_column == glyphs[j].start) || - (caret.column == glyphs[j].start && caret.line == line && caret_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { - if (brace_open_mismatch) { - current_color = brace_mismatch_color; + for (int c = 0; c < carets.size(); c++) { + if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) { + if (brace_matching[c].open_mismatch) { + gl_color = brace_mismatch_color; + } + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); + draw_rect(rect, gl_color); } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); - draw_rect(rect, current_color); - } - if ((brace_close_match_line == line && brace_close_match_column == glyphs[j].start) || - (caret.column == glyphs[j].start + 1 && caret.line == line && caret_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) { - if (brace_close_mismatch) { - current_color = brace_mismatch_color; + if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) { + if (brace_matching[c].close_mismatch) { + gl_color = brace_mismatch_color; + } + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); + draw_rect(rect, gl_color); } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); - draw_rect(rect, current_color); } } if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) { int yofs = (text_height - tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); - tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), current_color); + tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), gl_color); } else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE)) { int yofs = (text_height - space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index); int xofs = (glyphs[j].advance * glyphs[j].repeat - space_icon->get_width()) / 2; - space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), current_color); + space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), gl_color); } } @@ -1247,10 +1263,10 @@ void TextEdit::_notification(int p_what) { for (int k = 0; k < glyphs[j].repeat; k++) { if (!clipped && (char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) { if (glyphs[j].font_rid != RID()) { - TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color); + TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color); had_glyphs_drawn = true; } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color); + TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color); had_glyphs_drawn = true; } } @@ -1288,137 +1304,155 @@ void TextEdit::_notification(int p_what) { // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1). const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale()); - if (!clipped && caret.line == line && line_wrap_index == caret_wrap_index) { - caret.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); + for (int c = 0; c < carets.size(); c++) { + if (!clipped && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index) { + carets.write[c].draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); - if (ime_text.length() == 0) { - CaretInfo ts_caret; - if (str.length() != 0) { - // Get carets. - ts_caret = TS->shaped_text_get_carets(rid, caret.column); - } else { - // No carets, add one at the start. - int h = font->get_height(font_size); - if (rtl) { - ts_caret.l_dir = TextServer::DIRECTION_RTL; - ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h)); + if (ime_text.length() == 0) { + CaretInfo ts_caret; + if (str.length() != 0) { + // Get carets. + ts_caret = TS->shaped_text_get_carets(rid, get_caret_column(c)); } else { - ts_caret.l_dir = TextServer::DIRECTION_LTR; - ts_caret.l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h)); + // No carets, add one at the start. + int h = font->get_height(font_size); + if (rtl) { + ts_caret.l_dir = TextServer::DIRECTION_RTL; + ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h)); + } else { + ts_caret.l_dir = TextServer::DIRECTION_LTR; + ts_caret.l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h)); + } } - } - if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) { - caret.draw_pos.x = char_margin + ofs_x + ts_caret.l_caret.position.x; - } else { - caret.draw_pos.x = char_margin + ofs_x + ts_caret.t_caret.position.x; - } + if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) { + carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.l_caret.position.x; + } else { + carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.t_caret.position.x; + } - if (caret.draw_pos.x >= xmargin_beg && caret.draw_pos.x < xmargin_end) { - caret.visible = true; - if (draw_caret || drag_caret_force_displayed) { - if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) { - //Block or underline caret, draw trailing carets at full height. - int h = font->get_height(font_size); - - if (ts_caret.t_caret != Rect2()) { - if (overtype_mode) { - ts_caret.t_caret.position.y = TS->shaped_text_get_descent(rid); - ts_caret.t_caret.size.y = caret_width; - } else { - ts_caret.t_caret.position.y = -TS->shaped_text_get_ascent(rid); - ts_caret.t_caret.size.y = h; - } - ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); - draw_rect(ts_caret.t_caret, caret_color, overtype_mode); + if (get_caret_draw_pos(c).x >= xmargin_beg && get_caret_draw_pos(c).x < xmargin_end) { + carets.write[c].visible = true; + if (draw_caret || drag_caret_force_displayed) { + if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) { + //Block or underline caret, draw trailing carets at full height. + int h = font->get_height(font_size); - if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { - ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, caret_color * Color(1, 1, 1, 0.5)); - } - } else { // End of the line. - if (gl_size > 0) { - // Adjust for actual line dimensions. + if (ts_caret.t_caret != Rect2()) { if (overtype_mode) { - ts_caret.l_caret.position.y = TS->shaped_text_get_descent(rid); + ts_caret.t_caret.position.y = TS->shaped_text_get_descent(rid); + ts_caret.t_caret.size.y = caret_width; + } else { + ts_caret.t_caret.position.y = -TS->shaped_text_get_ascent(rid); + ts_caret.t_caret.size.y = h; + } + ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + draw_rect(ts_caret.t_caret, caret_color, overtype_mode); + + if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { + // Draw split caret (leading part). + ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); + ts_caret.l_caret.size.x = caret_width; + draw_rect(ts_caret.l_caret, caret_color); + // Draw extra direction marker on top of split caret. + float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + } + } else { // End of the line. + if (gl_size > 0) { + // Adjust for actual line dimensions. + if (overtype_mode) { + ts_caret.l_caret.position.y = TS->shaped_text_get_descent(rid); + ts_caret.l_caret.size.y = caret_width; + } else { + ts_caret.l_caret.position.y = -TS->shaped_text_get_ascent(rid); + ts_caret.l_caret.size.y = h; + } + } else if (overtype_mode) { + ts_caret.l_caret.position.y += ts_caret.l_caret.size.y; ts_caret.l_caret.size.y = caret_width; + } + if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) { + ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x; } else { - ts_caret.l_caret.position.y = -TS->shaped_text_get_ascent(rid); - ts_caret.l_caret.size.y = h; + ts_caret.l_caret.size.x = 3 * caret_width; + } + ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); + if (ts_caret.l_dir == TextServer::DIRECTION_RTL) { + ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x; } - } else if (overtype_mode) { - ts_caret.l_caret.position.y += ts_caret.l_caret.size.y; - ts_caret.l_caret.size.y = caret_width; + draw_rect(ts_caret.l_caret, caret_color, overtype_mode); } - if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) { - ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x; - } else { - ts_caret.l_caret.size.x = 3 * caret_width; + } else { + // Normal caret. + if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) { + // Draw extra marker on top of mid caret. + Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); + trect.position += Vector2(char_margin + ofs_x, ofs_y); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { + // Draw extra direction marker on top of split caret. + float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); + trect.position += Vector2(char_margin + ofs_x, ofs_y); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + + d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width); + trect.position += Vector2(char_margin + ofs_x, ofs_y); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - if (ts_caret.l_dir == TextServer::DIRECTION_RTL) { - ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x; - } - draw_rect(ts_caret.l_caret, caret_color, overtype_mode); - } - } else { - // Normal caret. - if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) { - // Draw extra marker on top of mid caret. - Rect2 trect = Rect2(ts_caret.l_caret.position.x - 3 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); - trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); - } - ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - ts_caret.l_caret.size.x = caret_width; + ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, caret_color); + draw_rect(ts_caret.l_caret, caret_color); - ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); - ts_caret.t_caret.size.x = caret_width; + ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + ts_caret.t_caret.size.x = caret_width; - draw_rect(ts_caret.t_caret, caret_color); + draw_rect(ts_caret.t_caret, caret_color); + } } } - } - } else { - { - // IME Intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column, caret.column + ime_text.length()); - for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); - if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { - continue; - } - if (rect.position.x < xmargin_beg) { - rect.size.x -= (xmargin_beg - rect.position.x); - rect.position.x = xmargin_beg; - } else if (rect.position.x + rect.size.x > xmargin_end) { - rect.size.x = xmargin_end - rect.position.x; + } else { + { + // IME Intermediate text range. + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); + for (int j = 0; j < sel.size(); j++) { + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); + if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { + continue; + } + if (rect.position.x < xmargin_beg) { + rect.size.x -= (xmargin_beg - rect.position.x); + rect.position.x = xmargin_beg; + } else if (rect.position.x + rect.size.x > xmargin_end) { + rect.size.x = xmargin_end - rect.position.x; + } + rect.size.y = caret_width; + draw_rect(rect, caret_color); + carets.write[c].draw_pos.x = rect.position.x; } - rect.size.y = caret_width; - draw_rect(rect, caret_color); - caret.draw_pos.x = rect.position.x; } - } - { - // IME caret. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, caret.column + ime_selection.x, caret.column + ime_selection.x + ime_selection.y); - for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); - if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { - continue; - } - if (rect.position.x < xmargin_beg) { - rect.size.x -= (xmargin_beg - rect.position.x); - rect.position.x = xmargin_beg; - } else if (rect.position.x + rect.size.x > xmargin_end) { - rect.size.x = xmargin_end - rect.position.x; + { + // IME caret. + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); + for (int j = 0; j < sel.size(); j++) { + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); + if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { + continue; + } + if (rect.position.x < xmargin_beg) { + rect.size.x -= (xmargin_beg - rect.position.x); + rect.position.x = xmargin_beg; + } else if (rect.position.x + rect.size.x > xmargin_end) { + rect.size.x = xmargin_end - rect.position.x; + } + rect.size.y = caret_width * 3; + draw_rect(rect, caret_color); + carets.write[c].draw_pos.x = rect.position.x; } - rect.size.y = caret_width * 3; - draw_rect(rect, caret_color); - caret.draw_pos.x = rect.position.x; } } } @@ -1433,7 +1467,7 @@ void TextEdit::_notification(int p_what) { if (has_focus()) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret.draw_pos, get_viewport()->get_window_id()); + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + get_caret_draw_pos(), get_viewport()->get_window_id()); } } } break; @@ -1454,13 +1488,13 @@ void TextEdit::_notification(int p_what) { int caret_start = -1; int caret_end = -1; - if (!selection.active) { - String full_text = _base_get_text(0, 0, caret.line, caret.column); + if (!has_selection(0)) { + String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); caret_start = full_text.length(); } else { - String pre_text = _base_get_text(0, 0, selection.from_line, selection.from_column); - String post_text = get_selected_text(); + String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); + String post_text = get_selected_text(0); caret_start = pre_text.length(); caret_end = caret_start + post_text.length(); @@ -1482,14 +1516,16 @@ void TextEdit::_notification(int p_what) { if (!ime_text.is_empty()) { ime_text = ""; ime_selection = Point2(); - text.invalidate_cache(caret.line, caret.column, true, ime_text); + for (int i = 0; i < carets.size(); i++) { + text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, ime_text); + } } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); } - if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { + if (deselect_on_focus_loss_enabled && !selection_drag_attempt) { deselect(); } } break; @@ -1499,20 +1535,21 @@ void TextEdit::_notification(int p_what) { ime_text = DisplayServer::get_singleton()->ime_get_text(); ime_selection = DisplayServer::get_singleton()->ime_get_selection(); - String t; - if (caret.column >= 0) { - t = text[caret.line].substr(0, caret.column) + ime_text + text[caret.line].substr(caret.column, text[caret.line].length()); - } else { - t = ime_text; + for (int i = 0; i < carets.size(); i++) { + String t; + if (get_caret_column(i) >= 0) { + t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length()); + } else { + t = ime_text; + } + text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t)); } - - text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t)); queue_redraw(); } } break; case NOTIFICATION_DRAG_BEGIN: { - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + selecting_mode = SelectionMode::SELECTION_MODE_NONE; drag_action = true; dragging_minimap = false; dragging_selection = false; @@ -1522,8 +1559,8 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_DRAG_END: { if (is_drag_successful()) { - if (selection.drag_attempt) { - selection.drag_attempt = false; + if (selection_drag_attempt) { + selection_drag_attempt = false; if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { delete_selection(); } else if (deselect_on_focus_loss_enabled) { @@ -1531,7 +1568,7 @@ void TextEdit::_notification(int p_what) { } } } else { - selection.drag_attempt = false; + selection_drag_attempt = false; } drag_action = false; drag_caret_force_displayed = false; @@ -1655,7 +1692,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { continue; } - if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) { + if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) { emit_signal(SNAME("gutter_clicked"), row, i); return; } @@ -1671,77 +1708,103 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - int prev_col = caret.column; - int prev_line = caret.line; - - set_caret_line(row, false, false); - set_caret_column(col); - selection.drag_attempt = false; - - if (selecting_enabled && mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { - if (!selection.active) { - selection.active = true; - selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; - selection.from_column = prev_col; - selection.from_line = prev_line; - selection.to_column = caret.column; - selection.to_line = caret.line; - - if (selection.from_line > selection.to_line || (selection.from_line == selection.to_line && selection.from_column > selection.to_column)) { - SWAP(selection.from_column, selection.to_column); - SWAP(selection.from_line, selection.to_line); - selection.shiftclick_left = false; + int caret = carets.size() - 1; + int prev_col = get_caret_column(caret); + int prev_line = get_caret_line(caret); + + const int triple_click_timeout = 600; + const int triple_click_tolerance = 5; + bool is_triple_click = (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance); + + if (!is_mouse_over_selection() && !mb->is_double_click() && !is_triple_click) { + if (mb->is_alt_pressed()) { + prev_line = row; + prev_col = col; + + caret = add_caret(row, col); + if (caret == -1) { + return; + } + + carets.write[caret].selection.selecting_line = row; + carets.write[caret].selection.selecting_column = col; + + last_dblclk = 0; + } else if (!mb->is_shift_pressed()) { + caret = 0; + remove_secondary_carets(); + } + } + + set_caret_line(row, false, true, 0, caret); + set_caret_column(col, false, caret); + selection_drag_attempt = false; + + if (selecting_enabled && mb->is_shift_pressed() && (get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line)) { + if (!has_selection(caret)) { + carets.write[caret].selection.active = true; + selecting_mode = SelectionMode::SELECTION_MODE_POINTER; + carets.write[caret].selection.from_column = prev_col; + carets.write[caret].selection.from_line = prev_line; + carets.write[caret].selection.to_column = carets[caret].column; + carets.write[caret].selection.to_line = carets[caret].line; + + if (carets[caret].selection.from_line > carets[caret].selection.to_line || (carets[caret].selection.from_line == carets[caret].selection.to_line && carets[caret].selection.from_column > carets[caret].selection.to_column)) { + SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column); + SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line); + carets.write[caret].selection.shiftclick_left = false; } else { - selection.shiftclick_left = true; + carets.write[caret].selection.shiftclick_left = true; } - selection.selecting_line = prev_line; - selection.selecting_column = prev_col; + carets.write[caret].selection.selecting_line = prev_line; + carets.write[caret].selection.selecting_column = prev_col; + caret_index_edit_dirty = true; + merge_overlapping_carets(); queue_redraw(); } else { - if (caret.line < selection.selecting_line || (caret.line == selection.selecting_line && caret.column < selection.selecting_column)) { - if (selection.shiftclick_left) { - selection.shiftclick_left = !selection.shiftclick_left; + if (carets[caret].line < carets[caret].selection.selecting_line || (carets[caret].line == carets[caret].selection.selecting_line && carets[caret].column < carets[caret].selection.selecting_column)) { + if (carets[caret].selection.shiftclick_left) { + carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left; } - selection.from_column = caret.column; - selection.from_line = caret.line; - - } else if (caret.line > selection.selecting_line || (caret.line == selection.selecting_line && caret.column > selection.selecting_column)) { - if (!selection.shiftclick_left) { - SWAP(selection.from_column, selection.to_column); - SWAP(selection.from_line, selection.to_line); - selection.shiftclick_left = !selection.shiftclick_left; + carets.write[caret].selection.from_column = carets[caret].column; + carets.write[caret].selection.from_line = carets[caret].line; + + } else if (carets[caret].line > carets[caret].selection.selecting_line || (carets[caret].line == carets[caret].selection.selecting_line && carets[caret].column > carets[caret].selection.selecting_column)) { + if (!carets[caret].selection.shiftclick_left) { + SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column); + SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line); + carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left; } - selection.to_column = caret.column; - selection.to_line = caret.line; + carets.write[caret].selection.to_column = carets[caret].column; + carets.write[caret].selection.to_line = carets[caret].line; } else { - selection.active = false; + deselect(caret); } - + caret_index_edit_dirty = true; + merge_overlapping_carets(); queue_redraw(); } } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) { - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; - selection.drag_attempt = true; - } else { - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; - selection.selecting_line = row; - selection.selecting_column = col; + set_selection_mode(SelectionMode::SELECTION_MODE_NONE, get_selection_line(caret), get_selection_column(caret), caret); + // We use the main caret for dragging, so reset this one. + set_caret_line(prev_line, false, true, 0, caret); + set_caret_column(prev_col, false, caret); + selection_drag_attempt = true; + } else if (caret == 0) { + deselect(); + set_selection_mode(SelectionMode::SELECTION_MODE_POINTER, row, col); } - const int triple_click_timeout = 600; - const int triple_click_tolerance = 5; - - if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance) { + if (is_triple_click) { // Triple-click select line. - selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE; - selection.drag_attempt = false; + selecting_mode = SelectionMode::SELECTION_MODE_LINE; + selection_drag_attempt = false; _update_selection_mode_line(); last_dblclk = 0; - } else if (mb->is_double_click() && text[caret.line].length()) { + } else if (mb->is_double_click() && text[get_caret_line(caret)].length()) { // Double-click select word. - selection.selecting_mode = SelectionMode::SELECTION_MODE_WORD; + selecting_mode = SelectionMode::SELECTION_MODE_WORD; _update_selection_mode_word(); last_dblclk = OS::get_singleton()->get_ticks_msec(); last_dblclk_pos = mb->get_position(); @@ -1759,23 +1822,25 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { Point2i pos = get_line_column_at_pos(mpos); int row = pos.y; int col = pos.x; + int caret = carets.size() - 1; if (is_move_caret_on_right_click_enabled()) { - if (has_selection()) { - int from_line = get_selection_from_line(); - int to_line = get_selection_to_line(); - int from_column = get_selection_from_column(); - int to_column = get_selection_to_column(); + if (has_selection(caret)) { + int from_line = get_selection_from_line(caret); + int to_line = get_selection_to_line(caret); + int from_column = get_selection_from_column(caret); + int to_column = get_selection_to_column(caret); if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { // Right click is outside the selected text. - deselect(); + deselect(caret); } } - if (!has_selection()) { - set_caret_line(row, true, false); - set_caret_column(col); + if (!has_selection(caret)) { + set_caret_line(row, true, false, 0, caret); + set_caret_column(col, true, caret); } + merge_overlapping_carets(); } if (context_menu_enabled) { @@ -1788,15 +1853,21 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } else { if (mb->get_button_index() == MouseButton::LEFT) { - if (selection.drag_attempt && is_mouse_over_selection()) { - selection.active = false; + if (selection_drag_attempt && is_mouse_over_selection()) { + remove_secondary_carets(); + + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + set_caret_line(pos.y, false, true, 0, 0); + set_caret_column(pos.x, true, 0); + + deselect(); } dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; click_select_held->stop(); if (!drag_action) { - selection.drag_attempt = false; + selection_drag_attempt = false; } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); @@ -1840,7 +1911,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (!dragging_minimap) { - switch (selection.selecting_mode) { + switch (selecting_mode) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); } break; @@ -1868,7 +1939,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { continue; } - if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) { + if (mpos.x >= left_margin && mpos.x < left_margin + gutters[i].width) { // We are in this gutter i's horizontal area. current_hovered_gutter = Vector2i(i, hovered_row); break; @@ -1886,8 +1957,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) { drag_caret_force_displayed = true; Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - set_caret_line(pos.y, false); - set_caret_column(pos.x); + set_caret_line(pos.y, false, true, 0, 0); + set_caret_column(pos.x, true, 0); dragging_selection = true; } } @@ -1922,8 +1993,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { // * No Modifiers are pressed (except shift) bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); - selection.selecting_text = false; - // Check and handle all built in shortcuts. // NEWLINES. @@ -1988,7 +2057,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (is_shortcut_keys_enabled()) { - // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE. + // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, + // CLEAR CARETS AND SELECTIONS, CUT, COPY, PASTE. if (k->is_action("ui_text_select_all", true)) { select_all(); accept_event(); @@ -1999,6 +2069,18 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } + if (k->is_action("ui_text_add_selection_for_next_occurrence", true)) { + add_selection_for_next_occurrence(); + accept_event(); + return; + } + if (k->is_action("ui_text_clear_carets_and_selection", true)) { + // Since the default shortcut is ESC, accepts the event only if it's actually performed. + if (_clear_carets_and_selection()) { + accept_event(); + return; + } + } if (k->is_action("ui_cut", true)) { cut(); accept_event(); @@ -2026,6 +2108,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } + + if (k->is_action("ui_text_caret_add_below", true)) { + add_caret_at_carets(true); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_add_above", true)) { + add_caret_at_carets(false); + accept_event(); + return; + } } // MISC. @@ -2129,8 +2222,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // Handle Unicode (if no modifiers active). Tab has a value of 0x09. - if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == Key::TAB)) { + // Handle tab as it has no set unicode value. + if (k->is_action("ui_text_indent", true)) { + if (editable) { + insert_text_at_caret("\t"); + } + accept_event(); + return; + } + + // Handle Unicode (if no modifiers active). + if (allow_unicode_handling && editable && k->get_unicode() >= 32) { handle_unicode_input(k->get_unicode()); accept_event(); return; @@ -2145,7 +2247,9 @@ void TextEdit::_swap_current_input_direction() { } else { input_direction = TEXT_DIRECTION_LTR; } - set_caret_column(caret.column); + for (int i = 0; i < carets.size(); i++) { + set_caret_column(get_caret_column(i), i == 0, i); + } queue_redraw(); } @@ -2155,325 +2259,429 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { } begin_complex_operation(); - - bool first_line = false; - if (!p_split_current_line) { - deselect(); - if (p_above) { - if (caret.line > 0) { - set_caret_line(caret.line - 1, false); - set_caret_column(text[caret.line].length()); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + bool first_line = false; + if (!p_split_current_line) { + deselect(i); + if (p_above) { + if (get_caret_line(i) > 0) { + set_caret_line(get_caret_line(i) - 1, false, true, 0, i); + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); + } else { + set_caret_column(0, i == 0, i); + first_line = true; + } } else { - set_caret_column(0); - first_line = true; + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); } - } else { - set_caret_column(text[caret.line].length()); } - } - insert_text_at_caret("\n"); + insert_text_at_caret("\n", i); - if (first_line) { - set_caret_line(0); + if (first_line) { + set_caret_line(0, i == 0, true, 0, i); + } } - end_complex_operation(); } void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { - // Handle selection - if (p_select) { - _pre_shift_selection(); - } else if (selection.active && !p_move_by_word) { - // If a selection is active, move caret to start of selection - set_caret_line(selection.from_line); - set_caret_column(selection.from_column); - deselect(); - return; - } else { - deselect(); - } - - if (p_move_by_word) { - int cc = caret.column; - // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. - if (cc == 0 && caret.line > 0) { - set_caret_line(caret.line - 1); - set_caret_column(text[caret.line].length()); + for (int i = 0; i < carets.size(); i++) { + // Handle selection. + if (p_select) { + _pre_shift_selection(i); + } else if (has_selection(i) && !p_move_by_word) { + // If a selection is active, move caret to start of selection. + set_caret_line(get_selection_from_line(i), false, true, 0, i); + set_caret_column(get_selection_from_column(i), i == 0, i); + deselect(i); + continue; } else { - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - if (words.is_empty() || cc <= words[0]) { - // This solves the scenario where there are no words but glyfs that can be ignored. - cc = 0; + deselect(i); + } + + if (p_move_by_word) { + int cc = get_caret_column(i); + // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. + if (cc == 0 && get_caret_line(i) > 0) { + set_caret_line(get_caret_line(i) - 1, false, true, 0, i); + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); } else { - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); + if (words.is_empty() || cc <= words[0]) { + // This solves the scenario where there are no words but glyfs that can be ignored. + cc = 0; + } else { + for (int j = words.size() - 2; j >= 0; j = j - 2) { + if (words[j] < cc) { + cc = words[j]; + break; + } } } - } - set_caret_column(cc); - } - } else { - // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. - if (caret.column == 0) { - if (caret.line > 0) { - set_caret_line(caret.line - get_next_visible_line_offset_from(CLAMP(caret.line - 1, 0, text.size() - 1), -1)); - set_caret_column(text[caret.line].length()); + set_caret_column(cc, i == 0, i); } } else { - if (caret_mid_grapheme_enabled) { - set_caret_column(get_caret_column() - 1); + // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line. + if (get_caret_column(i) == 0) { + if (get_caret_line(i) > 0) { + set_caret_line(get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1), false, true, 0, i); + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); + } } else { - set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column())); + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column(i) - 1, i == 0, i); + } else { + set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i); + } } } - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { - // Handle selection - if (p_select) { - _pre_shift_selection(); - } else if (selection.active && !p_move_by_word) { - // If a selection is active, move caret to end of selection - set_caret_line(selection.to_line); - set_caret_column(selection.to_column); - deselect(); - return; - } else { - deselect(); - } - - if (p_move_by_word) { - int cc = caret.column; - // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line. - if (cc == text[caret.line].length() && caret.line < text.size() - 1) { - set_caret_line(caret.line + 1); - set_caret_column(0); + for (int i = 0; i < carets.size(); i++) { + // Handle selection + if (p_select) { + _pre_shift_selection(i); + } else if (has_selection(i) && !p_move_by_word) { + // If a selection is active, move caret to end of selection + set_caret_line(get_selection_to_line(i), false, true, 0, i); + set_caret_column(get_selection_to_column(i), i == 0, i); + deselect(i); + continue; } else { - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - if (words.is_empty() || cc >= words[words.size() - 1]) { - // This solves the scenario where there are no words but glyfs that can be ignored. - cc = text[caret.line].length(); + deselect(i); + } + + if (p_move_by_word) { + int cc = get_caret_column(i); + // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line. + if (cc == text[get_caret_line(i)].length() && get_caret_line(i) < text.size() - 1) { + set_caret_line(get_caret_line(i) + 1, false, true, 0, i); + set_caret_column(0, i == 0, i); } else { - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); + if (words.is_empty() || cc >= words[words.size() - 1]) { + // This solves the scenario where there are no words but glyfs that can be ignored. + cc = text[get_caret_line(i)].length(); + } else { + for (int j = 1; j < words.size(); j = j + 2) { + if (words[j] > cc) { + cc = words[j]; + break; + } } } - } - set_caret_column(cc); - } - } else { - // If we are at the end of the line, move the caret to the next line down. - if (caret.column == text[caret.line].length()) { - if (caret.line < text.size() - 1) { - set_caret_line(get_caret_line() + get_next_visible_line_offset_from(CLAMP(caret.line + 1, 0, text.size() - 1), 1), true, false); - set_caret_column(0); + set_caret_column(cc, i == 0, i); } } else { - if (caret_mid_grapheme_enabled) { - set_caret_column(get_caret_column() + 1); + // If we are at the end of the line, move the caret to the next line down. + if (get_caret_column(i) == text[get_caret_line(i)].length()) { + if (get_caret_line(i) < text.size() - 1) { + set_caret_line(get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1), false, false, 0, i); + set_caret_column(0, i == 0, i); + } } else { - set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), get_caret_column())); + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column(i) + 1, i == 0, i); + } else { + set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i); + } } } - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_up(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - int cur_wrap_index = get_caret_wrap_index(); - if (cur_wrap_index > 0) { - set_caret_line(caret.line, true, false, cur_wrap_index - 1); - } else if (caret.line == 0) { - set_caret_column(0); - } else { - int new_line = caret.line - get_next_visible_line_offset_from(caret.line - 1, -1); - if (is_line_wrapped(new_line)) { - set_caret_line(new_line, true, false, get_line_wrap_count(new_line)); + int cur_wrap_index = get_caret_wrap_index(i); + if (cur_wrap_index > 0) { + set_caret_line(get_caret_line(i), true, false, cur_wrap_index - 1, i); + } else if (get_caret_line(i) == 0) { + set_caret_column(0, i == 0, i); } else { - set_caret_line(new_line, true, false); + int new_line = get_caret_line(i) - get_next_visible_line_offset_from(get_caret_line(i) - 1, -1); + if (is_line_wrapped(new_line)) { + set_caret_line(new_line, i == 0, false, get_line_wrap_count(new_line), i); + } else { + set_caret_line(new_line, i == 0, false, 0, i); + } } - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_down(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - int cur_wrap_index = get_caret_wrap_index(); - if (cur_wrap_index < get_line_wrap_count(caret.line)) { - set_caret_line(caret.line, true, false, cur_wrap_index + 1); - } else if (caret.line == get_last_unhidden_line()) { - set_caret_column(text[caret.line].length()); - } else { - int new_line = caret.line + get_next_visible_line_offset_from(CLAMP(caret.line + 1, 0, text.size() - 1), 1); - set_caret_line(new_line, true, false, 0); - } + int cur_wrap_index = get_caret_wrap_index(i); + if (cur_wrap_index < get_line_wrap_count(get_caret_line(i))) { + set_caret_line(get_caret_line(i), i == 0, false, cur_wrap_index + 1, i); + } else if (get_caret_line(i) == get_last_unhidden_line()) { + set_caret_column(text[get_caret_line(i)].length()); + } else { + int new_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1); + set_caret_line(new_line, i == 0, false, 0, i); + } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_to_line_start(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - // Move caret column to start of wrapped row and then to start of text. - Vector<String> rows = get_line_wrapped_text(caret.line); - int wi = get_caret_wrap_index(); - int row_start_col = 0; - for (int i = 0; i < wi; i++) { - row_start_col += rows[i].length(); - } - if (caret.column == row_start_col || wi == 0) { - // Compute whitespace symbols sequence length. - int current_line_whitespace_len = get_first_non_whitespace_column(caret.line); - if (get_caret_column() == current_line_whitespace_len) { - set_caret_column(0); + // Move caret column to start of wrapped row and then to start of text. + Vector<String> rows = get_line_wrapped_text(get_caret_line(i)); + int wi = get_caret_wrap_index(i); + int row_start_col = 0; + for (int j = 0; j < wi; j++) { + row_start_col += rows[j].length(); + } + if (get_caret_column(i) == row_start_col || wi == 0) { + // Compute whitespace symbols sequence length. + int current_line_whitespace_len = get_first_non_whitespace_column(get_caret_line(i)); + if (get_caret_column(i) == current_line_whitespace_len) { + set_caret_column(0, i == 0, i); + } else { + set_caret_column(current_line_whitespace_len, i == 0, i); + } } else { - set_caret_column(current_line_whitespace_len); + set_caret_column(row_start_col, i == 0, i); } - } else { - set_caret_column(row_start_col); - } - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_to_line_end(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - // Move caret column to end of wrapped row and then to end of text. - Vector<String> rows = get_line_wrapped_text(caret.line); - int wi = get_caret_wrap_index(); - int row_end_col = -1; - for (int i = 0; i < wi + 1; i++) { - row_end_col += rows[i].length(); - } - if (wi == rows.size() - 1 || caret.column == row_end_col) { - set_caret_column(text[caret.line].length()); - } else { - set_caret_column(row_end_col); - } + // Move caret column to end of wrapped row and then to end of text. + Vector<String> rows = get_line_wrapped_text(get_caret_line(i)); + int wi = get_caret_wrap_index(i); + int row_end_col = -1; + for (int j = 0; j < wi + 1; j++) { + row_end_col += rows[j].length(); + } + if (wi == rows.size() - 1 || get_caret_column(i) == row_end_col) { + set_caret_column(text[get_caret_line(i)].length(), i == 0, i); + } else { + set_caret_column(row_end_col, i == 0, i); + } - if (p_select) { - _post_shift_selection(); + carets.write[i].last_fit_x = INT_MAX; + + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_page_up(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), -get_visible_line_count()); - int n_line = caret.line - next_line.x + 1; - set_caret_line(n_line, true, false, next_line.y); + Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), -get_visible_line_count()); + int n_line = get_caret_line(i) - next_line.x + 1; + set_caret_line(n_line, i == 0, false, next_line.y, i); - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_move_caret_page_down(bool p_select) { - if (p_select) { - _pre_shift_selection(); - } else { - deselect(); - } + for (int i = 0; i < carets.size(); i++) { + if (p_select) { + _pre_shift_selection(i); + } else { + deselect(i); + } - Point2i next_line = get_next_visible_line_index_offset_from(caret.line, get_caret_wrap_index(), get_visible_line_count()); - int n_line = caret.line + next_line.x - 1; - set_caret_line(n_line, true, false, next_line.y); + Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), get_visible_line_count()); + int n_line = get_caret_line(i) + next_line.x - 1; + set_caret_line(n_line, i == 0, false, next_line.y, i); - if (p_select) { - _post_shift_selection(); + if (p_select) { + _post_shift_selection(i); + } } + merge_overlapping_carets(); } void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { - if (!editable || (caret.column == 0 && caret.line == 0 && !has_selection())) { + if (!editable) { return; } - if (has_selection() || (!p_all_to_left && !p_word) || caret.column == 0) { - backspace(); - return; - } + start_action(EditAction::ACTION_BACKSPACE); + Vector<int> carets_to_remove; - if (p_all_to_left) { - int caret_current_column = caret.column; - set_caret_column(0); - _remove_text(caret.line, 0, caret.line, caret_current_column); - return; - } + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int caret_idx = caret_edit_order[i]; + if (get_caret_column(caret_idx) == 0 && get_caret_line(caret_idx) == 0 && !has_selection(caret_idx)) { + continue; + } - if (p_word) { - int column = caret.column; - // Check for the case "<word><space><caret>" and ignore the space. - // No need to check for column being 0 since it is checked above. - if (is_whitespace(text[caret.line][caret.column - 1])) { - column -= 1; + if (has_selection(caret_idx) || (!p_all_to_left && !p_word) || get_caret_column(caret_idx) == 0) { + backspace(caret_idx); + continue; } - // Get a list with the indices of the word bounds of the given text line. - const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - if (words.is_empty() || column <= words[0]) { - // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. - column = 0; - } else { - // Otherwise search for the first word break that is smaller than the index from which we're currently deleting. - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < column) { - column = words[i]; + + if (p_all_to_left) { + int caret_current_column = get_caret_column(caret_idx); + set_caret_column(0, caret_idx == 0, caret_idx); + _remove_text(get_caret_line(caret_idx), 0, get_caret_line(caret_idx), caret_current_column); + adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), caret_current_column, get_caret_line(caret_idx), get_caret_column(caret_idx)); + + // Check for any overlapping carets since we removed the entire line. + for (int j = i + 1; j < caret_edit_order.size(); j++) { + // Selection only end on this line, only the one as carets cannot overlap. + if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx) && get_selection_to_line(caret_edit_order[j]) == get_caret_line(caret_idx)) { + carets.write[caret_edit_order[j]].selection.to_column = 0; + break; + } + + // Check for caret. + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) || (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx))) { break; } + + deselect(caret_edit_order[j]); + carets_to_remove.push_back(caret_edit_order[j]); + set_caret_column(0, caret_idx == 0, caret_idx); + i = j; } + continue; } - _remove_text(caret.line, column, caret.line, caret.column); + if (p_word) { + // Save here as the caret may change when resolving overlaps. + int from_column = get_caret_column(caret_idx); + int column = get_caret_column(caret_idx); + // Check for the case "<word><space><caret>" and ignore the space. + // No need to check for column being 0 since it is checked above. + if (is_whitespace(text[get_caret_line(caret_idx)][get_caret_column(caret_idx) - 1])) { + column -= 1; + } + // Get a list with the indices of the word bounds of the given text line. + const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_idx))->get_rid()); + if (words.is_empty() || column <= words[0]) { + // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. + column = 0; + } else { + // Otherwise search for the first word break that is smaller than the index from we're currently deleting. + for (int c = words.size() - 2; c >= 0; c = c - 2) { + if (words[c] < column) { + column = words[c]; + break; + } + } + } - set_caret_line(caret.line, false); - set_caret_column(column); - return; + // Check for any other carets in this range. + int overlapping_caret_index = -1; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + // Check caret and selection in on the right line. + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) && (!has_selection(caret_edit_order[j]) || get_selection_to_line(caret_edit_order[j]) != get_caret_line(caret_idx))) { + break; + } + + // If it has a selection, check it ends with in the range. + if ((has_selection(caret_edit_order[j]) && get_selection_to_column(caret_edit_order[j]) < column)) { + break; + } + + // If it has a selection and it starts outside our word, we need to adjust the selection, and handle it later to prevent overlap. + if ((has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) < column)) { + carets.write[caret_edit_order[j]].selection.to_column = column; + overlapping_caret_index = caret_edit_order[j]; + break; + } + + // Otherwise we can remove it. + if (get_caret_column(caret_edit_order[j]) > column || (has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) > column)) { + deselect(caret_edit_order[j]); + carets_to_remove.push_back(caret_edit_order[j]); + set_caret_column(0, caret_idx == 0, caret_idx); + i = j; + } + } + + _remove_text(get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column); + + set_caret_line(get_caret_line(caret_idx), false, true, 0, caret_idx); + set_caret_column(column, caret_idx == 0, caret_idx); + + // Now we can clean up the overlapping caret. + if (overlapping_caret_index != -1) { + backspace(overlapping_caret_index); + i++; + carets_to_remove.push_back(overlapping_caret_index); + set_caret_column(get_caret_column(overlapping_caret_index), caret_idx == 0, caret_idx); + } + continue; + } + } + + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + remove_caret(carets_to_remove[i]); } + end_action(); } void TextEdit::_delete(bool p_word, bool p_all_to_right) { @@ -2481,82 +2689,206 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { return; } - if (has_selection()) { - delete_selection(); - return; - } - int curline_len = text[caret.line].length(); - - if (caret.line == text.size() - 1 && caret.column == curline_len) { - return; // Last line, last column: Nothing to do. - } + start_action(EditAction::ACTION_DELETE); + Vector<int> carets_to_remove; - int next_line = caret.column < curline_len ? caret.line : caret.line + 1; - int next_column; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int caret_idx = caret_edit_order[i]; + if (has_selection(caret_idx)) { + delete_selection(caret_idx); + continue; + } + int curline_len = text[get_caret_line(caret_idx)].length(); - if (p_all_to_right) { - if (caret.column == curline_len) { - return; + if (get_caret_line(caret_idx) == text.size() - 1 && get_caret_column(caret_idx) == curline_len) { + continue; // Last line, last column: Nothing to do. } - // Delete everything to right of caret - next_column = curline_len; - next_line = caret.line; - } else if (p_word && caret.column < curline_len - 1) { - // Delete next word to right of caret - int line = caret.line; - int column = caret.column; + int next_line = get_caret_column(caret_idx) < curline_len ? get_caret_line(caret_idx) : get_caret_line(caret_idx) + 1; + int next_column; - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > column) { - column = words[i]; - break; + if (p_all_to_right) { + // Get caret furthest to the left + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + + if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + + if (!has_selection(caret_edit_order[j])) { + i = j; + caret_idx = caret_edit_order[i]; + } } - } - next_line = line; - next_column = column; - } else { - // Delete one character - if (caret_mid_grapheme_enabled) { - next_column = caret.column < curline_len ? (caret.column + 1) : 0; + if (get_caret_column(caret_idx) == curline_len) { + continue; + } + + // Delete everything to right of caret + next_column = curline_len; + next_line = get_caret_line(caret_idx); + + // Remove overlapping carets. + for (int j = i - 1; j >= 0; j--) { + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + carets_to_remove.push_back(caret_edit_order[j]); + } + + } else if (p_word && get_caret_column(caret_idx) < curline_len - 1) { + // Delete next word to right of caret + int line = get_caret_line(caret_idx); + int column = get_caret_column(caret_idx); + + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); + for (int j = 1; j < words.size(); j = j + 2) { + if (words[j] > column) { + column = words[j]; + break; + } + } + + next_line = line; + next_column = column; + + // Remove overlapping carets. + for (int j = i - 1; j >= 0; j--) { + if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) { + break; + } + + if (get_caret_column(caret_edit_order[j]) > column) { + break; + } + carets_to_remove.push_back(caret_edit_order[j]); + } } else { - next_column = caret.column < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(caret.line)->get_rid(), (caret.column)) : 0; + // Delete one character + if (caret_mid_grapheme_enabled) { + next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0; + } else { + next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0; + } + + // Remove overlapping carets. + if (i > 0) { + int prev_caret_idx = caret_edit_order[i - 1]; + if (get_caret_line(prev_caret_idx) == next_line && get_caret_column(prev_caret_idx) == next_column) { + carets_to_remove.push_back(prev_caret_idx); + } + } } + + _remove_text(get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column); + adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column); + } + + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + remove_caret(carets_to_remove[i]); } - _remove_text(caret.line, caret.column, next_line, next_column); + // If we are deleting from the end of a line, due to column preservation we could still overlap with another caret. + merge_overlapping_carets(); + end_action(); queue_redraw(); } void TextEdit::_move_caret_document_start(bool p_select) { + remove_secondary_carets(); if (p_select) { - _pre_shift_selection(); + _pre_shift_selection(0); } else { deselect(); } - set_caret_line(0); + set_caret_line(0, false); set_caret_column(0); if (p_select) { - _post_shift_selection(); + _post_shift_selection(0); } } void TextEdit::_move_caret_document_end(bool p_select) { + remove_secondary_carets(); if (p_select) { - _pre_shift_selection(); + _pre_shift_selection(0); } else { deselect(); } set_caret_line(get_last_unhidden_line(), true, false, 9999); - set_caret_column(text[caret.line].length()); + set_caret_column(text[get_caret_line()].length()); if (p_select) { - _post_shift_selection(); + _post_shift_selection(0); + } +} + +bool TextEdit::_clear_carets_and_selection() { + if (get_caret_count() > 1) { + remove_secondary_carets(); + return true; + } + + if (has_selection()) { + deselect(); + return true; + } + + return false; +} + +void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const { + if (p_last_fit_x == -1) { + p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column); + } + + // Calculate the new line and wrap index + p_new_line = p_old_line; + int caret_wrap_index = p_old_wrap_index; + if (p_below) { + if (caret_wrap_index < get_line_wrap_count(p_new_line)) { + caret_wrap_index++; + } else { + p_new_line++; + caret_wrap_index = 0; + } + } else { + if (caret_wrap_index == 0) { + p_new_line--; + caret_wrap_index = get_line_wrap_count(p_new_line); + } else { + caret_wrap_index--; + } + } + + // Boundary checks + if (p_new_line < 0) { + p_new_line = 0; + } + if (p_new_line >= text.size()) { + p_new_line = text.size() - 1; + } + + p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index); + if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) { + Vector<String> rows = get_line_wrapped_text(p_new_line); + int row_end_col = 0; + for (int i = 0; i < caret_wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (p_new_column >= row_end_col) { + p_new_column -= 1; + } } } @@ -2618,6 +2950,7 @@ void TextEdit::_update_caches() { /* Selection */ font_selected_color = get_theme_color(SNAME("font_selected_color")); selection_color = get_theme_color(SNAME("selection_color")); + use_selected_font_color = font_selected_color != Color(0, 0, 0, 0); /* Visual. */ style_normal = get_theme_stylebox(SNAME("normal")); @@ -2676,7 +3009,7 @@ bool TextEdit::is_text_field() const { } Variant TextEdit::get_drag_data(const Point2 &p_point) { - if (selection.active && selection.drag_attempt) { + if (has_selection() && selection_drag_attempt) { String t = get_selected_text(); Label *l = memnew(Label); l->set_text(t); @@ -2703,34 +3036,41 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); int caret_row_tmp = pos.y; int caret_column_tmp = pos.x; - if (selection.drag_attempt) { - selection.drag_attempt = false; + if (selection_drag_attempt) { + selection_drag_attempt = false; if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) { + // Set caret back at selection for undo / redo. + set_caret_line(get_selection_to_line(), false, false); + set_caret_column(get_selection_to_column()); + begin_complex_operation(); if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) { - if (caret_row_tmp > selection.to_line) { - caret_row_tmp = caret_row_tmp - (selection.to_line - selection.from_line); - } else if (caret_row_tmp == selection.to_line && caret_column_tmp >= selection.to_column) { - caret_column_tmp = caret_column_tmp - (selection.to_column - selection.from_column); + if (caret_row_tmp > get_selection_to_line()) { + caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line()); + } else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) { + caret_column_tmp = caret_column_tmp - (get_selection_to_column() - get_selection_from_column()); } delete_selection(); } else { deselect(); } + remove_secondary_carets(); set_caret_line(caret_row_tmp, true, false); set_caret_column(caret_column_tmp); insert_text_at_caret(p_data); end_complex_operation(); } } else if (is_mouse_over_selection()) { - caret_row_tmp = selection.from_line; - caret_column_tmp = selection.from_column; + remove_secondary_carets(); + caret_row_tmp = get_selection_from_line(); + caret_column_tmp = get_selection_from_column(); set_caret_line(caret_row_tmp, true, false); set_caret_column(caret_column_tmp); insert_text_at_caret(p_data); grab_focus(); } else { + remove_secondary_carets(); deselect(); set_caret_line(caret_row_tmp, true, false); set_caret_column(caret_column_tmp); @@ -2738,8 +3078,8 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { grab_focus(); } - if (caret_row_tmp != caret.line || caret_column_tmp != caret.column) { - select(caret_row_tmp, caret_column_tmp, caret.line, caret.column); + if (caret_row_tmp != get_caret_line() || caret_column_tmp != get_caret_column()) { + select(caret_row_tmp, caret_column_tmp, get_caret_line(), get_caret_column()); } } } @@ -2756,7 +3096,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { continue; } - if (p_pos.x > left_margin && p_pos.x <= (left_margin + gutters[i].width) - 3) { + if (p_pos.x >= left_margin && p_pos.x < left_margin + gutters[i].width) { if (gutters[i].clickable || is_line_gutter_clickable(row, i)) { return CURSOR_POINTING_HAND; } @@ -2975,6 +3315,7 @@ void TextEdit::clear() { void TextEdit::_clear() { if (editable && undo_enabled) { + remove_secondary_carets(); _move_caret_document_start(false); begin_complex_operation(); @@ -2990,13 +3331,14 @@ void TextEdit::_clear() { clear_undo_history(); text.clear(); + remove_secondary_carets(); set_caret_line(0, false); set_caret_column(0); - caret.x_ofs = 0; - caret.line_ofs = 0; - caret.wrap_ofs = 0; - caret.last_fit_x = 0; - selection.active = false; + first_visible_col = 0; + first_visible_line = 0; + first_visible_line_wrap_ofs = 0; + carets.write[0].last_fit_x = 0; + deselect(); emit_signal(SNAME("lines_edited_from"), old_text_size, 0); } @@ -3009,6 +3351,7 @@ void TextEdit::set_text(const String &p_text) { } if (undo_enabled) { + remove_secondary_carets(); set_caret_line(0); set_caret_column(0); @@ -3064,11 +3407,14 @@ void TextEdit::set_line(int p_line, const String &p_new_text) { begin_complex_operation(); _remove_text(p_line, 0, p_line, text[p_line].length()); _insert_text(p_line, 0, p_new_text); - if (caret.line == p_line && caret.column > p_new_text.length()) { - set_caret_column(p_new_text.length(), false); - } - if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) { - selection.to_column = text[p_line].length(); + for (int i = 0; i < carets.size(); i++) { + if (get_caret_line(i) == p_line && get_caret_column(i) > p_new_text.length()) { + set_caret_column(p_new_text.length(), false, i); + } + + if (has_selection(i) && p_line == get_selection_to_line(i) && get_selection_to_column(i) > text[p_line].length()) { + carets.write[i].selection.to_column = text[p_line].length(); + } } end_complex_operation(); } @@ -3135,42 +3481,54 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { ERR_FAIL_INDEX(p_at, text.size()); _insert_text(p_at, 0, p_text + "\n"); - if (caret.line >= p_at) { - // offset caret when located after inserted line - set_caret_line(caret.line + 1, false); - } - if (has_selection()) { - if (selection.from_line >= p_at) { - // offset selection when located after inserted line - ++selection.from_line; - ++selection.to_line; - } else if (selection.to_line >= p_at) { - // extend selection that includes inserted line - ++selection.to_line; + + for (int i = 0; i < carets.size(); i++) { + if (get_caret_line(i) >= p_at) { + // offset caret when located after inserted line + set_caret_line(get_caret_line(i) + 1, false, true, 0, i); + } + if (has_selection(i)) { + if (get_selection_from_line(i) >= p_at) { + // offset selection when located after inserted line + select(get_selection_from_line(i) + 1, get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i); + } else if (get_selection_to_line(i) >= p_at) { + // extend selection that includes inserted line + select(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i); + } } } + + // Need to apply the above adjustments to the undo / redo carets. + current_op.end_carets = carets; queue_redraw(); } -void TextEdit::insert_text_at_caret(const String &p_text) { - bool had_selection = has_selection(); - if (had_selection) { - begin_complex_operation(); - } +void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); - delete_selection(); + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - int new_column, new_line; - _insert_text(caret.line, caret.column, p_text, &new_line, &new_column); - _update_scrollbars(); + delete_selection(i); - set_caret_line(new_line, false); - set_caret_column(new_column); - queue_redraw(); + int from_line = get_caret_line(i); + int from_col = get_caret_column(i); - if (had_selection) { - end_complex_operation(); + int new_column, new_line; + _insert_text(from_line, from_col, p_text, &new_line, &new_column); + _update_scrollbars(); + + set_caret_line(new_line, false, true, 0, i); + set_caret_column(new_column, i == 0, i); + + adjust_carets_after_edit(i, new_line, new_column, from_line, from_col); } + end_complex_operation(); + queue_redraw(); } void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -3294,46 +3652,46 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p } // Overridable actions -void TextEdit::handle_unicode_input(const uint32_t p_unicode) { - if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode)) { +void TextEdit::handle_unicode_input(const uint32_t p_unicode, int p_caret) { + if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode, p_caret)) { return; } - _handle_unicode_input_internal(p_unicode); + _handle_unicode_input_internal(p_unicode, p_caret); } -void TextEdit::backspace() { - if (GDVIRTUAL_CALL(_backspace)) { +void TextEdit::backspace(int p_caret) { + if (GDVIRTUAL_CALL(_backspace, p_caret)) { return; } - _backspace_internal(); + _backspace_internal(p_caret); } -void TextEdit::cut() { - if (GDVIRTUAL_CALL(_cut)) { +void TextEdit::cut(int p_caret) { + if (GDVIRTUAL_CALL(_cut, p_caret)) { return; } - _cut_internal(); + _cut_internal(p_caret); } -void TextEdit::copy() { - if (GDVIRTUAL_CALL(_copy)) { +void TextEdit::copy(int p_caret) { + if (GDVIRTUAL_CALL(_copy, p_caret)) { return; } - _copy_internal(); + _copy_internal(p_caret); } -void TextEdit::paste() { - if (GDVIRTUAL_CALL(_paste)) { +void TextEdit::paste(int p_caret) { + if (GDVIRTUAL_CALL(_paste, p_caret)) { return; } - _paste_internal(); + _paste_internal(p_caret); } -void TextEdit::paste_primary_clipboard() { - if (GDVIRTUAL_CALL(_paste_primary_clipboard)) { +void TextEdit::paste_primary_clipboard(int p_caret) { + if (GDVIRTUAL_CALL(_paste_primary_clipboard, p_caret)) { return; } - _paste_primary_clipboard_internal(); + _paste_primary_clipboard_internal(p_caret); } // Context menu. @@ -3470,22 +3828,53 @@ void TextEdit::menu_option(int p_option) { } /* Versioning */ +void TextEdit::start_action(EditAction p_action) { + if (current_action != p_action) { + if (current_action != EditAction::ACTION_NONE) { + in_action = false; + pending_action_end = false; + end_complex_operation(); + } + + if (p_action != EditAction::ACTION_NONE) { + in_action = true; + begin_complex_operation(); + } + } else if (current_action != EditAction::ACTION_NONE) { + pending_action_end = false; + } + current_action = p_action; +} + +void TextEdit::end_action() { + if (current_action != EditAction::ACTION_NONE) { + pending_action_end = true; + } +} + +TextEdit::EditAction TextEdit::get_current_action() const { + return current_action; +} + void TextEdit::begin_complex_operation() { _push_current_op(); if (complex_operation_count == 0) { next_operation_is_complex = true; + current_op.start_carets = carets; } complex_operation_count++; } void TextEdit::end_complex_operation() { _push_current_op(); - ERR_FAIL_COND(undo_stack.size() == 0); complex_operation_count = MAX(complex_operation_count - 1, 0); if (complex_operation_count > 0) { return; } + ERR_FAIL_COND(undo_stack.size() == 0); + + undo_stack.back()->get().end_carets = carets; if (undo_stack.back()->get().chain_forward) { undo_stack.back()->get().chain_forward = false; return; @@ -3511,6 +3900,9 @@ void TextEdit::undo() { return; } + if (in_action) { + pending_action_end = true; + } _push_current_op(); if (undo_stack_pos == nullptr) { @@ -3545,25 +3937,36 @@ void TextEdit::undo() { } } - if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) { - select(op.from_line, op.from_column, op.to_line, op.to_column); + _update_scrollbars(); + bool dirty_carets = carets.size() != undo_stack_pos->get().start_carets.size(); + if (!dirty_carets) { + for (int i = 0; i < carets.size(); i++) { + if (carets[i].line != undo_stack_pos->get().start_carets[i].line || carets[i].column != undo_stack_pos->get().start_carets[i].column) { + dirty_carets = true; + break; + } + } } - _update_scrollbars(); - if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) { - set_caret_line(undo_stack_pos->get().to_line, false); - set_caret_column(undo_stack_pos->get().to_column); - } else { - set_caret_line(undo_stack_pos->get().from_line, false); - set_caret_column(undo_stack_pos->get().from_column); + carets = undo_stack_pos->get().start_carets; + + if (dirty_carets && !caret_pos_dirty) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); + } + caret_pos_dirty = true; } - queue_redraw(); + adjust_viewport_to_caret(); } void TextEdit::redo() { if (!editable) { return; } + + if (in_action) { + pending_action_end = true; + } _push_current_op(); if (undo_stack_pos == nullptr) { @@ -3589,10 +3992,26 @@ void TextEdit::redo() { } _update_scrollbars(); - set_caret_line(undo_stack_pos->get().to_line, false); - set_caret_column(undo_stack_pos->get().to_column); + bool dirty_carets = carets.size() != undo_stack_pos->get().end_carets.size(); + if (!dirty_carets) { + for (int i = 0; i < carets.size(); i++) { + if (carets[i].line != undo_stack_pos->get().end_carets[i].line || carets[i].column != undo_stack_pos->get().end_carets[i].column) { + dirty_carets = true; + break; + } + } + } + + carets = undo_stack_pos->get().end_carets; undo_stack_pos = undo_stack_pos->next(); - queue_redraw(); + + if (dirty_carets && !caret_pos_dirty) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); + } + caret_pos_dirty = true; + } + adjust_viewport_to_caret(); } void TextEdit::clear_undo_history() { @@ -3603,7 +4022,7 @@ void TextEdit::clear_undo_history() { } bool TextEdit::is_insert_text_operation() const { - return (current_op.type == TextOperation::TYPE_INSERT); + return (current_op.type == TextOperation::TYPE_INSERT || current_action == EditAction::ACTION_TYPING); } void TextEdit::tag_saved_version() { @@ -3792,7 +4211,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ int wrap_index = 0; if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SIGN(rows))); + Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows))); wrap_index = f_ofs.y; if (rows < 0) { @@ -3820,7 +4239,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ int col = 0; int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); - colx += caret.x_ofs; + colx += first_visible_col; col = _get_char_pos_for_line(colx, row, wrap_index); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { // Move back one if we are at the end of the row. @@ -3895,21 +4314,21 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { // calculate visible lines int minimap_visible_lines = get_minimap_visible_lines(); int visible_rows = get_visible_line_count() + 1; - int first_visible_line = get_first_visible_line() - 1; + int first_vis_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += get_line_wrap_count(first_visible_line + 1); + draw_amount += get_line_wrap_count(first_vis_line + 1); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); // calculate viewport size and y offset int viewport_height = (draw_amount - 1) * minimap_line_height; int control_height = _get_control_height() - viewport_height; - int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + int viewport_offset_y = round(get_scroll_pos_for_line(first_vis_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); // calculate the first line. int num_lines_before = round((viewport_offset_y) / minimap_line_height); - int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; - if (first_visible_line > 0 && minimap_line >= 0) { - minimap_line -= get_next_visible_line_index_offset_from(first_visible_line, 0, -num_lines_before).x; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_vis_line; + if (first_vis_line > 0 && minimap_line >= 0) { + minimap_line -= get_next_visible_line_index_offset_from(first_vis_line, 0, -num_lines_before).x; minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0); } else { minimap_line = 0; @@ -3917,7 +4336,7 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { int row = minimap_line + Math::floor(rows); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SIGN(rows))).x - 1; + int f_ofs = get_next_visible_line_index_offset_from(minimap_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows))).x - 1; if (rows < 0) { row = minimap_line - f_ofs; } else { @@ -3940,19 +4359,31 @@ bool TextEdit::is_dragging_cursor() const { return dragging_selection || dragging_minimap; } -bool TextEdit::is_mouse_over_selection(bool p_edges) const { - if (!has_selection()) { - return false; - } - Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - int row = pos.y; - int col = pos.x; - if (p_edges) { - if ((row == selection.from_line && col == selection.from_column) || (row == selection.to_line && col == selection.to_column)) { +bool TextEdit::is_mouse_over_selection(bool p_edges, int p_caret) const { + for (int i = 0; i < carets.size(); i++) { + if (p_caret != -1 && p_caret != i) { + continue; + } + + if (!has_selection(i)) { + continue; + } + + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + int row = pos.y; + int col = pos.x; + if (p_edges) { + if ((row == get_selection_from_line(i) && col == get_selection_from_column(i)) || (row == get_selection_to_line(i) && col == get_selection_to_column(i))) { + return true; + } + } + + if (row >= get_selection_from_line(i) && row <= get_selection_to_line(i) && (row > get_selection_from_line(i) || col > get_selection_from_column(i)) && (row < get_selection_to_line(i) || col < get_selection_to_column(i))) { return true; } } - return (row >= selection.from_line && row <= selection.to_line && (row > selection.from_line || col > selection.from_column) && (row < selection.to_line || col < selection.to_column)); + + return false; } /* Caret */ @@ -4015,15 +4446,295 @@ bool TextEdit::is_caret_mid_grapheme_enabled() const { return caret_mid_grapheme_enabled; } -bool TextEdit::is_caret_visible() const { - return caret.visible; +void TextEdit::set_multiple_carets_enabled(bool p_enabled) { + multi_carets_enabled = p_enabled; + if (!multi_carets_enabled) { + remove_secondary_carets(); + } +} + +bool TextEdit::is_multiple_carets_enabled() const { + return multi_carets_enabled; +} + +int TextEdit::add_caret(int p_line, int p_col) { + if (!multi_carets_enabled) { + return -1; + } + + p_line = CLAMP(p_line, 0, text.size() - 1); + p_col = CLAMP(p_col, 0, get_line(p_line).length()); + + for (int i = 0; i < carets.size(); i++) { + if (get_caret_line(i) == p_line && get_caret_column(i) == p_col) { + return -1; + } + + if (has_selection(i)) { + if (p_line >= get_selection_from_line(i) && p_line <= get_selection_to_line(i) && (p_line > get_selection_from_line(i) || p_col >= get_selection_from_column(i)) && (p_line < get_selection_to_line(i) || p_col <= get_selection_to_column(i))) { + return -1; + } + } + } + + carets.push_back(Caret()); + set_caret_line(p_line, false, false, 0, carets.size() - 1); + set_caret_column(p_col, false, carets.size() - 1); + caret_index_edit_dirty = true; + return carets.size() - 1; +} + +void TextEdit::remove_caret(int p_caret) { + ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed."); + ERR_FAIL_INDEX(p_caret, carets.size()); + carets.remove_at(p_caret); + caret_index_edit_dirty = true; +} + +void TextEdit::remove_secondary_carets() { + carets.resize(1); + caret_index_edit_dirty = true; + queue_redraw(); +} + +void TextEdit::merge_overlapping_carets() { + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size() - 1; i++) { + int first_caret = caret_edit_order[i]; + int second_caret = caret_edit_order[i + 1]; + + // Both have selection. + if (has_selection(first_caret) && has_selection(second_caret)) { + bool should_merge = false; + if (get_selection_from_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_from_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_from_line(first_caret) > get_selection_from_line(second_caret) || get_selection_from_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_from_line(first_caret) < get_selection_to_line(second_caret) || get_selection_from_column(first_caret) <= get_selection_to_column(second_caret))) { + should_merge = true; + } + + if (get_selection_to_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_to_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_to_line(first_caret) > get_selection_from_line(second_caret) || get_selection_to_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_to_line(first_caret) < get_selection_to_line(second_caret) || get_selection_to_column(first_caret) <= get_selection_to_column(second_caret))) { + should_merge = true; + } + + if (!should_merge) { + continue; + } + + // Save the newest one for click+drag. + int caret_to_save = first_caret; + int caret_to_remove = second_caret; + if (first_caret < second_caret) { + caret_to_save = second_caret; + caret_to_remove = first_caret; + } + + int from_line = MIN(get_selection_from_line(caret_to_save), get_selection_from_line(caret_to_remove)); + int to_line = MAX(get_selection_to_line(caret_to_save), get_selection_to_line(caret_to_remove)); + int from_col = get_selection_from_column(caret_to_save); + int to_col = get_selection_to_column(caret_to_save); + int selection_line = get_selection_line(caret_to_save); + int selection_col = get_selection_column(caret_to_save); + + bool at_from = (get_caret_line(caret_to_save) == get_selection_from_line(caret_to_save) && get_caret_column(caret_to_save) == get_selection_from_column(caret_to_save)); + + if (at_from) { + if (get_selection_line(caret_to_remove) > get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) >= get_selection_column(caret_to_save))) { + selection_line = get_selection_line(caret_to_remove); + selection_col = get_selection_column(caret_to_remove); + } + } else if (get_selection_line(caret_to_remove) < get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) <= get_selection_column(caret_to_save))) { + selection_line = get_selection_line(caret_to_remove); + selection_col = get_selection_column(caret_to_remove); + } + + if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save) || (get_selection_from_line(caret_to_remove) == get_selection_from_line(caret_to_save) && get_selection_from_column(caret_to_remove) <= get_selection_from_column(caret_to_save))) { + from_col = get_selection_from_column(caret_to_remove); + } else { + to_col = get_selection_to_column(caret_to_remove); + } + + select(from_line, from_col, to_line, to_col, caret_to_save); + set_selection_mode(selecting_mode, selection_line, selection_col, caret_to_save); + set_caret_line((at_from ? from_line : to_line), caret_to_save == 0, true, 0, caret_to_save); + set_caret_column((at_from ? from_col : to_col), caret_to_save == 0, caret_to_save); + remove_caret(caret_to_remove); + i--; + caret_edit_order = get_caret_index_edit_order(); + continue; + } + + // Only first has selection. + if (has_selection(first_caret)) { + if (get_caret_line(second_caret) >= get_selection_from_line(first_caret) && get_caret_line(second_caret) <= get_selection_to_line(first_caret) && (get_caret_line(second_caret) > get_selection_from_line(first_caret) || get_caret_column(second_caret) >= get_selection_from_column(first_caret)) && (get_caret_line(second_caret) < get_selection_to_line(first_caret) || get_caret_column(second_caret) <= get_selection_to_column(first_caret))) { + remove_caret(second_caret); + caret_edit_order = get_caret_index_edit_order(); + i--; + } + continue; + } + + // Only second has Selection. + if (has_selection(second_caret)) { + if (get_caret_line(first_caret) >= get_selection_from_line(second_caret) && get_caret_line(first_caret) <= get_selection_to_line(second_caret) && (get_caret_line(first_caret) > get_selection_from_line(second_caret) || get_caret_column(first_caret) >= get_selection_from_column(second_caret)) && (get_caret_line(first_caret) < get_selection_to_line(second_caret) || get_caret_column(first_caret) <= get_selection_to_column(second_caret))) { + remove_caret(first_caret); + caret_edit_order = get_caret_index_edit_order(); + i--; + } + continue; + } + + // Both have no selection. + if (get_caret_line(first_caret) == get_caret_line(second_caret) && get_caret_column(first_caret) == get_caret_column(second_caret)) { + // Save the newest one for click+drag. + if (first_caret < second_caret) { + remove_caret(first_caret); + } else { + remove_caret(second_caret); + } + i--; + caret_edit_order = get_caret_index_edit_order(); + continue; + } + } +} + +int TextEdit::get_caret_count() const { + return carets.size(); +} + +void TextEdit::add_caret_at_carets(bool p_below) { + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &caret_index : caret_edit_order) { + const int caret_line = get_caret_line(caret_index); + const int caret_column = get_caret_column(caret_index); + + // The last fit x will be cleared if the caret has a selection, + // but if it does not have a selection the last fit x will be + // transferred to the new caret + int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x; + if (has_selection(caret_index)) { + // If the selection goes over multiple lines, deselect it. + if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) { + deselect(caret_index); + } else { + caret_from_column = get_selection_from_column(caret_index); + caret_to_column = get_selection_to_column(caret_index); + caret_last_fit_x = -1; + carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column); + } + } + + // Get the line and column of the new caret as if you would move the caret by pressing the arrow keys + int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0; + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x); + + // If the caret does have a selection calculate the new from and to columns + if (caret_from_column != caret_to_column) { + // We only need to calculate the selection columns if the column of the caret changed + if (caret_column != new_caret_column) { + int _; // unused placeholder for p_new_line + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column); + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column); + } else { + new_caret_from_column = caret_from_column; + new_caret_to_column = caret_to_column; + } + } + + // Add the new caret + const int new_caret_index = add_caret(new_caret_line, new_caret_column); + + if (new_caret_index == -1) { + continue; + } + // Also add the selection if there should be one + if (new_caret_from_column != new_caret_to_column) { + select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index); + // Necessary to properly modify the selection after adding the new caret + carets.write[new_caret_index].selection.selecting_line = new_caret_line; + carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column; + continue; + } + + // Copy the last fit x over + carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x; + } + + merge_overlapping_carets(); + queue_redraw(); +} + +Vector<int> TextEdit::get_caret_index_edit_order() { + if (!caret_index_edit_dirty) { + return caret_index_edit_order; + } + + caret_index_edit_order.clear(); + caret_index_edit_order.push_back(0); + for (int i = 1; i < carets.size(); i++) { + int j = 0; + + int line = carets[i].selection.active ? carets[i].selection.to_line : carets[i].line; + int col = carets[i].selection.active ? carets[i].selection.to_column : carets[i].column; + + for (; j < caret_index_edit_order.size(); j++) { + int idx = caret_index_edit_order[j]; + int other_line = carets[idx].selection.active ? carets[idx].selection.to_line : carets[idx].line; + int other_col = carets[idx].selection.active ? carets[idx].selection.to_column : carets[idx].column; + if (line > other_line || (line == other_line && col > other_col)) { + break; + } + } + caret_index_edit_order.insert(j, i); + } + caret_index_edit_dirty = false; + return caret_index_edit_order; +} + +void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) { + int edit_height = p_from_line - p_to_line; + int edit_size = ((edit_height == 0) ? p_from_col : 0) - p_to_col; + + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int j = 0; j < caret_edit_order.size(); j++) { + if (caret_edit_order[j] == p_caret) { + return; + } + + // Adjust caret. + // set_caret_line could adjust the column, so save here. + int cc = get_caret_column(caret_edit_order[j]); + if (edit_height != 0) { + set_caret_line(get_caret_line(caret_edit_order[j]) + edit_height, false, true, 0, caret_edit_order[j]); + } + if (get_caret_line(p_caret) == get_caret_line(caret_edit_order[j])) { + set_caret_column(cc + edit_size, false, caret_edit_order[j]); + } + + // Adjust selection. + if (!has_selection(caret_edit_order[j])) { + continue; + } + if (edit_height != 0) { + carets.write[caret_edit_order[j]].selection.from_line += edit_height; + carets.write[caret_edit_order[j]].selection.to_line += edit_height; + } + if (get_caret_line(p_caret) == carets[caret_edit_order[j]].selection.from_line) { + carets.write[caret_edit_order[j]].selection.from_column += edit_size; + } + } +} + +bool TextEdit::is_caret_visible(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return carets[p_caret].visible; } -Point2 TextEdit::get_caret_draw_pos() const { - return caret.draw_pos; +Point2 TextEdit::get_caret_draw_pos(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), Point2(0, 0)); + return carets[p_caret].draw_pos; } -void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { +void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); if (setting_caret_line) { return; } @@ -4052,10 +4763,10 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ } } } - bool caret_moved = caret.line != p_line; - caret.line = p_line; + bool caret_moved = get_caret_line(p_caret) != p_line; + carets.write[p_caret].line = p_line; - int n_col = _get_char_pos_for_line(caret.last_fit_x, p_line, p_wrap_index); + int n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index); if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) { Vector<String> rows = get_line_wrapped_text(p_line); int row_end_col = 0; @@ -4066,11 +4777,11 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ n_col -= 1; } } - caret_moved = (caret_moved || caret.column != n_col); - caret.column = n_col; + caret_moved = (caret_moved || get_caret_column(p_caret) != n_col); + carets.write[p_caret].column = n_col; if (is_inside_tree() && p_adjust_viewport) { - adjust_viewport_to_caret(); + adjust_viewport_to_caret(p_caret); } setting_caret_line = false; @@ -4083,25 +4794,27 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ } } -int TextEdit::get_caret_line() const { - return caret.line; +int TextEdit::get_caret_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return carets[p_caret].line; } -void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { +void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); if (p_col < 0) { p_col = 0; } - if (p_col > get_line(caret.line).length()) { - p_col = get_line(caret.line).length(); + if (p_col > get_line(get_caret_line(p_caret)).length()) { + p_col = get_line(get_caret_line(p_caret)).length(); } - bool caret_moved = caret.column != p_col; - caret.column = p_col; + bool caret_moved = get_caret_column(p_caret) != p_col; + carets.write[p_caret].column = p_col; - caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line); + carets.write[p_caret].last_fit_x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); if (is_inside_tree() && p_adjust_viewport) { - adjust_viewport_to_caret(); + adjust_viewport_to_caret(p_caret); } if (caret_moved && !caret_pos_dirty) { @@ -4112,24 +4825,36 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { } } -int TextEdit::get_caret_column() const { - return caret.column; +int TextEdit::get_caret_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return carets[p_caret].column; } -int TextEdit::get_caret_wrap_index() const { - return get_line_wrap_index_at_column(caret.line, caret.column); +int TextEdit::get_caret_wrap_index(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), 0); + return get_line_wrap_index_at_column(get_caret_line(p_caret), get_caret_column(p_caret)); } -String TextEdit::get_word_under_caret() const { - ERR_FAIL_INDEX_V(caret.line, text.size(), ""); - ERR_FAIL_INDEX_V(caret.column, text[caret.line].length() + 1, ""); - PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - for (int i = 0; i < words.size(); i = i + 2) { - if (words[i] <= caret.column && words[i + 1] > caret.column) { - return text[caret.line].substr(words[i], words[i + 1] - words[i]); +String TextEdit::get_word_under_caret(int p_caret) const { + ERR_FAIL_COND_V(p_caret > carets.size(), ""); + + StringBuilder selected_text; + for (int c = 0; c < carets.size(); c++) { + if (p_caret != -1 && p_caret != c) { + continue; + } + + PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if (words[i] <= get_caret_column(c) && words[i + 1] > get_caret_column(c)) { + selected_text += text[get_caret_line(c)].substr(words[i], words[i + 1] - words[i]); + if (p_caret == -1 && c != carets.size() - 1) { + selected_text += "\n"; + } + } } } - return ""; + return selected_text.as_string(); } /* Selection. */ @@ -4155,7 +4880,7 @@ void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) { } deselect_on_focus_loss_enabled = p_enabled; - if (p_enabled && selection.active && !has_focus()) { + if (p_enabled && has_selection() && !has_focus()) { deselect(); } } @@ -4172,30 +4897,24 @@ bool TextEdit::is_drag_and_drop_selection_enabled() const { return drag_and_drop_selection_enabled; } -void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; -} - -bool TextEdit::is_overriding_selected_font_color() const { - return override_selected_font_color; -} +void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); -void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column) { - selection.selecting_mode = p_mode; + selecting_mode = p_mode; if (p_line >= 0) { ERR_FAIL_INDEX(p_line, text.size()); - selection.selecting_line = p_line; - selection.selecting_column = CLAMP(selection.selecting_column, 0, text[selection.selecting_line].length()); + carets.write[p_caret].selection.selecting_line = p_line; + carets.write[p_caret].selection.selecting_column = CLAMP(carets[p_caret].selection.selecting_column, 0, text[carets[p_caret].selection.selecting_line].length()); } if (p_column >= 0) { - ERR_FAIL_INDEX(selection.selecting_line, text.size()); - ERR_FAIL_INDEX(p_column, text[selection.selecting_line].length()); - selection.selecting_column = p_column; + ERR_FAIL_INDEX(carets[p_caret].selection.selecting_line, text.size()); + ERR_FAIL_INDEX(p_column, text[carets[p_caret].selection.selecting_line].length() + 1); + carets.write[p_caret].selection.selecting_column = p_column; } } TextEdit::SelectionMode TextEdit::get_selection_mode() const { - return selection.selecting_mode; + return selecting_mode; } void TextEdit::select_all() { @@ -4206,21 +4925,19 @@ void TextEdit::select_all() { if (text.size() == 1 && text[0].length() == 0) { return; } - selection.active = true; - selection.from_line = 0; - selection.from_column = 0; - selection.selecting_line = 0; - selection.selecting_column = 0; - selection.to_line = text.size() - 1; - selection.to_column = text[selection.to_line].length(); - selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; - selection.shiftclick_left = true; - set_caret_line(selection.to_line, false); - set_caret_column(selection.to_column, false); + + remove_secondary_carets(); + select(0, 0, text.size() - 1, text[text.size() - 1].length()); + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, 0, 0); + carets.write[0].selection.shiftclick_left = true; + set_caret_line(get_selection_to_line(), false); + set_caret_column(get_selection_to_column(), false); queue_redraw(); } -void TextEdit::select_word_under_caret() { +void TextEdit::select_word_under_caret(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + if (!selecting_enabled) { return; } @@ -4229,36 +4946,83 @@ void TextEdit::select_word_under_caret() { return; } - if (selection.active) { - /* Allow toggling selection by pressing the shortcut a second time. */ - /* This is also usable as a general-purpose "deselect" shortcut after */ - /* selecting anything. */ - deselect(); + for (int c = 0; c < carets.size(); c++) { + if (p_caret != -1 && p_caret != c) { + continue; + } + + if (has_selection(c)) { + // Allow toggling selection by pressing the shortcut a second time. + // This is also usable as a general-purpose "deselect" shortcut after + // selecting anything. + deselect(c); + continue; + } + + int begin = 0; + int end = 0; + const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if ((words[i] <= get_caret_column(c) && words[i + 1] >= get_caret_column(c)) || (i == words.size() - 2 && get_caret_column(c) == words[i + 1])) { + begin = words[i]; + end = words[i + 1]; + break; + } + } + + // No word found. + if (begin == 0 && end == 0) { + continue; + } + + select(get_caret_line(c), begin, get_caret_line(c), end, c); + // Move the caret to the end of the word for easier editing. + set_caret_column(end, false, c); + } + merge_overlapping_carets(); +} + +void TextEdit::add_selection_for_next_occurrence() { + if (!selecting_enabled || !is_multiple_carets_enabled()) { return; } - int begin = 0; - int end = 0; - const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); - for (int i = 0; i < words.size(); i = i + 2) { - if ((words[i] <= caret.column && words[i + 1] >= caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) { - begin = words[i]; - end = words[i + 1]; - break; - } + if (text.size() == 1 && text[0].length() == 0) { + return; + } + + // Always use the last caret, to correctly search for + // the next occurrence that comes after this caret. + int caret = get_caret_count() - 1; + + if (!has_selection(caret)) { + select_word_under_caret(caret); + return; } - // No word found. - if (begin == 0 && end == 0) { + const String &highlighted_text = get_selected_text(caret); + int column = get_selection_from_column(caret) + 1; + int line = get_caret_line(caret); + + const Point2i next_occurrence = search(highlighted_text, SEARCH_MATCH_CASE, line, column); + + if (next_occurrence.x == -1 || next_occurrence.y == -1) { return; } - select(caret.line, begin, caret.line, end); - /* Move the caret to the end of the word for easier editing. */ - set_caret_column(end, false); + int to_column = get_selection_to_column(caret) + 1; + int end = next_occurrence.x + (to_column - column); + int new_caret = add_caret(next_occurrence.y, end); + + if (new_caret != -1) { + select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret); + adjust_viewport_to_caret(new_caret); + merge_overlapping_carets(); + } } -void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { +void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); if (!selecting_enabled) { return; } @@ -4287,91 +5051,143 @@ void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_t p_to_column = 0; } - selection.from_line = p_from_line; - selection.from_column = p_from_column; - selection.to_line = p_to_line; - selection.to_column = p_to_column; + carets.write[p_caret].selection.from_line = p_from_line; + carets.write[p_caret].selection.from_column = p_from_column; + carets.write[p_caret].selection.to_line = p_to_line; + carets.write[p_caret].selection.to_column = p_to_column; - selection.active = true; + carets.write[p_caret].selection.active = true; - if (selection.from_line == selection.to_line) { - if (selection.from_column == selection.to_column) { - selection.active = false; + if (carets[p_caret].selection.from_line == carets[p_caret].selection.to_line) { + if (carets[p_caret].selection.from_column == carets[p_caret].selection.to_column) { + carets.write[p_caret].selection.active = false; - } else if (selection.from_column > selection.to_column) { - selection.shiftclick_left = false; - SWAP(selection.from_column, selection.to_column); + } else if (carets[p_caret].selection.from_column > carets[p_caret].selection.to_column) { + carets.write[p_caret].selection.shiftclick_left = false; + SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column); } else { - selection.shiftclick_left = true; + carets.write[p_caret].selection.shiftclick_left = true; } - } else if (selection.from_line > selection.to_line) { - selection.shiftclick_left = false; - SWAP(selection.from_line, selection.to_line); - SWAP(selection.from_column, selection.to_column); + } else if (carets[p_caret].selection.from_line > carets[p_caret].selection.to_line) { + carets.write[p_caret].selection.shiftclick_left = false; + SWAP(carets.write[p_caret].selection.from_line, carets.write[p_caret].selection.to_line); + SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column); } else { - selection.shiftclick_left = true; + carets.write[p_caret].selection.shiftclick_left = true; } + caret_index_edit_dirty = true; queue_redraw(); } -bool TextEdit::has_selection() const { - return selection.active; +bool TextEdit::has_selection(int p_caret) const { + ERR_FAIL_COND_V(p_caret > carets.size(), false); + for (int i = 0; i < carets.size(); i++) { + if (p_caret != -1 && p_caret != i) { + continue; + } + + if (carets[i].selection.active) { + return true; + } + } + return false; } -String TextEdit::get_selected_text() const { - if (!selection.active) { - return ""; +String TextEdit::get_selected_text(int p_caret) { + ERR_FAIL_COND_V(p_caret > carets.size(), ""); + + StringBuilder selected_text; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = caret_edit_order.size() - 1; i >= 0; i--) { + int caret_idx = caret_edit_order[i]; + if (p_caret != -1 && p_caret != caret_idx) { + continue; + } + + if (!has_selection(caret_idx)) { + continue; + } + selected_text += _base_get_text(get_selection_from_line(caret_idx), get_selection_from_column(caret_idx), get_selection_to_line(caret_idx), get_selection_to_column(caret_idx)); + if (p_caret == -1 && i != 0) { + selected_text += "\n"; + } } - return _base_get_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); + return selected_text.as_string(); } -int TextEdit::get_selection_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.selecting_line; +int TextEdit::get_selection_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.selecting_line; } -int TextEdit::get_selection_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.selecting_column; +int TextEdit::get_selection_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.selecting_column; } -int TextEdit::get_selection_from_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.from_line; +int TextEdit::get_selection_from_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.from_line; } -int TextEdit::get_selection_from_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.from_column; +int TextEdit::get_selection_from_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.from_column; } -int TextEdit::get_selection_to_line() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.to_line; +int TextEdit::get_selection_to_line(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.to_line; } -int TextEdit::get_selection_to_column() const { - ERR_FAIL_COND_V(!selection.active, -1); - return selection.to_column; +int TextEdit::get_selection_to_column(int p_caret) const { + ERR_FAIL_INDEX_V(p_caret, carets.size(), -1); + ERR_FAIL_COND_V(!has_selection(p_caret), -1); + return carets[p_caret].selection.to_column; } -void TextEdit::deselect() { - selection.active = false; +void TextEdit::deselect(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + for (int i = 0; i < carets.size(); i++) { + if (p_caret != -1 && p_caret != i) { + continue; + } + carets.write[i].selection.active = false; + } + caret_index_edit_dirty = true; queue_redraw(); } -void TextEdit::delete_selection() { - if (!has_selection()) { - return; - } +void TextEdit::delete_selection(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - set_caret_line(selection.from_line, false, false); - set_caret_column(selection.from_column); + if (!has_selection(i)) { + continue; + } + + selecting_mode = SelectionMode::SELECTION_MODE_NONE; + _remove_text(carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column); + set_caret_line(carets[i].selection.from_line, false, false, 0, i); + set_caret_column(carets[i].selection.from_column, i == 0, i); + carets.write[i].selection.active = false; + + adjust_carets_after_edit(i, carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column); + } + end_complex_operation(); queue_redraw(); } @@ -4471,6 +5287,14 @@ bool TextEdit::is_scroll_past_end_of_file_enabled() const { return scroll_past_end_of_file_enabled; } +VScrollBar *TextEdit::get_v_scroll_bar() const { + return v_scroll; +} + +HScrollBar *TextEdit::get_h_scroll_bar() const { + return h_scroll; +} + void TextEdit::set_v_scroll(double p_scroll) { v_scroll->set_value(p_scroll); int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); @@ -4542,7 +5366,7 @@ void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) { } int TextEdit::get_first_visible_line() const { - return CLAMP(caret.line_ofs, 0, text.size() - 1); + return CLAMP(first_visible_line, 0, text.size() - 1); } void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { @@ -4581,14 +5405,14 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { int TextEdit::get_last_full_visible_line() const { int first_vis_line = get_first_visible_line(); int last_vis_line = 0; - last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).x - 1; + last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).x - 1; last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1); return last_vis_line; } int TextEdit::get_last_full_visible_line_wrap_index() const { int first_vis_line = get_first_visible_line(); - return get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, get_visible_line_count()).y; + return get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).y; } int TextEdit::get_visible_line_count() const { @@ -4624,16 +5448,18 @@ int TextEdit::get_total_visible_line_count() const { } // Auto adjust -void TextEdit::adjust_viewport_to_caret() { +void TextEdit::adjust_viewport_to_caret(int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); + // Make sure Caret is visible on the screen. scrolling = false; minimap_clicked = false; - int cur_line = caret.line; - int cur_wrap = get_caret_wrap_index(); + int cur_line = get_caret_line(p_caret); + int cur_wrap = get_caret_wrap_index(p_caret); int first_vis_line = get_first_visible_line(); - int first_vis_wrap = caret.wrap_ofs; + int first_vis_wrap = first_visible_line_wrap_ofs; int last_vis_line = get_last_full_visible_line(); int last_vis_wrap = get_last_full_visible_line_wrap_index(); @@ -4660,43 +5486,45 @@ void TextEdit::adjust_viewport_to_caret() { // Get position of the start of caret. if (ime_text.length() != 0 && ime_selection.x != 0) { - caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } // Get position of the end of caret. if (ime_text.length() != 0) { if (ime_selection.y != 0) { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); } } else { caret_pos.y = caret_pos.x; } - if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) { - caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; + if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) { + first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; } - if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) { - caret.x_ofs = MIN(caret_pos.x, caret_pos.y); + if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) { + first_visible_col = MIN(caret_pos.x, caret_pos.y); } } else { - caret.x_ofs = 0; + first_visible_col = 0; } - h_scroll->set_value(caret.x_ofs); + h_scroll->set_value(first_visible_col); queue_redraw(); } -void TextEdit::center_viewport_to_caret() { +void TextEdit::center_viewport_to_caret(int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); + // Move viewport so the caret is in the center of the screen. scrolling = false; minimap_clicked = false; - set_line_as_center_visible(caret.line, get_caret_wrap_index()); + set_line_as_center_visible(get_caret_line(p_caret), get_caret_wrap_index(p_caret)); int visible_width = get_size().width - style_normal->get_minimum_size().width - gutters_width - gutter_padding; if (draw_minimap) { visible_width -= minimap_width; @@ -4713,33 +5541,33 @@ void TextEdit::center_viewport_to_caret() { // Get position of the start of caret. if (ime_text.length() != 0 && ime_selection.x != 0) { - caret_pos.x = _get_column_x_offset_for_line(caret.column + ime_selection.x, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.x = _get_column_x_offset_for_line(caret.column, caret.line); + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); } // Get position of the end of caret. if (ime_text.length() != 0) { if (ime_selection.y != 0) { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_selection.x + ime_selection.y, caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.y = _get_column_x_offset_for_line(caret.column + ime_text.size(), caret.line); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); } } else { caret_pos.y = caret_pos.x; } - if (MAX(caret_pos.x, caret_pos.y) > (caret.x_ofs + visible_width)) { - caret.x_ofs = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; + if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) { + first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; } - if (MIN(caret_pos.x, caret_pos.y) < caret.x_ofs) { - caret.x_ofs = MIN(caret_pos.x, caret_pos.y); + if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) { + first_visible_col = MIN(caret_pos.x, caret_pos.y); } } else { - caret.x_ofs = 0; + first_visible_col = 0; } - h_scroll->set_value(caret.x_ofs); + h_scroll->set_value(first_visible_col); queue_redraw(); } @@ -5186,7 +6014,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("swap_lines", "from_line", "to_line"), &TextEdit::swap_lines); ClassDB::bind_method(D_METHOD("insert_line_at", "line", "text"), &TextEdit::insert_line_at); - ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &TextEdit::insert_text_at_caret); + ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text", "caret_index"), &TextEdit::insert_text_at_caret, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text); @@ -5195,18 +6023,19 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_next_visible_line_index_offset_from", "line", "wrap_index", "visible_amount"), &TextEdit::get_next_visible_line_index_offset_from); // Overridable actions - ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace); + ClassDB::bind_method(D_METHOD("backspace", "caret_index"), &TextEdit::backspace, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut); - ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); - ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste); + ClassDB::bind_method(D_METHOD("cut", "caret_index"), &TextEdit::cut, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("copy", "caret_index"), &TextEdit::copy, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("paste", "caret_index"), &TextEdit::paste, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("paste_primary_clipboard", "caret_index"), &TextEdit::paste_primary_clipboard, DEFVAL(-1)); - GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char") - GDVIRTUAL_BIND(_backspace) - GDVIRTUAL_BIND(_cut) - GDVIRTUAL_BIND(_copy) - GDVIRTUAL_BIND(_paste) - GDVIRTUAL_BIND(_paste_primary_clipboard) + GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char", "caret_index") + GDVIRTUAL_BIND(_backspace, "caret_index") + GDVIRTUAL_BIND(_cut, "caret_index") + GDVIRTUAL_BIND(_copy, "caret_index") + GDVIRTUAL_BIND(_paste, "caret_index") + GDVIRTUAL_BIND(_paste_primary_clipboard, "caret_index") // Context Menu BIND_ENUM_CONSTANT(MENU_CUT); @@ -5240,6 +6069,14 @@ void TextEdit::_bind_methods() { BIND_ENUM_CONSTANT(MENU_MAX); /* Versioning */ + BIND_ENUM_CONSTANT(ACTION_NONE); + BIND_ENUM_CONSTANT(ACTION_TYPING); + BIND_ENUM_CONSTANT(ACTION_BACKSPACE); + BIND_ENUM_CONSTANT(ACTION_DELETE); + + ClassDB::bind_method(D_METHOD("start_action", "action"), &TextEdit::start_action); + ClassDB::bind_method(D_METHOD("end_action"), &TextEdit::end_complex_operation); + ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation); ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation); @@ -5279,7 +6116,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos); ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor); - ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges"), &TextEdit::is_mouse_over_selection); + ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges", "caret_index"), &TextEdit::is_mouse_over_selection, DEFVAL(-1)); /* Caret. */ BIND_ENUM_CONSTANT(CARET_TYPE_LINE); @@ -5303,18 +6140,31 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &TextEdit::set_caret_mid_grapheme_enabled); ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &TextEdit::is_caret_mid_grapheme_enabled); - ClassDB::bind_method(D_METHOD("is_caret_visible"), &TextEdit::is_caret_visible); - ClassDB::bind_method(D_METHOD("get_caret_draw_pos"), &TextEdit::get_caret_draw_pos); + ClassDB::bind_method(D_METHOD("set_multiple_carets_enabled", "enabled"), &TextEdit::set_multiple_carets_enabled); + ClassDB::bind_method(D_METHOD("is_multiple_carets_enabled"), &TextEdit::is_multiple_carets_enabled); + + ClassDB::bind_method(D_METHOD("add_caret", "line", "col"), &TextEdit::add_caret); + ClassDB::bind_method(D_METHOD("remove_caret", "caret"), &TextEdit::remove_caret); + ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets); + ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets); + ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count); + ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets); + + ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order); + ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit); + + ClassDB::bind_method(D_METHOD("is_caret_visible", "caret_index"), &TextEdit::is_caret_visible, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_draw_pos", "caret_index"), &TextEdit::get_caret_draw_pos, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_caret_line"), &TextEdit::get_caret_line); + ClassDB::bind_method(D_METHOD("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index", "caret_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_line", "caret_index"), &TextEdit::get_caret_line, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport"), &TextEdit::set_caret_column, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("get_caret_column"), &TextEdit::get_caret_column); + ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport", "caret_index"), &TextEdit::set_caret_column, DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_column", "caret_index"), &TextEdit::get_caret_column, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_caret_wrap_index"), &TextEdit::get_caret_wrap_index); + ClassDB::bind_method(D_METHOD("get_caret_wrap_index", "caret_index"), &TextEdit::get_caret_wrap_index, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_word_under_caret"), &TextEdit::get_word_under_caret); + ClassDB::bind_method(D_METHOD("get_word_under_caret", "caret_index"), &TextEdit::get_word_under_caret, DEFVAL(-1)); /* Selection. */ BIND_ENUM_CONSTANT(SELECTION_MODE_NONE); @@ -5332,30 +6182,28 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled); ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled); - ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color); - ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color); - - ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode); ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); - ClassDB::bind_method(D_METHOD("select_word_under_caret"), &TextEdit::select_word_under_caret); - ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column"), &TextEdit::select); + ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence); + ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("has_selection"), &TextEdit::has_selection); + ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_selected_text"), &TextEdit::get_selected_text); + ClassDB::bind_method(D_METHOD("get_selected_text", "caret_index"), &TextEdit::get_selected_text, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_selection_line"), &TextEdit::get_selection_line); - ClassDB::bind_method(D_METHOD("get_selection_column"), &TextEdit::get_selection_column); + ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("get_selection_from_line"), &TextEdit::get_selection_from_line); - ClassDB::bind_method(D_METHOD("get_selection_from_column"), &TextEdit::get_selection_from_column); - ClassDB::bind_method(D_METHOD("get_selection_to_line"), &TextEdit::get_selection_to_line); - ClassDB::bind_method(D_METHOD("get_selection_to_column"), &TextEdit::get_selection_to_column); + ClassDB::bind_method(D_METHOD("get_selection_from_line", "caret_index"), &TextEdit::get_selection_from_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_from_column", "caret_index"), &TextEdit::get_selection_from_column, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_to_line", "caret_index"), &TextEdit::get_selection_to_line, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_selection_to_column", "caret_index"), &TextEdit::get_selection_to_column, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("deselect"), &TextEdit::deselect); - ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); + ClassDB::bind_method(D_METHOD("deselect", "caret_index"), &TextEdit::deselect, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("delete_selection", "caret_index"), &TextEdit::delete_selection, DEFVAL(-1)); /* Line wrapping. */ BIND_ENUM_CONSTANT(LINE_WRAPPING_NONE); @@ -5378,6 +6226,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_smooth_scroll_enabled", "enable"), &TextEdit::set_smooth_scroll_enabled); ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled); + ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &TextEdit::get_v_scroll_bar); + ClassDB::bind_method(D_METHOD("get_h_scroll_bar"), &TextEdit::get_h_scroll_bar); + ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &TextEdit::set_v_scroll); ClassDB::bind_method(D_METHOD("get_v_scroll"), &TextEdit::get_v_scroll); @@ -5410,8 +6261,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_total_visible_line_count"), &TextEdit::get_total_visible_line_count); // Auto adjust - ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret"), &TextEdit::adjust_viewport_to_caret); - ClassDB::bind_method(D_METHOD("center_viewport_to_caret"), &TextEdit::center_viewport_to_caret); + ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret", "caret_index"), &TextEdit::adjust_viewport_to_caret, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("center_viewport_to_caret", "caret_index"), &TextEdit::center_viewport_to_caret, DEFVAL(0)); // Minimap ClassDB::bind_method(D_METHOD("set_draw_minimap", "enabled"), &TextEdit::set_draw_minimap); @@ -5500,7 +6351,6 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_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::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); @@ -5527,6 +6377,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_interval", "get_caret_blink_interval"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); @@ -5612,157 +6463,238 @@ void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { /* Text manipulation */ // Overridable actions -void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { +void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } - bool had_selection = has_selection(); - if (had_selection) { - begin_complex_operation(); - delete_selection(); - } - - /* Remove the old character if in insert mode and no selection. */ - if (overtype_mode && !had_selection) { - begin_complex_operation(); - - /* Make sure we don't try and remove empty space. */ - int cl = get_caret_line(); - int cc = get_caret_column(); - if (cc < get_line(cl).length()) { - _remove_text(cl, cc, cl, cc + 1); + start_action(EditAction::ACTION_TYPING); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; } - } - const char32_t chr[2] = { (char32_t)p_unicode, 0 }; - insert_text_at_caret(chr); + /* Remove the old character if in insert mode and no selection. */ + if (overtype_mode && !has_selection(i)) { + /* Make sure we don't try and remove empty space. */ + int cl = get_caret_line(i); + int cc = get_caret_column(i); + if (cc < get_line(cl).length()) { + _remove_text(cl, cc, cl, cc + 1); + } + } - if ((overtype_mode && !had_selection) || (had_selection)) { - end_complex_operation(); + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + insert_text_at_caret(chr, i); } + end_action(); } -void TextEdit::_backspace_internal() { +void TextEdit::_backspace_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } - if (has_selection()) { - delete_selection(); + if (has_selection(p_caret)) { + delete_selection(p_caret); return; } - int cc = get_caret_column(); - int cl = get_caret_line(); + begin_complex_operation(); + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - if (cc == 0 && cl == 0) { - return; - } + int cc = get_caret_column(i); + int cl = get_caret_line(i); - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); + if (cc == 0 && cl == 0) { + continue; + } - merge_gutters(prev_line, cl); + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); - if (_is_line_hidden(cl)) { - _set_line_as_hidden(prev_line, true); - } - _remove_text(prev_line, prev_column, cl, cc); + merge_gutters(prev_line, cl); + + if (_is_line_hidden(cl)) { + _set_line_as_hidden(prev_line, true); + } + _remove_text(prev_line, prev_column, cl, cc); + + set_caret_line(prev_line, false, true, 0, i); + set_caret_column(prev_column, i == 0, i); - set_caret_line(prev_line, false, true); - set_caret_column(prev_column); + adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); + } + merge_overlapping_carets(); + end_complex_operation(); } -void TextEdit::_cut_internal() { +void TextEdit::_cut_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } - if (has_selection()) { - DisplayServer::get_singleton()->clipboard_set(get_selected_text()); - delete_selection(); + if (has_selection(p_caret)) { + DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret)); + delete_selection(p_caret); cut_copy_line = ""; return; } - int cl = get_caret_line(); - int cc = get_caret_column(); - int indent_level = get_indent_level(cl); - double hscroll = get_h_scroll(); + begin_complex_operation(); + Vector<int> carets_to_remove; + + StringBuilder clipboard; + // This is the exception and has to edit in reverse order else the string copied to the clipboard will be backwards. + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = caret_edit_order.size() - 1; i >= 0; i--) { + int caret_idx = caret_edit_order[i]; + if (p_caret != -1 && p_caret != caret_idx) { + continue; + } - String clipboard = text[cl]; - DisplayServer::get_singleton()->clipboard_set(clipboard); - set_caret_column(0); + int cl = get_caret_line(caret_idx); + int cc = get_caret_column(caret_idx); + int indent_level = get_indent_level(cl); + double hscroll = get_h_scroll(); - if (cl == 0 && get_line_count() > 1) { - _remove_text(cl, 0, cl + 1, 0); - } else { - _remove_text(cl, 0, cl, text[cl].length()); - backspace(); - set_caret_line(get_caret_line() + 1); - } + // Check for overlapping carets. + // We don't need to worry about selections as that is caught before this entire section. + for (int j = i - 1; j >= 0; j--) { + if (get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + } + } - // Correct the visually perceived caret column taking care of indentation level of the lines. - int diff_indent = indent_level - get_indent_level(get_caret_line()); - cc += diff_indent; - if (diff_indent != 0) { - cc += diff_indent > 0 ? -1 : 1; + clipboard += text[cl]; + if (p_caret == -1 && caret_idx != 0) { + clipboard += "\n"; + } + + if (cl == 0 && get_line_count() > 1) { + _remove_text(cl, 0, cl + 1, 0); + adjust_carets_after_edit(caret_idx, cl, 0, cl + 1, text[cl].length()); + } else { + _remove_text(cl, 0, cl, text[cl].length()); + set_caret_column(0, false, caret_idx); + backspace(caret_idx); + set_caret_line(get_caret_line(caret_idx) + 1, caret_idx == 0, 0, 0, caret_idx); + } + + // Correct the visually perceived caret column taking care of indentation level of the lines. + int diff_indent = indent_level - get_indent_level(get_caret_line(caret_idx)); + cc += diff_indent; + if (diff_indent != 0) { + cc += diff_indent > 0 ? -1 : 1; + } + + // Restore horizontal scroll and caret column modified by the backspace() call. + set_h_scroll(hscroll); + set_caret_column(cc, caret_idx == 0, caret_idx); } - // Restore horizontal scroll and caret column modified by the backspace() call. - set_h_scroll(hscroll); - set_caret_column(cc); + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + remove_caret(carets_to_remove[i]); + } + end_complex_operation(); - cut_copy_line = clipboard; + String clipboard_string = clipboard.as_string(); + DisplayServer::get_singleton()->clipboard_set(clipboard_string); + cut_copy_line = clipboard_string; } -void TextEdit::_copy_internal() { - if (has_selection()) { - DisplayServer::get_singleton()->clipboard_set(get_selected_text()); +void TextEdit::_copy_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); + if (has_selection(p_caret)) { + DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret)); cut_copy_line = ""; return; } - int cl = get_caret_line(); - if (text[cl].length() != 0) { - String clipboard = _base_get_text(cl, 0, cl, text[cl].length()); - DisplayServer::get_singleton()->clipboard_set(clipboard); - cut_copy_line = clipboard; + StringBuilder clipboard; + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (int i = caret_edit_order.size() - 1; i >= 0; i--) { + int caret_idx = caret_edit_order[i]; + if (p_caret != -1 && p_caret != caret_idx) { + continue; + } + + int cl = get_caret_line(caret_idx); + if (text[cl].length() != 0) { + clipboard += _base_get_text(cl, 0, cl, text[cl].length()); + if (p_caret == -1 && i != 0) { + clipboard += "\n"; + } + } } + + String clipboard_string = clipboard.as_string(); + DisplayServer::get_singleton()->clipboard_set(clipboard_string); + cut_copy_line = clipboard_string; } -void TextEdit::_paste_internal() { +void TextEdit::_paste_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!editable) { return; } String clipboard = DisplayServer::get_singleton()->clipboard_get(); + Vector<String> clipboad_lines = clipboard.split("\n"); + bool insert_line_per_caret = p_caret == -1 && carets.size() > 1 && clipboad_lines.size() == carets.size(); begin_complex_operation(); - if (has_selection()) { - delete_selection(); - } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { - set_caret_column(0); - String ins = "\n"; - clipboard += ins; - } + Vector<int> caret_edit_order = get_caret_index_edit_order(); + int clipboad_line = clipboad_lines.size() - 1; + for (const int &i : caret_edit_order) { + if (p_caret != -1 && p_caret != i) { + continue; + } - insert_text_at_caret(clipboard); + if (has_selection(i)) { + delete_selection(i); + } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { + set_caret_column(0, i == 0, i); + String ins = "\n"; + clipboard += ins; + } + + if (insert_line_per_caret) { + clipboard = clipboad_lines[clipboad_line]; + } + + insert_text_at_caret(clipboard, i); + clipboad_line--; + } end_complex_operation(); } -void TextEdit::_paste_primary_clipboard_internal() { +void TextEdit::_paste_primary_clipboard_internal(int p_caret) { + ERR_FAIL_COND(p_caret > carets.size()); if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { return; } String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary(); - Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); - deselect(); - set_caret_line(pos.y, true, false); - set_caret_column(pos.x); + if (carets.size() == 1) { + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + deselect(); + set_caret_line(pos.y, true, false); + set_caret_column(pos.x); + } + if (!paste_buffer.is_empty()) { insert_text_at_caret(paste_buffer); } @@ -5879,6 +6811,10 @@ Key TextEdit::_get_menu_action_accelerator(const String &p_action) { /* Versioning */ void TextEdit::_push_current_op() { + if (pending_action_end) { + start_action(EditAction::ACTION_NONE); + return; + } if (current_op.type == TextOperation::TYPE_NONE) { return; // Nothing to do. } @@ -5980,6 +6916,7 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con void TextEdit::_emit_caret_changed() { emit_signal(SNAME("caret_changed")); caret_pos_dirty = false; + caret_index_edit_dirty = true; } void TextEdit::_reset_caret_blink_timer() { @@ -6002,7 +6939,7 @@ void TextEdit::_toggle_draw_caret() { } } -int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { +int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); int row = 0; @@ -6015,7 +6952,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { } RID text_rid = text.get_line_data(p_line)->get_line_rid(row); - CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, caret.column); + CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, p_column); if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) { return ts_caret.l_caret.position.x; } else { @@ -6028,8 +6965,8 @@ void TextEdit::_click_selection_held() { // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. // I'm unsure if there's an actual fix that doesn't have a ton of side effects. - if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { - switch (selection.selecting_mode) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && get_selection_mode() != SelectionMode::SELECTION_MODE_NONE) { + switch (get_selection_mode()) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); } break; @@ -6055,14 +6992,16 @@ void TextEdit::_update_selection_mode_pointer() { Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; + int caret_idx = carets.size() - 1; - select(selection.selecting_line, selection.selecting_column, line, col); + select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx); - set_caret_line(line, false); - set_caret_column(col); + set_caret_line(line, false, true, 0, caret_idx); + set_caret_column(col, true, caret_idx); queue_redraw(); click_select_held->start(); + merge_overlapping_carets(); } void TextEdit::_update_selection_mode_word() { @@ -6072,6 +7011,7 @@ void TextEdit::_update_selection_mode_word() { Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; + int caret_idx = carets.size() - 1; int caret_pos = CLAMP(col, 0, text[line].length()); int beg = caret_pos; @@ -6086,25 +7026,25 @@ void TextEdit::_update_selection_mode_word() { } /* Initial selection. */ - if (!selection.active) { - select(line, beg, line, end); - selection.selecting_column = beg; - selection.selected_word_beg = beg; - selection.selected_word_end = end; - selection.selected_word_origin = beg; - set_caret_line(line, false); - set_caret_column(end); + if (!has_selection(caret_idx)) { + select(line, beg, line, end, caret_idx); + carets.write[caret_idx].selection.selecting_column = beg; + carets.write[caret_idx].selection.selected_word_beg = beg; + carets.write[caret_idx].selection.selected_word_end = end; + carets.write[caret_idx].selection.selected_word_origin = beg; + set_caret_line(line, false, true, 0, caret_idx); + set_caret_column(end, true, caret_idx); } else { - if ((col <= selection.selected_word_origin && line == selection.selecting_line) || line < selection.selecting_line) { - selection.selecting_column = selection.selected_word_end; - select(line, beg, selection.selecting_line, selection.selected_word_end); - set_caret_line(selection.from_line, false); - set_caret_column(selection.from_column); + if ((col <= carets[caret_idx].selection.selected_word_origin && line == get_selection_line(caret_idx)) || line < get_selection_line(caret_idx)) { + carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_end; + select(line, beg, get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_end, caret_idx); + set_caret_line(get_selection_from_line(caret_idx), false, true, 0, caret_idx); + set_caret_column(get_selection_from_column(caret_idx), true, caret_idx); } else { - selection.selecting_column = selection.selected_word_beg; - select(selection.selecting_line, selection.selected_word_beg, line, end); - set_caret_line(selection.to_line, false); - set_caret_column(selection.to_column); + carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_beg; + select(get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_beg, line, end, caret_idx); + set_caret_line(get_selection_to_line(caret_idx), false, true, 0, caret_idx); + set_caret_column(get_selection_to_column(caret_idx), true, caret_idx); } } @@ -6115,6 +7055,7 @@ void TextEdit::_update_selection_mode_word() { queue_redraw(); click_select_held->start(); + merge_overlapping_carets(); } void TextEdit::_update_selection_mode_line() { @@ -6124,21 +7065,22 @@ void TextEdit::_update_selection_mode_line() { Point2i pos = get_line_column_at_pos(mp); int line = pos.y; int col = pos.x; + int caret_idx = carets.size() - 1; col = 0; - if (line < selection.selecting_line) { + if (line < carets[caret_idx].selection.selecting_line) { /* Caret is above us. */ - set_caret_line(line - 1, false); - selection.selecting_column = text[selection.selecting_line].length(); + set_caret_line(line - 1, false, true, 0, caret_idx); + carets.write[caret_idx].selection.selecting_column = text[get_selection_line(caret_idx)].length(); } else { /* Caret is below us. */ - set_caret_line(line + 1, false); - selection.selecting_column = 0; + set_caret_line(line + 1, false, true, 0, caret_idx); + carets.write[caret_idx].selection.selecting_column = 0; col = text[line].length(); } - set_caret_column(0); + set_caret_column(0, false, caret_idx); - select(selection.selecting_line, selection.selecting_column, line, col); + select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } @@ -6146,33 +7088,31 @@ void TextEdit::_update_selection_mode_line() { queue_redraw(); click_select_held->start(); + merge_overlapping_carets(); } -void TextEdit::_pre_shift_selection() { +void TextEdit::_pre_shift_selection(int p_caret) { if (!selecting_enabled) { return; } - if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) { - selection.selecting_line = caret.line; - selection.selecting_column = caret.column; - selection.active = true; + if (!has_selection(p_caret) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) { + carets.write[p_caret].selection.active = true; + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_caret_line(p_caret), get_caret_column(p_caret), p_caret); + return; } - selection.selecting_mode = SelectionMode::SELECTION_MODE_SHIFT; + set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_selection_line(p_caret), get_selection_column(p_caret), p_caret); } -void TextEdit::_post_shift_selection() { +void TextEdit::_post_shift_selection(int p_caret) { if (!selecting_enabled) { return; } - if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) { - select(selection.selecting_line, selection.selecting_column, caret.line, caret.column); - queue_redraw(); + if (has_selection(p_caret) && get_selection_mode() == SelectionMode::SELECTION_MODE_SHIFT) { + select(get_selection_line(p_caret), get_selection_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret), p_caret); } - - selection.selecting_text = true; } /* Line Wrapping */ @@ -6198,17 +7138,14 @@ void TextEdit::_update_wrap_at_column(bool p_force) { _update_placeholder(); } - _update_caret_wrap_offset(); -} - -void TextEdit::_update_caret_wrap_offset() { + // Update viewport. int first_vis_line = get_first_visible_line(); if (is_line_wrapped(first_vis_line)) { - caret.wrap_ofs = MIN(caret.wrap_ofs, get_line_wrap_count(first_vis_line)); + first_visible_line_wrap_ofs = MIN(first_visible_line_wrap_ofs, get_line_wrap_count(first_vis_line)); } else { - caret.wrap_ofs = 0; + first_visible_line_wrap_ofs = 0; } - set_line_as_first_visible(caret.line_ofs, caret.wrap_ofs); + set_line_as_first_visible(first_visible_line, first_visible_line_wrap_ofs); } /* Viewport. */ @@ -6257,8 +7194,8 @@ void TextEdit::_update_scrollbars() { set_v_scroll(get_v_scroll()); } else { - caret.line_ofs = 0; - caret.wrap_ofs = 0; + first_visible_line = 0; + first_visible_line_wrap_ofs = 0; v_scroll->set_value(0); v_scroll->set_max(0); v_scroll->hide(); @@ -6268,15 +7205,15 @@ void TextEdit::_update_scrollbars() { h_scroll->show(); h_scroll->set_max(total_width); h_scroll->set_page(visible_width); - if (caret.x_ofs > (total_width - visible_width)) { - caret.x_ofs = (total_width - visible_width); + if (first_visible_col > (total_width - visible_width)) { + first_visible_col = (total_width - visible_width); } - if (fabs(h_scroll->get_value() - (double)caret.x_ofs) >= 1) { - h_scroll->set_value(caret.x_ofs); + if (fabs(h_scroll->get_value() - (double)first_visible_col) >= 1) { + h_scroll->set_value(first_visible_col); } } else { - caret.x_ofs = 0; + first_visible_col = 0; h_scroll->set_value(0); h_scroll->set_max(0); h_scroll->hide(); @@ -6305,7 +7242,7 @@ void TextEdit::_scroll_moved(double p_to_val) { } if (h_scroll->is_visible_in_tree()) { - caret.x_ofs = h_scroll->get_value(); + first_visible_col = h_scroll->get_value(); } if (v_scroll->is_visible_in_tree()) { // Set line ofs and wrap ofs. @@ -6328,8 +7265,8 @@ void TextEdit::_scroll_moved(double p_to_val) { int wi = line_wrap_amount - (sc - v_scroll_i - 1); wi = CLAMP(wi, 0, line_wrap_amount); - caret.line_ofs = n_line; - caret.wrap_ofs = wi; + first_visible_line = n_line; + first_visible_line_wrap_ofs = wi; } queue_redraw(); } @@ -6410,16 +7347,18 @@ void TextEdit::_scroll_lines_up() { set_v_scroll(get_v_scroll() - 1); // Adjust the caret to viewport. - if (!selection.active) { - int cur_line = caret.line; - int cur_wrap = get_caret_wrap_index(); + for (int i = 0; i < carets.size(); i++) { + if (has_selection(i)) { + continue; + } + int last_vis_line = get_last_full_visible_line(); int last_vis_wrap = get_last_full_visible_line_wrap_index(); - - if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { - set_caret_line(last_vis_line, false, false, last_vis_wrap); + if (get_caret_line(i) > last_vis_line || (get_caret_line(i) == last_vis_line && get_caret_wrap_index(i) > last_vis_wrap)) { + set_caret_line(last_vis_line, false, false, last_vis_wrap, i); } } + merge_overlapping_carets(); } void TextEdit::_scroll_lines_down() { @@ -6430,16 +7369,17 @@ void TextEdit::_scroll_lines_down() { set_v_scroll(get_v_scroll() + 1); // Adjust the caret to viewport. - if (!selection.active) { - int cur_line = caret.line; - int cur_wrap = get_caret_wrap_index(); - int first_vis_line = get_first_visible_line(); - int first_vis_wrap = caret.wrap_ofs; + for (int i = 0; i < carets.size(); i++) { + if (has_selection(i)) { + continue; + } - if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { - set_caret_line(first_vis_line, false, false, first_vis_wrap); + int first_vis_line = get_first_visible_line(); + if (get_caret_line(i) < first_vis_line || (get_caret_line(i) == first_vis_line && get_caret_wrap_index(i) < first_visible_line_wrap_ofs)) { + set_caret_line(first_vis_line, false, false, first_visible_line_wrap_ofs, i); } } + merge_overlapping_carets(); } // Minimap @@ -6576,6 +7516,8 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r op.version = ++version; op.chain_forward = false; op.chain_backward = false; + op.start_carets = carets; + op.end_carets = carets; // See if it should just be set as current op. if (current_op.type != op.type) { @@ -6598,6 +7540,7 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r current_op.to_column = retchar; current_op.to_line = retline; current_op.version = op.version; + current_op.end_carets = carets; } void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -6605,10 +7548,10 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i idle_detect->start(); } - String text; + String txt; if (undo_enabled) { _clear_redo(); - text = _base_get_text(p_from_line, p_from_column, p_to_line, p_to_column); + txt = _base_get_text(p_from_line, p_from_column, p_to_line, p_to_column); } _base_remove_text(p_from_line, p_from_column, p_to_line, p_to_column); @@ -6624,10 +7567,12 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i op.from_column = p_from_column; op.to_line = p_to_line; op.to_column = p_to_column; - op.text = text; + op.text = txt; op.version = ++version; op.chain_forward = false; op.chain_backward = false; + op.start_carets = carets; + op.end_carets = carets; // See if it should just be set as current op. if (current_op.type != op.type) { @@ -6639,9 +7584,10 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i // See if it can be merged. if (current_op.from_line == p_to_line && current_op.from_column == p_to_column) { // Backspace or similar. - current_op.text = text + current_op.text; + current_op.text = txt + current_op.text; current_op.from_line = p_from_line; current_op.from_column = p_from_column; + current_op.end_carets = carets; return; // Update current op. } @@ -6691,7 +7637,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); - TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? caret.column : 0, r_end_column); + TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? carets[0].column : 0, r_end_column); if (dir != TextServer::DIRECTION_AUTO) { input_direction = (TextDirection)dir; } @@ -6753,6 +7699,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li TextEdit::TextEdit(const String &p_placeholder) { placeholder_data_buf.instantiate(); + carets.push_back(Caret()); clear(); set_focus_mode(FOCUS_ALL); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index a8da878ede..92acbaf521 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -42,6 +42,14 @@ class TextEdit : public Control { GDCLASS(TextEdit, Control); public: + /* Edit Actions. */ + enum EditAction { + ACTION_NONE, + ACTION_TYPING, + ACTION_BACKSPACE, + ACTION_DELETE, + }; + /* Caret. */ enum CaretType { CARET_TYPE_LINE, @@ -299,12 +307,15 @@ private: Key _get_menu_action_accelerator(const String &p_action); /* Versioning */ + struct Caret; struct TextOperation { enum Type { TYPE_NONE, TYPE_INSERT, TYPE_REMOVE }; + Vector<Caret> start_carets; + Vector<Caret> end_carets; Type type = TYPE_NONE; int from_line = 0; @@ -321,6 +332,10 @@ private: bool undo_enabled = true; int undo_stack_max_size = 50; + EditAction current_action = EditAction::ACTION_NONE; + bool pending_action_end = false; + bool in_action = false; + int complex_operation_count = 0; bool next_operation_is_complex = false; @@ -361,19 +376,39 @@ private: int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; /* Caret. */ + struct Selection { + bool active = false; + bool shiftclick_left = false; + + int selecting_line = 0; + int selecting_column = 0; + int selected_word_beg = 0; + int selected_word_end = 0; + int selected_word_origin = 0; + + int from_line = 0; + int from_column = 0; + int to_line = 0; + int to_column = 0; + }; + struct Caret { + Selection selection; + Point2 draw_pos; bool visible = false; int last_fit_x = 0; int line = 0; int column = 0; - int x_ofs = 0; - int line_ofs = 0; - int wrap_ofs = 0; - } caret; + }; + + // Vector containing all the carets, index '0' is the "main caret" and should never be removed. + Vector<Caret> carets; + Vector<int> caret_index_edit_order; bool setting_caret_line = false; bool caret_pos_dirty = false; + bool caret_index_edit_dirty = true; Color caret_color = Color(1, 1, 1); Color caret_background_color = Color(0, 0, 0); @@ -389,6 +424,8 @@ private: bool caret_mid_grapheme_enabled = true; + bool multi_carets_enabled = true; + bool drag_action = false; bool drag_caret_force_displayed = false; @@ -397,37 +434,20 @@ private: void _reset_caret_blink_timer(); void _toggle_draw_caret(); - int _get_column_x_offset_for_line(int p_char, int p_line) const; + int _get_column_x_offset_for_line(int p_char, int p_line, int p_column) const; /* Selection. */ - struct Selection { - SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE; - int selecting_line = 0; - int selecting_column = 0; - int selected_word_beg = 0; - int selected_word_end = 0; - int selected_word_origin = 0; - bool selecting_text = false; - - bool active = false; - - int from_line = 0; - int from_column = 0; - int to_line = 0; - int to_column = 0; - - bool shiftclick_left = false; - bool drag_attempt = false; - } selection; + SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE; bool selecting_enabled = true; bool deselect_on_focus_loss_enabled = true; bool drag_and_drop_selection_enabled = true; - Color font_selected_color = Color(1, 1, 1); + Color font_selected_color = Color(0, 0, 0, 0); Color selection_color = Color(1, 1, 1); - bool override_selected_font_color = false; + bool use_selected_font_color = false; + bool selection_drag_attempt = false; bool dragging_selection = false; Timer *click_select_held = nullptr; @@ -439,8 +459,8 @@ private: void _update_selection_mode_word(); void _update_selection_mode_line(); - void _pre_shift_selection(); - void _post_shift_selection(); + void _pre_shift_selection(int p_caret); + void _post_shift_selection(int p_caret); /* Line wrapping. */ LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE; @@ -450,8 +470,6 @@ private: void _update_wrap_at_column(bool p_force = false); - void _update_caret_wrap_offset(); - /* Viewport. */ HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; @@ -466,6 +484,10 @@ private: float v_scroll_speed = 80.0; // Scrolling. + int first_visible_line = 0; + int first_visible_line_wrap_ofs = 0; + int first_visible_col = 0; + bool scrolling = false; bool updating_scrolls = false; @@ -575,6 +597,10 @@ private: void _delete(bool p_word = false, bool p_all_to_right = false); void _move_caret_document_start(bool p_select); void _move_caret_document_end(bool p_select); + bool _clear_carets_and_selection(); + + // Used in add_caret_at_carets + void _get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x = -1) const; protected: void _notification(int p_what); @@ -583,6 +609,17 @@ protected: /* Internal API for CodeEdit, pending public API. */ // brace matching + struct BraceMatchingData { + int open_match_line = -1; + int open_match_column = -1; + bool open_matching = false; + bool open_mismatch = false; + int close_match_line = -1; + int close_match_column = -1; + bool close_matching = false; + bool close_mismatch = false; + }; + bool highlight_matching_braces_enabled = false; Color brace_mismatch_color; @@ -607,20 +644,20 @@ protected: /* Text manipulation */ // Overridable actions - virtual void _handle_unicode_input_internal(const uint32_t p_unicode); - virtual void _backspace_internal(); + virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret); + virtual void _backspace_internal(int p_caret); - virtual void _cut_internal(); - virtual void _copy_internal(); - virtual void _paste_internal(); - virtual void _paste_primary_clipboard_internal(); + virtual void _cut_internal(int p_caret); + virtual void _copy_internal(int p_caret); + virtual void _paste_internal(int p_caret); + virtual void _paste_primary_clipboard_internal(int p_caret); - GDVIRTUAL1(_handle_unicode_input, int) - GDVIRTUAL0(_backspace) - GDVIRTUAL0(_cut) - GDVIRTUAL0(_copy) - GDVIRTUAL0(_paste) - GDVIRTUAL0(_paste_primary_clipboard) + GDVIRTUAL2(_handle_unicode_input, int, int) + GDVIRTUAL1(_backspace, int) + GDVIRTUAL1(_cut, int) + GDVIRTUAL1(_copy, int) + GDVIRTUAL1(_paste, int) + GDVIRTUAL1(_paste_primary_clipboard, int) public: /* General overrides. */ @@ -696,7 +733,7 @@ public: void swap_lines(int p_from_line, int p_to_line); void insert_line_at(int p_at, const String &p_text); - void insert_text_at_caret(const String &p_text); + void insert_text_at_caret(const String &p_text, int p_caret = -1); void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); @@ -705,13 +742,13 @@ public: Point2i get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const; // Overridable actions - void handle_unicode_input(const uint32_t p_unicode); - void backspace(); + void handle_unicode_input(const uint32_t p_unicode, int p_caret = -1); + void backspace(int p_caret = -1); - void cut(); - void copy(); - void paste(); - void paste_primary_clipboard(); + void cut(int p_caret = -1); + void copy(int p_caret = -1); + void paste(int p_caret = -1); + void paste_primary_clipboard(int p_caret = -1); // Context menu. PopupMenu *get_menu() const; @@ -719,6 +756,10 @@ public: void menu_option(int p_option); /* Versioning */ + void start_action(EditAction p_action); + void end_action(); + EditAction get_current_action() const; + void begin_complex_operation(); void end_complex_operation(); @@ -753,7 +794,7 @@ public: int get_minimap_line_at_pos(const Point2i &p_pos) const; bool is_dragging_cursor() const; - bool is_mouse_over_selection(bool p_edges = true) const; + bool is_mouse_over_selection(bool p_edges = true, int p_caret = -1) const; /* Caret */ void set_caret_type(CaretType p_type); @@ -771,18 +812,31 @@ public: void set_caret_mid_grapheme_enabled(const bool p_enabled); bool is_caret_mid_grapheme_enabled() const; - bool is_caret_visible() const; - Point2 get_caret_draw_pos() const; + void set_multiple_carets_enabled(bool p_enabled); + bool is_multiple_carets_enabled() const; + + int add_caret(int p_line, int p_col); + void remove_caret(int p_caret); + void remove_secondary_carets(); + void merge_overlapping_carets(); + int get_caret_count() const; + void add_caret_at_carets(bool p_below); - void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); - int get_caret_line() const; + Vector<int> get_caret_index_edit_order(); + void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col); - void set_caret_column(int p_col, bool p_adjust_viewport = true); - int get_caret_column() const; + bool is_caret_visible(int p_caret = 0) const; + Point2 get_caret_draw_pos(int p_caret = 0) const; - int get_caret_wrap_index() const; + void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0, int p_caret = 0); + int get_caret_line(int p_caret = 0) const; - String get_word_under_caret() const; + void set_caret_column(int p_col, bool p_adjust_viewport = true, int p_caret = 0); + int get_caret_column(int p_caret = 0) const; + + int get_caret_wrap_index(int p_caret = 0) const; + + String get_word_under_caret(int p_caret = -1) const; /* Selection. */ void set_selecting_enabled(const bool p_enabled); @@ -797,27 +851,28 @@ public: void set_override_selected_font_color(bool p_override_selected_font_color); bool is_overriding_selected_font_color() const; - void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1); + void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0); SelectionMode get_selection_mode() const; void select_all(); - void select_word_under_caret(); - void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column); + void select_word_under_caret(int p_caret = -1); + void add_selection_for_next_occurrence(); + void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0); - bool has_selection() const; + bool has_selection(int p_caret = -1) const; - String get_selected_text() const; + String get_selected_text(int p_caret = -1); - int get_selection_line() const; - int get_selection_column() const; + int get_selection_line(int p_caret = 0) const; + int get_selection_column(int p_caret = 0) const; - int get_selection_from_line() const; - int get_selection_from_column() const; - int get_selection_to_line() const; - int get_selection_to_column() const; + int get_selection_from_line(int p_caret = 0) const; + int get_selection_from_column(int p_caret = 0) const; + int get_selection_to_line(int p_caret = 0) const; + int get_selection_to_column(int p_caret = 0) const; - void deselect(); - void delete_selection(); + void deselect(int p_caret = -1); + void delete_selection(int p_caret = -1); /* Line wrapping. */ void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode); @@ -837,6 +892,9 @@ public: void set_scroll_past_end_of_file_enabled(const bool p_enabled); bool is_scroll_past_end_of_file_enabled() const; + VScrollBar *get_v_scroll_bar() const; + HScrollBar *get_h_scroll_bar() const; + void set_v_scroll(double p_scroll); double get_v_scroll() const; @@ -866,8 +924,8 @@ public: int get_total_visible_line_count() const; // Auto Adjust - void adjust_viewport_to_caret(); - void center_viewport_to_caret(); + void adjust_viewport_to_caret(int p_caret = 0); + void center_viewport_to_caret(int p_caret = 0); // Minimap void set_draw_minimap(bool p_enabled); @@ -949,6 +1007,7 @@ public: TextEdit(const String &p_placeholder = String()); }; +VARIANT_ENUM_CAST(TextEdit::EditAction); VARIANT_ENUM_CAST(TextEdit::CaretType); VARIANT_ENUM_CAST(TextEdit::LineWrappingMode); VARIANT_ENUM_CAST(TextEdit::SelectionMode); diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index d9ab1c2c55..ad12757921 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -64,7 +64,7 @@ Size2 TextureButton::get_minimum_size() const { bool TextureButton::has_point(const Point2 &p_point) const { if (click_mask.is_valid()) { Point2 point = p_point; - Rect2 rect = Rect2(); + Rect2 rect; Size2 mask_size = click_mask->get_size(); if (!_position_rect.has_area()) { diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index a9982b3ece..48c6dc5cfc 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -510,18 +510,38 @@ void TextureProgressBar::_notification(int p_what) { } pts.append(to); + Ref<AtlasTexture> atlas_progress = progress; + bool valid_atlas_progress = atlas_progress.is_valid() && atlas_progress->get_atlas().is_valid(); + Rect2 region_rect; + Size2 atlas_size; + if (valid_atlas_progress) { + region_rect = atlas_progress->get_region(); + atlas_size = atlas_progress->get_atlas()->get_size(); + } + Vector<Point2> uvs; Vector<Point2> points; - uvs.push_back(get_relative_center()); - points.push_back(progress_offset + s * get_relative_center()); for (int i = 0; i < pts.size(); i++) { Point2 uv = unit_val_to_uv(pts[i]); if (uvs.find(uv) >= 0) { continue; } - uvs.push_back(uv); points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y)); + if (valid_atlas_progress) { + uv.x = Math::remap(uv.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x); + uv.y = Math::remap(uv.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y); + } + uvs.push_back(uv); + } + + Point2 center_point = get_relative_center(); + points.push_back(progress_offset + s * center_point); + if (valid_atlas_progress) { + center_point.x = Math::remap(center_point.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x); + center_point.y = Math::remap(center_point.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y); } + uvs.push_back(center_point); + Vector<Color> colors; colors.push_back(tint_progress); draw_polygon(points, colors, uvs, progress); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 237c78407b..2da76883b4 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -563,6 +563,57 @@ bool TreeItem::is_collapsed() { return collapsed; } +void TreeItem::set_collapsed_recursive(bool p_collapsed) { + if (!tree) { + return; + } + + set_collapsed(p_collapsed); + + TreeItem *child = get_first_child(); + while (child) { + child->set_collapsed_recursive(p_collapsed); + child = child->get_next(); + } +} + +bool TreeItem::_is_any_collapsed(bool p_only_visible) { + TreeItem *child = get_first_child(); + + // Check on children directly first (avoid recursing if possible). + while (child) { + if (child->get_first_child() && child->is_collapsed() && (!p_only_visible || (child->is_visible() && child->get_visible_child_count()))) { + return true; + } + child = child->get_next(); + } + + child = get_first_child(); + + // Otherwise recurse on children. + while (child) { + if (child->get_first_child() && (!p_only_visible || (child->is_visible() && child->get_visible_child_count())) && child->_is_any_collapsed(p_only_visible)) { + return true; + } + child = child->get_next(); + } + + return false; +} + +bool TreeItem::is_any_collapsed(bool p_only_visible) { + if (p_only_visible && !is_visible()) { + return false; + } + + // Collapsed if this is collapsed and it has children (only considers visible if only visible is set). + if (is_collapsed() && get_first_child() && (!p_only_visible || get_visible_child_count())) { + return true; + } + + return _is_any_collapsed(p_only_visible); +} + void TreeItem::set_visible(bool p_visible) { if (visible == p_visible) { return; @@ -687,9 +738,9 @@ TreeItem *TreeItem::get_first_child() const { TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *current = this; - TreeItem *prev = current->get_prev(); + TreeItem *prev_item = current->get_prev(); - if (!prev) { + if (!prev_item) { current = current->parent; if (current == tree->root && tree->hide_root) { return nullptr; @@ -706,7 +757,7 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { } } } else { - current = prev; + current = prev_item; while (!current->collapsed && current->first_child) { //go to the very end @@ -722,16 +773,16 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *TreeItem::get_prev_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *prev = this->_get_prev_visible(p_wrap); - while (prev && !prev->is_visible()) { - prev = prev->_get_prev_visible(p_wrap); - if (prev == loop) { + TreeItem *prev_item = this->_get_prev_visible(p_wrap); + while (prev_item && !prev_item->is_visible()) { + prev_item = prev_item->_get_prev_visible(p_wrap); + if (prev_item == loop) { // Check that we haven't looped all the way around to the start. - prev = nullptr; + prev_item = nullptr; break; } } - return prev; + return prev_item; } TreeItem *TreeItem::_get_next_visible(bool p_wrap) { @@ -763,16 +814,16 @@ TreeItem *TreeItem::_get_next_visible(bool p_wrap) { TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *next = this->_get_next_visible(p_wrap); - while (next && !next->is_visible()) { - next = next->_get_next_visible(p_wrap); - if (next == loop) { + TreeItem *next_item = this->_get_next_visible(p_wrap); + while (next_item && !next_item->is_visible()) { + next_item = next_item->_get_next_visible(p_wrap); + if (next_item == loop) { // Check that we haven't looped all the way around to the start. - next = nullptr; + next_item = nullptr; break; } } - return next; + return next_item; } TreeItem *TreeItem::get_child(int p_idx) { @@ -1112,7 +1163,7 @@ bool TreeItem::is_editable(int p_column) { void TreeItem::set_custom_color(int p_column, const Color &p_color) { ERR_FAIL_INDEX(p_column, cells.size()); - if (cells[p_column].custom_color == true) { + if (cells[p_column].custom_color && cells[p_column].color == p_color) { return; } @@ -1266,8 +1317,8 @@ bool TreeItem::is_folding_disabled() const { Size2 TreeItem::get_minimum_size(int p_column) { ERR_FAIL_INDEX_V(p_column, cells.size(), Size2()); - Tree *tree = get_tree(); - ERR_FAIL_COND_V(!tree, Size2()); + Tree *parent_tree = get_tree(); + ERR_FAIL_COND_V(!parent_tree, Size2()); const TreeItem::Cell &cell = cells[p_column]; @@ -1277,7 +1328,7 @@ Size2 TreeItem::get_minimum_size(int p_column) { // Text. if (!cell.text.is_empty()) { if (cell.dirty) { - tree->update_item_cell(this, p_column); + parent_tree->update_item_cell(this, p_column); } Size2 text_size = cell.text_buf->get_size(); size.width += text_size.width; @@ -1286,14 +1337,14 @@ Size2 TreeItem::get_minimum_size(int p_column) { // Icon. if (cell.mode == CELL_MODE_CHECK) { - size.width += tree->theme_cache.checked->get_width() + tree->theme_cache.hseparation; + size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.hseparation; } if (cell.icon.is_valid()) { Size2i icon_size = cell.get_icon_size(); if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { icon_size.width = cell.icon_max_w; } - size.width += icon_size.width + tree->theme_cache.hseparation; + size.width += icon_size.width + parent_tree->theme_cache.hseparation; size.height = MAX(size.height, icon_size.height); } @@ -1301,13 +1352,13 @@ Size2 TreeItem::get_minimum_size(int p_column) { for (int i = 0; i < cell.buttons.size(); i++) { Ref<Texture2D> texture = cell.buttons[i].texture; if (texture.is_valid()) { - Size2 button_size = texture->get_size() + tree->theme_cache.button_pressed->get_minimum_size(); + Size2 button_size = texture->get_size() + parent_tree->theme_cache.button_pressed->get_minimum_size(); size.width += button_size.width; size.height = MAX(size.height, button_size.height); } } if (cell.buttons.size() >= 2) { - size.width += (cell.buttons.size() - 1) * tree->theme_cache.button_margin; + size.width += (cell.buttons.size() - 1) * parent_tree->theme_cache.button_margin; } cells.write[p_column].cached_minimum_size = size; @@ -1406,6 +1457,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed); ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed); + ClassDB::bind_method(D_METHOD("set_collapsed_recursive", "enable"), &TreeItem::set_collapsed_recursive); + ClassDB::bind_method(D_METHOD("is_any_collapsed", "only_visible"), &TreeItem::is_any_collapsed, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_visible", "enable"), &TreeItem::set_visible); ClassDB::bind_method(D_METHOD("is_visible"), &TreeItem::is_visible); @@ -1450,6 +1504,7 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button); ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button); ClassDB::bind_method(D_METHOD("set_button_disabled", "column", "button_idx", "disabled"), &TreeItem::set_button_disabled); + ClassDB::bind_method(D_METHOD("set_button_color", "column", "button_idx", "color"), &TreeItem::set_button_color); ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled); ClassDB::bind_method(D_METHOD("set_tooltip_text", "column", "tooltip"), &TreeItem::set_tooltip_text); @@ -2572,7 +2627,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + theme_cache.item_margin))) { - p_item->set_collapsed(!p_item->is_collapsed()); + if (enable_recursive_folding && p_mod->is_shift_pressed()) { + p_item->set_collapsed_recursive(!p_item->is_collapsed()); + } else { + p_item->set_collapsed(!p_item->is_collapsed()); + } return -1; } @@ -2623,7 +2682,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) { - p_item->set_collapsed(!p_item->is_collapsed()); + if (enable_recursive_folding && p_mod->is_shift_pressed()) { + p_item->set_collapsed_recursive(!p_item->is_collapsed()); + } else { + p_item->set_collapsed(!p_item->is_collapsed()); + } return -1; //collapse/uncollapse because nothing can be done with item } @@ -3128,7 +3191,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; bool is_command = k.is_valid() && k->is_command_or_control_pressed(); - if (p_event->is_action("ui_right") && p_event->is_pressed()) { + if (p_event->is_action("ui_right", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3146,7 +3209,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } else { _go_right(); } - } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3166,21 +3229,21 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { _go_left(); } - } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) { + } else if (p_event->is_action("ui_up", true) && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); } _go_up(); - } else if (p_event->is_action("ui_down") && p_event->is_pressed() && !is_command) { + } else if (p_event->is_action("ui_down", true) && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); } _go_down(); - } else if (p_event->is_action("ui_page_down") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_page_down", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3218,7 +3281,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } ensure_cursor_is_visible(); - } else if (p_event->is_action("ui_page_up") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_page_up", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3255,7 +3318,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { prev->select(selected_col); } ensure_cursor_is_visible(); - } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) { if (selected_item) { //bring up editor if possible if (!edit_selected()) { @@ -3264,7 +3327,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } accept_event(); - } else if (p_event->is_action("ui_select") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_select", true) && p_event->is_pressed()) { if (select_mode == SELECT_MULTI) { if (!selected_item) { return; @@ -4275,6 +4338,12 @@ TreeItem *Tree::get_selected() const { return selected_item; } +void Tree::set_selected(TreeItem *p_item, int p_column) { + ERR_FAIL_INDEX(p_column, columns.size()); + ERR_FAIL_COND(!p_item); + select_single_item(p_item, get_root(), p_column); +} + int Tree::get_selected_column() const { return selected_col; } @@ -4484,6 +4553,7 @@ void Tree::ensure_cursor_is_visible() { return; // Nothing under cursor. } + // Note: Code below similar to Tree::scroll_to_item(), in case of bug fix both. const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size(); int y_offset = get_item_offset(selected_item); @@ -4492,7 +4562,10 @@ void Tree::ensure_cursor_is_visible() { y_offset -= tbh; const int cell_h = compute_item_height(selected_item) + theme_cache.vseparation; - const int screen_h = area_size.height - h_scroll->get_combined_minimum_size().height - tbh; + int screen_h = area_size.height - tbh; + if (h_scroll->is_visible()) { + screen_h -= h_scroll->get_combined_minimum_size().height; + } if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. v_scroll->set_value(y_offset); @@ -4643,26 +4716,32 @@ Point2 Tree::get_scroll() const { void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { ERR_FAIL_NULL(p_item); - if (!is_visible_in_tree() || !p_item->is_visible()) { - return; // Hack to work around crash in get_item_rect() if Tree is not in tree. - } update_scrollbars(); - const real_t tree_height = get_size().y; - const Rect2 item_rect = get_item_rect(p_item); - const real_t item_y = item_rect.position.y; - const real_t item_height = item_rect.size.y + theme_cache.vseparation; + // Note: Code below similar to Tree::ensure_cursor_is_visible(), in case of bug fix both. + const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size(); - if (p_center_on_item) { - v_scroll->set_value(item_y - (tree_height - item_height) / 2.0f); - } else { - if (item_y < v_scroll->get_value()) { - v_scroll->set_value(item_y); + int y_offset = get_item_offset(p_item); + if (y_offset != -1) { + const int tbh = _get_title_button_height(); + y_offset -= tbh; + + const int cell_h = compute_item_height(p_item) + theme_cache.vseparation; + int screen_h = area_size.height - tbh; + if (h_scroll->is_visible()) { + screen_h -= h_scroll->get_combined_minimum_size().height; + } + + if (p_center_on_item) { + v_scroll->set_value(y_offset - (screen_h - cell_h) / 2.0f); } else { - const real_t new_position = item_y + item_height - tree_height; - if (new_position > v_scroll->get_value()) { - v_scroll->set_value(new_position); + if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. + v_scroll->set_value(y_offset); + } else if (y_offset + cell_h > v_scroll->get_value() + screen_h) { + v_scroll->set_value(y_offset - screen_h + cell_h); + } else if (y_offset < v_scroll->get_value()) { + v_scroll->set_value(y_offset); } } } @@ -4755,7 +4834,7 @@ TreeItem *Tree::get_item_with_text(const String &p_find) const { void Tree::_do_incr_search(const String &p_add) { uint64_t time = OS::get_singleton()->get_ticks_usec() / 1000; // convert to msec uint64_t diff = time - last_keypress; - if (diff > uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000))) { + if (diff > uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec"))) { incr_search = p_add; } else if (incr_search != p_add) { incr_search += p_add; @@ -5026,6 +5105,14 @@ bool Tree::is_folding_hidden() const { return hide_folding; } +void Tree::set_enable_recursive_folding(bool p_enable) { + enable_recursive_folding = p_enable; +} + +bool Tree::is_recursive_folding_enabled() const { + return enable_recursive_folding; +} + void Tree::set_drop_mode_flags(int p_flags) { if (drop_mode_flags == p_flags) { return; @@ -5085,6 +5172,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("is_root_hidden"), &Tree::is_root_hidden); ClassDB::bind_method(D_METHOD("get_next_selected", "from"), &Tree::get_next_selected); ClassDB::bind_method(D_METHOD("get_selected"), &Tree::get_selected); + ClassDB::bind_method(D_METHOD("set_selected", "item", "column"), &Tree::set_selected); ClassDB::bind_method(D_METHOD("get_selected_column"), &Tree::get_selected_column); ClassDB::bind_method(D_METHOD("get_pressed_button"), &Tree::get_pressed_button); ClassDB::bind_method(D_METHOD("set_select_mode", "mode"), &Tree::set_select_mode); @@ -5129,6 +5217,9 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding); ClassDB::bind_method(D_METHOD("is_folding_hidden"), &Tree::is_folding_hidden); + ClassDB::bind_method(D_METHOD("set_enable_recursive_folding", "enable"), &Tree::set_enable_recursive_folding); + ClassDB::bind_method(D_METHOD("is_recursive_folding_enabled"), &Tree::is_recursive_folding_enabled); + ClassDB::bind_method(D_METHOD("set_drop_mode_flags", "flags"), &Tree::set_drop_mode_flags); ClassDB::bind_method(D_METHOD("get_drop_mode_flags"), &Tree::get_drop_mode_flags); @@ -5143,6 +5234,7 @@ void Tree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_recursive_folding"), "set_enable_recursive_folding", "is_recursive_folding_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden"); ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode"); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 450943c048..77a62e1d6a 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -173,6 +173,8 @@ private: } } + bool _is_any_collapsed(bool p_only_visible); + protected: static void _bind_methods(); @@ -272,6 +274,9 @@ public: void set_collapsed(bool p_collapsed); bool is_collapsed(); + void set_collapsed_recursive(bool p_collapsed); + bool is_any_collapsed(bool p_only_visible = false); + void set_visible(bool p_visible); bool is_visible(); @@ -613,6 +618,8 @@ private: bool hide_folding = false; + bool enable_recursive_folding = true; + int _count_selected_items(TreeItem *p_from) const; bool _is_branch_selected(TreeItem *p_from) const; bool _is_sibling_branch_selected(TreeItem *p_from) const; @@ -659,6 +666,7 @@ public: bool is_root_hidden() const; TreeItem *get_next_selected(TreeItem *p_item); TreeItem *get_selected() const; + void set_selected(TreeItem *p_item, int p_column = 0); int get_selected_column() const; int get_pressed_button() const; void set_select_mode(SelectMode p_mode); @@ -712,6 +720,9 @@ public: void set_hide_folding(bool p_hide); bool is_folding_hidden() const; + void set_enable_recursive_folding(bool p_enable); + bool is_recursive_folding_enabled() const; + void set_drop_mode_flags(int p_flags); int get_drop_mode_flags() const; diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index 1e03ed6e76..0ea49b1645 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -381,14 +381,14 @@ String VideoStreamPlayer::get_stream_name() const { return stream->get_name(); } -float VideoStreamPlayer::get_stream_position() const { +double VideoStreamPlayer::get_stream_position() const { if (playback.is_null()) { return 0; } return playback->get_playback_position(); } -void VideoStreamPlayer::set_stream_position(float p_position) { +void VideoStreamPlayer::set_stream_position(double p_position) { if (playback.is_valid()) { playback->seek(p_position); } diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index 9974eb8488..b1ba8a65d7 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -105,8 +105,8 @@ public: float get_volume_db() const; String get_stream_name() const; - float get_stream_position() const; - void set_stream_position(float p_position); + double get_stream_position() const; + void set_stream_position(double p_position); void set_autoplay(bool p_enable); bool has_autoplay() const; diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp index 3b7f499a07..e8e3e3e556 100644 --- a/scene/gui/view_panner.cpp +++ b/scene/gui/view_panner.cpp @@ -38,7 +38,9 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { Vector2 scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP)); - if (scroll_vec != Vector2()) { + // Moving the scroll wheel sends two events: one with pressed as true, + // and one with pressed as false. Make sure we only process one of them. + if (scroll_vec != Vector2() && mb->is_pressed()) { if (control_scheme == SCROLL_PANS) { if (mb->is_ctrl_pressed()) { scroll_vec.y *= mb->get_factor(); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 05d86f77f2..7bcd4721fc 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -168,9 +168,6 @@ Transform2D CanvasItem::get_screen_transform() const { } Transform2D CanvasItem::get_global_transform() const { -#ifdef DEBUG_ENABLED - ERR_FAIL_COND_V(!is_inside_tree(), get_transform()); -#endif if (global_invalid) { const CanvasItem *pi = get_parent_item(); if (pi) { @@ -222,6 +219,7 @@ void CanvasItem::_enter_canvas() { } RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, canvas); + RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, visibility_layer); canvas_group = "root_canvas" + itos(canvas.get_id()); @@ -239,6 +237,7 @@ void CanvasItem::_enter_canvas() { canvas_layer = parent->canvas_layer; RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, parent->get_canvas_item()); RenderingServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index()); + RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, visibility_layer); } pending_update = false; @@ -980,14 +979,19 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("make_canvas_position_local", "screen_point"), &CanvasItem::make_canvas_position_local); ClassDB::bind_method(D_METHOD("make_input_local", "event"), &CanvasItem::make_input_local); + ClassDB::bind_method(D_METHOD("set_visibility_layer", "layer"), &CanvasItem::set_visibility_layer); + ClassDB::bind_method(D_METHOD("get_visibility_layer"), &CanvasItem::get_visibility_layer); + ClassDB::bind_method(D_METHOD("set_visibility_layer_bit", "layer", "enabled"), &CanvasItem::set_visibility_layer_bit); + ClassDB::bind_method(D_METHOD("get_visibility_layer_bit", "layer"), &CanvasItem::get_visibility_layer_bit); + ClassDB::bind_method(D_METHOD("set_texture_filter", "mode"), &CanvasItem::set_texture_filter); ClassDB::bind_method(D_METHOD("get_texture_filter"), &CanvasItem::get_texture_filter); ClassDB::bind_method(D_METHOD("set_texture_repeat", "mode"), &CanvasItem::set_texture_repeat); ClassDB::bind_method(D_METHOD("get_texture_repeat"), &CanvasItem::get_texture_repeat); - ClassDB::bind_method(D_METHOD("set_clip_children", "enable"), &CanvasItem::set_clip_children); - ClassDB::bind_method(D_METHOD("is_clipping_children"), &CanvasItem::is_clipping_children); + ClassDB::bind_method(D_METHOD("set_clip_children_mode", "mode"), &CanvasItem::set_clip_children_mode); + ClassDB::bind_method(D_METHOD("get_clip_children_mode"), &CanvasItem::get_clip_children_mode); GDVIRTUAL_BIND(_draw); @@ -997,8 +1001,9 @@ void CanvasItem::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "self_modulate"), "set_self_modulate", "get_self_modulate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_behind_parent"), "set_draw_behind_parent", "is_draw_behind_parent_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_children"), "set_clip_children", "is_clipping_children"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "clip_children", PROPERTY_HINT_ENUM, "Disabled,Clip Only,Clip + Draw"), "set_clip_children_mode", "get_clip_children_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_light_mask", "get_light_mask"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "visibility_layer", PROPERTY_HINT_LAYERS_2D_RENDER), "set_visibility_layer", "get_visibility_layer"); ADD_GROUP("Texture", "texture_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Inherit,Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter"); @@ -1035,6 +1040,11 @@ void CanvasItem::_bind_methods() { BIND_ENUM_CONSTANT(TEXTURE_REPEAT_ENABLED); BIND_ENUM_CONSTANT(TEXTURE_REPEAT_MIRROR); BIND_ENUM_CONSTANT(TEXTURE_REPEAT_MAX); + + BIND_ENUM_CONSTANT(CLIP_CHILDREN_DISABLED); + BIND_ENUM_CONSTANT(CLIP_CHILDREN_ONLY); + BIND_ENUM_CONSTANT(CLIP_CHILDREN_AND_DRAW); + BIND_ENUM_CONSTANT(CLIP_CHILDREN_MAX); } Transform2D CanvasItem::get_canvas_transform() const { @@ -1092,7 +1102,30 @@ int CanvasItem::get_canvas_layer() const { } } -void CanvasItem::_update_texture_filter_changed(bool p_propagate) { +void CanvasItem::set_visibility_layer(uint32_t p_visibility_layer) { + visibility_layer = p_visibility_layer; + RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, p_visibility_layer); +} + +uint32_t CanvasItem::get_visibility_layer() const { + return visibility_layer; +} + +void CanvasItem::set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable) { + ERR_FAIL_UNSIGNED_INDEX(p_visibility_layer, 32); + if (p_enable) { + set_visibility_layer(visibility_layer | (1 << p_visibility_layer)); + } else { + set_visibility_layer(visibility_layer & (~(1 << p_visibility_layer))); + } +} + +bool CanvasItem::get_visibility_layer_bit(uint32_t p_visibility_layer) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_visibility_layer, 32, false); + return (visibility_layer & (1 << p_visibility_layer)); +} + +void CanvasItem::_refresh_texture_filter_cache() { if (!is_inside_tree()) { return; } @@ -1107,6 +1140,14 @@ void CanvasItem::_update_texture_filter_changed(bool p_propagate) { } else { texture_filter_cache = RS::CanvasItemTextureFilter(texture_filter); } +} + +void CanvasItem::_update_texture_filter_changed(bool p_propagate) { + if (!is_inside_tree()) { + return; + } + _refresh_texture_filter_cache(); + RS::get_singleton()->canvas_item_set_default_texture_filter(get_canvas_item(), texture_filter_cache); queue_redraw(); @@ -1133,7 +1174,7 @@ CanvasItem::TextureFilter CanvasItem::get_texture_filter() const { return texture_filter; } -void CanvasItem::_update_texture_repeat_changed(bool p_propagate) { +void CanvasItem::_refresh_texture_repeat_cache() { if (!is_inside_tree()) { return; } @@ -1148,6 +1189,14 @@ void CanvasItem::_update_texture_repeat_changed(bool p_propagate) { } else { texture_repeat_cache = RS::CanvasItemTextureRepeat(texture_repeat); } +} + +void CanvasItem::_update_texture_repeat_changed(bool p_propagate) { + if (!is_inside_tree()) { + return; + } + _refresh_texture_repeat_cache(); + RS::get_singleton()->canvas_item_set_default_texture_repeat(get_canvas_item(), texture_repeat_cache); queue_redraw(); if (p_propagate) { @@ -1169,26 +1218,39 @@ void CanvasItem::set_texture_repeat(TextureRepeat p_texture_repeat) { notify_property_list_changed(); } -void CanvasItem::set_clip_children(bool p_enabled) { - if (clip_children == p_enabled) { +void CanvasItem::set_clip_children_mode(ClipChildrenMode p_clip_mode) { + ERR_FAIL_COND(p_clip_mode >= CLIP_CHILDREN_MAX); + + if (clip_children_mode == p_clip_mode) { return; } - clip_children = p_enabled; + clip_children_mode = p_clip_mode; if (Object::cast_to<CanvasGroup>(this) != nullptr) { //avoid accidental bugs, make this not work on CanvasGroup return; } - RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), clip_children ? RS::CANVAS_GROUP_MODE_OPAQUE : RS::CANVAS_GROUP_MODE_DISABLED); + + RS::get_singleton()->canvas_item_set_canvas_group_mode(get_canvas_item(), RS::CanvasGroupMode(clip_children_mode)); } -bool CanvasItem::is_clipping_children() const { - return clip_children; +CanvasItem::ClipChildrenMode CanvasItem::get_clip_children_mode() const { + return clip_children_mode; } CanvasItem::TextureRepeat CanvasItem::get_texture_repeat() const { return texture_repeat; } +CanvasItem::TextureFilter CanvasItem::get_texture_filter_in_tree() { + _refresh_texture_filter_cache(); + return (TextureFilter)texture_filter_cache; +} + +CanvasItem::TextureRepeat CanvasItem::get_texture_repeat_in_tree() { + _refresh_texture_repeat_cache(); + return (TextureRepeat)texture_repeat_cache; +} + CanvasItem::CanvasItem() : xform_change(this) { canvas_item = RenderingServer::get_singleton()->canvas_item_create(); diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h index b80289fdb4..4e78a175dc 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -66,8 +66,16 @@ public: TEXTURE_REPEAT_MAX, }; + enum ClipChildrenMode { + CLIP_CHILDREN_DISABLED, + CLIP_CHILDREN_ONLY, + CLIP_CHILDREN_AND_DRAW, + CLIP_CHILDREN_MAX, + }; + private: - mutable SelfList<Node> xform_change; + mutable SelfList<Node> + xform_change; RID canvas_item; StringName canvas_group; @@ -81,11 +89,11 @@ private: List<CanvasItem *>::Element *C = nullptr; int light_mask = 1; + uint32_t visibility_layer = 1; Window *window = nullptr; bool visible = true; bool parent_visible_in_tree = false; - bool clip_children = false; bool pending_update = false; bool top_level = false; bool drawing = false; @@ -95,6 +103,8 @@ private: bool notify_local_transform = false; bool notify_transform = false; + ClipChildrenMode clip_children_mode = CLIP_CHILDREN_DISABLED; + RS::CanvasItemTextureFilter texture_filter_cache = RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR; RS::CanvasItemTextureRepeat texture_repeat_cache = RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED; TextureFilter texture_filter = TEXTURE_FILTER_PARENT_NODE; @@ -121,7 +131,9 @@ private: static CanvasItem *current_item_drawn; friend class Viewport; + void _refresh_texture_repeat_cache(); void _update_texture_repeat_changed(bool p_propagate); + void _refresh_texture_filter_cache(); void _update_texture_filter_changed(bool p_propagate); protected: @@ -200,8 +212,8 @@ public: void queue_redraw(); void move_to_front(); - void set_clip_children(bool p_enabled); - bool is_clipping_children() const; + void set_clip_children_mode(ClipChildrenMode p_clip_mode); + ClipChildrenMode get_clip_children_mode() const; virtual void set_light_mask(int p_light_mask); int get_light_mask() const; @@ -212,6 +224,12 @@ public: void set_self_modulate(const Color &p_self_modulate); Color get_self_modulate() const; + void set_visibility_layer(uint32_t p_visibility_layer); + uint32_t get_visibility_layer() const; + + void set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable); + bool get_visibility_layer_bit(uint32_t p_visibility_layer) const; + /* DRAWING API */ void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = 1.0, real_t p_dash = 2.0); @@ -310,6 +328,9 @@ public: virtual void set_texture_repeat(TextureRepeat p_texture_repeat); TextureRepeat get_texture_repeat() const; + TextureFilter get_texture_filter_in_tree(); + TextureRepeat get_texture_repeat_in_tree(); + // Used by control nodes to retrieve the parent's anchorable area virtual Rect2 get_anchorable_rect() const { return Rect2(0, 0, 0, 0); }; @@ -321,6 +342,7 @@ public: VARIANT_ENUM_CAST(CanvasItem::TextureFilter) VARIANT_ENUM_CAST(CanvasItem::TextureRepeat) +VARIANT_ENUM_CAST(CanvasItem::ClipChildrenMode) class CanvasTexture : public Texture2D { GDCLASS(CanvasTexture, Texture2D); diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index 214efe432b..be5788739b 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -38,6 +38,7 @@ void CanvasLayer::set_layer(int p_xform) { layer = p_xform; if (viewport.is_valid()) { RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index()); + vp->_gui_set_root_order_dirty(); } } diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index bec378dd91..2c395ec07d 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -32,9 +32,6 @@ #include "core/io/compression.h" #include "scene/main/timer.h" -void HTTPRequest::_redirect_request(const String &p_new_url) { -} - Error HTTPRequest::_request() { return client->connect_to_host(url, port, use_tls, validate_tls); } @@ -48,6 +45,7 @@ Error HTTPRequest::_parse_url(const String &p_url) { body_len = -1; body.clear(); downloaded.set(0); + final_body_size.set(0); redirections = 0; String scheme; @@ -153,7 +151,7 @@ Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_cust client->set_blocking_mode(false); err = _request(); if (err != OK) { - call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); return ERR_CANT_CONNECT; } @@ -169,7 +167,7 @@ void HTTPRequest::_thread_func(void *p_userdata) { Error err = hr->_request(); if (err != OK) { - hr->call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + hr->_defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); } else { while (!hr->thread_request_quit.is_set()) { bool exit = hr->_update_connection(); @@ -198,6 +196,7 @@ void HTTPRequest::cancel_request() { } file.unref(); + decompressor.unref(); client->close(); body.clear(); got_response = false; @@ -208,7 +207,7 @@ void HTTPRequest::cancel_request() { bool HTTPRequest::_handle_response(bool *ret_value) { if (!client->has_response()) { - call_deferred(SNAME("_request_done"), RESULT_NO_RESPONSE, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_NO_RESPONSE, 0, PackedStringArray(), PackedByteArray()); *ret_value = true; return true; } @@ -219,6 +218,9 @@ bool HTTPRequest::_handle_response(bool *ret_value) { client->get_response_headers(&rheaders); response_headers.clear(); downloaded.set(0); + final_body_size.set(0); + decompressor.unref(); + for (const String &E : rheaders) { response_headers.push_back(E); } @@ -227,7 +229,7 @@ bool HTTPRequest::_handle_response(bool *ret_value) { // Handle redirect. if (max_redirects >= 0 && redirections >= max_redirects) { - call_deferred(SNAME("_request_done"), RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, PackedByteArray()); + _defer_done(RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, PackedByteArray()); *ret_value = true; return true; } @@ -259,6 +261,7 @@ bool HTTPRequest::_handle_response(bool *ret_value) { body_len = -1; body.clear(); downloaded.set(0); + final_body_size.set(0); redirections = new_redirs; *ret_value = false; return true; @@ -266,13 +269,26 @@ bool HTTPRequest::_handle_response(bool *ret_value) { } } + // Check if we need to start streaming decompression. + String content_encoding; + if (accept_gzip) { + content_encoding = get_header_value(response_headers, "Content-Encoding").to_lower(); + } + if (content_encoding == "gzip") { + decompressor.instantiate(); + decompressor->start_decompression(false, get_download_chunk_size() * 2); + } else if (content_encoding == "deflate") { + decompressor.instantiate(); + decompressor->start_decompression(true, get_download_chunk_size() * 2); + } + return false; } bool HTTPRequest::_update_connection() { switch (client->get_status()) { case HTTPClient::STATUS_DISCONNECTED: { - call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); return true; // End it, since it's disconnected. } break; case HTTPClient::STATUS_RESOLVING: { @@ -281,7 +297,7 @@ bool HTTPRequest::_update_connection() { return false; } break; case HTTPClient::STATUS_CANT_RESOLVE: { - call_deferred(SNAME("_request_done"), RESULT_CANT_RESOLVE, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_CANT_RESOLVE, 0, PackedStringArray(), PackedByteArray()); return true; } break; @@ -291,7 +307,7 @@ bool HTTPRequest::_update_connection() { return false; } break; // Connecting to IP. case HTTPClient::STATUS_CANT_CONNECT: { - call_deferred(SNAME("_request_done"), RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray()); return true; } break; @@ -306,16 +322,16 @@ bool HTTPRequest::_update_connection() { return ret_value; } - call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); + _defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); return true; } if (body_len < 0) { // Chunked transfer is done. - call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, body); + _defer_done(RESULT_SUCCESS, response_code, response_headers, body); return true; } - call_deferred(SNAME("_request_done"), RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, PackedByteArray()); + _defer_done(RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, PackedByteArray()); return true; // Request might have been done. } else { @@ -324,7 +340,7 @@ bool HTTPRequest::_update_connection() { int size = request_data.size(); Error err = client->request(method, request_string, headers, size > 0 ? request_data.ptr() : nullptr, size); if (err != OK) { - call_deferred(SNAME("_request_done"), RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); return true; } @@ -347,7 +363,7 @@ bool HTTPRequest::_update_connection() { } if (!client->is_response_chunked() && client->get_response_body_length() == 0) { - call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); + _defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray()); return true; } @@ -356,14 +372,14 @@ bool HTTPRequest::_update_connection() { body_len = client->get_response_body_length(); if (body_size_limit >= 0 && body_len > body_size_limit) { - call_deferred(SNAME("_request_done"), RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); + _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); return true; } if (!download_to_file.is_empty()) { file = FileAccess::open(download_to_file, FileAccess::WRITE); if (file.is_null()) { - call_deferred(SNAME("_request_done"), RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, PackedByteArray()); + _defer_done(RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, PackedByteArray()); return true; } } @@ -375,14 +391,33 @@ bool HTTPRequest::_update_connection() { } PackedByteArray chunk = client->read_response_body_chunk(); + downloaded.add(chunk.size()); + + // Decompress chunk if needed. + if (decompressor.is_valid()) { + Error err = decompressor->put_data(chunk.ptr(), chunk.size()); + if (err == OK) { + chunk.resize(decompressor->get_available_bytes()); + err = decompressor->get_data(chunk.ptrw(), chunk.size()); + } + if (err != OK) { + _defer_done(RESULT_BODY_DECOMPRESS_FAILED, response_code, response_headers, PackedByteArray()); + return true; + } + } + final_body_size.add(chunk.size()); + + if (body_size_limit >= 0 && final_body_size.get() > body_size_limit) { + _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); + return true; + } if (chunk.size()) { - downloaded.add(chunk.size()); if (file.is_valid()) { const uint8_t *r = chunk.ptr(); file->store_buffer(r, chunk.size()); if (file->get_error() != OK) { - call_deferred(SNAME("_request_done"), RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PackedByteArray()); + _defer_done(RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PackedByteArray()); return true; } } else { @@ -390,19 +425,14 @@ bool HTTPRequest::_update_connection() { } } - if (body_size_limit >= 0 && downloaded.get() > body_size_limit) { - call_deferred(SNAME("_request_done"), RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray()); - return true; - } - if (body_len >= 0) { if (downloaded.get() == body_len) { - call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, body); + _defer_done(RESULT_SUCCESS, response_code, response_headers, body); return true; } } else if (client->get_status() == HTTPClient::STATUS_DISCONNECTED) { // We read till EOF, with no errors. Request is done. - call_deferred(SNAME("_request_done"), RESULT_SUCCESS, response_code, response_headers, body); + _defer_done(RESULT_SUCCESS, response_code, response_headers, body); return true; } @@ -410,11 +440,11 @@ bool HTTPRequest::_update_connection() { } break; // Request resulted in body: break which must be read. case HTTPClient::STATUS_CONNECTION_ERROR: { - call_deferred(SNAME("_request_done"), RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); return true; } break; case HTTPClient::STATUS_TLS_HANDSHAKE_ERROR: { - call_deferred(SNAME("_request_done"), RESULT_TLS_HANDSHAKE_ERROR, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_TLS_HANDSHAKE_ERROR, 0, PackedStringArray(), PackedByteArray()); return true; } break; } @@ -422,41 +452,13 @@ bool HTTPRequest::_update_connection() { ERR_FAIL_V(false); } +void HTTPRequest::_defer_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data) { + call_deferred(SNAME("_request_done"), p_status, p_code, p_headers, p_data); +} + void HTTPRequest::_request_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data) { cancel_request(); - // Determine if the request body is compressed. - bool is_compressed; - String content_encoding = get_header_value(p_headers, "Content-Encoding").to_lower(); - Compression::Mode mode; - if (content_encoding == "gzip") { - mode = Compression::Mode::MODE_GZIP; - is_compressed = true; - } else if (content_encoding == "deflate") { - mode = Compression::Mode::MODE_DEFLATE; - is_compressed = true; - } else { - is_compressed = false; - } - - if (accept_gzip && is_compressed && p_data.size() > 0) { - // Decompress request body - PackedByteArray decompressed; - int result = Compression::decompress_dynamic(&decompressed, body_size_limit, p_data.ptr(), p_data.size(), mode); - if (result == OK) { - emit_signal(SNAME("request_completed"), p_status, p_code, p_headers, decompressed); - return; - } else if (result == -5) { - WARN_PRINT("Decompressed size of HTTP response body exceeded body_size_limit"); - p_status = RESULT_BODY_SIZE_LIMIT_EXCEEDED; - // Just return the raw data if we failed to decompress it. - } else { - WARN_PRINT("Failed to decompress HTTP response body"); - p_status = RESULT_BODY_DECOMPRESS_FAILED; - // Just return the raw data if we failed to decompress it. - } - } - emit_signal(SNAME("request_completed"), p_status, p_code, p_headers, p_data); } @@ -566,7 +568,7 @@ double HTTPRequest::get_timeout() { void HTTPRequest::_timeout() { cancel_request(); - call_deferred(SNAME("_request_done"), RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray()); + _defer_done(RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray()); } void HTTPRequest::_bind_methods() { @@ -594,7 +596,6 @@ void HTTPRequest::_bind_methods() { ClassDB::bind_method(D_METHOD("get_downloaded_bytes"), &HTTPRequest::get_downloaded_bytes); ClassDB::bind_method(D_METHOD("get_body_size"), &HTTPRequest::get_body_size); - ClassDB::bind_method(D_METHOD("_redirect_request"), &HTTPRequest::_redirect_request); ClassDB::bind_method(D_METHOD("_request_done"), &HTTPRequest::_request_done); ClassDB::bind_method(D_METHOD("set_timeout", "timeout"), &HTTPRequest::set_timeout); diff --git a/scene/main/http_request.h b/scene/main/http_request.h index 290bacd9d2..80445684b0 100644 --- a/scene/main/http_request.h +++ b/scene/main/http_request.h @@ -32,6 +32,7 @@ #define HTTP_REQUEST_H #include "core/io/http_client.h" +#include "core/io/stream_peer_gzip.h" #include "core/os/thread.h" #include "core/templates/safe_refcount.h" #include "scene/main/node.h" @@ -84,10 +85,12 @@ private: String download_to_file; + Ref<StreamPeerGZIP> decompressor; Ref<FileAccess> file; int body_len = -1; SafeNumeric<int> downloaded; + SafeNumeric<int> final_body_size; int body_size_limit = -1; int redirections = 0; @@ -113,6 +116,7 @@ private: Thread thread; + void _defer_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data); void _request_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data); static void _thread_func(void *p_userdata); diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index 6dd83e4636..56e719968b 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -100,7 +100,7 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene } if (p_replace) { - queue_delete(); + queue_free(); base->remove_child(this); } diff --git a/scene/main/missing_node.cpp b/scene/main/missing_node.cpp index 395fdad9e4..7ce527fd9c 100644 --- a/scene/main/missing_node.cpp +++ b/scene/main/missing_node.cpp @@ -74,9 +74,9 @@ bool MissingNode::is_recording_properties() const { return recording_properties; } -TypedArray<String> MissingNode::get_configuration_warnings() const { +PackedStringArray MissingNode::get_configuration_warnings() const { // The mere existence of this node is warning. - TypedArray<String> ret; + PackedStringArray ret; ret.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class)); ret.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss.")); return ret; diff --git a/scene/main/missing_node.h b/scene/main/missing_node.h index d200fbb47f..0003f71f29 100644 --- a/scene/main/missing_node.h +++ b/scene/main/missing_node.h @@ -55,7 +55,7 @@ public: void set_recording_properties(bool p_enable); bool is_recording_properties() const; - virtual TypedArray<String> get_configuration_warnings() const override; + virtual PackedStringArray get_configuration_warnings() const override; MissingNode(); }; diff --git a/scene/main/multiplayer_api.cpp b/scene/main/multiplayer_api.cpp index 2d2103f031..8b4b98c172 100644 --- a/scene/main/multiplayer_api.cpp +++ b/scene/main/multiplayer_api.cpp @@ -39,7 +39,7 @@ #include "core/os/os.h" #endif -StringName MultiplayerAPI::default_interface = StringName(); +StringName MultiplayerAPI::default_interface; void MultiplayerAPI::set_default_interface(const StringName &p_interface) { ERR_FAIL_COND_MSG(!ClassDB::is_parent_class(p_interface, MultiplayerAPI::get_class_static()), vformat("Can't make %s the default multiplayer interface since it does not extend MultiplayerAPI.", p_interface)); @@ -329,11 +329,9 @@ void MultiplayerAPI::_bind_methods() { /// MultiplayerAPIExtension Error MultiplayerAPIExtension::poll() { - int err; - if (GDVIRTUAL_CALL(_poll, err)) { - return (Error)err; - } - return OK; + int err = OK; + GDVIRTUAL_CALL(_poll, err); + return (Error)err; } void MultiplayerAPIExtension::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) { @@ -342,26 +340,20 @@ void MultiplayerAPIExtension::set_multiplayer_peer(const Ref<MultiplayerPeer> &p Ref<MultiplayerPeer> MultiplayerAPIExtension::get_multiplayer_peer() { Ref<MultiplayerPeer> peer; - if (GDVIRTUAL_CALL(_get_multiplayer_peer, peer)) { - return peer; - } - return nullptr; + GDVIRTUAL_CALL(_get_multiplayer_peer, peer); + return peer; } int MultiplayerAPIExtension::get_unique_id() { - int id; - if (GDVIRTUAL_CALL(_get_unique_id, id)) { - return id; - } - return 1; + int id = 1; + GDVIRTUAL_CALL(_get_unique_id, id); + return id; } Vector<int> MultiplayerAPIExtension::get_peer_ids() { Vector<int> ids; - if (GDVIRTUAL_CALL(_get_peer_ids, ids)) { - return ids; - } - return Vector<int>(); + GDVIRTUAL_CALL(_get_peer_ids, ids); + return ids; } Error MultiplayerAPIExtension::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) { @@ -372,35 +364,27 @@ Error MultiplayerAPIExtension::rpcp(Object *p_obj, int p_peer_id, const StringNa for (int i = 0; i < p_argcount; i++) { args.push_back(*p_arg[i]); } - int ret; - if (GDVIRTUAL_CALL(_rpc, p_peer_id, p_obj, p_method, args, ret)) { - return (Error)ret; - } - return FAILED; + int ret = FAILED; + GDVIRTUAL_CALL(_rpc, p_peer_id, p_obj, p_method, args, ret); + return (Error)ret; } int MultiplayerAPIExtension::get_remote_sender_id() { - int id; - if (GDVIRTUAL_CALL(_get_remote_sender_id, id)) { - return id; - } - return 0; + int id = 0; + GDVIRTUAL_CALL(_get_remote_sender_id, id); + return id; } Error MultiplayerAPIExtension::object_configuration_add(Object *p_object, Variant p_config) { - int err; - if (GDVIRTUAL_CALL(_object_configuration_add, p_object, p_config, err)) { - return (Error)err; - } - return ERR_UNAVAILABLE; + int err = ERR_UNAVAILABLE; + GDVIRTUAL_CALL(_object_configuration_add, p_object, p_config, err); + return (Error)err; } Error MultiplayerAPIExtension::object_configuration_remove(Object *p_object, Variant p_config) { - int err; - if (GDVIRTUAL_CALL(_object_configuration_remove, p_object, p_config, err)) { - return (Error)err; - } - return ERR_UNAVAILABLE; + int err = ERR_UNAVAILABLE; + GDVIRTUAL_CALL(_object_configuration_remove, p_object, p_config, err); + return (Error)err; } void MultiplayerAPIExtension::_bind_methods() { diff --git a/scene/main/multiplayer_peer.cpp b/scene/main/multiplayer_peer.cpp index 9b63118f7c..92ba3debd1 100644 --- a/scene/main/multiplayer_peer.cpp +++ b/scene/main/multiplayer_peer.cpp @@ -78,6 +78,10 @@ bool MultiplayerPeer::is_refusing_new_connections() const { return refuse_connections; } +bool MultiplayerPeer::is_server_relay_supported() const { + return false; +} + void MultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &MultiplayerPeer::set_transfer_channel); ClassDB::bind_method(D_METHOD("get_transfer_channel"), &MultiplayerPeer::get_transfer_channel); @@ -86,8 +90,12 @@ void MultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer); ClassDB::bind_method(D_METHOD("get_packet_peer"), &MultiplayerPeer::get_packet_peer); + ClassDB::bind_method(D_METHOD("get_packet_channel"), &MultiplayerPeer::get_packet_channel); + ClassDB::bind_method(D_METHOD("get_packet_mode"), &MultiplayerPeer::get_packet_mode); ClassDB::bind_method(D_METHOD("poll"), &MultiplayerPeer::poll); + ClassDB::bind_method(D_METHOD("close"), &MultiplayerPeer::close); + ClassDB::bind_method(D_METHOD("disconnect_peer", "peer", "force"), &MultiplayerPeer::disconnect_peer, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_connection_status"), &MultiplayerPeer::get_connection_status); ClassDB::bind_method(D_METHOD("get_unique_id"), &MultiplayerPeer::get_unique_id); @@ -96,6 +104,8 @@ void MultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "enable"), &MultiplayerPeer::set_refuse_new_connections); ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerPeer::is_refusing_new_connections); + ClassDB::bind_method(D_METHOD("is_server_relay_supported"), &MultiplayerPeer::is_server_relay_supported); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel"); @@ -113,9 +123,6 @@ void MultiplayerPeer::_bind_methods() { ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id"))); - ADD_SIGNAL(MethodInfo("server_disconnected")); - ADD_SIGNAL(MethodInfo("connection_succeeded")); - ADD_SIGNAL(MethodInfo("connection_failed")); } /*************/ @@ -177,6 +184,14 @@ bool MultiplayerPeerExtension::is_refusing_new_connections() const { return MultiplayerPeer::is_refusing_new_connections(); } +bool MultiplayerPeerExtension::is_server_relay_supported() const { + bool can_relay; + if (GDVIRTUAL_CALL(_is_server_relay_supported, can_relay)) { + return can_relay; + } + return MultiplayerPeer::is_server_relay_supported(); +} + void MultiplayerPeerExtension::_bind_methods() { GDVIRTUAL_BIND(_get_packet, "r_buffer", "r_buffer_size"); GDVIRTUAL_BIND(_put_packet, "p_buffer", "p_buffer_size"); @@ -197,6 +212,8 @@ void MultiplayerPeerExtension::_bind_methods() { GDVIRTUAL_BIND(_get_packet_peer); GDVIRTUAL_BIND(_is_server); GDVIRTUAL_BIND(_poll); + GDVIRTUAL_BIND(_close); + GDVIRTUAL_BIND(_disconnect_peer, "p_peer", "p_force"); GDVIRTUAL_BIND(_get_unique_id); GDVIRTUAL_BIND(_set_refuse_new_connections, "p_enable"); GDVIRTUAL_BIND(_is_refusing_new_connections); diff --git a/scene/main/multiplayer_peer.h b/scene/main/multiplayer_peer.h index ab7483ece5..4b5909538e 100644 --- a/scene/main/multiplayer_peer.h +++ b/scene/main/multiplayer_peer.h @@ -74,14 +74,20 @@ public: virtual TransferMode get_transfer_mode() const; virtual void set_refuse_new_connections(bool p_enable); virtual bool is_refusing_new_connections() const; + virtual bool is_server_relay_supported() const; virtual void set_target_peer(int p_peer_id) = 0; virtual int get_packet_peer() const = 0; + virtual TransferMode get_packet_mode() const = 0; + virtual int get_packet_channel() const = 0; + + virtual void disconnect_peer(int p_peer, bool p_force = false) = 0; virtual bool is_server() const = 0; virtual void poll() = 0; + virtual void close() = 0; virtual int get_unique_id() const = 0; @@ -123,14 +129,21 @@ public: virtual bool is_refusing_new_connections() const override; GDVIRTUAL0RC(bool, _is_refusing_new_connections); // Optional. + virtual bool is_server_relay_supported() const override; + GDVIRTUAL0RC(bool, _is_server_relay_supported); // Optional. + EXBIND1(set_transfer_channel, int); EXBIND0RC(int, get_transfer_channel); EXBIND1(set_transfer_mode, TransferMode); EXBIND0RC(TransferMode, get_transfer_mode); EXBIND1(set_target_peer, int); EXBIND0RC(int, get_packet_peer); + EXBIND0RC(TransferMode, get_packet_mode); + EXBIND0RC(int, get_packet_channel); EXBIND0RC(bool, is_server); EXBIND0(poll); + EXBIND0(close); + EXBIND2(disconnect_peer, int, bool); EXBIND0RC(int, get_unique_id); EXBIND0RC(ConnectionStatus, get_connection_status); }; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 29f4d4fb1c..5dd0911694 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -322,62 +322,62 @@ void Node::_propagate_exit_tree() { data.depth = -1; } -void Node::move_child(Node *p_child, int p_pos) { +void Node::move_child(Node *p_child, int p_index) { ERR_FAIL_NULL(p_child); ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node."); // We need to check whether node is internal and move it only in the relevant node range. if (p_child->_is_internal_front()) { - if (p_pos < 0) { - p_pos += data.internal_children_front; + if (p_index < 0) { + p_index += data.internal_children_front; } - ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_front, vformat("Invalid new child position: %d. Child is internal.", p_pos)); - _move_child(p_child, p_pos); + ERR_FAIL_INDEX_MSG(p_index, data.internal_children_front, vformat("Invalid new child index: %d. Child is internal.", p_index)); + _move_child(p_child, p_index); } else if (p_child->_is_internal_back()) { - if (p_pos < 0) { - p_pos += data.internal_children_back; + if (p_index < 0) { + p_index += data.internal_children_back; } - ERR_FAIL_INDEX_MSG(p_pos, data.internal_children_back, vformat("Invalid new child position: %d. Child is internal.", p_pos)); - _move_child(p_child, data.children.size() - data.internal_children_back + p_pos); + ERR_FAIL_INDEX_MSG(p_index, data.internal_children_back, vformat("Invalid new child index: %d. Child is internal.", p_index)); + _move_child(p_child, data.children.size() - data.internal_children_back + p_index); } else { - if (p_pos < 0) { - p_pos += get_child_count(false); + if (p_index < 0) { + p_index += get_child_count(false); } - ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child position: %d.", p_pos)); - _move_child(p_child, p_pos + data.internal_children_front); + ERR_FAIL_INDEX_MSG(p_index, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child index: %d.", p_index)); + _move_child(p_child, p_index + data.internal_children_front); } } -void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { +void Node::_move_child(Node *p_child, int p_index, bool p_ignore_end) { ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, move_child() failed. Consider using call_deferred(\"move_child\") instead (or \"popup\" if this is from a popup)."); // Specifying one place beyond the end - // means the same as moving to the last position + // means the same as moving to the last index if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly. if (p_child->_is_internal_front()) { - if (p_pos == data.internal_children_front) { - p_pos--; + if (p_index == data.internal_children_front) { + p_index--; } } else if (p_child->_is_internal_back()) { - if (p_pos == data.children.size()) { - p_pos--; + if (p_index == data.children.size()) { + p_index--; } } else { - if (p_pos == data.children.size() - data.internal_children_back) { - p_pos--; + if (p_index == data.children.size() - data.internal_children_back) { + p_index--; } } } - if (p_child->data.pos == p_pos) { + if (p_child->data.index == p_index) { return; //do nothing } - int motion_from = MIN(p_pos, p_child->data.pos); - int motion_to = MAX(p_pos, p_child->data.pos); + int motion_from = MIN(p_index, p_child->data.index); + int motion_to = MAX(p_index, p_child->data.index); - data.children.remove_at(p_child->data.pos); - data.children.insert(p_pos, p_child); + data.children.remove_at(p_child->data.index); + data.children.insert(p_index, p_child); if (data.tree) { data.tree->tree_changed(); @@ -386,7 +386,7 @@ void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { data.blocked++; //new pos first for (int i = motion_from; i <= motion_to; i++) { - data.children[i]->data.pos = i; + data.children[i]->data.index = i; } // notification second move_child_notify(p_child); @@ -1024,11 +1024,9 @@ String increase_numeric_string(const String &s) { void Node::_generate_serial_child_name(const Node *p_child, StringName &name) const { if (name == StringName()) { - //no name and a new name is needed, create one. + // No name and a new name is needed, create one. name = p_child->get_class(); - // Adjust casing according to project setting. - name = adjust_name_casing(name); } //quickly test if proposed name exists @@ -1106,7 +1104,7 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) { //add a child node quickly, without name validation p_child->data.name = p_name; - p_child->data.pos = data.children.size(); + p_child->data.index = data.children.size(); data.children.push_back(p_child); p_child->data.parent = this; @@ -1173,9 +1171,9 @@ void Node::remove_child(Node *p_child) { Node **children = data.children.ptrw(); int idx = -1; - if (p_child->data.pos >= 0 && p_child->data.pos < child_count) { - if (children[p_child->data.pos] == p_child) { - idx = p_child->data.pos; + if (p_child->data.index >= 0 && p_child->data.index < child_count) { + if (children[p_child->data.index] == p_child) { + idx = p_child->data.index; } } @@ -1211,12 +1209,12 @@ void Node::remove_child(Node *p_child) { children = data.children.ptrw(); for (int i = idx; i < child_count; i++) { - children[i]->data.pos = i; + children[i]->data.index = i; children[i]->notification(NOTIFICATION_MOVED_IN_PARENT); } p_child->data.parent = nullptr; - p_child->data.pos = -1; + p_child->data.index = -1; if (data.inside_tree) { p_child->_propagate_after_exit_tree(); @@ -1343,12 +1341,23 @@ Node *Node::get_node(const NodePath &p_path) const { Node *node = get_node_or_null(p_path); if (unlikely(!node)) { + // Try to get a clear description of this node in the error message. + String desc; + if (is_inside_tree()) { + desc = get_path(); + } else { + desc = get_name(); + if (desc.is_empty()) { + desc = get_class(); + } + } + if (p_path.is_absolute()) { ERR_FAIL_V_MSG(nullptr, - vformat(R"(Node not found: "%s" (absolute path attempted from "%s").)", p_path, get_path())); + vformat(R"(Node not found: "%s" (absolute path attempted from "%s").)", p_path, desc)); } else { ERR_FAIL_V_MSG(nullptr, - vformat(R"(Node not found: "%s" (relative to "%s").)", p_path, get_path())); + vformat(R"(Node not found: "%s" (relative to "%s").)", p_path, desc)); } } @@ -1411,14 +1420,14 @@ TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_ty if (cptr[i]->is_class(p_type)) { ret.append(cptr[i]); } else if (cptr[i]->get_script_instance()) { - Ref<Script> script = cptr[i]->get_script_instance()->get_script(); - while (script.is_valid()) { - if ((ScriptServer::is_global_class(p_type) && ScriptServer::get_global_class_path(p_type) == script->get_path()) || p_type == script->get_path()) { + Ref<Script> scr = cptr[i]->get_script_instance()->get_script(); + while (scr.is_valid()) { + if ((ScriptServer::is_global_class(p_type) && ScriptServer::get_global_class_path(p_type) == scr->get_path()) || p_type == scr->get_path()) { ret.append(cptr[i]); break; } - script = script->get_base_script(); + scr = scr->get_base_script(); } } @@ -1466,26 +1475,16 @@ bool Node::is_greater_than(const Node *p_node) const { ERR_FAIL_COND_V(data.depth < 0, false); ERR_FAIL_COND_V(p_node->data.depth < 0, false); -#ifdef NO_ALLOCA - - Vector<int> this_stack; - Vector<int> that_stack; - this_stack.resize(data.depth); - that_stack.resize(p_node->data.depth); - -#else int *this_stack = (int *)alloca(sizeof(int) * data.depth); int *that_stack = (int *)alloca(sizeof(int) * p_node->data.depth); -#endif - const Node *n = this; int idx = data.depth - 1; while (n) { ERR_FAIL_INDEX_V(idx, data.depth, false); - this_stack[idx--] = n->data.pos; + this_stack[idx--] = n->data.index; n = n->data.parent; } ERR_FAIL_COND_V(idx != -1, false); @@ -1493,7 +1492,7 @@ bool Node::is_greater_than(const Node *p_node) const { idx = p_node->data.depth - 1; while (n) { ERR_FAIL_INDEX_V(idx, p_node->data.depth, false); - that_stack[idx--] = n->data.pos; + that_stack[idx--] = n->data.index; n = n->data.parent; } @@ -1760,11 +1759,11 @@ void Node::add_to_group(const StringName &p_identifier, bool p_persistent) { } void Node::remove_from_group(const StringName &p_identifier) { - ERR_FAIL_COND(!data.grouped.has(p_identifier)); - HashMap<StringName, GroupData>::Iterator E = data.grouped.find(p_identifier); - ERR_FAIL_COND(!E); + if (!E) { + return; + } if (data.tree) { data.tree->remove_from_group(E->key, this); @@ -1904,9 +1903,9 @@ int Node::get_index(bool p_include_internal) const { ERR_FAIL_COND_V_MSG(!p_include_internal && (_is_internal_front() || _is_internal_back()), -1, "Node is internal. Can't get index with 'include_internal' being false."); if (data.parent && !p_include_internal) { - return data.pos - data.parent->data.internal_children_front; + return data.index - data.parent->data.internal_children_front; } - return data.pos; + return data.index; } Ref<Tween> Node::create_tween() { @@ -2131,9 +2130,9 @@ Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) c if (p_flags & DUPLICATE_SCRIPTS) { bool is_valid = false; - Variant script = N->get()->get(script_property_name, &is_valid); + Variant scr = N->get()->get(script_property_name, &is_valid); if (is_valid) { - current_node->set(script_property_name, script); + current_node->set(script_property_name, scr); } } @@ -2401,12 +2400,12 @@ void Node::replace_by(Node *p_node, bool p_keep_groups) { } Node *parent = data.parent; - int pos_in_parent = data.pos; + int index_in_parent = data.index; if (data.parent) { parent->remove_child(this); parent->add_child(p_node); - parent->move_child(p_node, pos_in_parent); + parent->move_child(p_node, index_in_parent); } while (get_child_count()) { @@ -2581,11 +2580,15 @@ void Node::print_orphan_nodes() { #endif } -void Node::queue_delete() { +void Node::queue_free() { + // There are users which instantiate multiple scene trees for their games. + // Use the node's own tree to handle its deletion when relevant. if (is_inside_tree()) { get_tree()->queue_delete(this); } else { - SceneTree::get_singleton()->queue_delete(this); + SceneTree *tree = SceneTree::get_singleton(); + ERR_FAIL_NULL_MSG(tree, "Can't queue free a node when no SceneTree is available."); + tree->queue_delete(this); } } @@ -2640,29 +2643,27 @@ void Node::clear_internal_tree_resource_paths() { } } -TypedArray<String> Node::get_configuration_warnings() const { - TypedArray<String> ret; +PackedStringArray Node::get_configuration_warnings() const { + PackedStringArray ret; Vector<String> warnings; if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) { - for (int i = 0; i < warnings.size(); i++) { - ret.push_back(warnings[i]); - } + ret.append_array(warnings); } return ret; } String Node::get_configuration_warnings_as_string() const { - TypedArray<String> warnings = get_configuration_warnings(); - String all_warnings = String(); + PackedStringArray warnings = get_configuration_warnings(); + String all_warnings; for (int i = 0; i < warnings.size(); i++) { if (i > 0) { all_warnings += "\n\n"; } // Format as a bullet point list to make multiple warnings easier to distinguish // from each other. - all_warnings += String::utf8("• ") + String(warnings[i]); + all_warnings += String::utf8("• ") + warnings[i]; } return all_warnings; } @@ -2771,7 +2772,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("add_to_group", "group", "persistent"), &Node::add_to_group, DEFVAL(false)); ClassDB::bind_method(D_METHOD("remove_from_group", "group"), &Node::remove_from_group); ClassDB::bind_method(D_METHOD("is_in_group", "group"), &Node::is_in_group); - ClassDB::bind_method(D_METHOD("move_child", "child_node", "to_position"), &Node::move_child); + ClassDB::bind_method(D_METHOD("move_child", "child_node", "to_index"), &Node::move_child); ClassDB::bind_method(D_METHOD("get_groups"), &Node::_get_groups); ClassDB::bind_method(D_METHOD("set_owner", "owner"), &Node::set_owner); ClassDB::bind_method(D_METHOD("get_owner"), &Node::get_owner); @@ -2825,7 +2826,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_viewport"), &Node::get_viewport); - ClassDB::bind_method(D_METHOD("queue_free"), &Node::queue_delete); + ClassDB::bind_method(D_METHOD("queue_free"), &Node::queue_free); ClassDB::bind_method(D_METHOD("request_ready"), &Node::request_ready); @@ -2961,7 +2962,7 @@ void Node::_bind_methods() { } String Node::_get_name_num_separator() { - switch (ProjectSettings::get_singleton()->get("editor/node_naming/name_num_separator").operator int()) { + switch (GLOBAL_GET("editor/node_naming/name_num_separator").operator int()) { case 0: return ""; case 1: diff --git a/scene/main/node.h b/scene/main/node.h index 13a938ef97..4a3ec253b1 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -91,6 +91,7 @@ private: SceneTree::Group *group = nullptr; }; + // This Data struct is to avoid namespace pollution in derived classes. struct Data { String scene_file_path; Ref<SceneState> instance_state; @@ -104,7 +105,7 @@ private: int internal_children_front = 0; int internal_children_back = 0; - int pos = -1; + int index = -1; int depth = -1; int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. StringName name; @@ -186,8 +187,8 @@ private: Error _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); Error _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); - _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.pos < data.parent->data.internal_children_front; } - _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.pos >= data.parent->data.children.size() - data.parent->data.internal_children_back; } + _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.index < data.parent->data.internal_children_front; } + _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.index >= data.parent->data.children.size() - data.parent->data.internal_children_back; } friend class SceneTree; @@ -346,8 +347,8 @@ public: void get_groups(List<GroupInfo> *p_groups) const; int get_persistent_group_count() const; - void move_child(Node *p_child, int p_pos); - void _move_child(Node *p_child, int p_pos, bool p_ignore_end = false); + void move_child(Node *p_child, int p_index); + void _move_child(Node *p_child, int p_index, bool p_ignore_end = false); void set_owner(Node *p_owner); Node *get_owner() const; @@ -459,7 +460,7 @@ public: #endif static String adjust_name_casing(const String &p_name); - void queue_delete(); + void queue_free(); //hacks for speed static void init_node_hrcr(); @@ -477,7 +478,7 @@ public: _FORCE_INLINE_ Viewport *get_viewport() const { return data.viewport; } - virtual TypedArray<String> get_configuration_warnings() const; + virtual PackedStringArray get_configuration_warnings() const; String get_configuration_warnings_as_string() const; void update_configuration_warnings(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index c18aa5aaa2..81a4e3073b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -44,6 +44,7 @@ #include "node.h" #include "scene/animation/tween.h" #include "scene/debugger/scene_debugger.h" +#include "scene/gui/control.h" #include "scene/main/multiplayer_api.h" #include "scene/main/viewport.h" #include "scene/resources/environment.h" @@ -105,10 +106,10 @@ bool SceneTreeTimer::is_ignore_time_scale() { } void SceneTreeTimer::release_connections() { - List<Connection> connections; - get_all_signal_connections(&connections); + List<Connection> signal_connections; + get_all_signal_connections(&signal_connections); - for (const Connection &connection : connections) { + for (const Connection &connection : signal_connections) { disconnect(connection.signal.get_name(), connection.callable); } } @@ -122,6 +123,9 @@ void SceneTree::tree_changed() { void SceneTree::node_added(Node *p_node) { emit_signal(node_added_name, p_node); + if (call_lock > 0) { + call_skip.erase(p_node->get_instance_id()); + } } void SceneTree::node_removed(Node *p_node) { @@ -130,7 +134,7 @@ void SceneTree::node_removed(Node *p_node) { } emit_signal(node_removed_name, p_node); if (call_lock > 0) { - call_skip.insert(p_node); + call_skip.insert(p_node->get_instance_id()); } } @@ -207,15 +211,15 @@ void SceneTree::_update_group_order(Group &g, bool p_use_priority) { return; } - Node **nodes = g.nodes.ptrw(); - int node_count = g.nodes.size(); + Node **gr_nodes = g.nodes.ptrw(); + int gr_node_count = g.nodes.size(); if (p_use_priority) { SortArray<Node *, Node::ComparatorWithPriority> node_sort; - node_sort.sort(nodes, node_count); + node_sort.sort(gr_nodes, gr_node_count); } else { SortArray<Node *, Node::Comparator> node_sort; - node_sort.sort(nodes, node_count); + node_sort.sort(gr_nodes, gr_node_count); } g.changed = false; } @@ -253,36 +257,36 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro _update_group_order(g); Vector<Node *> nodes_copy = g.nodes; - Node **nodes = nodes_copy.ptrw(); - int node_count = nodes_copy.size(); + Node **gr_nodes = nodes_copy.ptrw(); + int gr_node_count = nodes_copy.size(); call_lock++; if (p_call_flags & GROUP_CALL_REVERSE) { - for (int i = node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(nodes[i])) { + for (int i = gr_node_count - 1; i >= 0; i--) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; - nodes[i]->callp(p_function, p_args, p_argcount, ce); + gr_nodes[i]->callp(p_function, p_args, p_argcount, ce); } else { - MessageQueue::get_singleton()->push_callp(nodes[i], p_function, p_args, p_argcount); + MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount); } } } else { - for (int i = 0; i < node_count; i++) { - if (call_lock && call_skip.has(nodes[i])) { + for (int i = 0; i < gr_node_count; i++) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } if (!(p_call_flags & GROUP_CALL_DEFERRED)) { Callable::CallError ce; - nodes[i]->callp(p_function, p_args, p_argcount, ce); + gr_nodes[i]->callp(p_function, p_args, p_argcount, ce); } else { - MessageQueue::get_singleton()->push_callp(nodes[i], p_function, p_args, p_argcount); + MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount); } } } @@ -306,34 +310,34 @@ void SceneTree::notify_group_flags(uint32_t p_call_flags, const StringName &p_gr _update_group_order(g); Vector<Node *> nodes_copy = g.nodes; - Node **nodes = nodes_copy.ptrw(); - int node_count = nodes_copy.size(); + Node **gr_nodes = nodes_copy.ptrw(); + int gr_node_count = nodes_copy.size(); call_lock++; if (p_call_flags & GROUP_CALL_REVERSE) { - for (int i = node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(nodes[i])) { + for (int i = gr_node_count - 1; i >= 0; i--) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } if (!(p_call_flags & GROUP_CALL_DEFERRED)) { - nodes[i]->notification(p_notification); + gr_nodes[i]->notification(p_notification); } else { - MessageQueue::get_singleton()->push_notification(nodes[i], p_notification); + MessageQueue::get_singleton()->push_notification(gr_nodes[i], p_notification); } } } else { - for (int i = 0; i < node_count; i++) { - if (call_lock && call_skip.has(nodes[i])) { + for (int i = 0; i < gr_node_count; i++) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } if (!(p_call_flags & GROUP_CALL_DEFERRED)) { - nodes[i]->notification(p_notification); + gr_nodes[i]->notification(p_notification); } else { - MessageQueue::get_singleton()->push_notification(nodes[i], p_notification); + MessageQueue::get_singleton()->push_notification(gr_nodes[i], p_notification); } } } @@ -357,34 +361,34 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group _update_group_order(g); Vector<Node *> nodes_copy = g.nodes; - Node **nodes = nodes_copy.ptrw(); - int node_count = nodes_copy.size(); + Node **gr_nodes = nodes_copy.ptrw(); + int gr_node_count = nodes_copy.size(); call_lock++; if (p_call_flags & GROUP_CALL_REVERSE) { - for (int i = node_count - 1; i >= 0; i--) { - if (call_lock && call_skip.has(nodes[i])) { + for (int i = gr_node_count - 1; i >= 0; i--) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } if (!(p_call_flags & GROUP_CALL_DEFERRED)) { - nodes[i]->set(p_name, p_value); + gr_nodes[i]->set(p_name, p_value); } else { - MessageQueue::get_singleton()->push_set(nodes[i], p_name, p_value); + MessageQueue::get_singleton()->push_set(gr_nodes[i], p_name, p_value); } } } else { - for (int i = 0; i < node_count; i++) { - if (call_lock && call_skip.has(nodes[i])) { + for (int i = 0; i < gr_node_count; i++) { + if (call_lock && call_skip.has(gr_nodes[i]->get_instance_id())) { continue; } if (!(p_call_flags & GROUP_CALL_DEFERRED)) { - nodes[i]->set(p_name, p_value); + gr_nodes[i]->set(p_name, p_value); } else { - MessageQueue::get_singleton()->push_set(nodes[i], p_name, p_value); + MessageQueue::get_singleton()->push_set(gr_nodes[i], p_name, p_value); } } } @@ -484,7 +488,7 @@ bool SceneTree::process(double p_time) { #ifndef _3D_DISABLED if (Engine::get_singleton()->is_editor_hint()) { //simple hack to reload fallback environment if it changed from editor - String env_path = ProjectSettings::get_singleton()->get(SNAME("rendering/environment/defaults/default_environment")); + String env_path = GLOBAL_GET(SNAME("rendering/environment/defaults/default_environment")); env_path = env_path.strip_edges(); //user may have added a space or two String cpath; Ref<Environment> fallback = get_root()->get_world_3d()->get_fallback_environment(); @@ -510,7 +514,7 @@ bool SceneTree::process(double p_time) { return _quit; } -void SceneTree::process_timers(float p_delta, bool p_physics_frame) { +void SceneTree::process_timers(double p_delta, bool p_physics_frame) { List<Ref<SceneTreeTimer>>::Element *L = timers.back(); //last element for (List<Ref<SceneTreeTimer>>::Element *E = timers.front(); E;) { @@ -542,7 +546,7 @@ void SceneTree::process_timers(float p_delta, bool p_physics_frame) { } } -void SceneTree::process_tweens(float p_delta, bool p_physics) { +void SceneTree::process_tweens(double p_delta, bool p_physics) { // This methods works similarly to how SceneTreeTimers are handled. List<Ref<Tween>>::Element *L = tweens.back(); @@ -846,14 +850,14 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio //performance is not lost because only if something is added/removed the vector is copied. Vector<Node *> nodes_copy = g.nodes; - int node_count = nodes_copy.size(); - Node **nodes = nodes_copy.ptrw(); + int gr_node_count = nodes_copy.size(); + Node **gr_nodes = nodes_copy.ptrw(); call_lock++; - for (int i = 0; i < node_count; i++) { - Node *n = nodes[i]; - if (call_lock && call_skip.has(n)) { + for (int i = 0; i < gr_node_count; i++) { + Node *n = gr_nodes[i]; + if (call_lock && call_skip.has(n->get_instance_id())) { continue; } @@ -865,7 +869,7 @@ void SceneTree::_notify_group_pause(const StringName &p_group, int p_notificatio } n->notification(p_notification); - //ERR_FAIL_COND(node_count != g.nodes.size()); + //ERR_FAIL_COND(gr_node_count != g.nodes.size()); } call_lock--; @@ -890,18 +894,20 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal //performance is not lost because only if something is added/removed the vector is copied. Vector<Node *> nodes_copy = g.nodes; - int node_count = nodes_copy.size(); - Node **nodes = nodes_copy.ptrw(); + int gr_node_count = nodes_copy.size(); + Node **gr_nodes = nodes_copy.ptrw(); call_lock++; - for (int i = node_count - 1; i >= 0; i--) { + Vector<ObjectID> no_context_node_ids; // Nodes may be deleted due to this shortcut input. + + for (int i = gr_node_count - 1; i >= 0; i--) { if (p_viewport->is_input_handled()) { break; } - Node *n = nodes[i]; - if (call_lock && call_skip.has(n)) { + Node *n = gr_nodes[i]; + if (call_lock && call_skip.has(n->get_instance_id())) { continue; } @@ -913,9 +919,22 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal case CALL_INPUT_TYPE_INPUT: n->_call_input(p_input); break; - case CALL_INPUT_TYPE_SHORTCUT_INPUT: + case CALL_INPUT_TYPE_SHORTCUT_INPUT: { + const Control *c = Object::cast_to<Control>(n); + if (c) { + // If calling shortcut input on a control, ensure it respects the shortcut context. + // Shortcut context (based on focus) only makes sense for controls (UI), so don't need to worry about it for nodes + if (c->get_shortcut_context() == nullptr) { + no_context_node_ids.append(n->get_instance_id()); + continue; + } + if (!c->is_focus_owner_in_shortcut_context()) { + continue; + } + } n->_call_shortcut_input(p_input); break; + } case CALL_INPUT_TYPE_UNHANDLED_INPUT: n->_call_unhandled_input(p_input); break; @@ -925,6 +944,13 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal } } + for (const ObjectID &id : no_context_node_ids) { + Node *n = Object::cast_to<Node>(ObjectDB::get_instance(id)); + if (n) { + n->_call_shortcut_input(p_input); + } + } + call_lock--; if (call_lock == 0) { call_skip.clear(); @@ -1085,6 +1111,7 @@ void SceneTree::_change_scene(Node *p_to) { if (p_to) { current_scene = p_to; root->add_child(p_to); + root->update_mouse_cursor_shape(); } } @@ -1366,7 +1393,7 @@ SceneTree::SceneTree() { root = memnew(Window); root->set_process_mode(Node::PROCESS_MODE_PAUSABLE); root->set_name("root"); - root->set_title(ProjectSettings::get_singleton()->get("application/config/name")); + root->set_title(GLOBAL_GET("application/config/name")); #ifndef _3D_DISABLED if (!root->get_world_3d().is_valid()) { @@ -1389,6 +1416,9 @@ SceneTree::SceneTree() { ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/msaa_3d", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/msaa_3d", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)"))); root->set_msaa_3d(Viewport::MSAA(msaa_mode_3d)); + const bool transparent_background = GLOBAL_DEF("rendering/transparent_background", false); + root->set_transparent_background(transparent_background); + const int ssaa_mode = GLOBAL_DEF_BASIC("rendering/anti_aliasing/quality/screen_space_aa", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/screen_space_aa", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/screen_space_aa", PROPERTY_HINT_ENUM, "Disabled (Fastest),FXAA (Fast)")); root->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode)); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 031a331a99..d4fcb288ae 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -135,7 +135,7 @@ private: // Safety for when a node is deleted while a group is being called. int call_lock = 0; - HashSet<Node *> call_skip; // Skip erased nodes. + HashSet<ObjectID> call_skip; // Skip erased nodes. Store ID instead of pointer to avoid false positives when node is freed and a new node is allocated at the pointed address. List<ObjectID> delete_queue; @@ -180,8 +180,8 @@ private: void node_added(Node *p_node); void node_removed(Node *p_node); void node_renamed(Node *p_node); - void process_timers(float p_delta, bool p_physics_frame); - void process_tweens(float p_delta, bool p_physics_frame); + void process_timers(double p_delta, bool p_physics_frame); + void process_tweens(double p_delta, bool p_physics_frame); Group *add_to_group(const StringName &p_group, Node *p_node); void remove_from_group(const StringName &p_group, Node *p_node); diff --git a/scene/main/shader_globals_override.cpp b/scene/main/shader_globals_override.cpp index 13034c5447..455b8c6866 100644 --- a/scene/main/shader_globals_override.cpp +++ b/scene/main/shader_globals_override.cpp @@ -271,8 +271,8 @@ void ShaderGlobalsOverride::_notification(int p_what) { } } -TypedArray<String> ShaderGlobalsOverride::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray ShaderGlobalsOverride::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (!active) { warnings.push_back(RTR("ShaderGlobalsOverride is not active because another node of the same type is in the scene.")); diff --git a/scene/main/shader_globals_override.h b/scene/main/shader_globals_override.h index af99bf9aa7..f3d0074f28 100644 --- a/scene/main/shader_globals_override.h +++ b/scene/main/shader_globals_override.h @@ -58,7 +58,7 @@ protected: static void _bind_methods(); public: - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; ShaderGlobalsOverride(); }; diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index bb9359ef59..210b60171a 100644 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -180,8 +180,8 @@ void Timer::_set_process(bool p_process, bool p_force) { processing = p_process; } -TypedArray<String> Timer::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray Timer::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (wait_time < 0.05 - CMP_EPSILON) { warnings.push_back(RTR("Very low timer wait times (< 0.05 seconds) may behave in significantly different ways depending on the rendered or physics frame rate.\nConsider using a script's process loop instead of relying on a Timer for very low wait times.")); diff --git a/scene/main/timer.h b/scene/main/timer.h index 8785d31a8a..53503e31b2 100644 --- a/scene/main/timer.h +++ b/scene/main/timer.h @@ -73,7 +73,7 @@ public: double get_time_left() const; - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_timer_process_callback(TimerProcessCallback p_callback); TimerProcessCallback get_timer_process_callback() const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 879d494909..4c2a761138 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -60,8 +60,8 @@ #include "servers/rendering/rendering_server_globals.h" void ViewportTexture::setup_local_to_scene() { - Node *local_scene = get_local_scene(); - if (!local_scene) { + Node *loc_scene = get_local_scene(); + if (!loc_scene) { return; } @@ -71,7 +71,7 @@ void ViewportTexture::setup_local_to_scene() { vp = nullptr; - Node *vpn = local_scene->get_node(path); + Node *vpn = loc_scene->get_node(path); ERR_FAIL_COND_MSG(!vpn, "ViewportTexture: Path to node is invalid."); vp = Object::cast_to<Viewport>(vpn); @@ -362,6 +362,7 @@ void Viewport::_notification(int p_what) { current_canvas = find_world_2d()->get_canvas(); RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas); + RenderingServer::get_singleton()->viewport_set_canvas_cull_mask(viewport, canvas_cull_mask); _update_audio_listener_2d(); #ifndef _3D_DISABLED RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario()); @@ -499,6 +500,13 @@ void Viewport::_notification(int p_what) { // exit event if the change in focus results in the mouse exiting // the window. } break; + + case NOTIFICATION_PREDELETE: { + if (gui_parent) { + gui_parent->gui.tooltip_popup = nullptr; + gui_parent->gui.tooltip_label = nullptr; + } + } break; } } @@ -628,19 +636,19 @@ void Viewport::_process_picking() { PhysicsDirectSpaceState2D::ShapeResult res[64]; for (const CanvasLayer *E : canvas_layers) { - Transform2D canvas_transform; + Transform2D canvas_layer_transform; ObjectID canvas_layer_id; if (E) { // A descendant CanvasLayer. - canvas_transform = E->get_transform(); + canvas_layer_transform = E->get_transform(); canvas_layer_id = E->get_instance_id(); } else { // This Viewport's builtin canvas. - canvas_transform = get_canvas_transform(); + canvas_layer_transform = get_canvas_transform(); canvas_layer_id = ObjectID(); } - Vector2 point = canvas_transform.affine_inverse().xform(pos); + Vector2 point = canvas_layer_transform.affine_inverse().xform(pos); PhysicsDirectSpaceState2D::PointParameters point_params; point_params.position = point; @@ -986,11 +994,6 @@ void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) { return; } - if (parent && parent->find_world_2d() == p_world_2d) { - WARN_PRINT("Unable to use parent world_2d as world_2d"); - return; - } - if (is_inside_tree()) { RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas); } @@ -1154,9 +1157,7 @@ void Viewport::_gui_cancel_tooltip() { gui.tooltip_timer = Ref<SceneTreeTimer>(); } if (gui.tooltip_popup) { - gui.tooltip_popup->queue_delete(); - gui.tooltip_popup = nullptr; - gui.tooltip_label = nullptr; + gui.tooltip_popup->queue_free(); } } @@ -1219,8 +1220,6 @@ void Viewport::_gui_show_tooltip() { // Remove previous popup if we change something. if (gui.tooltip_popup) { memdelete(gui.tooltip_popup); - gui.tooltip_popup = nullptr; - gui.tooltip_label = nullptr; } if (!tooltip_owner) { @@ -1252,16 +1251,23 @@ void Viewport::_gui_show_tooltip() { panel->set_flag(Window::FLAG_POPUP, false); panel->set_wrap_controls(true); panel->add_child(base_tooltip); + panel->gui_parent = this; gui.tooltip_popup = panel; tooltip_owner->add_child(gui.tooltip_popup); - Point2 tooltip_offset = ProjectSettings::get_singleton()->get("display/mouse_cursor/tooltip_position_offset"); + Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset"); Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size()); + r.size = r.size.min(panel->get_max_size()); Window *window = gui.tooltip_popup->get_parent_visible_window(); - Rect2i vr = window->get_usable_parent_rect(); + Rect2i vr; + if (gui.tooltip_popup->is_embedded()) { + vr = gui.tooltip_popup->_get_embedder()->get_visible_rect(); + } else { + vr = window->get_usable_parent_rect(); + } if (r.size.x + r.position.x > vr.size.x + vr.position.x) { // Place it in the opposite direction. If it fails, just hug the border. @@ -1399,10 +1405,6 @@ Control *Viewport::gui_find_control(const Point2 &p_global) { } Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform, Transform2D &r_inv_xform) { - if (Object::cast_to<Viewport>(p_node)) { - return nullptr; - } - if (!p_node->is_visible()) { return nullptr; // Canvas item hidden, discard. } @@ -1486,7 +1488,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.key_event_accepted = false; Point2 mpos = mb->get_position(); - gui.last_mouse_pos = mpos; if (mb->is_pressed()) { Size2 pos = mpos; if (gui.mouse_focus_mask != MouseButton::NONE) { @@ -1560,44 +1561,15 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { set_input_as_handled(); } - if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MouseButton::LEFT) { + if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) { // Alternate drop use (when using force_drag(), as proposed by #5342). - gui.drag_successful = false; - if (gui.mouse_focus) { - gui.drag_successful = _gui_drop(gui.mouse_focus, pos, false); - } - - gui.drag_data = Variant(); - gui.dragging = false; - - Control *drag_preview = _gui_get_drag_preview(); - if (drag_preview) { - memdelete(drag_preview); - gui.drag_preview_id = ObjectID(); - } - _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - // Change mouse accordingly. + _perform_drop(gui.mouse_focus, pos); } _gui_cancel_tooltip(); } else { - if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MouseButton::LEFT) { - gui.drag_successful = false; - if (gui.drag_mouse_over) { - gui.drag_successful = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, false); - } - - Control *drag_preview = _gui_get_drag_preview(); - if (drag_preview) { - memdelete(drag_preview); - gui.drag_preview_id = ObjectID(); - } - - gui.drag_data = Variant(); - gui.dragging = false; - gui.drag_mouse_over = nullptr; - _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - // Change mouse accordingly. + if (gui.dragging && mb->get_button_index() == MouseButton::LEFT) { + _perform_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos); } gui.mouse_focus_mask &= ~mouse_button_to_mask(mb->get_button_index()); // Remove from mask. @@ -1640,8 +1612,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.key_event_accepted = false; Point2 mpos = mm->get_position(); - gui.last_mouse_pos = mpos; - // Drag & drop. if (!gui.drag_attempted && gui.mouse_focus && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { gui.drag_accum += mm->get_relative(); @@ -1683,7 +1653,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } gui.drag_attempted = true; - if (gui.drag_data.get_type() != Variant::NIL) { + if (gui.dragging) { _propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN); } } @@ -1699,6 +1669,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { _gui_cancel_tooltip(); if (over) { + if (!gui.mouse_over) { + _drop_physics_mouseover(); + } _gui_call_notification(over, Control::NOTIFICATION_MOUSE_ENTER); gui.mouse_over = over; } @@ -1796,7 +1769,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - if (gui.drag_data.get_type() != Variant::NIL) { + if (gui.dragging) { // Handle drag & drop. Control *drag_preview = _gui_get_drag_preview(); @@ -1998,6 +1971,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (mm.is_null() && mb.is_null() && p_event->is_action_type()) { + if (gui.dragging && p_event->is_action_pressed("ui_cancel") && Input::get_singleton()->is_action_just_pressed("ui_cancel")) { + _perform_drop(); + set_input_as_handled(); + return; + } + if (gui.key_focus && !gui.key_focus->is_visible_in_tree()) { gui.key_focus->release_focus(); } @@ -2079,6 +2058,27 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } +void Viewport::_perform_drop(Control *p_control, Point2 p_pos) { + // Without any arguments, simply cancel Drag and Drop. + if (p_control) { + gui.drag_successful = _gui_drop(p_control, p_pos, false); + } else { + gui.drag_successful = false; + } + + Control *drag_preview = _gui_get_drag_preview(); + if (drag_preview) { + memdelete(drag_preview); + gui.drag_preview_id = ObjectID(); + } + + gui.drag_data = Variant(); + gui.dragging = false; + gui.drag_mouse_over = nullptr; + _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); + get_base_window()->update_mouse_cursor_shape(); +} + void Viewport::_gui_cleanup_internal_state(Ref<InputEvent> p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -2663,6 +2663,11 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { } else { gui.subwindow_resize_mode = _sub_window_get_resize_margin(sw.window, mb->get_position()); if (gui.subwindow_resize_mode != SUB_WINDOW_RESIZE_DISABLED) { + if (gui.subwindow_focused != sw.window) { + // Refocus. + _sub_window_grab_focus(sw.window); + } + gui.subwindow_resize_from_rect = r; gui.subwindow_drag_from = mb->get_position(); gui.subwindow_drag = SUB_WINDOW_DRAG_RESIZE; @@ -2755,6 +2760,11 @@ void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) { ev = p_event; } + Ref<InputEventMouse> me = ev; + if (me.is_valid()) { + gui.last_mouse_pos = me->get_position(); + } + if (is_embedding_subwindows() && _sub_windows_forward_input(ev)) { set_input_as_handled(); return; @@ -2799,7 +2809,7 @@ void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local } // Shortcut Input. - if (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr) { + if (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr || Object::cast_to<InputEventJoypadButton>(*ev) != nullptr) { get_tree()->_call_input_pause(shortcut_input_group, SceneTree::CALL_INPUT_TYPE_SHORTCUT_INPUT, ev, this); } @@ -2872,8 +2882,8 @@ Variant Viewport::gui_get_drag_data() const { return gui.drag_data; } -TypedArray<String> Viewport::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +PackedStringArray Viewport::get_configuration_warnings() const { + PackedStringArray warnings = Node::get_configuration_warnings(); if (size.x <= 1 || size.y <= 1) { warnings.push_back(RTR("The Viewport size must be greater than or equal to 2 pixels on both dimensions to render anything.")); @@ -3037,8 +3047,6 @@ bool Viewport::gui_is_drag_successful() const { } void Viewport::set_input_as_handled() { - _drop_physics_mouseover(); - if (!handle_input_locally) { ERR_FAIL_COND(!is_inside_tree()); Viewport *vp = this; @@ -3241,6 +3249,29 @@ Transform2D Viewport::get_screen_transform() const { return _get_input_pre_xform().affine_inverse() * get_final_transform(); } +void Viewport::set_canvas_cull_mask(uint32_t p_canvas_cull_mask) { + canvas_cull_mask = p_canvas_cull_mask; + RenderingServer::get_singleton()->viewport_set_canvas_cull_mask(viewport, canvas_cull_mask); +} + +uint32_t Viewport::get_canvas_cull_mask() const { + return canvas_cull_mask; +} + +void Viewport::set_canvas_cull_mask_bit(uint32_t p_layer, bool p_enable) { + ERR_FAIL_UNSIGNED_INDEX(p_layer, 32); + if (p_enable) { + set_canvas_cull_mask(canvas_cull_mask | (1 << p_layer)); + } else { + set_canvas_cull_mask(canvas_cull_mask & (~(1 << p_layer))); + } +} + +bool Viewport::get_canvas_cull_mask_bit(uint32_t p_layer) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_layer, 32, false); + return (canvas_cull_mask & (1 << p_layer)); +} + #ifndef _3D_DISABLED AudioListener3D *Viewport::get_audio_listener_3d() const { return audio_listener_3d; @@ -3723,6 +3754,7 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_global_canvas_transform", "xform"), &Viewport::set_global_canvas_transform); ClassDB::bind_method(D_METHOD("get_global_canvas_transform"), &Viewport::get_global_canvas_transform); ClassDB::bind_method(D_METHOD("get_final_transform"), &Viewport::get_final_transform); + ClassDB::bind_method(D_METHOD("get_screen_transform"), &Viewport::get_screen_transform); ClassDB::bind_method(D_METHOD("get_visible_rect"), &Viewport::get_visible_rect); ClassDB::bind_method(D_METHOD("set_transparent_background", "enable"), &Viewport::set_transparent_background); @@ -3811,6 +3843,12 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_embedding_subwindows", "enable"), &Viewport::set_embedding_subwindows); ClassDB::bind_method(D_METHOD("is_embedding_subwindows"), &Viewport::is_embedding_subwindows); + ClassDB::bind_method(D_METHOD("set_canvas_cull_mask", "mask"), &Viewport::set_canvas_cull_mask); + ClassDB::bind_method(D_METHOD("get_canvas_cull_mask"), &Viewport::get_canvas_cull_mask); + + ClassDB::bind_method(D_METHOD("set_canvas_cull_mask_bit", "layer", "enable"), &Viewport::set_canvas_cull_mask_bit); + ClassDB::bind_method(D_METHOD("get_canvas_cull_mask_bit", "layer"), &Viewport::get_canvas_cull_mask_bit); + ClassDB::bind_method(D_METHOD("set_default_canvas_item_texture_repeat", "mode"), &Viewport::set_default_canvas_item_texture_repeat); ClassDB::bind_method(D_METHOD("get_default_canvas_item_texture_repeat"), &Viewport::get_default_canvas_item_texture_repeat); @@ -3916,6 +3954,7 @@ void Viewport::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::INT, "positional_shadow_atlas_quad_3", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_positional_shadow_atlas_quadrant_subdiv", "get_positional_shadow_atlas_quadrant_subdiv", 3); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_canvas_transform", "get_canvas_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_canvas_transform", "get_global_canvas_transform"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_cull_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_canvas_cull_mask", "get_canvas_cull_mask"); ADD_SIGNAL(MethodInfo("size_changed")); ADD_SIGNAL(MethodInfo("gui_focus_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); @@ -4050,16 +4089,10 @@ Viewport::Viewport() { ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/tooltip_delay_sec", PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater")); // No negative numbers #ifndef _3D_DISABLED - Viewport::Scaling3DMode scaling_3d_mode = (Viewport::Scaling3DMode)(int)GLOBAL_GET("rendering/scaling_3d/mode"); - set_scaling_3d_mode(scaling_3d_mode); - + set_scaling_3d_mode((Viewport::Scaling3DMode)(int)GLOBAL_GET("rendering/scaling_3d/mode")); set_scaling_3d_scale(GLOBAL_GET("rendering/scaling_3d/scale")); - - float fsr_sharpness = GLOBAL_GET("rendering/scaling_3d/fsr_sharpness"); - set_fsr_sharpness(fsr_sharpness); - - float texture_mipmap_bias = GLOBAL_GET("rendering/textures/default_filters/texture_mipmap_bias"); - set_texture_mipmap_bias(texture_mipmap_bias); + set_fsr_sharpness((float)GLOBAL_GET("rendering/scaling_3d/fsr_sharpness")); + set_texture_mipmap_bias((float)GLOBAL_GET("rendering/textures/default_filters/texture_mipmap_bias")); #endif // _3D_DISABLED set_sdf_oversize(sdf_oversize); // Set to server. @@ -4132,10 +4165,10 @@ DisplayServer::WindowID SubViewport::get_window_id() const { } Transform2D SubViewport::_stretch_transform() { - Transform2D transform = Transform2D(); + Transform2D transform; Size2i view_size_2d_override = _get_size_2d_override(); if (size_2d_override_stretch && view_size_2d_override.width > 0 && view_size_2d_override.height > 0) { - Size2 scale = _get_size() / view_size_2d_override; + Size2 scale = Size2(_get_size()) / Size2(view_size_2d_override); transform.scale(scale); } @@ -4143,7 +4176,7 @@ Transform2D SubViewport::_stretch_transform() { } Transform2D SubViewport::get_screen_transform() const { - Transform2D container_transform = Transform2D(); + Transform2D container_transform; SubViewportContainer *c = Object::cast_to<SubViewportContainer>(get_parent()); if (c) { if (c->is_stretch_enabled()) { @@ -4202,6 +4235,8 @@ void SubViewport::_bind_methods() { BIND_ENUM_CONSTANT(UPDATE_ALWAYS); } -SubViewport::SubViewport() {} +SubViewport::SubViewport() { + RS::get_singleton()->viewport_set_size(get_viewport_rid(), get_size().width, get_size().height); +} SubViewport::~SubViewport() {} diff --git a/scene/main/viewport.h b/scene/main/viewport.h index c7c474c70f..dc69ec24d8 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -208,6 +208,7 @@ private: friend class ViewportTexture; Viewport *parent = nullptr; + Viewport *gui_parent = nullptr; // Whose gui.tooltip_popup it is. AudioListener2D *audio_listener_2d = nullptr; Camera2D *camera_2d = nullptr; @@ -316,6 +317,8 @@ private: SDFOversize sdf_oversize = SDF_OVERSIZE_120_PERCENT; SDFScale sdf_scale = SDF_SCALE_50_PERCENT; + uint32_t canvas_cull_mask = 0xffffffff; // by default show everything + enum SubWindowDrag { SUB_WINDOW_DRAG_DISABLED, SUB_WINDOW_DRAG_MOVE, @@ -402,6 +405,7 @@ private: Control *_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform, Transform2D &r_inv_xform); void _gui_input_event(Ref<InputEvent> p_event); + void _perform_drop(Control *p_control = nullptr, Point2 p_pos = Point2()); void _gui_cleanup_internal_state(Ref<InputEvent> p_event); _FORCE_INLINE_ Transform2D _get_input_pre_xform() const; @@ -581,7 +585,7 @@ public: void gui_release_focus(); Control *gui_get_focus_owner(); - TypedArray<String> get_configuration_warnings() const override; + PackedStringArray get_configuration_warnings() const override; void set_debug_draw(DebugDraw p_debug_draw); DebugDraw get_debug_draw() const; @@ -638,6 +642,12 @@ public: void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control); + void set_canvas_cull_mask(uint32_t p_layers); + uint32_t get_canvas_cull_mask() const; + + void set_canvas_cull_mask_bit(uint32_t p_layer, bool p_enable); + bool get_canvas_cull_mask_bit(uint32_t p_layer) const; + virtual Transform2D get_screen_transform() const; #ifndef _3D_DISABLED diff --git a/scene/main/window.cpp b/scene/main/window.cpp index ebe9587b31..aff2c594d9 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -157,26 +157,18 @@ void Window::set_flag(Flags p_flag, bool p_enabled) { embedder->_sub_window_update(this); } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { -#ifdef TOOLS_ENABLED - if ((p_flag != FLAG_POPUP) || !(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + if (!is_in_edited_scene_root()) { DisplayServer::get_singleton()->window_set_flag(DisplayServer::WindowFlags(p_flag), p_enabled, window_id); } -#else - DisplayServer::get_singleton()->window_set_flag(DisplayServer::WindowFlags(p_flag), p_enabled, window_id); -#endif } } bool Window::get_flag(Flags p_flag) const { ERR_FAIL_INDEX_V(p_flag, FLAG_MAX, false); if (window_id != DisplayServer::INVALID_WINDOW_ID) { -#ifdef TOOLS_ENABLED - if ((p_flag != FLAG_POPUP) || !(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + if (!is_in_edited_scene_root()) { flags[p_flag] = DisplayServer::get_singleton()->window_get_flag(DisplayServer::WindowFlags(p_flag), window_id); } -#else - flags[p_flag] = DisplayServer::get_singleton()->window_get_flag(DisplayServer::WindowFlags(p_flag), window_id); -#endif } return flags[p_flag]; } @@ -232,6 +224,14 @@ bool Window::is_embedded() const { return _get_embedder() != nullptr; } +bool Window::is_in_edited_scene_root() const { +#ifdef TOOLS_ENABLED + return (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this)); +#else + return false; +#endif +} + void Window::_make_window() { ERR_FAIL_COND(window_id != DisplayServer::INVALID_WINDOW_ID); @@ -259,15 +259,12 @@ void Window::_make_window() { #endif DisplayServer::get_singleton()->window_set_title(tr_title, window_id); DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id); -#ifdef TOOLS_ENABLED - if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { - DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); - } else { + + if (is_in_edited_scene_root()) { DisplayServer::get_singleton()->window_set_exclusive(window_id, false); + } else { + DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); } -#else - DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); -#endif _update_window_size(); @@ -389,9 +386,24 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { _propagate_window_notification(this, NOTIFICATION_WM_DPI_CHANGE); emit_signal(SNAME("dpi_changed")); } break; + case DisplayServer::WINDOW_EVENT_TITLEBAR_CHANGE: { + emit_signal(SNAME("titlebar_changed")); + } break; } } +void Window::update_mouse_cursor_shape() { + // The default shape is set in Viewport::_gui_input_event. To instantly + // see the shape in the viewport we need to trigger a mouse motion event. + Ref<InputEventMouseMotion> mm; + Vector2 pos = get_mouse_position(); + Transform2D xform = get_global_canvas_transform().affine_inverse(); + mm.instantiate(); + mm->set_position(pos); + mm->set_global_position(xform.xform(pos)); + push_input(mm); +} + void Window::show() { set_visible(true); } @@ -450,14 +462,10 @@ void Window::set_visible(bool p_visible) { //update transient exclusive if (transient_parent) { if (exclusive && visible) { -#ifdef TOOLS_ENABLED - if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + if (!is_in_edited_scene_root()) { ERR_FAIL_COND_MSG(transient_parent->exclusive_child && transient_parent->exclusive_child != this, "Transient parent has another exclusive child."); transient_parent->exclusive_child = this; } -#else - transient_parent->exclusive_child = this; -#endif } else { if (transient_parent->exclusive_child == this) { transient_parent->exclusive_child = nullptr; @@ -504,13 +512,9 @@ void Window::_make_transient() { window->transient_children.insert(this); if (is_inside_tree() && is_visible() && exclusive) { if (transient_parent->exclusive_child == nullptr) { -#ifdef TOOLS_ENABLED - if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + if (!is_in_edited_scene_root()) { transient_parent->exclusive_child = this; } -#else - transient_parent->exclusive_child = this; -#endif } else if (transient_parent->exclusive_child != this) { ERR_PRINT("Making child transient exclusive, but parent has another exclusive child"); } @@ -553,27 +557,19 @@ void Window::set_exclusive(bool p_exclusive) { exclusive = p_exclusive; if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) { -#ifdef TOOLS_ENABLED - if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { - DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); - } else { + if (is_in_edited_scene_root()) { DisplayServer::get_singleton()->window_set_exclusive(window_id, false); + } else { + DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); } -#else - DisplayServer::get_singleton()->window_set_exclusive(window_id, exclusive); -#endif } if (transient_parent) { if (p_exclusive && is_inside_tree() && is_visible()) { ERR_FAIL_COND_MSG(transient_parent->exclusive_child && transient_parent->exclusive_child != this, "Transient parent has another exclusive child."); -#ifdef TOOLS_ENABLED - if (!(Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this))) { + if (!is_in_edited_scene_root()) { transient_parent->exclusive_child = this; } -#else - transient_parent->exclusive_child = this; -#endif } else { if (transient_parent->exclusive_child == this) { transient_parent->exclusive_child = nullptr; @@ -591,6 +587,18 @@ bool Window::is_visible() const { } void Window::_update_window_size() { + // Force window to respect size limitations of rendering server + RenderingServer *rendering_server = RenderingServer::get_singleton(); + if (rendering_server) { + Size2i max_window_size = rendering_server->get_maximum_viewport_size(); + + if (max_window_size != Size2i()) { + size = size.min(max_window_size); + min_size = min_size.min(max_window_size); + max_size = max_size.min(max_window_size); + } + } + Size2i size_limit; if (wrap_controls) { size_limit = get_contents_minimum_size(); @@ -636,9 +644,9 @@ void Window::_update_window_size() { DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id); } - DisplayServer::get_singleton()->window_set_size(size, window_id); DisplayServer::get_singleton()->window_set_max_size(max_size_valid ? max_size : Size2i(), window_id); DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id); + DisplayServer::get_singleton()->window_set_size(size, window_id); } //update the viewport @@ -651,7 +659,7 @@ void Window::_update_viewport_size() { Size2i final_size; Size2i final_size_override; Rect2i attach_to_screen_rect(Point2i(), size); - Transform2D stretch_transform; + Transform2D stretch_transform_new; float font_oversampling = 1.0; if (content_scale_mode == CONTENT_SCALE_MODE_DISABLED || content_scale_size.x == 0 || content_scale_size.y == 0) { @@ -659,8 +667,8 @@ void Window::_update_viewport_size() { final_size = size; final_size_override = Size2(size) / content_scale_factor; - stretch_transform = Transform2D(); - stretch_transform.scale(Size2(content_scale_factor, content_scale_factor)); + stretch_transform_new = Transform2D(); + stretch_transform_new.scale(Size2(content_scale_factor, content_scale_factor)); } else { //actual screen video mode Size2 video_mode = size; @@ -737,7 +745,7 @@ void Window::_update_viewport_size() { font_oversampling = (screen_size.x / viewport_size.x) * content_scale_factor; Size2 scale = Vector2(screen_size) / Vector2(final_size_override); - stretch_transform.scale(scale); + stretch_transform_new.scale(scale); } break; case CONTENT_SCALE_MODE_VIEWPORT: { @@ -749,7 +757,7 @@ void Window::_update_viewport_size() { } bool allocate = is_inside_tree() && visible && (window_id != DisplayServer::INVALID_WINDOW_ID || embedder != nullptr); - _set_size(final_size, final_size_override, attach_to_screen_rect, stretch_transform, allocate); + _set_size(final_size, final_size_override, attach_to_screen_rect, stretch_transform_new, allocate); if (window_id != DisplayServer::INVALID_WINDOW_ID) { RenderingServer::get_singleton()->viewport_attach_to_screen(get_viewport_rid(), attach_to_screen_rect, window_id); @@ -1248,6 +1256,19 @@ void Window::popup(const Rect2i &p_screen_rect) { set_transient(true); set_visible(true); + + Rect2i parent_rect; + if (is_embedded()) { + parent_rect = _get_embedder()->get_visible_rect(); + } else { + int screen_id = DisplayServer::get_singleton()->window_get_current_screen(get_window_id()); + parent_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen_id); + } + if (parent_rect != Rect2i() && !parent_rect.intersects(Rect2i(position, size))) { + ERR_PRINT(vformat("Window %d spawned at invalid position: %s.", get_window_id(), position)); + set_position((parent_rect.size - size) / 2); + } + _post_popup(); notification(NOTIFICATION_POST_POPUP); } @@ -1270,17 +1291,17 @@ bool Window::has_focus() const { Rect2i Window::get_usable_parent_rect() const { ERR_FAIL_COND_V(!is_inside_tree(), Rect2()); - Rect2i parent; + Rect2i parent_rect; if (is_embedded()) { - parent = _get_embedder()->get_visible_rect(); + parent_rect = _get_embedder()->get_visible_rect(); } else { const Window *w = is_visible() ? this : get_parent_visible_window(); //find a parent that can contain us ERR_FAIL_COND_V(!w, Rect2()); - parent = DisplayServer::get_singleton()->screen_get_usable_rect(DisplayServer::get_singleton()->window_get_current_screen(w->get_window_id())); + parent_rect = DisplayServer::get_singleton()->screen_get_usable_rect(DisplayServer::get_singleton()->window_get_current_screen(w->get_window_id())); } - return parent; + return parent_rect; } void Window::add_child_notify(Node *p_child) { @@ -1548,9 +1569,9 @@ Window::LayoutDirection Window::get_layout_direction() const { bool Window::is_layout_rtl() const { if (layout_dir == LAYOUT_DIRECTION_INHERITED) { - Window *parent = Object::cast_to<Window>(get_parent()); - if (parent) { - return parent->is_layout_rtl(); + Window *parent_w = Object::cast_to<Window>(get_parent()); + if (parent_w) { + return parent_w->is_layout_rtl(); } else { if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { return true; @@ -1612,7 +1633,7 @@ void Window::_validate_property(PropertyInfo &p_property) const { } Transform2D Window::get_screen_transform() const { - Transform2D embedder_transform = Transform2D(); + Transform2D embedder_transform; if (_get_embedder()) { embedder_transform.translate_local(get_position()); embedder_transform = _get_embedder()->get_screen_transform() * embedder_transform; @@ -1782,6 +1803,7 @@ void Window::_bind_methods() { ADD_SIGNAL(MethodInfo("visibility_changed")); ADD_SIGNAL(MethodInfo("about_to_popup")); ADD_SIGNAL(MethodInfo("theme_changed")); + ADD_SIGNAL(MethodInfo("titlebar_changed")); BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED); BIND_CONSTANT(NOTIFICATION_THEME_CHANGED); @@ -1818,6 +1840,11 @@ void Window::_bind_methods() { } Window::Window() { + RenderingServer *rendering_server = RenderingServer::get_singleton(); + if (rendering_server) { + max_size = rendering_server->get_maximum_viewport_size(); + } + theme_owner = memnew(ThemeOwner); RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_DISABLED); } diff --git a/scene/main/window.h b/scene/main/window.h index 8c6ca65436..03597b309a 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -220,6 +220,8 @@ public: void set_visible(bool p_visible); bool is_visible() const; + void update_mouse_cursor_shape(); + void show(); void hide(); @@ -232,6 +234,8 @@ public: void set_clamp_to_embedder(bool p_enable); bool is_clamped_to_embedder() const; + bool is_in_edited_scene_root() const; + bool can_draw() const; void set_ime_active(bool p_active); diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp index a9b7e9acbe..f445634a45 100644 --- a/scene/property_utils.cpp +++ b/scene/property_utils.cpp @@ -169,8 +169,10 @@ static bool _collect_inheritance_chain(const Ref<SceneState> &p_state, const Nod state = state->get_base_scene_state(); } - for (int i = inheritance_states.size() - 1; i >= 0; --i) { - r_states_stack.push_back(inheritance_states[i]); + if (inheritance_states.size() > 0) { + for (int i = inheritance_states.size() - 1; i >= 0; --i) { + r_states_stack.push_back(inheritance_states[i]); + } } return found; @@ -214,10 +216,12 @@ Vector<SceneState::PackState> PropertyUtils::get_node_states_stack(const Node *p { states_stack_ret.resize(states_stack.size()); _FastPackState *ps = states_stack.ptr(); - for (int i = states_stack.size() - 1; i >= 0; --i) { - states_stack_ret.write[i].state.reference_ptr(ps->state); - states_stack_ret.write[i].node = ps->node; - ++ps; + if (states_stack.size() > 0) { + for (int i = states_stack.size() - 1; i >= 0; --i) { + states_stack_ret.write[i].state.reference_ptr(ps->state); + states_stack_ret.write[i].node = ps->node; + ++ps; + } } } return states_stack_ret; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 72c57f1bfc..0f55c7b3b8 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -794,6 +794,7 @@ void register_scene_types() { GDREGISTER_CLASS(CylinderMesh); GDREGISTER_CLASS(PlaneMesh); GDREGISTER_CLASS(PrismMesh); + GDREGISTER_CLASS(QuadMesh); GDREGISTER_CLASS(SphereMesh); GDREGISTER_CLASS(TextMesh); GDREGISTER_CLASS(TorusMesh); @@ -959,7 +960,6 @@ void register_scene_types() { ClassDB::add_compatibility_class("Navigation3D", "Node3D"); ClassDB::add_compatibility_class("Navigation2D", "Node2D"); ClassDB::add_compatibility_class("OpenSimplexNoise", "FastNoiseLite"); - ClassDB::add_compatibility_class("QuadMesh", "PlaneMesh"); ClassDB::add_compatibility_class("ToolButton", "Button"); ClassDB::add_compatibility_class("YSort", "Node2D"); // Portal and room occlusion was replaced by raster occlusion (OccluderInstance3D node). @@ -1184,7 +1184,7 @@ void unregister_scene_types() { ResourceLoader::remove_resource_format_loader(resource_loader_shader_include); resource_loader_shader_include.unref(); - // StandardMaterial3D is not initialised when 3D is disabled, so it shouldn't be cleaned up either + // StandardMaterial3D is not initialized when 3D is disabled, so it shouldn't be cleaned up either #ifndef _3D_DISABLED BaseMaterial3D::finish_shaders(); PhysicalSkyMaterial::cleanup_shader(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index a52bfe97e7..ef619c9893 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -35,7 +35,7 @@ #include "scene/scene_string_names.h" bool Animation::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; + String prop_name = p_name; if (p_name == SNAME("_compression")) { ERR_FAIL_COND_V(tracks.size() > 0, false); //can only set compression if no tracks exist @@ -63,9 +63,9 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } compression.enabled = true; return true; - } else if (name.begins_with("tracks/")) { - int track = name.get_slicec('/', 1).to_int(); - String what = name.get_slicec('/', 2); + } else if (prop_name.begins_with("tracks/")) { + int track = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); if (tracks.size() == track && what == "type") { String type = p_value; @@ -431,7 +431,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } bool Animation::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; + String prop_name = p_name; if (p_name == SNAME("_compression")) { ERR_FAIL_COND_V(!compression.enabled, false); @@ -456,15 +456,15 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { r_ret = comp; return true; - } else if (name == "length") { + } else if (prop_name == "length") { r_ret = length; - } else if (name == "loop_mode") { + } else if (prop_name == "loop_mode") { r_ret = loop_mode; - } else if (name == "step") { + } else if (prop_name == "step") { r_ret = step; - } else if (name.begins_with("tracks/")) { - int track = name.get_slicec('/', 1).to_int(); - String what = name.get_slicec('/', 2); + } else if (prop_name.begins_with("tracks/")) { + int track = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); ERR_FAIL_INDEX_V(track, tracks.size(), false); if (what == "type") { switch (track_get_type(track)) { @@ -2102,11 +2102,9 @@ bool Animation::track_is_compressed(int p_track) const { return bst->compressed_track >= 0; } break; default: { - return false; //animation does not really use transitions + return false; // Animation does not really use transitions. } break; } - - ERR_FAIL_V(false); } void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p_value) { @@ -4966,7 +4964,7 @@ void Animation::compress(uint32_t p_page_size, uint32_t p_fps, float p_split_tol if (rollback || best_frame == FRAME_MAX) { // Commit the page if had to rollback or if no track was found - print_animc("\tCommiting page.."); + print_animc("\tCommiting page..."); // The end frame for the page depends entirely on whether its valid or // no more keys were found. @@ -5241,9 +5239,7 @@ bool Animation::_fetch_compressed(uint32_t p_compressed_track, double p_time, Ve double page_base_time = compression.pages[page_index].time_offset; const uint8_t *page_data = compression.pages[page_index].data.ptr(); -#ifndef _MSC_VER -#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported -#endif + // Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported. const uint32_t *indices = (const uint32_t *)page_data; const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]]; uint32_t time_key_count = indices[p_compressed_track * 3 + 1]; @@ -5386,9 +5382,7 @@ void Animation::_get_compressed_key_indices_in_range(uint32_t p_compressed_track double page_base_time = compression.pages[page_index].time_offset; const uint8_t *page_data = compression.pages[page_index].data.ptr(); -#ifndef _MSC_VER -#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported -#endif + // Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported. const uint32_t *indices = (const uint32_t *)page_data; const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]]; uint32_t time_key_count = indices[p_compressed_track * 3 + 1]; @@ -5458,9 +5452,7 @@ int Animation::_get_compressed_key_count(uint32_t p_compressed_track) const { for (uint32_t i = 0; i < compression.pages.size(); i++) { const uint8_t *page_data = compression.pages[i].data.ptr(); -#ifndef _MSC_VER -#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported -#endif + // Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported. const uint32_t *indices = (const uint32_t *)page_data; const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]]; uint32_t time_key_count = indices[p_compressed_track * 3 + 1]; @@ -5494,9 +5486,7 @@ bool Animation::_fetch_compressed_by_index(uint32_t p_compressed_track, int p_in for (uint32_t i = 0; i < compression.pages.size(); i++) { const uint8_t *page_data = compression.pages[i].data.ptr(); -#ifndef _MSC_VER -#warning Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported -#endif + // Little endian assumed. No major big endian hardware exists any longer, but in case it does it will need to be supported. const uint32_t *indices = (const uint32_t *)page_data; const uint16_t *time_keys = (const uint16_t *)&page_data[indices[p_compressed_track * 3 + 0]]; uint32_t time_key_count = indices[p_compressed_track * 3 + 1]; @@ -5861,18 +5851,18 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float return dst; } case Variant::PACKED_INT32_ARRAY: { - const Vector<int32_t> *arr_a = Object::cast_to<Vector<int32_t>>(a); - const Vector<int32_t> *arr_b = Object::cast_to<Vector<int32_t>>(b); - int32_t sz = arr_a->size(); - if (sz == 0 || arr_b->size() != sz) { + const Vector<int32_t> arr_a = a; + const Vector<int32_t> arr_b = b; + int32_t sz = arr_a.size(); + if (sz == 0 || arr_b.size() != sz) { return a; } else { Vector<int32_t> v; v.resize(sz); { int32_t *vw = v.ptrw(); - const int32_t *ar = arr_a->ptr(); - const int32_t *br = arr_b->ptr(); + const int32_t *ar = arr_a.ptr(); + const int32_t *br = arr_b.ptr(); Variant va; for (int32_t i = 0; i < sz; i++) { @@ -5884,18 +5874,18 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float } } case Variant::PACKED_INT64_ARRAY: { - const Vector<int64_t> *arr_a = Object::cast_to<Vector<int64_t>>(a); - const Vector<int64_t> *arr_b = Object::cast_to<Vector<int64_t>>(b); - int64_t sz = arr_a->size(); - if (sz == 0 || arr_b->size() != sz) { + const Vector<int64_t> arr_a = a; + const Vector<int64_t> arr_b = b; + int64_t sz = arr_a.size(); + if (sz == 0 || arr_b.size() != sz) { return a; } else { Vector<int64_t> v; v.resize(sz); { int64_t *vw = v.ptrw(); - const int64_t *ar = arr_a->ptr(); - const int64_t *br = arr_b->ptr(); + const int64_t *ar = arr_a.ptr(); + const int64_t *br = arr_b.ptr(); Variant va; for (int64_t i = 0; i < sz; i++) { @@ -5907,18 +5897,18 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float } } case Variant::PACKED_FLOAT32_ARRAY: { - const Vector<float> *arr_a = Object::cast_to<Vector<float>>(a); - const Vector<float> *arr_b = Object::cast_to<Vector<float>>(b); - int sz = arr_a->size(); - if (sz == 0 || arr_b->size() != sz) { + const Vector<float> arr_a = a; + const Vector<float> arr_b = b; + int sz = arr_a.size(); + if (sz == 0 || arr_b.size() != sz) { return a; } else { Vector<float> v; v.resize(sz); { float *vw = v.ptrw(); - const float *ar = arr_a->ptr(); - const float *br = arr_b->ptr(); + const float *ar = arr_a.ptr(); + const float *br = arr_b.ptr(); Variant va; for (int i = 0; i < sz; i++) { @@ -5930,18 +5920,18 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float } } case Variant::PACKED_FLOAT64_ARRAY: { - const Vector<double> *arr_a = Object::cast_to<Vector<double>>(a); - const Vector<double> *arr_b = Object::cast_to<Vector<double>>(b); - int sz = arr_a->size(); - if (sz == 0 || arr_b->size() != sz) { + const Vector<double> arr_a = a; + const Vector<double> arr_b = b; + int sz = arr_a.size(); + if (sz == 0 || arr_b.size() != sz) { return a; } else { Vector<double> v; v.resize(sz); { double *vw = v.ptrw(); - const double *ar = arr_a->ptr(); - const double *br = arr_b->ptr(); + const double *ar = arr_a.ptr(); + const double *br = arr_b.ptr(); Variant va; for (int i = 0; i < sz; i++) { @@ -5953,18 +5943,18 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float } } case Variant::PACKED_VECTOR2_ARRAY: { - const Vector<Vector2> *arr_a = Object::cast_to<Vector<Vector2>>(a); - const Vector<Vector2> *arr_b = Object::cast_to<Vector<Vector2>>(b); - int sz = arr_a->size(); - if (sz == 0 || arr_b->size() != sz) { + const Vector<Vector2> arr_a = a; + const Vector<Vector2> arr_b = b; + int sz = arr_a.size(); + if (sz == 0 || arr_b.size() != sz) { return a; } else { Vector<Vector2> v; v.resize(sz); { Vector2 *vw = v.ptrw(); - const Vector2 *ar = arr_a->ptr(); - const Vector2 *br = arr_b->ptr(); + const Vector2 *ar = arr_a.ptr(); + const Vector2 *br = arr_b.ptr(); for (int i = 0; i < sz; i++) { vw[i] = ar[i].lerp(br[i], c); @@ -5974,18 +5964,18 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float } } case Variant::PACKED_VECTOR3_ARRAY: { - const Vector<Vector3> *arr_a = Object::cast_to<Vector<Vector3>>(a); - const Vector<Vector3> *arr_b = Object::cast_to<Vector<Vector3>>(b); - int sz = arr_a->size(); - if (sz == 0 || arr_b->size() != sz) { + const Vector<Vector3> arr_a = a; + const Vector<Vector3> arr_b = b; + int sz = arr_a.size(); + if (sz == 0 || arr_b.size() != sz) { return a; } else { Vector<Vector3> v; v.resize(sz); { Vector3 *vw = v.ptrw(); - const Vector3 *ar = arr_a->ptr(); - const Vector3 *br = arr_b->ptr(); + const Vector3 *ar = arr_a.ptr(); + const Vector3 *br = arr_b.ptr(); for (int i = 0; i < sz; i++) { vw[i] = ar[i].lerp(br[i], c); @@ -5995,18 +5985,18 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float } } case Variant::PACKED_COLOR_ARRAY: { - const Vector<Color> *arr_a = Object::cast_to<Vector<Color>>(a); - const Vector<Color> *arr_b = Object::cast_to<Vector<Color>>(b); - int sz = arr_a->size(); - if (sz == 0 || arr_b->size() != sz) { + const Vector<Color> arr_a = a; + const Vector<Color> arr_b = b; + int sz = arr_a.size(); + if (sz == 0 || arr_b.size() != sz) { return a; } else { Vector<Color> v; v.resize(sz); { Color *vw = v.ptrw(); - const Color *ar = arr_a->ptr(); - const Color *br = arr_b->ptr(); + const Color *ar = arr_a.ptr(); + const Color *br = arr_b.ptr(); for (int i = 0; i < sz; i++) { vw[i] = ar[i].lerp(br[i], c); diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index a87c8272ea..ce68936370 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -33,7 +33,7 @@ #include "core/io/file_access.h" #include "core/io/marshalls.h" -void AudioStreamPlaybackWAV::start(float p_from_pos) { +void AudioStreamPlaybackWAV::start(double p_from_pos) { if (base->format == AudioStreamWAV::FORMAT_IMA_ADPCM) { //no seeking in IMA_ADPCM for (int i = 0; i < 2; i++) { @@ -67,16 +67,16 @@ int AudioStreamPlaybackWAV::get_loop_count() const { return 0; } -float AudioStreamPlaybackWAV::get_playback_position() const { +double AudioStreamPlaybackWAV::get_playback_position() const { return float(offset >> MIX_FRAC_BITS) / base->mix_rate; } -void AudioStreamPlaybackWAV::seek(float p_time) { +void AudioStreamPlaybackWAV::seek(double p_time) { if (base->format == AudioStreamWAV::FORMAT_IMA_ADPCM) { return; //no seeking in ima-adpcm } - float max = base->get_length(); + double max = base->get_length(); if (p_time < 0) { p_time = 0; } else if (p_time >= max) { @@ -180,7 +180,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, final_r = p_src[pos + 1]; } - if (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */ + if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */ final <<= 8; if (is_stereo) { final_r <<= 8; @@ -194,7 +194,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, next = p_src[pos + 1]; } - if (sizeof(Depth) == 1) { + if constexpr (sizeof(Depth) == 1) { next <<= 8; if (is_stereo) { next_r <<= 8; @@ -463,7 +463,7 @@ bool AudioStreamWAV::is_stereo() const { return stereo; } -float AudioStreamWAV::get_length() const { +double AudioStreamWAV::get_length() const { int len = data_bytes; switch (format) { case AudioStreamWAV::FORMAT_8_BITS: @@ -481,7 +481,7 @@ float AudioStreamWAV::get_length() const { len /= 2; } - return float(len) / mix_rate; + return double(len) / mix_rate; } bool AudioStreamWAV::is_monophonic() const { @@ -580,8 +580,8 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) { file->store_32(sub_chunk_2_size); //Subchunk2Size // Add data - Vector<uint8_t> data = get_data(); - const uint8_t *read_data = data.ptr(); + Vector<uint8_t> stream_data = get_data(); + const uint8_t *read_data = stream_data.ptr(); switch (format) { case AudioStreamWAV::FORMAT_8_BITS: for (unsigned int i = 0; i < data_bytes; i++) { diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index d800388d96..d0edc52031 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -64,14 +64,14 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &offset, int32_t &increment, uint32_t amount, IMA_ADPCM_State *ima_adpcm); public: - virtual void start(float p_from_pos = 0.0) override; + virtual void start(double p_from_pos = 0.0) override; virtual void stop() override; virtual bool is_playing() const override; virtual int get_loop_count() const override; //times it looped - virtual float get_playback_position() const override; - virtual void seek(float p_time) override; + virtual double get_playback_position() const override; + virtual void seek(double p_time) override; virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override; @@ -137,7 +137,7 @@ public: void set_stereo(bool p_enable); bool is_stereo() const; - virtual float get_length() const override; //if supported, otherwise return 0 + virtual double get_length() const override; //if supported, otherwise return 0 virtual bool is_monophonic() const override; diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index 0505f6b559..1b06e09bb8 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -639,9 +639,7 @@ void BitMap::resize(const Size2i &p_new_size) { } Ref<Image> BitMap::convert_to_image() const { - Ref<Image> image; - image.instantiate(); - image->create(width, height, false, Image::FORMAT_L8); + Ref<Image> image = Image::create_empty(width, height, false, Image::FORMAT_L8); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp index dfaf82f36a..5698e61004 100644 --- a/scene/resources/bone_map.cpp +++ b/scene/resources/bone_map.cpp @@ -93,7 +93,7 @@ void BoneMap::set_skeleton_bone_name(StringName p_profile_bone_name, const Strin } StringName BoneMap::find_profile_bone_name(StringName p_skeleton_bone_name) const { - StringName profile_bone_name = StringName(); + StringName profile_bone_name; HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); while (E) { if (E->value == p_skeleton_bone_name) { diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp index b0454004a0..f7ed8d98cb 100644 --- a/scene/resources/capsule_shape_3d.cpp +++ b/scene/resources/capsule_shape_3d.cpp @@ -33,17 +33,17 @@ #include "servers/physics_server_3d.h" Vector<Vector3> CapsuleShape3D::get_debug_mesh_lines() const { - float radius = get_radius(); - float height = get_height(); + float c_radius = get_radius(); + float c_height = get_height(); Vector<Vector3> points; - Vector3 d(0, height * 0.5 - radius, 0); + Vector3 d(0, c_height * 0.5 - c_radius, 0); for (int i = 0; i < 360; i++) { float ra = Math::deg_to_rad((float)i); float rb = Math::deg_to_rad((float)i + 1); - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; - Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius; points.push_back(Vector3(a.x, 0, a.y) + d); points.push_back(Vector3(b.x, 0, b.y) + d); diff --git a/scene/resources/convex_polygon_shape_3d.cpp b/scene/resources/convex_polygon_shape_3d.cpp index e7960f1ba4..5bcefcd0e4 100644 --- a/scene/resources/convex_polygon_shape_3d.cpp +++ b/scene/resources/convex_polygon_shape_3d.cpp @@ -33,18 +33,18 @@ #include "servers/physics_server_3d.h" Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const { - Vector<Vector3> points = get_points(); + Vector<Vector3> poly_points = get_points(); - if (points.size() > 3) { - Vector<Vector3> varr = Variant(points); + if (poly_points.size() > 3) { + Vector<Vector3> varr = Variant(poly_points); Geometry3D::MeshData md; Error err = ConvexHullComputer::convex_hull(varr, md); if (err == OK) { Vector<Vector3> lines; lines.resize(md.edges.size() * 2); - for (int i = 0; i < md.edges.size(); i++) { - lines.write[i * 2 + 0] = md.vertices[md.edges[i].a]; - lines.write[i * 2 + 1] = md.vertices[md.edges[i].b]; + for (uint32_t i = 0; i < md.edges.size(); i++) { + lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a]; + lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b]; } return lines; } diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 0ea5264935..eda9af9dde 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -936,6 +936,46 @@ Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { } } +Transform2D Curve2D::sample_baked_with_rotation(real_t p_offset, bool p_cubic, bool p_loop, real_t p_lookahead) const { + real_t path_length = get_baked_length(); // Ensure baked. + ERR_FAIL_COND_V_MSG(path_length == 0, Transform2D(), "Length of Curve2D is 0."); + + Vector2 pos = sample_baked(p_offset, p_cubic); + + real_t ahead = p_offset + p_lookahead; + + if (p_loop && ahead >= path_length) { + // If our lookahead will loop, we need to check if the path is closed. + int point_count = get_point_count(); + if (point_count > 0) { + Vector2 start_point = get_point_position(0); + Vector2 end_point = get_point_position(point_count - 1); + if (start_point == end_point) { + // Since the path is closed we want to 'smooth off' + // the corner at the start/end. + // So we wrap the lookahead back round. + ahead = Math::fmod(ahead, path_length); + } + } + } + + Vector2 ahead_pos = sample_baked(ahead, p_cubic); + + Vector2 tangent_to_curve; + if (ahead_pos == pos) { + // This will happen at the end of non-looping or non-closed paths. + // We'll try a look behind instead, in order to get a meaningful angle. + tangent_to_curve = + (pos - sample_baked(p_offset - p_lookahead, p_cubic)).normalized(); + } else { + tangent_to_curve = (ahead_pos - pos).normalized(); + } + + Vector2 normal_of_curve = -tangent_to_curve.orthogonal(); + + return Transform2D(normal_of_curve, tangent_to_curve, pos); +} + PackedVector2Array Curve2D::get_baked_points() const { if (baked_cache_dirty) { _bake(); @@ -1184,6 +1224,7 @@ void Curve2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve2D::get_baked_length); ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve2D::sample_baked, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic", "loop", "lookahead"), &Curve2D::sample_baked_with_rotation, DEFVAL(false), DEFVAL(true), DEFVAL(4.0)); ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve2D::get_baked_points); ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve2D::get_closest_point); ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve2D::get_closest_offset); @@ -1667,6 +1708,10 @@ Vector3 Curve3D::sample_baked_up_vector(real_t p_offset, bool p_apply_tilt) cons idx = (end + start) / 2; } + if (idx == count - 1) { + return p_apply_tilt ? r[idx].rotated((rp[idx] - rp[idx - 1]).normalized(), rt[idx]) : r[idx]; + } + real_t offset_begin = baked_dist_cache[idx]; real_t offset_end = baked_dist_cache[idx + 1]; @@ -1675,10 +1720,6 @@ Vector3 Curve3D::sample_baked_up_vector(real_t p_offset, bool p_apply_tilt) cons real_t frac = (p_offset - offset_begin) / idx_interval; - if (idx == count - 1) { - return p_apply_tilt ? r[idx].rotated((rp[idx] - rp[idx - 1]).normalized(), rt[idx]) : r[idx]; - } - Vector3 forward = (rp[idx + 1] - rp[idx]).normalized(); Vector3 up = r[idx]; Vector3 up1 = r[idx + 1]; diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 88b6dda096..fa1d35aab1 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -216,6 +216,7 @@ public: real_t get_baked_length() const; Vector2 sample_baked(real_t p_offset, bool p_cubic = false) const; + Transform2D sample_baked_with_rotation(real_t p_offset, bool p_cubic = false, bool p_loop = true, real_t p_lookahead = 4.0) const; PackedVector2Array get_baked_points() const; //useful for going through Vector2 get_closest_point(const Vector2 &p_to_point) const; real_t get_closest_offset(const Vector2 &p_to_point) const; diff --git a/scene/resources/cylinder_shape_3d.cpp b/scene/resources/cylinder_shape_3d.cpp index a5951db8ea..e5f417cbcc 100644 --- a/scene/resources/cylinder_shape_3d.cpp +++ b/scene/resources/cylinder_shape_3d.cpp @@ -33,17 +33,17 @@ #include "servers/physics_server_3d.h" Vector<Vector3> CylinderShape3D::get_debug_mesh_lines() const { - float radius = get_radius(); - float height = get_height(); + float c_radius = get_radius(); + float c_height = get_height(); Vector<Vector3> points; - Vector3 d(0, height * 0.5, 0); + Vector3 d(0, c_height * 0.5, 0); for (int i = 0; i < 360; i++) { float ra = Math::deg_to_rad((float)i); float rb = Math::deg_to_rad((float)i + 1); - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius; - Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius; + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius; points.push_back(Vector3(a.x, 0, a.y) + d); points.push_back(Vector3(b.x, 0, b.y) + d); diff --git a/scene/resources/default_theme/color_picker_hue.svg b/scene/resources/default_theme/color_picker_hue.svg deleted file mode 100644 index ff75d5eb9e..0000000000 --- a/scene/resources/default_theme/color_picker_hue.svg +++ /dev/null @@ -1 +0,0 @@ -<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 1 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(0 256 -256 0 0 0)" gradientUnits="userSpaceOnUse" x1="0" x2="1" y1="0" y2="0"><stop offset="0" stop-color="#f00"/><stop offset=".04" stop-color="#ff4000"/><stop offset=".08" stop-color="#ff8000"/><stop offset=".17" stop-color="#ff0"/><stop offset=".25" stop-color="#80ff00"/><stop offset=".33" stop-color="#0f0"/><stop offset=".42" stop-color="#00ff80"/><stop offset=".5" stop-color="#0ff"/><stop offset=".58" stop-color="#0080ff"/><stop offset=".63" stop-color="#0040ff"/><stop offset=".67" stop-color="#00f"/><stop offset=".75" stop-color="#8000ff"/><stop offset=".83" stop-color="#f0f"/><stop offset=".92" stop-color="#ff0080"/><stop offset="1" stop-color="#f00"/></linearGradient><path d="m0 0h1v256h-1z" fill="url(#a)"/></svg> diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 869d582935..894936acd7 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -43,6 +43,8 @@ #include "modules/svg/image_loader_svg.h" #endif +static const int default_font_size = 16; + static float scale = 1.0; static const int default_margin = 4; @@ -431,7 +433,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("background_color", "TextEdit", Color(0, 0, 0, 0)); theme->set_color("font_color", "TextEdit", control_font_color); - theme->set_color("font_selected_color", "TextEdit", control_font_pressed_color); + theme->set_color("font_selected_color", "TextEdit", Color(0, 0, 0, 0)); theme->set_color("font_readonly_color", "TextEdit", control_font_disabled_color); theme->set_color("font_placeholder_color", "TextEdit", control_font_placeholder_color); theme->set_color("font_outline_color", "TextEdit", Color(1, 1, 1)); @@ -474,7 +476,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("completion_scroll_hovered_color", "CodeEdit", control_font_pressed_color * Color(1, 1, 1, 0.4)); theme->set_color("completion_font_color", "CodeEdit", Color(0.67, 0.67, 0.67)); theme->set_color("font_color", "CodeEdit", control_font_color); - theme->set_color("font_selected_color", "CodeEdit", Color(0, 0, 0)); + theme->set_color("font_selected_color", "CodeEdit", Color(0, 0, 0, 0)); theme->set_color("font_readonly_color", "CodeEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("font_placeholder_color", "CodeEdit", control_font_placeholder_color); theme->set_color("font_outline_color", "CodeEdit", Color(1, 1, 1)); @@ -551,6 +553,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("grabber_disabled", "HSlider", icons["slider_grabber_disabled"]); theme->set_icon("tick", "HSlider", icons["hslider_tick"]); + theme->set_constant("grabber_offset", "HSlider", 0); + // VSlider theme->set_stylebox("slider", "VSlider", style_slider); @@ -562,6 +566,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("grabber_disabled", "VSlider", icons["slider_grabber_disabled"]); theme->set_icon("tick", "VSlider", icons["vslider_tick"]); + theme->set_constant("grabber_offset", "VSlider", 0); + // SpinBox theme->set_icon("updown", "SpinBox", icons["updown"]); @@ -874,14 +880,72 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("h_width", "ColorPicker", 30 * scale); theme->set_constant("label_width", "ColorPicker", 10 * scale); + theme->set_icon("folded_arrow", "ColorPicker", icons["arrow_right"]); + theme->set_icon("expanded_arrow", "ColorPicker", icons["arrow_down"]); theme->set_icon("screen_picker", "ColorPicker", icons["color_picker_pipette"]); + theme->set_icon("shape_circle", "ColorPicker", icons["picker_shape_circle"]); + theme->set_icon("shape_rect", "ColorPicker", icons["picker_shape_rectangle"]); + theme->set_icon("shape_rect_wheel", "ColorPicker", icons["picker_shape_rectangle_wheel"]); theme->set_icon("add_preset", "ColorPicker", icons["add"]); - theme->set_icon("color_hue", "ColorPicker", icons["color_picker_hue"]); theme->set_icon("sample_bg", "ColorPicker", icons["mini_checkerboard"]); theme->set_icon("overbright_indicator", "ColorPicker", icons["color_picker_overbright"]); theme->set_icon("bar_arrow", "ColorPicker", icons["color_picker_bar_arrow"]); theme->set_icon("picker_cursor", "ColorPicker", icons["color_picker_cursor"]); + { + const int precision = 7; + + Ref<Gradient> hue_gradient; + hue_gradient.instantiate(); + PackedFloat32Array offsets; + offsets.resize(precision); + PackedColorArray colors; + colors.resize(precision); + + for (int i = 0; i < precision; i++) { + float h = i / float(precision - 1); + offsets.write[i] = h; + colors.write[i] = Color::from_hsv(h, 1, 1); + } + hue_gradient->set_offsets(offsets); + hue_gradient->set_colors(colors); + + Ref<GradientTexture2D> hue_texture; + hue_texture.instantiate(); + hue_texture->set_width(800); + hue_texture->set_height(6); + hue_texture->set_gradient(hue_gradient); + + theme->set_icon("color_hue", "ColorPicker", hue_texture); + } + + { + const int precision = 7; + + Ref<Gradient> hue_gradient; + hue_gradient.instantiate(); + PackedFloat32Array offsets; + offsets.resize(precision); + PackedColorArray colors; + colors.resize(precision); + + for (int i = 0; i < precision; i++) { + float h = i / float(precision - 1); + offsets.write[i] = h; + colors.write[i] = Color::from_ok_hsl(h, 1, 0.5); + } + hue_gradient->set_offsets(offsets); + hue_gradient->set_colors(colors); + + Ref<GradientTexture2D> hue_texture; + hue_texture.instantiate(); + hue_texture->set_width(800); + hue_texture->set_height(6); + hue_texture->set_gradient(hue_gradient); + + theme->set_icon("color_okhsl_hue", "ColorPicker", hue_texture); + } + // ColorPickerButton theme->set_icon("bg", "ColorPickerButton", icons["mini_checkerboard"]); @@ -948,7 +1012,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font_size("mono_font_size", "RichTextLabel", -1); theme->set_color("default_color", "RichTextLabel", Color(1, 1, 1)); - theme->set_color("font_selected_color", "RichTextLabel", Color(0, 0, 0)); + theme->set_color("font_selected_color", "RichTextLabel", Color(0, 0, 0, 0)); theme->set_color("selection_color", "RichTextLabel", Color(0.1, 0.1, 1, 0.8)); theme->set_color("font_shadow_color", "RichTextLabel", Color(0, 0, 0, 0)); @@ -969,6 +1033,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("table_even_row_bg", "RichTextLabel", Color(0, 0, 0, 0)); theme->set_color("table_border", "RichTextLabel", Color(0, 0, 0, 0)); + theme->set_constant("text_highlight_h_padding", "RichTextLabel", 3 * scale); + theme->set_constant("text_highlight_v_padding", "RichTextLabel", 3 * scale); + // Containers theme->set_icon("h_grabber", "SplitContainer", icons["hsplitter"]); diff --git a/scene/resources/default_theme/default_theme.h b/scene/resources/default_theme/default_theme.h index 003934ce90..5243bcefa7 100644 --- a/scene/resources/default_theme/default_theme.h +++ b/scene/resources/default_theme/default_theme.h @@ -33,8 +33,6 @@ #include "scene/resources/theme.h" -const int default_font_size = 16; - void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<Font> &bold_font, const Ref<Font> &bold_italics_font, const Ref<Font> &italics_font, Ref<Texture2D> &default_icon, Ref<StyleBox> &default_style, float p_scale); void make_default_theme(float p_scale, Ref<Font> p_font, TextServer::SubpixelPositioning p_font_subpixel = TextServer::SUBPIXEL_POSITIONING_AUTO, TextServer::Hinting p_font_hinting = TextServer::HINTING_LIGHT, TextServer::FontAntialiasing p_font_antialiased = TextServer::FONT_ANTIALIASING_GRAY, bool p_font_msdf = false, bool p_font_generate_mipmaps = false); diff --git a/scene/resources/default_theme/picker_shape_circle.svg b/scene/resources/default_theme/picker_shape_circle.svg new file mode 100644 index 0000000000..8e7fb7f06e --- /dev/null +++ b/scene/resources/default_theme/picker_shape_circle.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h16v16h-16z"/></clipPath><g clip-path="url(#a)" fill="#eaeaea"><rect height="11" rx="5.5" transform="translate(1 2)" width="11"/><path d="m0 0h2v11h-2z" transform="translate(13 2)"/></g></svg> diff --git a/scene/resources/default_theme/picker_shape_rectangle.svg b/scene/resources/default_theme/picker_shape_rectangle.svg new file mode 100644 index 0000000000..3c7dd46884 --- /dev/null +++ b/scene/resources/default_theme/picker_shape_rectangle.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h16v16h-16z"/></clipPath><g clip-path="url(#a)" fill="#eaeaea"><path d="m0 0h11v11h-11z" transform="translate(1 2)"/><path d="m0 0h2v11h-2z" transform="translate(13 2)"/></g></svg> diff --git a/scene/resources/default_theme/picker_shape_rectangle_wheel.svg b/scene/resources/default_theme/picker_shape_rectangle_wheel.svg new file mode 100644 index 0000000000..e85665a8f2 --- /dev/null +++ b/scene/resources/default_theme/picker_shape_rectangle_wheel.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="16" + viewBox="0 0 16 16" + width="16" + version="1.1" + id="svg11" + sodipodi:docname="PickerShapeRectangleWheel.svg" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs15" /> + <sodipodi:namedview + id="namedview13" + pagecolor="#505050" + bordercolor="#ffffff" + borderopacity="1" + inkscape:pageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="1" + showgrid="true" + inkscape:zoom="16" + inkscape:cx="0.53125" + inkscape:cy="5.28125" + inkscape:window-width="1920" + inkscape:window-height="1001" + inkscape:window-x="-9" + inkscape:window-y="-9" + inkscape:window-maximized="1" + inkscape:current-layer="svg11"> + <inkscape:grid + type="xygrid" + id="grid944" /> + </sodipodi:namedview> + <clipPath + id="a"> + <path + d="m0 0h16v16h-16z" + id="path2" /> + </clipPath> + <g + clip-path="url(#a)" + fill="#eaeaea" + id="g9" + transform="matrix(0.85714286,0,0,0.85714286,1.1428571,1.1428571)"> + <path + d="M 7,2 A 5,5 0 1 0 12,7 5.006,5.006 0 0 0 7,2 M 7,0 A 7,7 0 1 1 0,7 7,7 0 0 1 7,0 Z" + transform="translate(1,1)" + id="path5" /> + <path + d="M 0,0 H 7 V 7 H 0 Z" + transform="translate(4.5,4.5)" + id="path7" /> + </g> +</svg> diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index ebdaaaa95f..23bd8a4be4 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -78,7 +78,7 @@ float Environment::get_sky_custom_fov() const { void Environment::set_sky_rotation(const Vector3 &p_rotation) { bg_sky_rotation = p_rotation; - RS::get_singleton()->environment_set_sky_orientation(environment, Basis(p_rotation)); + RS::get_singleton()->environment_set_sky_orientation(environment, Basis::from_euler(p_rotation)); } Vector3 Environment::get_sky_rotation() const { diff --git a/scene/resources/fog_material.cpp b/scene/resources/fog_material.cpp index 0395ed0346..46b44d681f 100644 --- a/scene/resources/fog_material.cpp +++ b/scene/resources/fog_material.cpp @@ -122,7 +122,7 @@ void FogMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_density_texture", "density_texture"), &FogMaterial::set_density_texture); ClassDB::bind_method(D_METHOD("get_density_texture"), &FogMaterial::get_density_texture); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, "0.0,16.0,0.0001,or_greater,or_less"), "set_density", "get_density"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, "-8.0,8.0,0.0001,or_greater,or_less"), "set_density", "get_density"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo", PROPERTY_HINT_COLOR_NO_ALPHA), "set_albedo", "get_albedo"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_falloff", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_height_falloff", "get_height_falloff"); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 3d9e4e4a63..84814d939b 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -127,16 +127,18 @@ void Font::_invalidate_rids() { } bool Font::_is_cyclic(const Ref<Font> &p_f, int p_depth) const { - ERR_FAIL_COND_V(p_depth > MAX_FALLBACK_DEPTH, false); + ERR_FAIL_COND_V(p_depth > MAX_FALLBACK_DEPTH, true); if (p_f.is_null()) { return false; } + if (p_f == this) { + return true; + } for (int i = 0; i < p_f->fallbacks.size(); i++) { const Ref<Font> &f = p_f->fallbacks[i]; - if (f == this) { + if (_is_cyclic(f, p_depth + 1)) { return true; } - return _is_cyclic(f, p_depth + 1); } return false; } @@ -147,7 +149,10 @@ void Font::reset_state() { // Fallbacks. void Font::set_fallbacks(const TypedArray<Font> &p_fallbacks) { - ERR_FAIL_COND(_is_cyclic(this, 0)); + for (int i = 0; i < p_fallbacks.size(); i++) { + const Ref<Font> &f = p_fallbacks[i]; + ERR_FAIL_COND_MSG(_is_cyclic(f, 0), "Cyclic font fallback."); + } for (int i = 0; i < fallbacks.size(); i++) { Ref<Font> f = fallbacks[i]; if (f.is_valid()) { @@ -259,7 +264,7 @@ Size2 Font::get_string_size(const String &p_text, HorizontalAlignment p_alignmen hash = hash_djb2_one_64(p_font_size, hash); if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); - hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator int64_t(), hash); } hash = hash_djb2_one_64(p_direction, hash); hash = hash_djb2_one_64(p_orientation, hash); @@ -272,13 +277,15 @@ Size2 Font::get_string_size(const String &p_text, HorizontalAlignment p_alignmen buffer->set_direction(p_direction); buffer->set_orientation(p_orientation); buffer->add_string(p_text, Ref<Font>(this), p_font_size); - if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { - buffer->set_horizontal_alignment(p_alignment); - buffer->set_width(p_width); - buffer->set_flags(p_jst_flags); - } cache.insert(hash, buffer); } + + buffer->set_width(p_width); + buffer->set_horizontal_alignment(p_alignment); + if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + buffer->set_flags(p_jst_flags); + } + return buffer->get_size(); } @@ -286,8 +293,8 @@ Size2 Font::get_multiline_string_size(const String &p_text, HorizontalAlignment uint64_t hash = p_text.hash64(); hash = hash_djb2_one_64(p_font_size, hash); hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); - hash = hash_djb2_one_64(p_brk_flags.operator uint32_t(), hash); - hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_brk_flags.operator int64_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator int64_t(), hash); hash = hash_djb2_one_64(p_direction, hash); hash = hash_djb2_one_64(p_orientation, hash); @@ -316,7 +323,7 @@ void Font::draw_string(RID p_canvas_item, const Point2 &p_pos, const String &p_t hash = hash_djb2_one_64(p_font_size, hash); if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); - hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator int64_t(), hash); } hash = hash_djb2_one_64(p_direction, hash); hash = hash_djb2_one_64(p_orientation, hash); @@ -352,8 +359,8 @@ void Font::draw_multiline_string(RID p_canvas_item, const Point2 &p_pos, const S uint64_t hash = p_text.hash64(); hash = hash_djb2_one_64(p_font_size, hash); hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); - hash = hash_djb2_one_64(p_brk_flags.operator uint32_t(), hash); - hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_brk_flags.operator int64_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator int64_t(), hash); hash = hash_djb2_one_64(p_direction, hash); hash = hash_djb2_one_64(p_orientation, hash); @@ -389,7 +396,7 @@ void Font::draw_string_outline(RID p_canvas_item, const Point2 &p_pos, const Str hash = hash_djb2_one_64(p_font_size, hash); if (p_alignment == HORIZONTAL_ALIGNMENT_FILL) { hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); - hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator int64_t(), hash); } hash = hash_djb2_one_64(p_direction, hash); hash = hash_djb2_one_64(p_orientation, hash); @@ -425,8 +432,8 @@ void Font::draw_multiline_string_outline(RID p_canvas_item, const Point2 &p_pos, uint64_t hash = p_text.hash64(); hash = hash_djb2_one_64(p_font_size, hash); hash = hash_djb2_one_64(hash_murmur3_one_float(p_width), hash); - hash = hash_djb2_one_64(p_brk_flags.operator uint32_t(), hash); - hash = hash_djb2_one_64(p_jst_flags.operator uint32_t(), hash); + hash = hash_djb2_one_64(p_brk_flags.operator int64_t(), hash); + hash = hash_djb2_one_64(p_jst_flags.operator int64_t(), hash); hash = hash_djb2_one_64(p_direction, hash); hash = hash_djb2_one_64(p_orientation, hash); @@ -996,11 +1003,11 @@ void FontFile::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_data", "get_data"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_mipmaps", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_generate_mipmaps", "get_generate_mipmaps"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD sub-pixel", PROPERTY_USAGE_STORAGE), "set_antialiasing", "get_antialiasing"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_STORAGE), "set_antialiasing", "get_antialiasing"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "font_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_name", "get_font_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style_name", "get_font_style_name"); ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_FLAGS, "Bold,Italic,Fixed Size", PROPERTY_USAGE_STORAGE), "set_font_style", "get_font_style"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_pixel_range", "get_msdf_pixel_range"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_size", "get_msdf_size"); @@ -1058,12 +1065,12 @@ bool FontFile::_set(const StringName &p_name, const Variant &p_value) { } int chars = len / 9; for (int i = 0; i < chars; i++) { - const int32_t *data = &arr[i * 9]; - char32_t c = data[0]; - set_glyph_texture_idx(0, Vector2i(16, 0), c, data[1]); - set_glyph_uv_rect(0, Vector2i(16, 0), c, Rect2(data[2], data[3], data[4], data[5])); - set_glyph_offset(0, Vector2i(16, 0), c, Size2(data[6], data[7])); - set_glyph_advance(0, 16, c, Vector2(data[8], 0)); + const int32_t *char_data = &arr[i * 9]; + char32_t c = char_data[0]; + set_glyph_texture_idx(0, Vector2i(16, 0), c, char_data[1]); + set_glyph_uv_rect(0, Vector2i(16, 0), c, Rect2(char_data[2], char_data[3], char_data[4], char_data[5])); + set_glyph_offset(0, Vector2i(16, 0), c, Size2(char_data[6], char_data[7])); + set_glyph_advance(0, 16, c, Vector2(char_data[8], 0)); } } else if (tokens.size() == 1 && tokens[0] == "kernings") { // Compatibility, BitmapFont. @@ -1075,8 +1082,8 @@ bool FontFile::_set(const StringName &p_name, const Variant &p_value) { return false; } for (int i = 0; i < len / 3; i++) { - const int32_t *data = &arr[i * 3]; - set_kerning(0, 16, Vector2i(data[0], data[1]), Vector2(data[2], 0)); + const int32_t *kern_data = &arr[i * 3]; + set_kerning(0, 16, Vector2i(kern_data[0], kern_data[1]), Vector2(kern_data[2], 0)); } } else if (tokens.size() == 1 && tokens[0] == "height") { // Compatibility, BitmapFont. @@ -1101,12 +1108,12 @@ bool FontFile::_set(const StringName &p_name, const Variant &p_value) { #endif // DISABLE_DEPRECATED if (tokens.size() == 2 && tokens[0] == "language_support_override") { - String lang = tokens[1]; - set_language_support_override(lang, p_value); + String lang_code = tokens[1]; + set_language_support_override(lang_code, p_value); return true; } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { - String script = tokens[1]; - set_script_support_override(script, p_value); + String script_code = tokens[1]; + set_script_support_override(script_code, p_value); return true; } else if (tokens.size() >= 3 && tokens[0] == "cache") { int cache_index = tokens[1].to_int(); @@ -1180,12 +1187,12 @@ bool FontFile::_set(const StringName &p_name, const Variant &p_value) { bool FontFile::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> tokens = p_name.operator String().split("/"); if (tokens.size() == 2 && tokens[0] == "language_support_override") { - String lang = tokens[1]; - r_ret = get_language_support_override(lang); + String lang_code = tokens[1]; + r_ret = get_language_support_override(lang_code); return true; } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { - String script = tokens[1]; - r_ret = get_script_support_override(script); + String script_code = tokens[1]; + r_ret = get_script_support_override(script_code); return true; } else if (tokens.size() >= 3 && tokens[0] == "cache") { int cache_index = tokens[1].to_int(); @@ -1334,6 +1341,19 @@ void FontFile::reset_state() { /*************************************************************************/ +// OEM encoding mapping for 0x80..0xFF range. +static const char32_t _oem_to_unicode[][129] = { + U"\u20ac\ufffe\u201a\ufffe\u201e\u2026\u2020\u2021\ufffe\u2030\u0160\u2039\u015a\u0164\u017d\u0179\ufffe\u2018\u2019\u201c\u201d\u2022\u2013\u2014\ufffe\u2122\u0161\u203a\u015b\u0165\u017e\u017a\xa0\u02c7\u02d8\u0141\xa4\u0104\xa6\xa7\xa8\xa9\u015e\xab\xac\xad\xae\u017b\xb0\xb1\u02db\u0142\xb4\xb5\xb6\xb7\xb8\u0105\u015f\xbb\u013d\u02dd\u013e\u017c\u0154\xc1\xc2\u0102\xc4\u0139\u0106\xc7\u010c\xc9\u0118\xcb\u011a\xcd\xce\u010e\u0110\u0143\u0147\xd3\xd4\u0150\xd6\xd7\u0158\u016e\xda\u0170\xdc\xdd\u0162\xdf\u0155\xe1\xe2\u0103\xe4\u013a\u0107\xe7\u010d\xe9\u0119\xeb\u011b\xed\xee\u010f\u0111\u0144\u0148\xf3\xf4\u0151\xf6\xf7\u0159\u016f\xfa\u0171\xfc\xfd\u0163\u02d9", // 1250 - Latin 2 + U"\u0402\u0403\u201a\u0453\u201e\u2026\u2020\u2021\u20ac\u2030\u0409\u2039\u040a\u040c\u040b\u040f\u0452\u2018\u2019\u201c\u201d\u2022\u2013\u2014\ufffe\u2122\u0459\u203a\u045a\u045c\u045b\u045f\xa0\u040e\u045e\u0408\xa4\u0490\xa6\xa7\u0401\xa9\u0404\xab\xac\xad\xae\u0407\xb0\xb1\u0406\u0456\u0491\xb5\xb6\xb7\u0451\u2116\u0454\xbb\u0458\u0405\u0455\u0457\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f", // 1251 - Cyrillic + U"\u20ac\ufffe\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\u0160\u2039\u0152\ufffe\u017d\ufffe\ufffe\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u02dc\u2122\u0161\u203a\u0153\ufffe\u017e\u0178\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", // 1252 - Latin 1 + U"\u20ac\ufffe\u201a\u0192\u201e\u2026\u2020\u2021\ufffe\u2030\ufffe\u2039\ufffe\ufffe\ufffe\ufffe\ufffe\u2018\u2019\u201c\u201d\u2022\u2013\u2014\ufffe\u2122\ufffe\u203a\ufffe\ufffe\ufffe\ufffe\xa0\u0385\u0386\xa3\xa4\xa5\xa6\xa7\xa8\xa9\ufffe\xab\xac\xad\xae\u2015\xb0\xb1\xb2\xb3\u0384\xb5\xb6\xb7\u0388\u0389\u038a\xbb\u038c\xbd\u038e\u038f\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\ufffe\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\ufffe", // 1253 - Greek + U"\u20ac\ufffe\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\u0160\u2039\u0152\ufffe\ufffe\ufffe\ufffe\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u02dc\u2122\u0161\u203a\u0153\ufffe\ufffe\u0178\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\u011e\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\u0130\u015e\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\u011f\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\u0131\u015f\xff", // 1254 - Turkish + U"\u20ac\ufffe\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\ufffe\u2039\ufffe\ufffe\ufffe\ufffe\ufffe\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u02dc\u2122\ufffe\u203a\ufffe\ufffe\ufffe\ufffe\xa0\xa1\xa2\xa3\u20aa\xa5\xa6\xa7\xa8\xa9\xd7\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xf7\xbb\xbc\xbd\xbe\xbf\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\ufffe\u05bb\u05bc\u05bd\u05be\u05bf\u05c0\u05c1\u05c2\u05c3\u05f0\u05f1\u05f2\u05f3\u05f4\ufffe\ufffe\ufffe\ufffe\ufffe\ufffe\ufffe\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7\u05e8\u05e9\u05ea\ufffe\ufffe\u200e\u200f\ufffe", // 1255 - Hebrew + U"\u20ac\u067e\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\u0679\u2039\u0152\u0686\u0698\u0688\u06af\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u06a9\u2122\u0691\u203a\u0153\u200c\u200d\u06ba\xa0\u060c\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\u06be\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\u061b\xbb\xbc\xbd\xbe\u061f\u06c1\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\xd7\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\xe0\u0644\xe2\u0645\u0646\u0647\u0648\xe7\xe8\xe9\xea\xeb\u0649\u064a\xee\xef\u064b\u064c\u064d\u064e\xf4\u064f\u0650\xf7\u0651\xf9\u0652\xfb\xfc\u200e\u200f\u06d2", // 1256 - Arabic + U"\u20ac\ufffe\u201a\ufffe\u201e\u2026\u2020\u2021\ufffe\u2030\ufffe\u2039\ufffe\xa8\u02c7\xb8\ufffe\u2018\u2019\u201c\u201d\u2022\u2013\u2014\ufffe\u2122\ufffe\u203a\ufffe\xaf\u02db\ufffe\xa0\ufffe\xa2\xa3\xa4\ufffe\xa6\xa7\xd8\xa9\u0156\xab\xac\xad\xae\xc6\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xf8\xb9\u0157\xbb\xbc\xbd\xbe\xe6\u0104\u012e\u0100\u0106\xc4\xc5\u0118\u0112\u010c\xc9\u0179\u0116\u0122\u0136\u012a\u013b\u0160\u0143\u0145\xd3\u014c\xd5\xd6\xd7\u0172\u0141\u015a\u016a\xdc\u017b\u017d\xdf\u0105\u012f\u0101\u0107\xe4\xe5\u0119\u0113\u010d\xe9\u017a\u0117\u0123\u0137\u012b\u013c\u0161\u0144\u0146\xf3\u014d\xf5\xf6\xf7\u0173\u0142\u015b\u016b\xfc\u017c\u017e\u02d9", // 1257 - Baltic + U"\u20ac\ufffe\u201a\u0192\u201e\u2026\u2020\u2021\u02c6\u2030\ufffe\u2039\u0152\ufffe\ufffe\ufffe\ufffe\u2018\u2019\u201c\u201d\u2022\u2013\u2014\u02dc\u2122\ufffe\u203a\u0153\ufffe\ufffe\u0178\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\u0102\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\u0300\xcd\xce\xcf\u0110\xd1\u0309\xd3\xd4\u01a0\xd6\xd7\xd8\xd9\xda\xdb\xdc\u01af\u0303\xdf\xe0\xe1\xe2\u0103\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\u0301\xed\xee\xef\u0111\xf1\u0323\xf3\xf4\u01a1\xf6\xf7\xf8\xf9\xfa\xfb\xfc\u01b0\u20ab\xff", // 1258 - Vietnamese +}; + Error FontFile::load_bitmap_font(const String &p_path) { reset_state(); @@ -1364,25 +1384,64 @@ Error FontFile::load_bitmap_font(const String &p_path) { f->get_buffer((unsigned char *)&magic, 4); if (magic[0] == 'B' && magic[1] == 'M' && magic[2] == 'F') { // Binary BMFont file. - ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(RTR("Version %d of BMFont is not supported."), (int)magic[3])); + ERR_FAIL_COND_V_MSG(magic[3] != 3, ERR_CANT_CREATE, vformat(RTR("Version %d of BMFont is not supported (should be 3)."), (int)magic[3])); uint8_t block_type = f->get_8(); uint32_t block_size = f->get_32(); + bool unicode = false; + uint8_t encoding = 9; while (!f->eof_reached()) { uint64_t off = f->get_position(); switch (block_type) { case 1: /* info */ { ERR_FAIL_COND_V_MSG(block_size < 15, ERR_CANT_CREATE, RTR("Invalid BMFont info block size.")); base_size = f->get_16(); + if (base_size <= 0) { + base_size = 16; + } uint8_t flags = f->get_8(); - ERR_FAIL_COND_V_MSG(flags & 0x02, ERR_CANT_CREATE, RTR("Non-unicode version of BMFont is not supported.")); if (flags & (1 << 3)) { st_flags.set_flag(TextServer::FONT_BOLD); } if (flags & (1 << 2)) { st_flags.set_flag(TextServer::FONT_ITALIC); } - f->get_8(); // non-unicode charset, skip + unicode = (flags & 0x02); + uint8_t encoding_id = f->get_8(); // non-unicode charset + if (!unicode) { + switch (encoding_id) { + case 0x00: { + encoding = 2; + } break; + case 0xB2: { + encoding = 6; + } break; + case 0xBA: { + encoding = 7; + } break; + case 0xEE: { + encoding = 0; + } break; + case 0xA1: { + encoding = 3; + } break; + case 0xB1: { + encoding = 5; + } break; + case 0xCC: { + encoding = 1; + } break; + case 0xA2: { + encoding = 4; + } break; + case 0xA3: { + encoding = 8; + } break; + default: { + WARN_PRINT(vformat("Unknown BMFont OEM encoding %x, parsing as Unicode (should be 0x00 - Latin 1, 0xB2 - Arabic, 0xBA - Baltic, 0xEE - Latin 2, 0xA1 - Greek, 0xB1 - Hebrew, 0xCC - Cyrillic, 0xA2 - Turkish, 0xA3 - Vietnamese).", encoding_id)); + } break; + }; + } f->get_16(); // stretch_h, skip f->get_8(); // aa, skip f->get_32(); // padding, skip @@ -1485,6 +1544,14 @@ Error FontFile::load_bitmap_font(const String &p_path) { Rect2 uv_rect; char32_t idx = f->get_32(); + if (!unicode && encoding < 9) { + if (idx >= 0x80 && idx <= 0xFF) { + idx = _oem_to_unicode[encoding][idx - 0x80]; + } else if (idx > 0xFF) { + WARN_PRINT(vformat("Invalid BMFont OEM character %x (should be 0x00-0xFF).", idx)); + idx = 0x00; + } + } uv_rect.position.x = (int16_t)f->get_16(); uv_rect.position.y = (int16_t)f->get_16(); uv_rect.size.width = (int16_t)f->get_16(); @@ -1501,24 +1568,25 @@ Error FontFile::load_bitmap_font(const String &p_path) { int texture_idx = f->get_8(); uint8_t channel = f->get_8(); - ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, RTR("Invalid glyph channel.")); int ch_off = 0; - switch (channel) { - case 1: - ch_off = 2; - break; // B - case 2: - ch_off = 1; - break; // G - case 4: - ch_off = 0; - break; // R - case 8: - ch_off = 3; - break; // A - default: - ch_off = 0; - break; + if (packed) { + switch (channel) { + case 1: + ch_off = 2; + break; // B + case 2: + ch_off = 1; + break; // G + case 4: + ch_off = 0; + break; // R + case 8: + ch_off = 3; + break; // A + default: + ch_off = 0; + break; + } } set_glyph_advance(0, base_size, idx, advance); set_glyph_offset(0, Vector2i(base_size, 0), idx, offset); @@ -1539,6 +1607,20 @@ Error FontFile::load_bitmap_font(const String &p_path) { Vector2i kpk; kpk.x = f->get_32(); kpk.y = f->get_32(); + if (!unicode && encoding < 9) { + if (kpk.x >= 0x80 && kpk.x <= 0xFF) { + kpk.x = _oem_to_unicode[encoding][kpk.x - 0x80]; + } else if (kpk.x > 0xFF) { + WARN_PRINT(vformat("Invalid BMFont OEM character %x (should be 0x00-0xFF).", kpk.x)); + kpk.x = 0x00; + } + if (kpk.y >= 0x80 && kpk.y <= 0xFF) { + kpk.y = _oem_to_unicode[encoding][kpk.y - 0x80]; + } else if (kpk.y > 0xFF) { + WARN_PRINT(vformat("Invalid BMFont OEM character %x (should be 0x00-0xFF).", kpk.y)); + kpk.y = 0x00; + } + } set_kerning(0, base_size, kpk, Vector2((int16_t)f->get_16(), 0)); } } break; @@ -1554,6 +1636,8 @@ Error FontFile::load_bitmap_font(const String &p_path) { } else { // Text BMFont file. f->seek(0); + bool unicode = false; + uint8_t encoding = 9; while (true) { String line = f->get_line(); @@ -1600,7 +1684,6 @@ Error FontFile::load_bitmap_font(const String &p_path) { if (type == "info") { if (keys.has("size")) { base_size = keys["size"].to_int(); - set_fixed_size(base_size); } if (keys.has("outline")) { outline = keys["outline"].to_int(); @@ -1618,7 +1701,38 @@ Error FontFile::load_bitmap_font(const String &p_path) { if (keys.has("face")) { font_name = keys["face"]; } - ERR_FAIL_COND_V_MSG((!keys.has("unicode") || keys["unicode"].to_int() != 1), ERR_CANT_CREATE, RTR("Non-unicode version of BMFont is not supported.")); + if (keys.has("unicode")) { + unicode = keys["unicode"].to_int(); + } + if (!unicode) { + if (keys.has("charset")) { + String encoding_name = keys["charset"].to_upper(); + if (encoding_name == "" || encoding_name == "ASCII" || encoding_name == "ANSI") { + encoding = 2; + } else if (encoding_name == "ARABIC") { + encoding = 6; + } else if (encoding_name == "BALTIC") { + encoding = 7; + } else if (encoding_name == "EASTEUROPE") { + encoding = 0; + } else if (encoding_name == "GREEK") { + encoding = 3; + } else if (encoding_name == "HEBREW") { + encoding = 5; + } else if (encoding_name == "RUSSIAN") { + encoding = 1; + } else if (encoding_name == "TURKISH") { + encoding = 4; + } else if (encoding_name == "VIETNAMESE") { + encoding = 8; + } else { + WARN_PRINT(vformat("Unknown BMFont OEM encoding %s, parsing as Unicode (should be ANSI, ASCII, ARABIC, BALTIC, EASTEUROPE, GREEK, HEBREW, RUSSIAN, TURKISH or VIETNAMESE).", encoding_name)); + } + } else { + encoding = 2; + } + } + set_fixed_size(base_size); } else if (type == "common") { if (keys.has("lineHeight")) { height = keys["lineHeight"].to_int(); @@ -1677,7 +1791,10 @@ Error FontFile::load_bitmap_font(const String &p_path) { ERR_FAIL_V_MSG(ERR_CANT_CREATE, RTR("Unsupported BMFont texture format.")); } } else { - if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline + if ((ch[3] == 0) && (ch[0] == 4) && (ch[1] == 4) && (ch[2] == 4) && img->get_format() == Image::FORMAT_RGBA8) { // might be RGBA8 color, no outline (color part of the image should be sold white, but some apps designed for Godot 3 generate color fonts with this config) + outline = 0; + set_texture_image(0, Vector2i(base_size, 0), page, img); + } else if ((ch[0] == 0) && (ch[1] == 0) && (ch[2] == 0) && (ch[3] == 0)) { // RGBA8 color, no outline outline = 0; ERR_FAIL_COND_V_MSG(img->get_format() != Image::FORMAT_RGBA8, ERR_FILE_CANT_READ, RTR("Unsupported BMFont texture format.")); set_texture_image(0, Vector2i(base_size, 0), page, img); @@ -1712,6 +1829,14 @@ Error FontFile::load_bitmap_font(const String &p_path) { if (keys.has("id")) { idx = keys["id"].to_int(); + if (!unicode && encoding < 9) { + if (idx >= 0x80 && idx <= 0xFF) { + idx = _oem_to_unicode[encoding][idx - 0x80]; + } else if (idx > 0xFF) { + WARN_PRINT(vformat("Invalid BMFont OEM character %x (should be 0x00-0xFF).", idx)); + idx = 0x00; + } + } } if (keys.has("x")) { uv_rect.position.x = keys["x"].to_int(); @@ -1746,24 +1871,25 @@ Error FontFile::load_bitmap_font(const String &p_path) { channel = keys["chnl"].to_int(); } - ERR_FAIL_COND_V_MSG(!packed && channel != 15, ERR_CANT_CREATE, RTR("Invalid glyph channel.")); int ch_off = 0; - switch (channel) { - case 1: - ch_off = 2; - break; // B - case 2: - ch_off = 1; - break; // G - case 4: - ch_off = 0; - break; // R - case 8: - ch_off = 3; - break; // A - default: - ch_off = 0; - break; + if (packed) { + switch (channel) { + case 1: + ch_off = 2; + break; // B + case 2: + ch_off = 1; + break; // G + case 4: + ch_off = 0; + break; // R + case 8: + ch_off = 3; + break; // A + default: + ch_off = 0; + break; + } } set_glyph_advance(0, base_size, idx, advance); set_glyph_offset(0, Vector2i(base_size, 0), idx, offset); @@ -1784,6 +1910,20 @@ Error FontFile::load_bitmap_font(const String &p_path) { if (keys.has("second")) { kpk.y = keys["second"].to_int(); } + if (!unicode && encoding < 9) { + if (kpk.x >= 0x80 && kpk.x <= 0xFF) { + kpk.x = _oem_to_unicode[encoding][kpk.x - 0x80]; + } else if (kpk.x > 0xFF) { + WARN_PRINT(vformat("Invalid BMFont OEM character %x (should be 0x00-0xFF).", kpk.x)); + kpk.x = 0x00; + } + if (kpk.y >= 0x80 && kpk.y <= 0xFF) { + kpk.y = _oem_to_unicode[encoding][kpk.y - 0x80]; + } else if (kpk.y > 0xFF) { + WARN_PRINT(vformat("Invalid BMFont OEM character %x (should be 0x00-0xFF).", kpk.x)); + kpk.y = 0x00; + } + } if (keys.has("amount")) { set_kerning(0, base_size, kpk, Vector2(keys["amount"].to_int(), 0)); } @@ -1806,8 +1946,8 @@ Error FontFile::load_bitmap_font(const String &p_path) { Error FontFile::load_dynamic_font(const String &p_path) { reset_state(); - Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); - set_data(data); + Vector<uint8_t> font_data = FileAccess::get_file_as_array(p_path); + set_data(font_data); return OK; } @@ -2721,11 +2861,11 @@ void SystemFont::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "font_names"), "set_font_names", "get_font_names"); ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_FLAGS, "Bold,Italic"), "set_font_style", "get_font_style"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD sub-pixel", PROPERTY_USAGE_STORAGE), "set_antialiasing", "get_antialiasing"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_STORAGE), "set_antialiasing", "get_antialiasing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_mipmaps"), "set_generate_mipmaps", "get_generate_mipmaps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "is_force_autohinter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel"), "set_subpixel_positioning", "get_subpixel_positioning"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel"), "set_subpixel_positioning", "get_subpixel_positioning"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field"), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), "set_oversampling", "get_oversampling"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Font")), "set_fallbacks", "get_fallbacks"); diff --git a/scene/resources/importer_mesh.cpp b/scene/resources/importer_mesh.cpp index de3d502102..cec5569345 100644 --- a/scene/resources/importer_mesh.cpp +++ b/scene/resources/importer_mesh.cpp @@ -255,7 +255,7 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma } #define VERTEX_SKIN_FUNC(bone_count, vert_idx, read_array, write_array, transform_array, bone_array, weight_array) \ - Vector3 transformed_vert = Vector3(); \ + Vector3 transformed_vert; \ for (unsigned int weight_idx = 0; weight_idx < bone_count; weight_idx++) { \ int bone_idx = bone_array[vert_idx * bone_count + weight_idx]; \ float w = weight_array[vert_idx * bone_count + weight_idx]; \ @@ -829,9 +829,9 @@ void ImporterMesh::_set_data(const Dictionary &p_data) { ERR_CONTINUE(prim >= Mesh::PRIMITIVE_MAX); Array arr = s["arrays"]; Dictionary lods; - String name; + String surf_name; if (s.has("name")) { - name = s["name"]; + surf_name = s["name"]; } if (s.has("lods")) { lods = s["lods"]; @@ -848,7 +848,7 @@ void ImporterMesh::_set_data(const Dictionary &p_data) { if (s.has("flags")) { flags = s["flags"]; } - add_surface(prim, arr, b_shapes, lods, material, name, flags); + add_surface(prim, arr, b_shapes, lods, material, surf_name, flags); } } } diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index c1e30dd93c..10d193f950 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -107,18 +107,15 @@ Shader::Mode Material::get_shader_mode() const { } bool Material::_can_do_next_pass() const { - bool ret; - if (GDVIRTUAL_CALL(_can_do_next_pass, ret)) { - return ret; - } - return false; + bool ret = false; + GDVIRTUAL_CALL(_can_do_next_pass, ret); + return ret; } + bool Material::_can_use_render_priority() const { - bool ret; - if (GDVIRTUAL_CALL(_can_use_render_priority, ret)) { - return ret; - } - return false; + bool ret = false; + GDVIRTUAL_CALL(_can_use_render_priority, ret); + return ret; } void Material::_bind_methods() { @@ -2348,7 +2345,7 @@ void BaseMaterial3D::set_on_top_of_alpha() { set_flag(FLAG_DISABLE_DEPTH_TEST, true); } -void BaseMaterial3D::set_proximity_fade(bool p_enable) { +void BaseMaterial3D::set_proximity_fade_enabled(bool p_enable) { proximity_fade_enabled = p_enable; _queue_shader_change(); notify_property_list_changed(); @@ -2624,7 +2621,7 @@ void BaseMaterial3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_refraction_texture_channel", "channel"), &BaseMaterial3D::set_refraction_texture_channel); ClassDB::bind_method(D_METHOD("get_refraction_texture_channel"), &BaseMaterial3D::get_refraction_texture_channel); - ClassDB::bind_method(D_METHOD("set_proximity_fade", "enabled"), &BaseMaterial3D::set_proximity_fade); + ClassDB::bind_method(D_METHOD("set_proximity_fade_enabled", "enabled"), &BaseMaterial3D::set_proximity_fade_enabled); ClassDB::bind_method(D_METHOD("is_proximity_fade_enabled"), &BaseMaterial3D::is_proximity_fade_enabled); ClassDB::bind_method(D_METHOD("set_proximity_fade_distance", "distance"), &BaseMaterial3D::set_proximity_fade_distance); @@ -2808,7 +2805,7 @@ void BaseMaterial3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "point_size", PROPERTY_HINT_RANGE, "0.1,128,0.1,suffix:px"), "set_point_size", "get_point_size"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "use_particle_trails"), "set_flag", "get_flag", FLAG_PARTICLE_TRAILS_MODE); ADD_GROUP("Proximity Fade", "proximity_fade_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "proximity_fade_enable"), "set_proximity_fade", "is_proximity_fade_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "proximity_fade_enabled"), "set_proximity_fade_enabled", "is_proximity_fade_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "proximity_fade_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:m"), "set_proximity_fade_distance", "get_proximity_fade_distance"); ADD_GROUP("MSDF", "msdf_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), "set_msdf_pixel_range", "get_msdf_pixel_range"); @@ -3123,8 +3120,6 @@ bool StandardMaterial3D::_set(const StringName &p_name, const Variant &p_value) WARN_PRINT("Godot 3.x SpatialMaterial remapped parameter not found: " + String(p_name)); return true; } - - return false; } #endif // DISABLE_DEPRECATED diff --git a/scene/resources/material.h b/scene/resources/material.h index dd9589c577..b3c2159e70 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -719,7 +719,7 @@ public: void set_on_top_of_alpha(); - void set_proximity_fade(bool p_enable); + void set_proximity_fade_enabled(bool p_enable); bool is_proximity_fade_enabled() const; void set_proximity_fade_distance(float p_distance); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index b42e65c8df..d1e300e057 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -313,11 +313,11 @@ Ref<TriangleMesh> Mesh::generate_surface_triangle_mesh(int p_surface) const { } } - Ref<TriangleMesh> triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); - triangle_mesh->create(faces); - surface_triangle_meshes.set(p_surface, triangle_mesh); + Ref<TriangleMesh> tr_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); + tr_mesh->create(faces); + surface_triangle_meshes.set(p_surface, tr_mesh); - return triangle_mesh; + return tr_mesh; } void Mesh::generate_debug_mesh_lines(Vector<Vector3> &r_lines) { @@ -940,7 +940,7 @@ void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_forma dst[0] = (int16_t)CLAMP(src[0] / 127.0f * 32767, -32768, 32767); dst[1] = (int16_t)CLAMP(src[1] / 127.0f * 32767, -32768, 32767); } - src_offset += sizeof(int16_t) * 2; + src_offset += sizeof(int8_t) * 2; } else { for (uint32_t i = 0; i < p_elements; i++) { const int16_t *src = (const int16_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; @@ -962,7 +962,7 @@ void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_forma dst[0] = (uint16_t)CLAMP(res.x * 65535, 0, 65535); dst[1] = (uint16_t)CLAMP(res.y * 65535, 0, 65535); } - src_offset += sizeof(uint16_t) * 2; + src_offset += sizeof(uint8_t) * 4; // 1 byte padding } else { for (uint32_t i = 0; i < p_elements; i++) { const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; @@ -973,7 +973,7 @@ void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_forma dst[0] = (uint16_t)CLAMP(res.x * 65535, 0, 65535); dst[1] = (uint16_t)CLAMP(res.y * 65535, 0, 65535); } - src_offset += sizeof(uint16_t) * 2; + src_offset += sizeof(float) * 3; } } @@ -988,7 +988,7 @@ void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_forma dst[0] = (uint16_t)CLAMP((src[0] / 127.0f * .5f + .5f) * 65535, 0, 65535); dst[1] = (uint16_t)CLAMP((src[1] / 127.0f * .5f + .5f) * 65535, 0, 65535); } - src_offset += sizeof(uint16_t) * 2; + src_offset += sizeof(uint8_t) * 2; } else { // int16 SNORM -> uint16 UNORM for (uint32_t i = 0; i < p_elements; i++) { const int16_t *src = (const int16_t *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; @@ -1010,7 +1010,7 @@ void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_forma dst[0] = (uint16_t)CLAMP(res.x * 65535, 0, 65535); dst[1] = (uint16_t)CLAMP(res.y * 65535, 0, 65535); } - src_offset += sizeof(uint16_t) * 2; + src_offset += sizeof(uint8_t) * 4; } else { for (uint32_t i = 0; i < p_elements; i++) { const float *src = (const float *)&src_vertex_ptr[i * src_vertex_stride + src_offset]; @@ -1021,7 +1021,7 @@ void _fix_array_compatibility(const Vector<uint8_t> &p_src, uint32_t p_old_forma dst[0] = (uint16_t)CLAMP(res.x * 65535, 0, 65535); dst[1] = (uint16_t)CLAMP(res.y * 65535, 0, 65535); } - src_offset += sizeof(uint16_t) * 2; + src_offset += sizeof(float) * 4; } } } break; @@ -1251,7 +1251,7 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { index_count = d["index_count"]; } - Vector<uint8_t> blend_shapes; + Vector<uint8_t> blend_shapes_new; if (d.has("blend_shape_data")) { Array blend_shape_data = d["blend_shape_data"]; @@ -1263,7 +1263,7 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { Vector<uint8_t> shape = blend_shape_data[i]; _fix_array_compatibility(shape, old_format, new_format, vertex_count, blend_vertex_array, blend_attribute_array, blend_skin_array); - blend_shapes.append_array(blend_vertex_array); + blend_shapes_new.append_array(blend_vertex_array); } } @@ -1273,7 +1273,7 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { print_verbose("Mesh format post-conversion: " + itos(new_format)); ERR_FAIL_COND_V(!d.has("aabb"), false); - AABB aabb = d["aabb"]; + AABB aabb_new = d["aabb"]; Vector<AABB> bone_aabb; if (d.has("skeleton_aabb")) { @@ -1285,7 +1285,7 @@ bool ArrayMesh::_set(const StringName &p_name, const Variant &p_value) { } } - add_surface(new_format, PrimitiveType(primitive), vertex_array, attribute_array, skin_array, vertex_count, array_index_data, index_count, aabb, blend_shapes, bone_aabb); + add_surface(new_format, PrimitiveType(primitive), vertex_array, attribute_array, skin_array, vertex_count, array_index_data, index_count, aabb_new, blend_shapes_new, bone_aabb); } else { ERR_FAIL_V(false); @@ -1462,9 +1462,9 @@ void ArrayMesh::_set_surfaces(const Array &p_surfaces) { } } - String name; + String surf_name; if (d.has("name")) { - name = d["name"]; + surf_name = d["name"]; } bool _2d = false; @@ -1474,7 +1474,7 @@ void ArrayMesh::_set_surfaces(const Array &p_surfaces) { surface_data.push_back(surface); surface_materials.push_back(material); - surface_names.push_back(name); + surface_names.push_back(surf_name); surface_2d.push_back(_2d); } @@ -1576,9 +1576,8 @@ void ArrayMesh::_recompute_aabb() { } } } -#ifndef _MSC_VER -#warning need to add binding to add_surface using future MeshSurfaceData object -#endif + +// TODO: Need to add binding to add_surface using future MeshSurfaceData object. void ArrayMesh::add_surface(uint32_t p_format, PrimitiveType p_primitive, const Vector<uint8_t> &p_array, const Vector<uint8_t> &p_attribute_array, const Vector<uint8_t> &p_skin_array, int p_vertex_count, const Vector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<uint8_t> &p_blend_shape_data, const Vector<AABB> &p_bone_aabbs, const Vector<RS::SurfaceData::LOD> &p_lods) { _create_if_empty(); @@ -1657,17 +1656,17 @@ int ArrayMesh::get_surface_count() const { void ArrayMesh::add_blend_shape(const StringName &p_name) { ERR_FAIL_COND_MSG(surfaces.size(), "Can't add a shape key count if surfaces are already created."); - StringName name = p_name; + StringName shape_name = p_name; - if (blend_shapes.has(name)) { + if (blend_shapes.has(shape_name)) { int count = 2; do { - name = String(p_name) + " " + itos(count); + shape_name = String(p_name) + " " + itos(count); count++; - } while (blend_shapes.has(name)); + } while (blend_shapes.has(shape_name)); } - blend_shapes.push_back(name); + blend_shapes.push_back(shape_name); if (mesh.is_valid()) { RS::get_singleton()->mesh_set_blend_shape_count(mesh, blend_shapes.size()); @@ -1686,17 +1685,17 @@ StringName ArrayMesh::get_blend_shape_name(int p_index) const { void ArrayMesh::set_blend_shape_name(int p_index, const StringName &p_name) { ERR_FAIL_INDEX(p_index, blend_shapes.size()); - StringName name = p_name; - int found = blend_shapes.find(name); + StringName shape_name = p_name; + int found = blend_shapes.find(shape_name); if (found != -1 && found != p_index) { int count = 2; do { - name = String(p_name) + " " + itos(count); + shape_name = String(p_name) + " " + itos(count); count++; - } while (blend_shapes.find(name) != -1); + } while (blend_shapes.find(shape_name) != -1); } - blend_shapes.write[p_index] = name; + blend_shapes.write[p_index] = shape_name; } void ArrayMesh::clear_blend_shapes() { diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 2d3f9d9afc..7c78b757c7 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -33,10 +33,10 @@ #include "box_shape_3d.h" bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - if (name.begins_with("item/")) { - int idx = name.get_slicec('/', 1).to_int(); - String what = name.get_slicec('/', 2); + String prop_name = p_name; + if (prop_name.begins_with("item/")) { + int idx = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); if (!item_map.has(idx)) { create_item(idx); } @@ -72,10 +72,10 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { } bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - int idx = name.get_slicec('/', 1).to_int(); + String prop_name = p_name; + int idx = prop_name.get_slicec('/', 1).to_int(); ERR_FAIL_COND_V(!item_map.has(idx), false); - String what = name.get_slicec('/', 2); + String what = prop_name.get_slicec('/', 2); if (what == "name") { r_ret = get_item_name(idx); @@ -100,14 +100,14 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { void MeshLibrary::_get_property_list(List<PropertyInfo> *p_list) const { for (const KeyValue<int, Item> &E : item_map) { - String name = vformat("%s/%d/", PNAME("item"), E.key); - p_list->push_back(PropertyInfo(Variant::STRING, name + PNAME("name"))); - p_list->push_back(PropertyInfo(Variant::OBJECT, name + PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); - p_list->push_back(PropertyInfo(Variant::ARRAY, name + PNAME("shapes"))); - p_list->push_back(PropertyInfo(Variant::OBJECT, name + PNAME("navmesh"), PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, name + PNAME("navmesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); - p_list->push_back(PropertyInfo(Variant::OBJECT, name + PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT)); + String prop_name = vformat("%s/%d/", PNAME("item"), E.key); + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + PNAME("name"))); + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); + p_list->push_back(PropertyInfo(Variant::ARRAY, prop_name + PNAME("shapes"))); + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("navmesh"), PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("navmesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT)); } } diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 90ea879012..de97498674 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -527,7 +527,7 @@ void NavigationMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Both"), "set_parsed_geometry_type", "get_parsed_geometry_type"); ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); ADD_PROPERTY_DEFAULT("geometry_collision_mask", 0xFFFFFFFF); - ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_source_geometry_mode", PROPERTY_HINT_ENUM, "NavMesh Children, Group With Children, Group Explicit"), "set_source_geometry_mode", "get_source_geometry_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry_source_geometry_mode", PROPERTY_HINT_ENUM, "NavMesh Children,Group With Children,Group Explicit"), "set_source_geometry_mode", "get_source_geometry_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "geometry_source_group_name"), "set_source_group_name", "get_source_group_name"); ADD_PROPERTY_DEFAULT("geometry_source_group_name", StringName("navmesh")); ADD_GROUP("Cells", "cell_"); @@ -590,16 +590,16 @@ void NavigationMesh::_validate_property(PropertyInfo &p_property) const { #ifndef DISABLE_DEPRECATED bool NavigationMesh::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - if (name.find("/") != -1) { + String prop_name = p_name; + if (prop_name.find("/") != -1) { // Compatibility with pre-3.5 "category/path" property names. - name = name.replace("/", "_"); - if (name == "sample_partition_type_sample_partition_type") { + prop_name = prop_name.replace("/", "_"); + if (prop_name == "sample_partition_type_sample_partition_type") { set("sample_partition_type", p_value); - } else if (name == "filter_filter_walkable_low_height_spans") { + } else if (prop_name == "filter_filter_walkable_low_height_spans") { set("filter_walkable_low_height_spans", p_value); } else { - set(name, p_value); + set(prop_name, p_value); } return true; @@ -608,16 +608,16 @@ bool NavigationMesh::_set(const StringName &p_name, const Variant &p_value) { } bool NavigationMesh::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - if (name.find("/") != -1) { + String prop_name = p_name; + if (prop_name.find("/") != -1) { // Compatibility with pre-3.5 "category/path" property names. - name = name.replace("/", "_"); - if (name == "sample_partition_type_sample_partition_type") { + prop_name = prop_name.replace("/", "_"); + if (prop_name == "sample_partition_type_sample_partition_type") { r_ret = get("sample_partition_type"); - } else if (name == "filter_filter_walkable_low_height_spans") { + } else if (prop_name == "filter_filter_walkable_low_height_spans") { r_ret = get("filter_walkable_low_height_spans"); } else { - r_ret = get(name); + r_ret = get(prop_name); } return true; } diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index e0bedad595..1c99fa5554 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -150,15 +150,15 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } else if (n.instance >= 0) { //instance a scene into this node if (n.instance & FLAG_INSTANCE_IS_PLACEHOLDER) { - String path = props[n.instance & FLAG_MASK]; + String scene_path = props[n.instance & FLAG_MASK]; if (disable_placeholders) { - Ref<PackedScene> sdata = ResourceLoader::load(path, "PackedScene"); + Ref<PackedScene> sdata = ResourceLoader::load(scene_path, "PackedScene"); ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); ERR_FAIL_COND_V(!node, nullptr); } else { InstancePlaceholder *ip = memnew(InstancePlaceholder); - ip->set_instance_path(path); + ip->set_instance_path(scene_path); node = ip; } node->set_scene_instance_load_placeholder(true); @@ -938,8 +938,8 @@ Error SceneState::pack(Node *p_scene) { // If using scene inheritance, pack the scene it inherits from. if (scene->get_scene_inherited_state().is_valid()) { - String path = scene->get_scene_inherited_state()->get_path(); - Ref<PackedScene> instance = ResourceLoader::load(path); + String scene_path = scene->get_scene_inherited_state()->get_path(); + Ref<PackedScene> instance = ResourceLoader::load(scene_path); if (instance.is_valid()) { base_scene_idx = _vm_get_variant(instance, variant_map); } @@ -1546,7 +1546,7 @@ Array SceneState::get_connection_binds(int p_idx) const { return binds; } -bool SceneState::has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method) { +bool SceneState::has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method, bool p_no_inheritance) { // this method cannot be const because of this Ref<SceneState> ss = this; @@ -1578,6 +1578,10 @@ bool SceneState::has_connection(const NodePath &p_node_from, const StringName &p } } + if (p_no_inheritance) { + break; + } + ss = ss->get_base_scene_state(); } while (ss.is_valid()); diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 8e1a1d29b6..c6f82ddd5e 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -176,7 +176,7 @@ public: int get_connection_unbinds(int p_idx) const; Array get_connection_binds(int p_idx) const; - bool has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method); + bool has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method, bool p_no_inheritance = false); Vector<NodePath> get_editable_instances() const; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index e51c786786..b77430c154 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -115,6 +115,7 @@ void ParticleProcessMaterial::init_shaders() { shader_names->sub_emitter_frequency = "sub_emitter_frequency"; shader_names->sub_emitter_amount_at_end = "sub_emitter_amount_at_end"; + shader_names->sub_emitter_amount_at_collision = "sub_emitter_amount_at_collision"; shader_names->sub_emitter_keep_velocity = "sub_emitter_keep_velocity"; shader_names->collision_friction = "collision_friction"; @@ -235,6 +236,9 @@ void ParticleProcessMaterial::_update_shader() { if (sub_emitter_mode == SUB_EMITTER_AT_END) { code += "uniform int sub_emitter_amount_at_end;\n"; } + if (sub_emitter_mode == SUB_EMITTER_AT_COLLISION) { + code += "uniform int sub_emitter_amount_at_collision;\n"; + } code += "uniform bool sub_emitter_keep_velocity;\n"; } @@ -830,6 +834,13 @@ void ParticleProcessMaterial::_update_shader() { code += " TRANSFORM[3].z = 0.0;\n"; } + // scale by scale + code += " float base_scale = mix(scale_min, scale_max, scale_rand);\n"; + code += " base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n"; + code += " TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n"; + code += " TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n"; + code += " TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n"; + if (collision_mode == COLLISION_RIGID) { code += " if (COLLIDED) {\n"; code += " if (length(VELOCITY) > 3.0) {\n"; @@ -844,21 +855,6 @@ void ParticleProcessMaterial::_update_shader() { } code += " }\n"; code += " }\n"; - } - - // scale by scale - code += " float base_scale = mix(scale_min, scale_max, scale_rand);\n"; - code += " base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n"; - code += " TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n"; - code += " TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n"; - code += " TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n"; - - if (collision_mode == COLLISION_RIGID) { - code += " if (COLLIDED) {\n"; - code += " TRANSFORM[3].xyz+=COLLISION_NORMAL * COLLISION_DEPTH;\n"; - code += " VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n"; - code += " VELOCITY = mix(VELOCITY,vec3(0.0),collision_friction * DELTA * 100.0);\n"; - code += " }\n"; } else if (collision_mode == COLLISION_HIDE_ON_CONTACT) { code += " if (COLLIDED) {\n"; code += " ACTIVE = false;\n"; @@ -874,7 +870,7 @@ void ParticleProcessMaterial::_update_shader() { code += " if (DELTA >= interval_rem) emit_count = 1;\n"; } break; case SUB_EMITTER_AT_COLLISION: { - code += " if (COLLIDED) emit_count = 1;\n"; + code += " if (COLLIDED) emit_count = sub_emitter_amount_at_collision;\n"; } break; case SUB_EMITTER_AT_END: { code += " float unit_delta = DELTA/LIFETIME;\n"; @@ -1433,6 +1429,10 @@ void ParticleProcessMaterial::_validate_property(PropertyInfo &p_property) const p_property.usage = PROPERTY_USAGE_NONE; } + if (p_property.name == "sub_emitter_amount_at_collision" && sub_emitter_mode != SUB_EMITTER_AT_COLLISION) { + p_property.usage = PROPERTY_USAGE_NONE; + } + if (p_property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) { p_property.usage = PROPERTY_USAGE_NONE; } @@ -1488,6 +1488,15 @@ int ParticleProcessMaterial::get_sub_emitter_amount_at_end() const { return sub_emitter_amount_at_end; } +void ParticleProcessMaterial::set_sub_emitter_amount_at_collision(int p_amount) { + sub_emitter_amount_at_collision = p_amount; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_amount_at_collision, p_amount); +} + +int ParticleProcessMaterial::get_sub_emitter_amount_at_collision() const { + return sub_emitter_amount_at_collision; +} + void ParticleProcessMaterial::set_sub_emitter_keep_velocity(bool p_enable) { sub_emitter_keep_velocity = p_enable; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_keep_velocity, p_enable); @@ -1640,6 +1649,9 @@ void ParticleProcessMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("get_sub_emitter_amount_at_end"), &ParticleProcessMaterial::get_sub_emitter_amount_at_end); ClassDB::bind_method(D_METHOD("set_sub_emitter_amount_at_end", "amount"), &ParticleProcessMaterial::set_sub_emitter_amount_at_end); + ClassDB::bind_method(D_METHOD("get_sub_emitter_amount_at_collision"), &ParticleProcessMaterial::get_sub_emitter_amount_at_collision); + ClassDB::bind_method(D_METHOD("set_sub_emitter_amount_at_collision", "amount"), &ParticleProcessMaterial::set_sub_emitter_amount_at_collision); + ClassDB::bind_method(D_METHOD("get_sub_emitter_keep_velocity"), &ParticleProcessMaterial::get_sub_emitter_keep_velocity); ClassDB::bind_method(D_METHOD("set_sub_emitter_keep_velocity", "enable"), &ParticleProcessMaterial::set_sub_emitter_keep_velocity); @@ -1752,6 +1764,7 @@ void ParticleProcessMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,At End,At Collision"), "set_sub_emitter_mode", "get_sub_emitter_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01,suffix:Hz"), "set_sub_emitter_frequency", "get_sub_emitter_frequency"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_end", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_end", "get_sub_emitter_amount_at_end"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_collision", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_collision", "get_sub_emitter_amount_at_collision"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity"); ADD_GROUP("Attractor Interaction", "attractor_interaction_"); @@ -1859,6 +1872,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() : set_sub_emitter_mode(SUB_EMITTER_DISABLED); set_sub_emitter_frequency(4); set_sub_emitter_amount_at_end(1); + set_sub_emitter_amount_at_collision(1); set_sub_emitter_keep_velocity(false); set_attractor_interaction_enabled(true); diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index fe4741d6e5..64d828b3e7 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -28,12 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/templates/rid.h" -#include "scene/resources/material.h" - #ifndef PARTICLE_PROCESS_MATERIAL_H #define PARTICLE_PROCESS_MATERIAL_H +#include "core/templates/rid.h" +#include "scene/resources/material.h" + /* TODO: -Path following @@ -246,6 +246,7 @@ private: StringName sub_emitter_frequency; StringName sub_emitter_amount_at_end; + StringName sub_emitter_amount_at_collision; StringName sub_emitter_keep_velocity; StringName collision_friction; @@ -265,9 +266,9 @@ private: float spread = 0.0f; float flatness = 0.0f; - float params_min[PARAM_MAX]; - float params_max[PARAM_MAX]; - float params[PARAM_MAX]; + float params_min[PARAM_MAX] = {}; + float params_max[PARAM_MAX] = {}; + float params[PARAM_MAX] = {}; Ref<Texture2D> tex_parameters[PARAM_MAX]; Color color; @@ -304,6 +305,7 @@ private: SubEmitterMode sub_emitter_mode; double sub_emitter_frequency = 0.0; int sub_emitter_amount_at_end = 0; + int sub_emitter_amount_at_collision = 0; bool sub_emitter_keep_velocity = false; //do not save emission points here @@ -418,6 +420,9 @@ public: void set_sub_emitter_amount_at_end(int p_amount); int get_sub_emitter_amount_at_end() const; + void set_sub_emitter_amount_at_collision(int p_amount); + int get_sub_emitter_amount_at_collision() const; + void set_sub_emitter_keep_velocity(bool p_enable); bool get_sub_emitter_keep_velocity() const; diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp index 5e18671c11..2c80e234e9 100644 --- a/scene/resources/polygon_path_finder.cpp +++ b/scene/resources/polygon_path_finder.cpp @@ -441,9 +441,9 @@ Dictionary PolygonPathFinder::_get_data() const { Dictionary d; Vector<Vector2> p; Vector<int> ind; - Array connections; + Array path_connections; p.resize(MAX(0, points.size() - 2)); - connections.resize(MAX(0, points.size() - 2)); + path_connections.resize(MAX(0, points.size() - 2)); ind.resize(edges.size() * 2); Vector<real_t> penalties; penalties.resize(MAX(0, points.size() - 2)); @@ -463,7 +463,7 @@ Dictionary PolygonPathFinder::_get_data() const { cw[idx++] = E; } } - connections[i] = c; + path_connections[i] = c; } } { @@ -478,7 +478,7 @@ Dictionary PolygonPathFinder::_get_data() const { d["bounds"] = bounds; d["points"] = p; d["penalties"] = penalties; - d["connections"] = connections; + d["connections"] = path_connections; d["segments"] = ind; return d; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index c017c90370..4c6d533c72 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -30,6 +30,7 @@ #include "primitive_meshes.h" +#include "core/config/project_settings.h" #include "core/core_string_names.h" #include "scene/resources/theme.h" #include "scene/theme/theme_db.h" @@ -37,6 +38,8 @@ #include "thirdparty/misc/clipper.hpp" #include "thirdparty/misc/polypartition.h" +#define PADDING_REF_SIZE 1024.0 + /** PrimitiveMesh */ @@ -94,6 +97,26 @@ void PrimitiveMesh::_update() const { } } + if (add_uv2) { + // _create_mesh_array should populate our UV2, this is a fallback in case it doesn't. + // As we don't know anything about the geometry we only pad the right and bottom edge + // of our texture. + Vector<Vector2> uv = arr[RS::ARRAY_TEX_UV]; + Vector<Vector2> uv2 = arr[RS::ARRAY_TEX_UV2]; + + if (uv.size() > 0 && uv2.size() == 0) { + Vector2 uv2_scale = get_uv2_scale(); + uv2.resize(uv.size()); + + Vector2 *uv2w = uv2.ptrw(); + for (int i = 0; i < uv.size(); i++) { + uv2w[i] = uv[i] * uv2_scale; + } + } + + arr[RS::ARRAY_TEX_UV2] = uv2; + } + array_len = pc; index_array_len = indices.size(); // in with the new @@ -160,7 +183,12 @@ TypedArray<Array> PrimitiveMesh::surface_get_blend_shape_arrays(int p_surface) c uint32_t PrimitiveMesh::surface_get_format(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, 1, 0); - return RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX; + uint32_t mesh_format = RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX; + if (add_uv2) { + mesh_format |= RS::ARRAY_FORMAT_TEX_UV2; + } + + return mesh_format; } Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { @@ -219,9 +247,17 @@ void PrimitiveMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &PrimitiveMesh::set_flip_faces); ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces); + ClassDB::bind_method(D_METHOD("set_add_uv2", "add_uv2"), &PrimitiveMesh::set_add_uv2); + ClassDB::bind_method(D_METHOD("get_add_uv2"), &PrimitiveMesh::get_add_uv2); + + ClassDB::bind_method(D_METHOD("set_uv2_padding", "uv2_padding"), &PrimitiveMesh::set_uv2_padding); + ClassDB::bind_method(D_METHOD("get_uv2_padding"), &PrimitiveMesh::get_uv2_padding); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "add_uv2"), "set_add_uv2", "get_add_uv2"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv2_padding"), "set_uv2_padding", "get_uv2_padding"); GDVIRTUAL_BIND(_create_mesh_array); } @@ -233,7 +269,7 @@ void PrimitiveMesh::set_material(const Ref<Material> &p_material) { RenderingServer::get_singleton()->mesh_surface_set_material(mesh, 0, material.is_null() ? RID() : material->get_rid()); notify_property_list_changed(); emit_changed(); - }; + } } Ref<Material> PrimitiveMesh::get_material() const { @@ -263,6 +299,42 @@ bool PrimitiveMesh::get_flip_faces() const { return flip_faces; } +void PrimitiveMesh::set_add_uv2(bool p_enable) { + add_uv2 = p_enable; + _update_lightmap_size(); + _request_update(); +} + +void PrimitiveMesh::set_uv2_padding(float p_padding) { + uv2_padding = p_padding; + _update_lightmap_size(); + _request_update(); +} + +Vector2 PrimitiveMesh::get_uv2_scale(Vector2 p_margin_scale) const { + Vector2 uv2_scale; + Vector2 lightmap_size = get_lightmap_size_hint(); + + // Calculate it as a margin, if no lightmap size hint is given we assume "PADDING_REF_SIZE" as our texture size. + uv2_scale.x = p_margin_scale.x * uv2_padding / (lightmap_size.x == 0.0 ? PADDING_REF_SIZE : lightmap_size.x); + uv2_scale.y = p_margin_scale.y * uv2_padding / (lightmap_size.y == 0.0 ? PADDING_REF_SIZE : lightmap_size.y); + + // Inverse it to turn our margin into a scale + uv2_scale = Vector2(1.0, 1.0) - uv2_scale; + + return uv2_scale; +} + +float PrimitiveMesh::get_lightmap_texel_size() const { + float texel_size = GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"); + + if (texel_size <= 0.0) { + texel_size = 0.2; + } + + return texel_size; +} + PrimitiveMesh::PrimitiveMesh() { mesh = RenderingServer::get_singleton()->mesh_create(); } @@ -275,22 +347,52 @@ PrimitiveMesh::~PrimitiveMesh() { CapsuleMesh */ +void CapsuleMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend + float vertical_length = radial_length * 2 + (height - 2.0 * radius); // total vertical length + + _lightmap_size_hint.x = MAX(1.0, 4.0 * radial_length / texel_size) + padding; + _lightmap_size_hint.y = MAX(1.0, vertical_length / texel_size) + padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void CapsuleMesh::_create_mesh_array(Array &p_arr) const { - create_mesh_array(p_arr, radius, height, radial_segments, rings); + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, radius, height, radial_segments, rings, _add_uv2, _uv2_padding); } -void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const float height, const int radial_segments, const int rings) { +void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const float height, const int radial_segments, const int rings, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; float x, y, z, u, v, w; float onethird = 1.0 / 3.0; float twothirds = 2.0 / 3.0; + // Only used if we calculate UV2 + float radial_width = 2.0 * radius * Math_PI; + float radial_h = radial_width / (radial_width + p_uv2_padding); + float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend + float vertical_length = radial_length * 2 + (height - 2.0 * radius) + p_uv2_padding; // total vertical length + float radial_v = radial_length / vertical_length; // v size of top and bottom section + float height_v = (height - 2.0 * radius) / vertical_length; // v size of height section + // note, this has been aligned with our collision shape but I've left the descriptions as top/middle/bottom Vector<Vector3> points; Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -322,6 +424,9 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa normals.push_back(p.normalized()); ADD_TANGENT(-z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v * onethird)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, v * radial_v)); + } point++; if (i > 0 && j > 0) { @@ -332,12 +437,12 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } /* cylinder */ thisrow = point; @@ -361,6 +466,9 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa normals.push_back(Vector3(x, 0.0, -z)); ADD_TANGENT(-z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, onethird + (v * onethird))); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, radial_v + (v * height_v))); + } point++; if (i > 0 && j > 0) { @@ -371,12 +479,12 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } /* bottom hemisphere */ thisrow = point; @@ -390,17 +498,20 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa y = radius * cos(0.5 * Math_PI * v); for (i = 0; i <= radial_segments; i++) { - float u2 = i; - u2 /= radial_segments; + u = i; + u /= radial_segments; - x = -sin(u2 * Math_TAU); - z = cos(u2 * Math_TAU); + x = -sin(u * Math_TAU); + z = cos(u * Math_TAU); Vector3 p = Vector3(x * radius * w, y, -z * radius * w); points.push_back(p + Vector3(0.0, -0.5 * height + radius, 0.0)); normals.push_back(p.normalized()); ADD_TANGENT(-z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u2, twothirds + ((v - 1.0) * onethird))); + uvs.push_back(Vector2(u, twothirds + ((v - 1.0) * onethird))); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, radial_v + height_v + ((v - 1.0) * radial_v))); + } point++; if (i > 0 && j > 0) { @@ -411,17 +522,20 @@ void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const floa indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -450,6 +564,7 @@ void CapsuleMesh::set_radius(const float p_radius) { if (radius > height * 0.5) { height = radius * 2.0; } + _update_lightmap_size(); _request_update(); } @@ -462,6 +577,7 @@ void CapsuleMesh::set_height(const float p_height) { if (radius > height * 0.5) { radius = height * 0.5; } + _update_lightmap_size(); _request_update(); } @@ -493,16 +609,53 @@ CapsuleMesh::CapsuleMesh() {} BoxMesh */ +void BoxMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float width = (size.x + size.z) / texel_size; + float length = (size.y + size.y + MAX(size.x, size.z)) / texel_size; + + _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; + _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void BoxMesh::_create_mesh_array(Array &p_arr) const { - BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d); + // Note about padding, with our box each face of the box faces a different direction so we want a seam + // around every face. We thus add our padding to the right and bottom of each face. + // With 3 faces along the width and 2 along the height of the texture we need to adjust our scale + // accordingly. + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d, _add_uv2, _uv2_padding); } -void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int subdivide_h, int subdivide_d) { +void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int subdivide_h, int subdivide_d, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; float x, y, z; float onethird = 1.0 / 3.0; float twothirds = 2.0 / 3.0; + // Only used if we calculate UV2 + // TODO this could be improved by changing the order depending on which side is the longest (basically the below works best if size.y is the longest) + float total_h = (size.x + size.z + (2.0 * p_uv2_padding)); + float padding_h = p_uv2_padding / total_h; + float width_h = size.x / total_h; + float depth_h = size.z / total_h; + float total_v = (size.y + size.y + MAX(size.x, size.z) + (3.0 * p_uv2_padding)); + float padding_v = p_uv2_padding / total_v; + float width_v = size.x / total_v; + float height_v = size.y / total_v; + float depth_v = size.z / total_v; + Vector3 start_pos = size * -0.5; // set our bounding box @@ -511,6 +664,7 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -525,18 +679,24 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int thisrow = point; prevrow = 0; for (j = 0; j <= subdivide_h + 1; j++) { + float v = j; + float v2 = v / (subdivide_w + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + x = start_pos.x; for (i = 0; i <= subdivide_w + 1; i++) { float u = i; - float v = j; + float u2 = u / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); // front points.push_back(Vector3(x, -y, -start_pos.z)); // double negative on the Z! normals.push_back(Vector3(0.0, 0.0, 1.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, v2 * height_v)); + } point++; // back @@ -544,6 +704,9 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int normals.push_back(Vector3(0.0, 0.0, -1.0)); ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, height_v + padding_v + (v2 * height_v))); + } point++; if (i > 0 && j > 0) { @@ -564,33 +727,39 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } // left + right y = start_pos.y; thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_h + 1); j++) { + float v = j; + float v2 = v / (subdivide_h + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + z = start_pos.z; for (i = 0; i <= (subdivide_d + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_d + 1.0); u /= (3.0 * (subdivide_d + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); // right points.push_back(Vector3(-start_pos.x, -y, -z)); normals.push_back(Vector3(1.0, 0.0, 0.0)); ADD_TANGENT(0.0, 0.0, -1.0, 1.0); uvs.push_back(Vector2(onethird + u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), v2 * height_v)); + } point++; // left @@ -598,6 +767,9 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int normals.push_back(Vector3(-1.0, 0.0, 0.0)); ADD_TANGENT(0.0, 0.0, 1.0, 1.0); uvs.push_back(Vector2(u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), height_v + padding_v + (v2 * height_v))); + } point++; if (i > 0 && j > 0) { @@ -618,33 +790,39 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } z += size.z / (subdivide_d + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } // top + bottom z = start_pos.z; thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_d + 1); j++) { + float v = j; + float v2 = v / (subdivide_d + 1.0); + v /= (2.0 * (subdivide_d + 1.0)); + x = start_pos.x; for (i = 0; i <= (subdivide_w + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_d + 1.0)); // top points.push_back(Vector3(-x, -start_pos.y, -z)); normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(onethird + u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, ((height_v + padding_v) * 2.0) + (v2 * depth_v))); + } point++; // bottom @@ -652,6 +830,9 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), ((height_v + padding_v) * 2.0) + (v2 * width_v))); + } point++; if (i > 0 && j > 0) { @@ -672,20 +853,23 @@ void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } z += size.z / (subdivide_d + 1.0); prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -708,6 +892,7 @@ void BoxMesh::_bind_methods() { void BoxMesh::set_size(const Vector3 &p_size) { size = p_size; + _update_lightmap_size(); _request_update(); } @@ -748,18 +933,58 @@ BoxMesh::BoxMesh() {} CylinderMesh */ +void CylinderMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float top_circumference = top_radius * Math_PI * 2.0; + float bottom_circumference = bottom_radius * Math_PI * 2.0; + + float _width = MAX(top_circumference, bottom_circumference) / texel_size + padding; + _width = MAX(_width, (((top_radius + bottom_radius) / texel_size) + padding) * 2.0); // this is extremely unlikely to be larger, will only happen if padding is larger then our diameter. + _lightmap_size_hint.x = MAX(1.0, _width); + + float _height = ((height + (MAX(top_radius, bottom_radius) * 2.0)) / texel_size) + (2.0 * padding); + + _lightmap_size_hint.y = MAX(1.0, _height); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void CylinderMesh::_create_mesh_array(Array &p_arr) const { - create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom); + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom, _add_uv2, _uv2_padding); } -void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings, bool cap_top, bool cap_bottom) { +void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings, bool cap_top, bool cap_bottom, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; - float x, y, z, u, v, radius; + float x, y, z, u, v, radius, radius_h; + + // Only used if we calculate UV2 + float top_circumference = top_radius * Math_PI * 2.0; + float bottom_circumference = bottom_radius * Math_PI * 2.0; + float vertical_length = height + MAX(2.0 * top_radius, 2.0 * bottom_radius) + (2.0 * p_uv2_padding); + float height_v = height / vertical_length; + float padding_v = p_uv2_padding / vertical_length; + + float horizonal_length = MAX(MAX(2.0 * (top_radius + bottom_radius + p_uv2_padding), top_circumference + p_uv2_padding), bottom_circumference + p_uv2_padding); + float center_h = 0.5 * (horizonal_length - p_uv2_padding) / horizonal_length; + float top_h = top_circumference / horizonal_length; + float bottom_h = bottom_circumference / horizonal_length; + float padding_h = p_uv2_padding / horizonal_length; Vector<Vector3> points; Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -771,11 +996,13 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto thisrow = 0; prevrow = 0; + const real_t side_normal_y = (bottom_radius - top_radius) / height; for (j = 0; j <= (rings + 1); j++) { v = j; v /= (rings + 1); radius = top_radius + ((bottom_radius - top_radius) * v); + radius_h = top_h + ((bottom_h - top_h) * v); y = height * v; y = (height * 0.5) - y; @@ -789,9 +1016,12 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto Vector3 p = Vector3(x * radius, y, z * radius); points.push_back(p); - normals.push_back(Vector3(x, 0.0, z)); + normals.push_back(Vector3(x, side_normal_y, z).normalized()); ADD_TANGENT(z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v * 0.5)); + if (p_add_uv2) { + uv2s.push_back(Vector2(center_h + (u - 0.5) * radius_h, v * height_v)); + } point++; if (i > 0 && j > 0) { @@ -802,14 +1032,20 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } - // add top + // Adjust for bottom section, only used if we calculate UV2s. + top_h = top_radius / horizonal_length; + float top_v = top_radius / vertical_length; + bottom_h = bottom_radius / horizonal_length; + float bottom_v = bottom_radius / vertical_length; + + // Add top. if (cap_top && top_radius > 0.0) { y = height * 0.5; @@ -818,6 +1054,9 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(0.25, 0.75)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h, height_v + padding_v + MAX(top_v, bottom_v))); + } point++; for (i = 0; i <= radial_segments; i++) { @@ -835,17 +1074,20 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, 1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + (x * top_h), height_v + padding_v + MAX(top_v, bottom_v) + (z * top_v))); + } point++; if (i > 0) { indices.push_back(thisrow); indices.push_back(point - 1); indices.push_back(point - 2); - }; - }; - }; + } + } + } - // add bottom + // Add bottom. if (cap_bottom && bottom_radius > 0.0) { y = height * -0.5; @@ -854,6 +1096,9 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(0.75, 0.75)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h, height_v + padding_v + MAX(top_v, bottom_v))); + } point++; for (i = 0; i <= radial_segments; i++) { @@ -871,20 +1116,26 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0) uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h + (x * bottom_h), height_v + padding_v + MAX(top_v, bottom_v) - (z * bottom_v))); + } point++; if (i > 0) { indices.push_back(thisrow); indices.push_back(point - 2); indices.push_back(point - 1); - }; - }; - }; + } + } + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -918,6 +1169,7 @@ void CylinderMesh::_bind_methods() { void CylinderMesh::set_top_radius(const float p_radius) { top_radius = p_radius; + _update_lightmap_size(); _request_update(); } @@ -927,6 +1179,7 @@ float CylinderMesh::get_top_radius() const { void CylinderMesh::set_bottom_radius(const float p_radius) { bottom_radius = p_radius; + _update_lightmap_size(); _request_update(); } @@ -936,6 +1189,7 @@ float CylinderMesh::get_bottom_radius() const { void CylinderMesh::set_height(const float p_height) { height = p_height; + _update_lightmap_size(); _request_update(); } @@ -985,10 +1239,26 @@ CylinderMesh::CylinderMesh() {} PlaneMesh */ +void PlaneMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + _lightmap_size_hint.x = MAX(1.0, (size.x / texel_size) + padding); + _lightmap_size_hint.y = MAX(1.0, (size.y / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void PlaneMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, z; + // Plane mesh can use default UV2 calculation as implemented in Primitive Mesh + Size2 start_pos = size * -0.5; Vector3 normal = Vector3(0.0, 1.0, 0.0); @@ -1042,15 +1312,15 @@ void PlaneMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } z += size.y / (subdivide_d + 1.0); prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; @@ -1078,7 +1348,7 @@ void PlaneMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_center_offset", "get_center_offset"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "orientation", PROPERTY_HINT_ENUM, "Face X, Face Y, Face Z"), "set_orientation", "get_orientation"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "orientation", PROPERTY_HINT_ENUM, "Face X,Face Y,Face Z"), "set_orientation", "get_orientation"); BIND_ENUM_CONSTANT(FACE_X) BIND_ENUM_CONSTANT(FACE_Y) @@ -1087,6 +1357,7 @@ void PlaneMesh::_bind_methods() { void PlaneMesh::set_size(const Size2 &p_size) { size = p_size; + _update_lightmap_size(); _request_update(); } @@ -1136,12 +1407,49 @@ PlaneMesh::PlaneMesh() {} PrismMesh */ +void PrismMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + // left_to_right does not effect the surface area of the prism so we ignore that. + // TODO we could combine the two triangles and save some space but we need to re-align the uv1 and adjust the tangent. + + float width = (size.x + size.z) / texel_size; + float length = (size.y + size.y + size.z) / texel_size; + + _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; + _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void PrismMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, y, z; float onethird = 1.0 / 3.0; float twothirds = 2.0 / 3.0; + // Only used if we calculate UV2 + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + float horizontal_total = size.x + size.z + 2.0 * _uv2_padding; + float width_h = size.x / horizontal_total; + float depth_h = size.z / horizontal_total; + float padding_h = _uv2_padding / horizontal_total; + + float vertical_total = (size.y + size.y + size.z) + (3.0 * _uv2_padding); + float height_v = size.y / vertical_total; + float depth_v = size.z / vertical_total; + float padding_v = _uv2_padding / vertical_total; + + // and start building + Vector3 start_pos = size * -0.5; // set our bounding box @@ -1150,6 +1458,7 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -1170,12 +1479,15 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { float offset_front = (1.0 - scale) * onethird * left_to_right; float offset_back = (1.0 - scale) * onethird * (1.0 - left_to_right); + float v = j; + float v2 = j / (subdivide_h + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + x = 0.0; for (i = 0; i <= (subdivide_w + 1); i++) { float u = i; - float v = j; + float u2 = i / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); u *= scale; @@ -1184,6 +1496,9 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { normals.push_back(Vector3(0.0, 0.0, 1.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(offset_front + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * scale * width_h, v2 * height_v)); + } point++; /* back */ @@ -1191,6 +1506,9 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { normals.push_back(Vector3(0.0, 0.0, -1.0)); ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + offset_back + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * scale * width_h, height_v + padding_v + v2 * height_v)); + } point++; if (i > 0 && j == 1) { @@ -1223,15 +1541,15 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } x += scale * size.x / (subdivide_w + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } /* left + right */ Vector3 normal_left, normal_right; @@ -1245,6 +1563,10 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_h + 1); j++) { + float v = j; + float v2 = j / (subdivide_h + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + float left, right; float scale = (y - start_pos.y) / size.y; @@ -1254,15 +1576,17 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { z = start_pos.z; for (i = 0; i <= (subdivide_d + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_d + 1.0); u /= (3.0 * (subdivide_d + 1.0)); - v /= (2.0 * (subdivide_h + 1.0)); /* right */ points.push_back(Vector3(right, -y, -z)); normals.push_back(normal_right); ADD_TANGENT(0.0, 0.0, -1.0, 1.0); uvs.push_back(Vector2(onethird + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, v2 * height_v)); + } point++; /* left */ @@ -1270,6 +1594,9 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { normals.push_back(normal_left); ADD_TANGENT(0.0, 0.0, 1.0, 1.0); uvs.push_back(Vector2(u, 0.5 + v)); + if (_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, height_v + padding_v + v2 * height_v)); + } point++; if (i > 0 && j > 0) { @@ -1290,33 +1617,39 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i2 + 1); indices.push_back(thisrow + i2 + 1); indices.push_back(thisrow + i2 - 1); - }; + } z += size.z / (subdivide_d + 1.0); - }; + } y += size.y / (subdivide_h + 1.0); prevrow = thisrow; thisrow = point; - }; + } /* bottom */ z = start_pos.z; thisrow = point; prevrow = 0; for (j = 0; j <= (subdivide_d + 1); j++) { + float v = j; + float v2 = v / (subdivide_d + 1.0); + v /= (2.0 * (subdivide_d + 1.0)); + x = start_pos.x; for (i = 0; i <= (subdivide_w + 1); i++) { float u = i; - float v = j; + float u2 = u / (subdivide_w + 1.0); u /= (3.0 * (subdivide_w + 1.0)); - v /= (2.0 * (subdivide_d + 1.0)); /* bottom */ points.push_back(Vector3(x, start_pos.y, -z)); normals.push_back(Vector3(0.0, -1.0, 0.0)); ADD_TANGENT(1.0, 0.0, 0.0, 1.0); uvs.push_back(Vector2(twothirds + u, 0.5 + v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, 2.0 * (height_v + padding_v) + v2 * depth_v)); + } point++; if (i > 0 && j > 0) { @@ -1327,20 +1660,23 @@ void PrismMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; + } x += size.x / (subdivide_w + 1.0); - }; + } z += size.z / (subdivide_d + 1.0); prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -1376,6 +1712,7 @@ float PrismMesh::get_left_to_right() const { void PrismMesh::set_size(const Vector3 &p_size) { size = p_size; + _update_lightmap_size(); _request_update(); } @@ -1416,22 +1753,50 @@ PrismMesh::PrismMesh() {} SphereMesh */ +void SphereMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float _width = radius * Math_TAU; + _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); + float _height = (is_hemisphere ? 1.0 : 0.5) * height * Math_PI; // note, with hemisphere height is our radius, while with a full sphere it is the diameter.. + _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void SphereMesh::_create_mesh_array(Array &p_arr) const { - create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere); + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding); } -void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int radial_segments, int rings, bool is_hemisphere) { +void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int radial_segments, int rings, bool is_hemisphere, bool p_add_uv2, const float p_uv2_padding) { int i, j, prevrow, thisrow, point; float x, y, z; float scale = height * (is_hemisphere ? 1.0 : 0.5); + // Only used if we calculate UV2 + float circumference = radius * Math_TAU; + float horizontal_length = circumference + p_uv2_padding; + float center_h = 0.5 * circumference / horizontal_length; + + float height_v = scale * Math_PI / ((scale * Math_PI) + p_uv2_padding); + // set our bounding box Vector<Vector3> points; Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; point = 0; @@ -1466,9 +1831,13 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int points.push_back(p); Vector3 normal = Vector3(x * w * scale, radius * (y / scale), z * w * scale); normals.push_back(normal.normalized()); - }; + } ADD_TANGENT(z, 0.0, -x, 1.0) uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + float w_h = w * 2.0 * center_h; + uv2s.push_back(Vector2(center_h + ((u - 0.5) * w_h), v * height_v)); + } point++; if (i > 0 && j > 0) { @@ -1479,17 +1848,20 @@ void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int indices.push_back(prevrow + i); indices.push_back(thisrow + i); indices.push_back(thisrow + i - 1); - }; - }; + } + } prevrow = thisrow; thisrow = point; - }; + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -1516,6 +1888,7 @@ void SphereMesh::_bind_methods() { void SphereMesh::set_radius(const float p_radius) { radius = p_radius; + _update_lightmap_size(); _request_update(); } @@ -1525,6 +1898,7 @@ float SphereMesh::get_radius() const { void SphereMesh::set_height(const float p_height) { height = p_height; + _update_lightmap_size(); _request_update(); } @@ -1552,6 +1926,7 @@ int SphereMesh::get_rings() const { void SphereMesh::set_is_hemisphere(const bool p_is_hemisphere) { is_hemisphere = p_is_hemisphere; + _update_lightmap_size(); _request_update(); } @@ -1565,6 +1940,31 @@ SphereMesh::SphereMesh() {} TorusMesh */ +void TorusMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float min_radius = inner_radius; + float max_radius = outer_radius; + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + float _width = max_radius * Math_TAU; + _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); + float _height = radius * Math_TAU; + _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + void TorusMesh::_create_mesh_array(Array &p_arr) const { // set our bounding box @@ -1572,6 +1972,7 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { Vector<Vector3> normals; Vector<float> tangents; Vector<Vector2> uvs; + Vector<Vector2> uv2s; Vector<int> indices; #define ADD_TANGENT(m_x, m_y, m_z, m_d) \ @@ -1591,6 +1992,17 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { float radius = (max_radius - min_radius) * 0.5; + // Only used if we calculate UV2 + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + float horizontal_total = max_radius * Math_TAU + _uv2_padding; + float max_h = max_radius * Math_TAU / horizontal_total; + float delta_h = (max_radius - min_radius) * Math_TAU / horizontal_total; + + float height_v = radius * Math_TAU / (radius * Math_TAU + _uv2_padding); + for (int i = 0; i <= rings; i++) { int prevrow = (i - 1) * (ring_segments + 1); int thisrow = i * (ring_segments + 1); @@ -1606,10 +2018,17 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { Vector2 normalj = Vector2(-Math::cos(angj), Math::sin(angj)); Vector2 normalk = normalj * radius + Vector2(min_radius + radius, 0); + float offset_h = 0.5 * (1.0 - normalj.x) * delta_h; + float adj_h = max_h - offset_h; + offset_h *= 0.5; + points.push_back(Vector3(normali.x * normalk.x, normalk.y, normali.y * normalk.x)); normals.push_back(Vector3(normali.x * normalj.x, normalj.y, normali.y * normalj.x)); ADD_TANGENT(-Math::cos(angi), 0.0, Math::sin(angi), 1.0); uvs.push_back(Vector2(inci, incj)); + if (_add_uv2) { + uv2s.push_back(Vector2(offset_h + inci * adj_h, incj * height_v)); + } if (i > 0 && j > 0) { indices.push_back(thisrow + j - 1); @@ -1627,6 +2046,9 @@ void TorusMesh::_create_mesh_array(Array &p_arr) const { p_arr[RS::ARRAY_NORMAL] = normals; p_arr[RS::ARRAY_TANGENT] = tangents; p_arr[RS::ARRAY_TEX_UV] = uvs; + if (_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } p_arr[RS::ARRAY_INDEX] = indices; } @@ -1784,6 +2206,8 @@ Transform3D TubeTrailMesh::get_builtin_bind_pose(int p_index) const { } void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { + // Seeing use case for TubeTrailMesh, no need to do anything more then default UV2 calculation + PackedVector3Array points; PackedVector3Array normals; PackedFloat32Array tangents; @@ -1919,9 +2343,9 @@ void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(thisrow); indices.push_back(point - 1); indices.push_back(point - 2); - }; - }; - }; + } + } + } float scale_neg = 1.0; if (curve.is_valid() && curve->get_point_count() > 0) { @@ -1982,9 +2406,9 @@ void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { indices.push_back(thisrow); indices.push_back(point - 2); indices.push_back(point - 1); - }; - }; - }; + } + } + } p_arr[RS::ARRAY_VERTEX] = points; p_arr[RS::ARRAY_NORMAL] = normals; @@ -2108,6 +2532,8 @@ Transform3D RibbonTrailMesh::get_builtin_bind_pose(int p_index) const { } void RibbonTrailMesh::_create_mesh_array(Array &p_arr) const { + // Seeing use case of ribbon trail mesh, no need to implement special UV2 calculation + PackedVector3Array points; PackedVector3Array normals; PackedFloat32Array tangents; @@ -2443,17 +2869,17 @@ void TextMesh::_create_mesh_array(Array &p_arr) const { TS->shaped_text_clear(text_rid); TS->shaped_text_set_direction(text_rid, text_direction); - String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, font->get_opentype_features(), language); + String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language); for (int i = 0; i < TextServer::SPACING_MAX; i++) { TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); } Array stt; if (st_parser == TextServer::STRUCTURED_TEXT_CUSTOM) { - GDVIRTUAL_CALL(_structured_text_parser, st_args, text, stt); + GDVIRTUAL_CALL(_structured_text_parser, st_args, txt, stt); } else { - stt = TS->parse_structured_text(st_parser, st_args, text); + stt = TS->parse_structured_text(st_parser, st_args, txt); } TS->shaped_text_set_bidi_override(text_rid, stt); diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 280477ebfa..06f9781b84 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -56,6 +56,9 @@ private: Ref<Material> material; bool flip_faces = false; + bool add_uv2 = false; + float uv2_padding = 2.0; + // make sure we do an update after we've finished constructing our object mutable bool pending_request = true; void _update() const; @@ -70,6 +73,10 @@ protected: void _request_update(); GDVIRTUAL0RC(Array, _create_mesh_array) + 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(){}; + public: virtual int get_surface_count() const override; virtual int surface_get_array_len(int p_idx) const override; @@ -98,6 +105,12 @@ public: void set_flip_faces(bool p_enable); bool get_flip_faces() const; + void set_add_uv2(bool p_enable); + bool get_add_uv2() const { return add_uv2; } + + void set_uv2_padding(float p_padding); + float get_uv2_padding() const { return uv2_padding; } + PrimitiveMesh(); ~PrimitiveMesh(); }; @@ -118,8 +131,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 8); + static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 8, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_radius(const float p_radius); float get_radius() const; @@ -152,8 +167,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w = 0, int subdivide_h = 0, int subdivide_d = 0); + static void create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w = 0, int subdivide_h = 0, int subdivide_d = 0, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_size(const Vector3 &p_size); Vector3 get_size() const; @@ -190,8 +207,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true); + static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_top_radius(const float p_radius); float get_top_radius() const; @@ -241,6 +260,8 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: void set_size(const Size2 &p_size); Size2 get_size() const; @@ -262,6 +283,19 @@ public: VARIANT_ENUM_CAST(PlaneMesh::Orientation) +/* + A flat rectangle, inherits from PlaneMesh but defaults to facing the Z-plane. +*/ +class QuadMesh : public PlaneMesh { + GDCLASS(QuadMesh, PlaneMesh); + +public: + QuadMesh() { + set_orientation(FACE_Z); + set_size(Size2(1, 1)); + } +}; + /** A prism shapen, handy for ramps, triangles, etc. */ @@ -279,6 +313,8 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: void set_left_to_right(const float p_left_to_right); float get_left_to_right() const; @@ -315,8 +351,10 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: - static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false); + static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); void set_radius(const float p_radius); float get_radius() const; @@ -352,6 +390,8 @@ protected: static void _bind_methods(); virtual void _create_mesh_array(Array &p_arr) const override; + virtual void _update_lightmap_size() override; + public: void set_inner_radius(const float p_inner_radius); float get_inner_radius() const; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 0d798d2e27..98a4909599 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -445,7 +445,14 @@ Error ResourceLoaderText::load() { // If a UID is found and the path is valid, it will be used, otherwise, it falls back to the path. path = ResourceUID::get_singleton()->get_id_path(uid); } else { +#ifdef TOOLS_ENABLED + // Silence a warning that can happen during the initial filesystem scan due to cache being regenerated. + if (ResourceLoader::get_resource_uid(path) != uid) { + WARN_PRINT(String(res_path + ":" + itos(lines) + " - ext_resource, invalid UUID: " + uidt + " - using text path instead: " + path).utf8().get_data()); + } +#else WARN_PRINT(String(res_path + ":" + itos(lines) + " - ext_resource, invalid UUID: " + uidt + " - using text path instead: " + path).utf8().get_data()); +#endif } } @@ -504,6 +511,7 @@ Error ResourceLoaderText::load() { if (error) { _printerr(); + return error; } resource_current++; @@ -877,6 +885,7 @@ void ResourceLoaderText::get_dependencies(Ref<FileAccess> p_f, List<String> *p_d error_text = "Unexpected end of file"; _printerr(); error = ERR_FILE_CORRUPT; + return; } } } @@ -1114,10 +1123,10 @@ Error ResourceLoaderText::save_as_binary(const String &p_path) { //go with external resources DummyReadData dummy_read; - VariantParser::ResourceParser rp; - rp.ext_func = _parse_ext_resource_dummys; - rp.sub_func = _parse_sub_resource_dummys; - rp.userdata = &dummy_read; + VariantParser::ResourceParser rp_new; + rp_new.ext_func = _parse_ext_resource_dummys; + rp_new.sub_func = _parse_sub_resource_dummys; + rp_new.userdata = &dummy_read; while (next_tag.name == "ext_resource") { if (!next_tag.fields.has("path")) { @@ -1161,7 +1170,7 @@ Error ResourceLoaderText::save_as_binary(const String &p_path) { dummy_read.external_resources[dr] = lindex; dummy_read.rev_external_resources[id] = dr; - error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); + error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp_new); if (error) { _printerr(); @@ -1244,7 +1253,7 @@ Error ResourceLoaderText::save_as_binary(const String &p_path) { String assign; Variant value; - error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp); + error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp_new); if (error) { if (main_res && error == ERR_FILE_EOF) { @@ -1288,7 +1297,7 @@ Error ResourceLoaderText::save_as_binary(const String &p_path) { return error; } - Ref<PackedScene> packed_scene = _parse_node_tag(rp); + Ref<PackedScene> packed_scene = _parse_node_tag(rp_new); if (!packed_scene.is_valid()) { return error; @@ -1363,13 +1372,13 @@ Error ResourceLoaderText::get_classes_used(HashSet<StringName> *r_classes) { DummyReadData dummy_read; dummy_read.no_placeholders = true; - VariantParser::ResourceParser rp; - rp.ext_func = _parse_ext_resource_dummys; - rp.sub_func = _parse_sub_resource_dummys; - rp.userdata = &dummy_read; + VariantParser::ResourceParser rp_new; + rp_new.ext_func = _parse_ext_resource_dummys; + rp_new.sub_func = _parse_sub_resource_dummys; + rp_new.userdata = &dummy_read; while (next_tag.name == "ext_resource") { - error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); + error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp_new); if (error) { _printerr(); @@ -1396,7 +1405,7 @@ Error ResourceLoaderText::get_classes_used(HashSet<StringName> *r_classes) { String assign; Variant value; - error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp); + error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp_new); if (error) { if (error == ERR_FILE_EOF) { @@ -1444,7 +1453,7 @@ Error ResourceLoaderText::get_classes_used(HashSet<StringName> *r_classes) { String assign; Variant value; - error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp); + error = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, &rp_new); if (error) { if (error == ERR_FILE_MISSING_DEPENDENCIES) { diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 4d566178a5..3a671edeea 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -177,7 +177,7 @@ bool Shader::is_text_shader() const { } bool Shader::has_parameter(const StringName &p_name) const { - return params_cache.has("shader_parameter/" + p_name); + return params_cache.has(p_name); } void Shader::_update_shader() const { diff --git a/scene/resources/shape_2d.cpp b/scene/resources/shape_2d.cpp index fe43f345d4..af84144591 100644 --- a/scene/resources/shape_2d.cpp +++ b/scene/resources/shape_2d.cpp @@ -105,6 +105,7 @@ void Shape2D::_bind_methods() { ClassDB::bind_method(D_METHOD("collide_and_get_contacts", "local_xform", "with_shape", "shape_xform"), &Shape2D::collide_and_get_contacts); ClassDB::bind_method(D_METHOD("collide_with_motion_and_get_contacts", "local_xform", "local_motion", "with_shape", "shape_xform", "shape_motion"), &Shape2D::collide_with_motion_and_get_contacts); ClassDB::bind_method(D_METHOD("draw", "canvas_item", "color"), &Shape2D::draw); + ClassDB::bind_method(D_METHOD("get_rect"), &Shape2D::get_rect); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias"); } @@ -115,7 +116,7 @@ bool Shape2D::is_collision_outline_enabled() { return true; } #endif - return GLOBAL_DEF("debug/shapes/collision/draw_2d_outlines", true); + return GLOBAL_GET("debug/shapes/collision/draw_2d_outlines"); } Shape2D::Shape2D(const RID &p_rid) { diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp index 885cf0f1b8..1b7354853d 100644 --- a/scene/resources/skeleton_modification_2d.cpp +++ b/scene/resources/skeleton_modification_2d.cpp @@ -127,7 +127,7 @@ void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_b Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); + bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color"); } #endif // TOOLS_ENABLED @@ -230,7 +230,7 @@ void SkeletonModification2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_editor_draw_gizmo"), &SkeletonModification2D::get_editor_draw_gizmo); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode"); } SkeletonModification2D::SkeletonModification2D() { diff --git a/scene/resources/skeleton_modification_2d_fabrik.h b/scene/resources/skeleton_modification_2d_fabrik.h index 4a875d039f..0ca6582965 100644 --- a/scene/resources/skeleton_modification_2d_fabrik.h +++ b/scene/resources/skeleton_modification_2d_fabrik.h @@ -68,8 +68,8 @@ private: float chain_tolarance = 0.01; int chain_max_iterations = 10; int chain_iterations = 0; - Transform2D target_global_pose = Transform2D(); - Transform2D origin_global_pose = Transform2D(); + Transform2D target_global_pose; + Transform2D origin_global_pose; void fabrik_joint_update_bone2d_cache(int p_joint_idx); void chain_backwards(); diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp index d3c62e441f..6593f476f5 100644 --- a/scene/resources/skeleton_modification_2d_twoboneik.cpp +++ b/scene/resources/skeleton_modification_2d_twoboneik.cpp @@ -212,7 +212,7 @@ void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); + bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color"); } #endif // TOOLS_ENABLED diff --git a/scene/resources/skeleton_modification_3d.cpp b/scene/resources/skeleton_modification_3d.cpp index 2c0f6e779e..fa487cb061 100644 --- a/scene/resources/skeleton_modification_3d.cpp +++ b/scene/resources/skeleton_modification_3d.cpp @@ -142,7 +142,7 @@ void SkeletonModification3D::_bind_methods() { ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification3D::clamp_angle); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode"); } SkeletonModification3D::SkeletonModification3D() { diff --git a/scene/resources/skeleton_modification_3d_ccdik.cpp b/scene/resources/skeleton_modification_3d_ccdik.cpp index 3251ee4189..82dc30ec5f 100644 --- a/scene/resources/skeleton_modification_3d_ccdik.cpp +++ b/scene/resources/skeleton_modification_3d_ccdik.cpp @@ -98,7 +98,7 @@ void SkeletonModification3DCCDIK::_get_property_list(List<PropertyInfo> *p_list) p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::INT, base_string + "ccdik_axis", - PROPERTY_HINT_ENUM, "X Axis, Y Axis, Z Axis", PROPERTY_USAGE_DEFAULT)); + PROPERTY_HINT_ENUM, "X Axis,Y Axis,Z Axis", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_joint_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (ccdik_data_chain[i].enable_constraint) { diff --git a/scene/resources/skeleton_modification_3d_fabrik.h b/scene/resources/skeleton_modification_3d_fabrik.h index e2e490d636..3e3aa5e587 100644 --- a/scene/resources/skeleton_modification_3d_fabrik.h +++ b/scene/resources/skeleton_modification_3d_fabrik.h @@ -47,7 +47,7 @@ private: bool auto_calculate_length = true; bool use_tip_node = false; - NodePath tip_node = NodePath(); + NodePath tip_node; ObjectID tip_node_cache; bool use_target_basis = false; @@ -68,8 +68,8 @@ private: void update_joint_tip_cache(int p_joint_idx); int final_joint_idx = 0; - Transform3D target_global_pose = Transform3D(); - Transform3D origin_global_pose = Transform3D(); + Transform3D target_global_pose; + Transform3D origin_global_pose; void chain_backwards(); void chain_forwards(); diff --git a/scene/resources/skeleton_modification_3d_lookat.cpp b/scene/resources/skeleton_modification_3d_lookat.cpp index 69167cb308..8ada7d0a5b 100644 --- a/scene/resources/skeleton_modification_3d_lookat.cpp +++ b/scene/resources/skeleton_modification_3d_lookat.cpp @@ -67,7 +67,7 @@ bool SkeletonModification3DLookAt::_get(const StringName &p_path, Variant &r_ret void SkeletonModification3DLookAt::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::BOOL, "lock_rotation_to_plane", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (lock_rotation_to_plane) { - p_list->push_back(PropertyInfo(Variant::INT, "lock_rotation_plane", PROPERTY_HINT_ENUM, "X plane, Y plane, Z plane", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::INT, "lock_rotation_plane", PROPERTY_HINT_ENUM, "X plane,Y plane,Z plane", PROPERTY_USAGE_DEFAULT)); } p_list->push_back(PropertyInfo(Variant::VECTOR3, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); } diff --git a/scene/resources/skin.cpp b/scene/resources/skin.cpp index 1c04ba0cd4..9d320e0b2c 100644 --- a/scene/resources/skin.cpp +++ b/scene/resources/skin.cpp @@ -86,13 +86,13 @@ void Skin::reset_state() { } bool Skin::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - if (name == "bind_count") { + String prop_name = p_name; + if (prop_name == "bind_count") { set_bind_count(p_value); return true; - } else if (name.begins_with("bind/")) { - int index = name.get_slicec('/', 1).to_int(); - String what = name.get_slicec('/', 2); + } else if (prop_name.begins_with("bind/")) { + int index = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); if (what == "bone") { set_bind_bone(index, p_value); return true; @@ -108,13 +108,13 @@ bool Skin::_set(const StringName &p_name, const Variant &p_value) { } bool Skin::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - if (name == "bind_count") { + String prop_name = p_name; + if (prop_name == "bind_count") { r_ret = get_bind_count(); return true; - } else if (name.begins_with("bind/")) { - int index = name.get_slicec('/', 1).to_int(); - String what = name.get_slicec('/', 2); + } else if (prop_name.begins_with("bind/")) { + int index = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); if (what == "bone") { r_ret = get_bind_bone(index); return true; diff --git a/scene/resources/sky_material.h b/scene/resources/sky_material.h index fbb202d8d8..3de1a4b26f 100644 --- a/scene/resources/sky_material.h +++ b/scene/resources/sky_material.h @@ -28,12 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/templates/rid.h" -#include "scene/resources/material.h" - #ifndef SKY_MATERIAL_H #define SKY_MATERIAL_H +#include "core/templates/rid.h" +#include "scene/resources/material.h" + class ProceduralSkyMaterial : public Material { GDCLASS(ProceduralSkyMaterial, Material); diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index 3533e86c3a..838566f696 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -143,10 +143,10 @@ Array SpriteFrames::_get_animations() const { get_animation_list(&sorted_names); sorted_names.sort_custom<StringName::AlphCompare>(); - for (const StringName &name : sorted_names) { - const Anim &anim = animations[name]; + for (const StringName &anim_name : sorted_names) { + const Anim &anim = animations[anim_name]; Dictionary d; - d["name"] = name; + d["name"] = anim_name; d["speed"] = anim.speed; d["loop"] = anim.loop; Array frames; diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index cd893d8c23..f379f88bed 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -43,12 +43,9 @@ float StyleBox::get_style_margin(Side p_side) const { } bool StyleBox::test_mask(const Point2 &p_point, const Rect2 &p_rect) const { - bool ret; - if (GDVIRTUAL_CALL(_test_mask, p_point, p_rect, ret)) { - return ret; - } - - return true; + bool ret = true; + GDVIRTUAL_CALL(_test_mask, p_point, p_rect, ret); + return ret; } void StyleBox::draw(RID p_canvas_item, const Rect2 &p_rect) const { @@ -109,11 +106,8 @@ Point2 StyleBox::get_offset() const { Size2 StyleBox::get_center_size() const { Size2 ret; - if (GDVIRTUAL_CALL(_get_center_size, ret)) { - return ret; - } - - return Size2(); + GDVIRTUAL_CALL(_get_center_size, ret); + return ret; } Rect2 StyleBox::get_draw_rect(const Rect2 &p_rect) const { diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 9829c7e86b..94967352c8 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -986,7 +986,7 @@ void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const format = 0; } - uint32_t nformat; + uint32_t nformat = 0; LocalVector<Vertex> nvertices; LocalVector<int> nindices; _create_list(p_existing, p_surface, &nvertices, &nindices, nformat); diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index f1eddd8ffc..cb5cb4ef96 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -336,7 +336,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) { } String word = str.substr(j, to - j); - Color col = Color(); + Color col; if (keywords.has(word)) { col = keywords[word]; } else if (member_keywords.has(word)) { diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index 823d742d72..afab44834d 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -218,7 +218,48 @@ Array TextLine::get_objects() const { } Rect2 TextLine::get_object_rect(Variant p_key) const { - return TS->shaped_text_get_object_rect(rid, p_key); + Vector2 ofs; + + float length = TS->shaped_text_get_width(rid); + if (width > 0) { + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: + break; + case HORIZONTAL_ALIGNMENT_CENTER: { + if (length <= width) { + if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += Math::floor((width - length) / 2.0); + } else { + ofs.y += Math::floor((width - length) / 2.0); + } + } else if (TS->shaped_text_get_inferred_direction(rid) == TextServer::DIRECTION_RTL) { + if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += width - length; + } else { + ofs.y += width - length; + } + } + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += width - length; + } else { + ofs.y += width - length; + } + } break; + } + } + if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.y += TS->shaped_text_get_ascent(rid); + } else { + ofs.x += TS->shaped_text_get_ascent(rid); + } + + Rect2 rect = TS->shaped_text_get_object_rect(rid, p_key); + rect.position += ofs; + + return rect; } void TextLine::set_horizontal_alignment(HorizontalAlignment p_alignment) { @@ -276,11 +317,7 @@ float TextLine::get_width() const { Size2 TextLine::get_size() const { const_cast<TextLine *>(this)->_shape(); - if (TS->shaped_text_get_orientation(rid) == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y); - } else { - return Size2(TS->shaped_text_get_size(rid).x, TS->shaped_text_get_size(rid).y); - } + return TS->shaped_text_get_size(rid); } float TextLine::get_line_ascent() const { diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 7e9a2591e4..59bb24c8b8 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -540,16 +540,90 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { const_cast<TextParagraph *>(this)->_shape_lines(); ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)lines_rid.size(), Rect2()); - Rect2 xrect = TS->shaped_text_get_object_rect(lines_rid[p_line], p_key); - for (int i = 0; i < p_line; i++) { - Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); + + Vector2 ofs; + + float h_offset = 0.f; + if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { + h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; + } else { + h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y; + } + + for (int i = 0; i <= p_line; i++) { + float l_width = width; if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { - xrect.position.y += lsize.y; + ofs.x = 0.f; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]); + if (i <= dropcap_lines) { + if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { + ofs.x -= h_offset; + } + l_width -= h_offset; + } } else { - xrect.position.x += lsize.x; + ofs.y = 0.f; + ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); + if (i <= dropcap_lines) { + if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { + ofs.x -= h_offset; + } + l_width -= h_offset; + } + } + float length = TS->shaped_text_get_width(lines_rid[i]); + if (width > 0) { + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - length; + } else { + ofs.y += l_width - length; + } + } + break; + case HORIZONTAL_ALIGNMENT_LEFT: + break; + case HORIZONTAL_ALIGNMENT_CENTER: { + if (length <= l_width) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += Math::floor((l_width - length) / 2.0); + } else { + ofs.y += Math::floor((l_width - length) / 2.0); + } + } else if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - length; + } else { + ofs.y += l_width - length; + } + } + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - length; + } else { + ofs.y += l_width - length; + } + } break; + } + } + if (i != p_line) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x = 0.f; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]); + } else { + ofs.y = 0.f; + ofs.x += TS->shaped_text_get_descent(lines_rid[i]); + } } } - return xrect; + + Rect2 rect = TS->shaped_text_get_object_rect(lines_rid[p_line], p_key); + rect.position += ofs; + + return rect; } Size2 TextParagraph::get_line_size(int p_line) const { diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index d53dc1a8fc..b5754caa6a 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -38,6 +38,7 @@ #include "scene/resources/bit_map.h" #include "scene/resources/mesh.h" #include "servers/camera/camera_feed.h" + int Texture2D::get_width() const { int ret; if (GDVIRTUAL_REQUIRED_CALL(_get_width, ret)) { @@ -59,20 +60,15 @@ Size2 Texture2D::get_size() const { } bool Texture2D::is_pixel_opaque(int p_x, int p_y) const { - bool ret; - if (GDVIRTUAL_CALL(_is_pixel_opaque, p_x, p_y, ret)) { - return ret; - } - - return true; + bool ret = true; + GDVIRTUAL_CALL(_is_pixel_opaque, p_x, p_y, ret); + return ret; } -bool Texture2D::has_alpha() const { - bool ret; - if (GDVIRTUAL_CALL(_has_alpha, ret)) { - return ret; - } - return true; +bool Texture2D::has_alpha() const { + bool ret = true; + GDVIRTUAL_CALL(_has_alpha, ret); + return ret; } void Texture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { @@ -121,7 +117,7 @@ void Texture2D::_bind_methods() { GDVIRTUAL_BIND(_draw, "to_canvas_item", "pos", "modulate", "transpose") GDVIRTUAL_BIND(_draw_rect, "to_canvas_item", "rect", "tile", "modulate", "transpose") - GDVIRTUAL_BIND(_draw_rect_region, "tp_canvas_item", "rect", "src_rect", "modulate", "transpose", "clip_uv"); + GDVIRTUAL_BIND(_draw_rect_region, "to_canvas_item", "rect", "src_rect", "modulate", "transpose", "clip_uv"); } Texture2D::Texture2D() { @@ -652,7 +648,7 @@ Ref<Image> CompressedTexture2D::load_image_from_file(Ref<FileAccess> f, int p_si Image::Format format = Image::Format(f->get_32()); if (data_format == DATA_FORMAT_PNG || data_format == DATA_FORMAT_WEBP || data_format == DATA_FORMAT_BASIS_UNIVERSAL) { - //look for a PNG or WEBP file inside + //look for a PNG or WebP file inside int sw = w; int sh = h; @@ -739,7 +735,7 @@ Ref<Image> CompressedTexture2D::load_image_from_file(Ref<FileAccess> f, int p_si } } - image->create(w, h, true, mipmap_images[0]->get_format(), img_data); + image->set_data(w, h, true, mipmap_images[0]->get_format(), img_data); return image; } @@ -765,10 +761,7 @@ Ref<Image> CompressedTexture2D::load_image_from_file(Ref<FileAccess> f, int p_si f->get_buffer(wr, data.size()); } - Ref<Image> image; - image.instantiate(); - - image->create(tw, th, mipmaps - i ? true : false, format, data); + Ref<Image> image = Image::create_from_data(tw, th, mipmaps - i ? true : false, format, data); return image; } @@ -1208,6 +1201,8 @@ Error ImageTexture3D::create(Image::Format p_format, int p_width, int p_height, if (texture.is_valid()) { RenderingServer::get_singleton()->texture_replace(texture, tex); + } else { + texture = tex; } return OK; @@ -1285,15 +1280,15 @@ Error CompressedTexture3D::_load_data(const String &p_path, Vector<Ref<Image>> & f->get_32(); // ignored (data format) f->get_32(); //ignored - int mipmaps = f->get_32(); + int mipmap_count = f->get_32(); f->get_32(); //ignored f->get_32(); //ignored - r_mipmaps = mipmaps != 0; + r_mipmaps = mipmap_count != 0; r_data.clear(); - for (int i = 0; i < (r_depth + mipmaps); i++) { + for (int i = 0; i < (r_depth + mipmap_count); i++) { Ref<Image> image = CompressedTexture2D::load_image_from_file(f, 0); ERR_FAIL_COND_V(image.is_null() || image->is_empty(), ERR_CANT_OPEN); if (i == 0) { @@ -1488,7 +1483,15 @@ void AtlasTexture::set_atlas(const Ref<Texture2D> &p_atlas) { if (atlas == p_atlas) { return; } + // Support recursive AtlasTextures. + if (Ref<AtlasTexture>(atlas).is_valid()) { + atlas->disconnect(CoreStringNames::get_singleton()->changed, callable_mp((Resource *)this, &AtlasTexture::emit_changed)); + } atlas = p_atlas; + if (Ref<AtlasTexture>(atlas).is_valid()) { + atlas->connect(CoreStringNames::get_singleton()->changed, callable_mp((Resource *)this, &AtlasTexture::emit_changed)); + } + emit_changed(); } @@ -1661,7 +1664,7 @@ Ref<Image> AtlasTexture::get_image() const { return Ref<Image>(); } - return atlas->get_image()->get_rect(region); + return atlas->get_image()->get_region(region); } AtlasTexture::AtlasTexture() {} @@ -2325,11 +2328,11 @@ void GradientTexture2D::_update() { image.instantiate(); if (gradient->get_points_count() <= 1) { // No need to interpolate. - image->create(width, height, false, (use_hdr) ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8); + image->initialize_data(width, height, false, (use_hdr) ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8); image->fill((gradient->get_points_count() == 1) ? gradient->get_color(0) : Color(0, 0, 0, 1)); } else { if (use_hdr) { - image->create(width, height, false, Image::FORMAT_RGBAF); + image->initialize_data(width, height, false, Image::FORMAT_RGBAF); Gradient &g = **gradient; // `create()` isn't available for non-uint8_t data, so fill in the data manually. for (int y = 0; y < height; y++) { @@ -2356,7 +2359,7 @@ void GradientTexture2D::_update() { } } } - image->create(width, height, false, Image::FORMAT_RGBA8, data); + image->set_data(width, height, false, Image::FORMAT_RGBA8, data); } } @@ -3105,7 +3108,7 @@ Error CompressedTextureLayered::_load_data(const String &p_path, Vector<Ref<Imag uint32_t layer_count = f->get_32(); //layer count uint32_t type = f->get_32(); //layer count - ERR_FAIL_COND_V(type != layered_type, ERR_INVALID_DATA); + ERR_FAIL_COND_V((int)type != layered_type, ERR_INVALID_DATA); uint32_t df = f->get_32(); //data format mipmap_limit = int(f->get_32()); diff --git a/scene/resources/texture.h b/scene/resources/texture.h index da4b8046a5..9a9f0ad1af 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -238,7 +238,7 @@ private: Error _load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit = 0); String path_to_file; mutable RID texture; - Image::Format format = Image::FORMAT_MAX; + Image::Format format = Image::FORMAT_L8; int w = 0; int h = 0; mutable Ref<BitMap> alpha_cache; @@ -415,7 +415,7 @@ class ImageTextureLayered : public TextureLayered { LayeredType layered_type; mutable RID texture; - Image::Format format = Image::FORMAT_MAX; + Image::Format format = Image::FORMAT_L8; int width = 0; int height = 0; @@ -495,7 +495,7 @@ private: Error _load_data(const String &p_path, Vector<Ref<Image>> &images, int &mipmap_limit, int p_size_limit = 0); String path_to_file; mutable RID texture; - Image::Format format = Image::FORMAT_MAX; + Image::Format format = Image::FORMAT_L8; int w = 0; int h = 0; int layers = 0; @@ -587,7 +587,7 @@ class ImageTexture3D : public Texture3D { mutable RID texture; - Image::Format format = Image::FORMAT_MAX; + Image::Format format = Image::FORMAT_L8; int width = 1; int height = 1; int depth = 1; @@ -641,7 +641,7 @@ private: Error _load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps); String path_to_file; mutable RID texture; - Image::Format format = Image::FORMAT_MAX; + Image::Format format = Image::FORMAT_L8; int w = 0; int h = 0; int d = 0; @@ -769,15 +769,6 @@ public: class GradientTexture1D : public Texture2D { GDCLASS(GradientTexture1D, Texture2D); -public: - struct Point { - float offset = 0.0; - Color color; - bool operator<(const Point &p_ponit) const { - return offset < p_ponit.offset; - } - }; - private: Ref<Gradient> gradient; bool update_pending = false; diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index 3321392821..ed0d5ee688 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -40,20 +40,20 @@ bool Theme::_set(const StringName &p_name, const Variant &p_value) { if (sname.contains("/")) { String type = sname.get_slicec('/', 1); String theme_type = sname.get_slicec('/', 0); - String name = sname.get_slicec('/', 2); + String prop_name = sname.get_slicec('/', 2); if (type == "icons") { - set_icon(name, theme_type, p_value); + set_icon(prop_name, theme_type, p_value); } else if (type == "styles") { - set_stylebox(name, theme_type, p_value); + set_stylebox(prop_name, theme_type, p_value); } else if (type == "fonts") { - set_font(name, theme_type, p_value); + set_font(prop_name, theme_type, p_value); } else if (type == "font_sizes") { - set_font_size(name, theme_type, p_value); + set_font_size(prop_name, theme_type, p_value); } else if (type == "colors") { - set_color(name, theme_type, p_value); + set_color(prop_name, theme_type, p_value); } else if (type == "constants") { - set_constant(name, theme_type, p_value); + set_constant(prop_name, theme_type, p_value); } else if (type == "base_type") { set_type_variation(theme_type, p_value); } else { @@ -72,32 +72,32 @@ bool Theme::_get(const StringName &p_name, Variant &r_ret) const { if (sname.contains("/")) { String type = sname.get_slicec('/', 1); String theme_type = sname.get_slicec('/', 0); - String name = sname.get_slicec('/', 2); + String prop_name = sname.get_slicec('/', 2); if (type == "icons") { - if (!has_icon(name, theme_type)) { + if (!has_icon(prop_name, theme_type)) { r_ret = Ref<Texture2D>(); } else { - r_ret = get_icon(name, theme_type); + r_ret = get_icon(prop_name, theme_type); } } else if (type == "styles") { - if (!has_stylebox(name, theme_type)) { + if (!has_stylebox(prop_name, theme_type)) { r_ret = Ref<StyleBox>(); } else { - r_ret = get_stylebox(name, theme_type); + r_ret = get_stylebox(prop_name, theme_type); } } else if (type == "fonts") { - if (!has_font(name, theme_type)) { + if (!has_font(prop_name, theme_type)) { r_ret = Ref<Font>(); } else { - r_ret = get_font(name, theme_type); + r_ret = get_font(prop_name, theme_type); } } else if (type == "font_sizes") { - r_ret = get_font_size(name, theme_type); + r_ret = get_font_size(prop_name, theme_type); } else if (type == "colors") { - r_ret = get_color(name, theme_type); + r_ret = get_color(prop_name, theme_type); } else if (type == "constants") { - r_ret = get_constant(name, theme_type); + r_ret = get_constant(prop_name, theme_type); } else if (type == "base_type") { r_ret = get_type_variation_base(theme_type); } else { diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 9138a82ba8..d4ad81614d 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -1537,7 +1537,6 @@ Vector<Point2> TileSet::get_terrain_polygon(int p_terrain_set) { } return _get_half_offset_terrain_polygon(tile_size, overlap, tile_offset_axis); } - return Vector<Point2>(); } Vector<Point2> TileSet::get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit) { @@ -1802,11 +1801,11 @@ Vector<Vector<Ref<Texture2D>>> TileSet::generate_terrains_icons(Size2i p_size) { // Get the best tile. Ref<Texture2D> texture = counts[terrain_set][terrain].texture; Rect2i region = counts[terrain_set][terrain].region; - image->create(region.size.x, region.size.y, false, Image::FORMAT_RGBA8); + image->initialize_data(region.size.x, region.size.y, false, Image::FORMAT_RGBA8); image->blit_rect(texture->get_image(), region, Point2i()); image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST); } else { - image->create(1, 1, false, Image::FORMAT_RGBA8); + image->initialize_data(1, 1, false, Image::FORMAT_RGBA8); image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain)); } Ref<ImageTexture> icon = ImageTexture::create_from_image(image); @@ -3780,17 +3779,17 @@ bool TileSetAtlasSource::get_use_texture_padding() const { } Vector2i TileSetAtlasSource::get_atlas_grid_size() const { - Ref<Texture2D> texture = get_texture(); - if (!texture.is_valid()) { + Ref<Texture2D> txt = get_texture(); + if (!txt.is_valid()) { return Vector2i(); } ERR_FAIL_COND_V(texture_region_size.x <= 0 || texture_region_size.y <= 0, Vector2i()); - Size2i valid_area = texture->get_size() - margins; + Size2i valid_area = txt->get_size() - margins; // Compute the number of valid tiles in the tiles atlas - Size2i grid_size = Size2i(); + Size2i grid_size; if (valid_area.x >= texture_region_size.x && valid_area.y >= texture_region_size.y) { valid_area -= texture_region_size; grid_size = Size2i(1, 1) + valid_area / (texture_region_size + separation); @@ -4603,9 +4602,7 @@ void TileSetAtlasSource::_update_padded_texture() { return; } - Ref<Image> image; - image.instantiate(); - image->create(size.x, size.y, false, src->get_format()); + Ref<Image> image = Image::create_empty(size.x, size.y, false, src->get_format()); for (KeyValue<Vector2i, TileAlternativesData> kv : tiles) { for (int frame = 0; frame < (int)kv.value.animation_frames_durations.size(); frame++) { @@ -5018,7 +5015,7 @@ void TileData::add_custom_data_layer(int p_to_pos) { void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) { ERR_FAIL_INDEX(p_from_index, custom_data.size()); ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); - custom_data.insert(p_to_pos, navigation[p_from_index]); + custom_data.insert(p_to_pos, custom_data[p_from_index]); custom_data.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); } @@ -5276,6 +5273,7 @@ void TileData::set_terrain_set(int p_terrain_set) { } if (tile_set) { ERR_FAIL_COND(p_terrain_set >= tile_set->get_terrain_sets_count()); + terrain = -1; for (int i = 0; i < 16; i++) { terrain_peering_bits[i] = -1; } diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index e156679711..9f465a17e9 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -277,6 +277,9 @@ public: bool operator<(const TerrainsPattern &p_terrains_pattern) const; bool operator==(const TerrainsPattern &p_terrains_pattern) const; + bool operator!=(const TerrainsPattern &p_terrains_pattern) const { + return !operator==(p_terrains_pattern); + }; void set_terrain(int p_terrain); int get_terrain() const; @@ -782,7 +785,7 @@ private: bool flip_h = false; bool flip_v = false; bool transpose = false; - Vector2i tex_offset = Vector2i(); + Vector2i tex_offset; Ref<Material> material = Ref<Material>(); Color modulate = Color(1.0, 1.0, 1.0, 1.0); int z_index = 0; diff --git a/scene/resources/video_stream.h b/scene/resources/video_stream.h index 35686b293c..e14081c681 100644 --- a/scene/resources/video_stream.h +++ b/scene/resources/video_stream.h @@ -50,16 +50,16 @@ public: virtual void set_loop(bool p_enable) = 0; virtual bool has_loop() const = 0; - virtual float get_length() const = 0; + virtual double get_length() const = 0; - virtual float get_playback_position() const = 0; - virtual void seek(float p_time) = 0; + virtual double get_playback_position() const = 0; + virtual void seek(double p_time) = 0; virtual void set_audio_track(int p_idx) = 0; virtual Ref<Texture2D> get_texture() const = 0; - virtual void update(float p_delta) = 0; + virtual void update(double p_delta) = 0; virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata) = 0; virtual int get_channels() const = 0; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 262dbe28ed..b30ca3e721 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -456,11 +456,9 @@ void VisualShaderNodeCustom::update_ports() { } String VisualShaderNodeCustom::get_caption() const { - String ret; - if (GDVIRTUAL_CALL(_get_name, ret)) { - return ret; - } - return "Unnamed"; + String ret = "Unnamed"; + GDVIRTUAL_CALL(_get_name, ret); + return ret; } int VisualShaderNodeCustom::get_input_port_count() const { @@ -559,11 +557,9 @@ String VisualShaderNodeCustom::generate_global_per_func(Shader::Mode p_mode, Vis } bool VisualShaderNodeCustom::is_available(Shader::Mode p_mode, VisualShader::Type p_type) const { - bool ret; - if (GDVIRTUAL_CALL(_is_available, p_mode, p_type, ret)) { - return ret; - } - return true; + bool ret = true; + GDVIRTUAL_CALL(_is_available, p_mode, p_type, ret); + return ret; } void VisualShaderNodeCustom::set_input_port_default_value(int p_port, const Variant &p_value, const Variant &p_prev_value) { @@ -955,7 +951,7 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po } bool VisualShader::is_port_types_compatible(int p_a, int p_b) const { - return MAX(0, p_a - 4) == (MAX(0, p_b - 4)); + return MAX(0, p_a - 5) == (MAX(0, p_b - 5)); } void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { @@ -1148,7 +1144,7 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port StringBuilder global_code; StringBuilder global_code_per_node; HashMap<Type, StringBuilder> global_code_per_func; - StringBuilder code; + StringBuilder shader_code; HashSet<StringName> classes; global_code += String() + "shader_type canvas_item;\n"; @@ -1189,69 +1185,69 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port input_connections.insert(to_key, E); } - code += "\nvoid fragment() {\n"; + shader_code += "\nvoid fragment() {\n"; HashSet<int> processed; - Error err = _write_node(p_type, &global_code, &global_code_per_node, &global_code_per_func, code, default_tex_params, input_connections, output_connections, p_node, processed, true, classes); + Error err = _write_node(p_type, &global_code, &global_code_per_node, &global_code_per_func, shader_code, default_tex_params, input_connections, output_connections, p_node, processed, true, classes); ERR_FAIL_COND_V(err != OK, String()); switch (node->get_output_port_type(p_port)) { case VisualShaderNode::PORT_TYPE_SCALAR: { - code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ");\n"; + shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ");\n"; } break; case VisualShaderNode::PORT_TYPE_SCALAR_INT: { - code += " COLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; + shader_code += " COLOR.rgb = vec3(float(n_out" + itos(p_node) + "p" + itos(p_port) + "));\n"; } break; case VisualShaderNode::PORT_TYPE_BOOLEAN: { - code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " ? 1.0 : 0.0);\n"; + shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + " ? 1.0 : 0.0);\n"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ", 0.0);\n"; + shader_code += " COLOR.rgb = vec3(n_out" + itos(p_node) + "p" + itos(p_port) + ", 0.0);\n"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; + shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ".xyz;\n"; + shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ".xyz;\n"; } break; default: { - code += " COLOR.rgb = vec3(0.0);\n"; + shader_code += " COLOR.rgb = vec3(0.0);\n"; } break; } - code += "}\n"; + shader_code += "}\n"; //set code secretly global_code += "\n\n"; String final_code = global_code; final_code += global_code_per_node; - final_code += code; + final_code += shader_code; return final_code; } String VisualShader::validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const { - String name = p_port_name; + String port_name = p_port_name; - if (name.is_empty()) { + if (port_name.is_empty()) { return String(); } - while (name.length() && !is_ascii_char(name[0])) { - name = name.substr(1, name.length() - 1); + while (port_name.length() && !is_ascii_char(port_name[0])) { + port_name = port_name.substr(1, port_name.length() - 1); } - if (!name.is_empty()) { + if (!port_name.is_empty()) { String valid_name; - for (int i = 0; i < name.length(); i++) { - if (is_ascii_identifier_char(name[i])) { - valid_name += String::chr(name[i]); - } else if (name[i] == ' ') { + for (int i = 0; i < port_name.length(); i++) { + if (is_ascii_identifier_char(port_name[i])) { + valid_name += String::chr(port_name[i]); + } else if (port_name[i] == ' ') { valid_name += "_"; } } - name = valid_name; + port_name = valid_name; } else { return String(); } @@ -1263,7 +1259,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN if (!p_output && i == p_port_id) { continue; } - if (name == p_node->get_input_port_name(i)) { + if (port_name == p_node->get_input_port_name(i)) { return String(); } } @@ -1271,35 +1267,35 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN if (p_output && i == p_port_id) { continue; } - if (name == p_node->get_output_port_name(i)) { + if (port_name == p_node->get_output_port_name(i)) { return String(); } } - return name; + return port_name; } String VisualShader::validate_parameter_name(const String &p_name, const Ref<VisualShaderNodeParameter> &p_parameter) const { - String name = p_name; //validate name first - while (name.length() && !is_ascii_char(name[0])) { - name = name.substr(1, name.length() - 1); + String param_name = p_name; //validate name first + while (param_name.length() && !is_ascii_char(param_name[0])) { + param_name = param_name.substr(1, param_name.length() - 1); } - if (!name.is_empty()) { + if (!param_name.is_empty()) { String valid_name; - for (int i = 0; i < name.length(); i++) { - if (is_ascii_identifier_char(name[i])) { - valid_name += String::chr(name[i]); - } else if (name[i] == ' ') { + for (int i = 0; i < param_name.length(); i++) { + if (is_ascii_identifier_char(param_name[i])) { + valid_name += String::chr(param_name[i]); + } else if (param_name[i] == ' ') { valid_name += "_"; } } - name = valid_name; + param_name = valid_name; } - if (name.is_empty()) { - name = p_parameter->get_caption(); + if (param_name.is_empty()) { + param_name = p_parameter->get_caption(); } int attempt = 1; @@ -1312,7 +1308,7 @@ String VisualShader::validate_parameter_name(const String &p_name, const Ref<Vis if (node == p_parameter) { //do not test on self continue; } - if (node.is_valid() && node->get_parameter_name() == name) { + if (node.is_valid() && node->get_parameter_name() == param_name) { exists = true; break; } @@ -1325,17 +1321,17 @@ String VisualShader::validate_parameter_name(const String &p_name, const Ref<Vis if (exists) { //remove numbers, put new and try again attempt++; - while (name.length() && is_digit(name[name.length() - 1])) { - name = name.substr(0, name.length() - 1); + while (param_name.length() && is_digit(param_name[param_name.length() - 1])) { + param_name = param_name.substr(0, param_name.length() - 1); } - ERR_FAIL_COND_V(name.is_empty(), String()); - name += itos(attempt); + ERR_FAIL_COND_V(param_name.is_empty(), String()); + param_name += itos(attempt); } else { break; } } - return name; + return param_name; } static const char *type_string[VisualShader::TYPE_MAX] = { @@ -1352,12 +1348,12 @@ static const char *type_string[VisualShader::TYPE_MAX] = { }; bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - if (name == "mode") { + String prop_name = p_name; + if (prop_name == "mode") { set_mode(Shader::Mode(int(p_value))); return true; - } else if (name.begins_with("flags/")) { - StringName flag = name.get_slicec('/', 1); + } else if (prop_name.begins_with("flags/")) { + StringName flag = prop_name.get_slicec('/', 1); bool enable = p_value; if (enable) { flags.insert(flag); @@ -1366,18 +1362,18 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { } _queue_update(); return true; - } else if (name.begins_with("modes/")) { - String mode = name.get_slicec('/', 1); + } else if (prop_name.begins_with("modes/")) { + String mode_name = prop_name.get_slicec('/', 1); int value = p_value; if (value == 0) { - modes.erase(mode); //means it's default anyway, so don't store it + modes.erase(mode_name); //means it's default anyway, so don't store it } else { - modes[mode] = value; + modes[mode_name] = value; } _queue_update(); return true; - } else if (name.begins_with("varyings/")) { - String var_name = name.get_slicec('/', 1); + } else if (prop_name.begins_with("varyings/")) { + String var_name = prop_name.get_slicec('/', 1); Varying value = Varying(); value.name = var_name; if (value.from_string(p_value) && !varyings.has(var_name)) { @@ -1386,8 +1382,8 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { } _queue_update(); return true; - } else if (name.begins_with("nodes/")) { - String typestr = name.get_slicec('/', 1); + } else if (prop_name.begins_with("nodes/")) { + String typestr = prop_name.get_slicec('/', 1); Type type = TYPE_VERTEX; for (int i = 0; i < TYPE_MAX; i++) { if (typestr == type_string[i]) { @@ -1396,7 +1392,7 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { } } - String index = name.get_slicec('/', 2); + String index = prop_name.get_slicec('/', 2); if (index == "connections") { Vector<int> conns = p_value; if (conns.size() % 4 == 0) { @@ -1408,7 +1404,7 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { } int id = index.to_int(); - String what = name.get_slicec('/', 3); + String what = prop_name.get_slicec('/', 3); if (what == "node") { add_node(type, p_value, Vector2(), id); @@ -1434,32 +1430,32 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { } bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - if (name == "mode") { + String prop_name = p_name; + if (prop_name == "mode") { r_ret = get_mode(); return true; - } else if (name.begins_with("flags/")) { - StringName flag = name.get_slicec('/', 1); + } else if (prop_name.begins_with("flags/")) { + StringName flag = prop_name.get_slicec('/', 1); r_ret = flags.has(flag); return true; - } else if (name.begins_with("modes/")) { - String mode = name.get_slicec('/', 1); - if (modes.has(mode)) { - r_ret = modes[mode]; + } else if (prop_name.begins_with("modes/")) { + String mode_name = prop_name.get_slicec('/', 1); + if (modes.has(mode_name)) { + r_ret = modes[mode_name]; } else { r_ret = 0; } return true; - } else if (name.begins_with("varyings/")) { - String var_name = name.get_slicec('/', 1); + } else if (prop_name.begins_with("varyings/")) { + String var_name = prop_name.get_slicec('/', 1); if (varyings.has(var_name)) { r_ret = varyings[var_name].to_string(); } else { r_ret = String(); } return true; - } else if (name.begins_with("nodes/")) { - String typestr = name.get_slicec('/', 1); + } else if (prop_name.begins_with("nodes/")) { + String typestr = prop_name.get_slicec('/', 1); Type type = TYPE_VERTEX; for (int i = 0; i < TYPE_MAX; i++) { if (typestr == type_string[i]) { @@ -1468,7 +1464,7 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { } } - String index = name.get_slicec('/', 2); + String index = prop_name.get_slicec('/', 2); if (index == "connections") { Vector<int> conns; for (const Connection &E : graph[type].connections) { @@ -1483,7 +1479,7 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { } int id = index.to_int(); - String what = name.get_slicec('/', 3); + String what = prop_name.get_slicec('/', 3); if (what == "node") { r_ret = get_node(type, id); @@ -1509,11 +1505,10 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { } void VisualShader::reset_state() { -#ifndef _MSC_VER -#warning everything needs to be cleared here -#endif + // TODO: Everything needs to be cleared here. emit_changed(); } + void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { //mode p_list->push_back(PropertyInfo(Variant::INT, PNAME("mode"), PROPERTY_HINT_ENUM, "Node3D,CanvasItem,Particles,Sky,Fog")); @@ -1580,12 +1575,12 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { } } -Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBuilder *global_code_per_node, HashMap<Type, StringBuilder> *global_code_per_func, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, HashSet<int> &processed, bool for_preview, HashSet<StringName> &r_classes) const { - const Ref<VisualShaderNode> vsnode = graph[type].nodes[node].node; +Error VisualShader::_write_node(Type type, StringBuilder *p_global_code, StringBuilder *p_global_code_per_node, HashMap<Type, StringBuilder> *p_global_code_per_func, StringBuilder &r_code, Vector<VisualShader::DefaultTextureParam> &r_def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &p_input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &p_output_connections, int p_node, HashSet<int> &r_processed, bool p_for_preview, HashSet<StringName> &r_classes) const { + const Ref<VisualShaderNode> vsnode = graph[type].nodes[p_node].node; if (vsnode->is_disabled()) { - code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n"; - code += " // Node is disabled and code is not generated.\n"; + r_code += "// " + vsnode->get_caption() + ":" + itos(p_node) + "\n"; + r_code += " // Node is disabled and code is not generated.\n"; return OK; } @@ -1593,16 +1588,16 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui int input_count = vsnode->get_input_port_count(); for (int i = 0; i < input_count; i++) { ConnectionKey ck; - ck.node = node; + ck.node = p_node; ck.port = i; - if (input_connections.has(ck)) { - int from_node = input_connections[ck]->get().from_node; - if (processed.has(from_node)) { + if (p_input_connections.has(ck)) { + int from_node = p_input_connections[ck]->get().from_node; + if (r_processed.has(from_node)) { continue; } - Error err = _write_node(type, global_code, global_code_per_node, global_code_per_func, code, def_tex_params, input_connections, output_connections, from_node, processed, for_preview, r_classes); + Error err = _write_node(type, p_global_code, p_global_code_per_node, p_global_code_per_func, r_code, r_def_tex_params, p_input_connections, p_output_connections, from_node, r_processed, p_for_preview, r_classes); if (err) { return err; } @@ -1611,19 +1606,19 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui // then this node - Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node); + Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, p_node); for (int i = 0; i < params.size(); i++) { - def_tex_params.push_back(params[i]); + r_def_tex_params.push_back(params[i]); } Ref<VisualShaderNodeInput> input = vsnode; - bool skip_global = input.is_valid() && for_preview; + bool skip_global = input.is_valid() && p_for_preview; if (!skip_global) { Ref<VisualShaderNodeParameter> parameter = vsnode; if (!parameter.is_valid() || !parameter->is_global_code_generated()) { - if (global_code) { - *global_code += vsnode->generate_global(get_mode(), type, node); + if (p_global_code) { + *p_global_code += vsnode->generate_global(get_mode(), type, p_node); } } @@ -1632,12 +1627,12 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui class_name = vsnode->get_script_instance()->get_script()->get_path(); } if (!r_classes.has(class_name)) { - if (global_code_per_node) { - *global_code_per_node += vsnode->generate_global_per_node(get_mode(), node); + if (p_global_code_per_node) { + *p_global_code_per_node += vsnode->generate_global_per_node(get_mode(), p_node); } for (int i = 0; i < TYPE_MAX; i++) { - if (global_code_per_func) { - (*global_code_per_func)[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node); + if (p_global_code_per_func) { + (*p_global_code_per_func)[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), p_node); } } r_classes.insert(class_name); @@ -1645,11 +1640,11 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui } if (!vsnode->is_code_generated()) { // just generate globals and ignore locals - processed.insert(node); + r_processed.insert(p_node); return OK; } - String node_name = "// " + vsnode->get_caption() + ":" + itos(node) + "\n"; + String node_name = "// " + vsnode->get_caption() + ":" + itos(p_node) + "\n"; String node_code; Vector<String> input_vars; @@ -1658,18 +1653,18 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui for (int i = 0; i < input_count; i++) { ConnectionKey ck; - ck.node = node; + ck.node = p_node; ck.port = i; - if (input_connections.has(ck)) { + if (p_input_connections.has(ck)) { //connected to something, use that output - int from_node = input_connections[ck]->get().from_node; + int from_node = p_input_connections[ck]->get().from_node; if (graph[type].nodes[from_node].node->is_disabled()) { continue; } - int from_port = input_connections[ck]->get().from_port; + int from_port = p_input_connections[ck]->get().from_port; VisualShaderNode::PortType in_type = vsnode->get_input_port_type(i); VisualShaderNode::PortType out_type = graph[type].nodes[from_node].node->get_output_port_type(from_port); @@ -1826,32 +1821,32 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui Variant defval = vsnode->get_input_port_default_value(i); if (defval.get_type() == Variant::FLOAT) { float val = defval; - inputs[i] = "n_in" + itos(node) + "p" + itos(i); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); node_code += " float " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; } else if (defval.get_type() == Variant::INT) { int val = defval; - inputs[i] = "n_in" + itos(node) + "p" + itos(i); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); node_code += " int " + inputs[i] + " = " + itos(val) + ";\n"; } else if (defval.get_type() == Variant::BOOL) { bool val = defval; - inputs[i] = "n_in" + itos(node) + "p" + itos(i); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); node_code += " bool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; } else if (defval.get_type() == Variant::VECTOR2) { Vector2 val = defval; - inputs[i] = "n_in" + itos(node) + "p" + itos(i); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); node_code += " vec2 " + inputs[i] + " = " + vformat("vec2(%.5f, %.5f);\n", val.x, val.y); } else if (defval.get_type() == Variant::VECTOR3) { Vector3 val = defval; - inputs[i] = "n_in" + itos(node) + "p" + itos(i); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); node_code += " vec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); } else if (defval.get_type() == Variant::QUATERNION) { Quaternion val = defval; - inputs[i] = "n_in" + itos(node) + "p" + itos(i); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); node_code += " vec4 " + inputs[i] + " = " + vformat("vec4(%.5f, %.5f, %.5f, %.5f);\n", val.x, val.y, val.z, val.w); } else if (defval.get_type() == Variant::TRANSFORM3D) { Transform3D val = defval; val.basis.transpose(); - inputs[i] = "n_in" + itos(node) + "p" + itos(i); + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); Array values; for (int j = 0; j < 3; j++) { values.push_back(val.basis[j].x); @@ -1903,7 +1898,7 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui if (vsnode->is_simple_decl()) { // less code to generate for some simple_decl nodes for (int i = 0, j = 0; i < initial_output_count; i++, j++) { - String var_name = "n_out" + itos(node) + "p" + itos(j); + String var_name = "n_out" + itos(p_node) + "p" + itos(j); switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_SCALAR: outputs[i] = "float " + var_name; @@ -1948,28 +1943,28 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui } else { for (int i = 0, j = 0; i < initial_output_count; i++, j++) { - outputs[i] = "n_out" + itos(node) + "p" + itos(j); + outputs[i] = "n_out" + itos(p_node) + "p" + itos(j); switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_SCALAR: - code += " float " + outputs[i] + ";\n"; + r_code += " float " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_SCALAR_INT: - code += " int " + outputs[i] + ";\n"; + r_code += " int " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_VECTOR_2D: - code += " vec2 " + outputs[i] + ";\n"; + r_code += " vec2 " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: - code += " vec3 " + outputs[i] + ";\n"; + r_code += " vec3 " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: - code += " vec4 " + outputs[i] + ";\n"; + r_code += " vec4 " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_BOOLEAN: - code += " bool " + outputs[i] + ";\n"; + r_code += " bool " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_TRANSFORM: - code += " mat4 " + outputs[i] + ";\n"; + r_code += " mat4 " + outputs[i] + ";\n"; break; default: break; @@ -1992,73 +1987,73 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui } } - node_code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview); + node_code += vsnode->generate_code(get_mode(), type, p_node, inputs, outputs, p_for_preview); if (!node_code.is_empty()) { - code += node_name; - code += node_code; + r_code += node_name; + r_code += node_code; } for (int i = 0; i < output_count; i++) { if (expanded_output_ports[i]) { switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - String r = "n_out" + itos(node) + "p" + itos(i + 1); - code += " float " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; + if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + String r = "n_out" + itos(p_node) + "p" + itos(i + 1); + r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; outputs[i + 1] = r; } - if (vsnode->is_output_port_connected(i + 2) || (for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - String g = "n_out" + itos(node) + "p" + itos(i + 2); - code += " float " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; + if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + String g = "n_out" + itos(p_node) + "p" + itos(i + 2); + r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; outputs[i + 2] = g; } i += 2; } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - String r = "n_out" + itos(node) + "p" + itos(i + 1); - code += " float " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; + if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + String r = "n_out" + itos(p_node) + "p" + itos(i + 1); + r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; outputs[i + 1] = r; } - if (vsnode->is_output_port_connected(i + 2) || (for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - String g = "n_out" + itos(node) + "p" + itos(i + 2); - code += " float " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; + if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + String g = "n_out" + itos(p_node) + "p" + itos(i + 2); + r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; outputs[i + 2] = g; } - if (vsnode->is_output_port_connected(i + 3) || (for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component - String b = "n_out" + itos(node) + "p" + itos(i + 3); - code += " float " + b + " = n_out" + itos(node) + "p" + itos(i) + ".b;\n"; + if (vsnode->is_output_port_connected(i + 3) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component + String b = "n_out" + itos(p_node) + "p" + itos(i + 3); + r_code += " float " + b + " = n_out" + itos(p_node) + "p" + itos(i) + ".b;\n"; outputs[i + 3] = b; } i += 3; } break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - if (vsnode->is_output_port_connected(i + 1) || (for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component - String r = "n_out" + itos(node) + "p" + itos(i + 1); - code += " float " + r + " = n_out" + itos(node) + "p" + itos(i) + ".r;\n"; + if (vsnode->is_output_port_connected(i + 1) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 1))) { // red-component + String r = "n_out" + itos(p_node) + "p" + itos(i + 1); + r_code += " float " + r + " = n_out" + itos(p_node) + "p" + itos(i) + ".r;\n"; outputs[i + 1] = r; } - if (vsnode->is_output_port_connected(i + 2) || (for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component - String g = "n_out" + itos(node) + "p" + itos(i + 2); - code += " float " + g + " = n_out" + itos(node) + "p" + itos(i) + ".g;\n"; + if (vsnode->is_output_port_connected(i + 2) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 2))) { // green-component + String g = "n_out" + itos(p_node) + "p" + itos(i + 2); + r_code += " float " + g + " = n_out" + itos(p_node) + "p" + itos(i) + ".g;\n"; outputs[i + 2] = g; } - if (vsnode->is_output_port_connected(i + 3) || (for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component - String b = "n_out" + itos(node) + "p" + itos(i + 3); - code += " float " + b + " = n_out" + itos(node) + "p" + itos(i) + ".b;\n"; + if (vsnode->is_output_port_connected(i + 3) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 3))) { // blue-component + String b = "n_out" + itos(p_node) + "p" + itos(i + 3); + r_code += " float " + b + " = n_out" + itos(p_node) + "p" + itos(i) + ".b;\n"; outputs[i + 3] = b; } - if (vsnode->is_output_port_connected(i + 4) || (for_preview && vsnode->get_output_port_for_preview() == (i + 4))) { // alpha-component - String a = "n_out" + itos(node) + "p" + itos(i + 4); - code += " float " + a + " = n_out" + itos(node) + "p" + itos(i) + ".a;\n"; + if (vsnode->is_output_port_connected(i + 4) || (p_for_preview && vsnode->get_output_port_for_preview() == (i + 4))) { // alpha-component + String a = "n_out" + itos(p_node) + "p" + itos(i + 4); + r_code += " float " + a + " = n_out" + itos(p_node) + "p" + itos(i) + ".a;\n"; outputs[i + 4] = a; } @@ -2071,11 +2066,11 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui } if (!node_code.is_empty()) { - code += "\n"; + r_code += "\n"; } - code += "\n"; // - processed.insert(node); + r_code += "\n"; // + r_processed.insert(p_node); return OK; } @@ -2103,7 +2098,7 @@ void VisualShader::_update_shader() const { StringBuilder global_code; StringBuilder global_code_per_node; HashMap<Type, StringBuilder> global_code_per_func; - StringBuilder code; + StringBuilder shader_code; Vector<VisualShader::DefaultTextureParam> default_tex_params; HashSet<StringName> classes; HashMap<int, int> insertion_pos; @@ -2340,7 +2335,7 @@ void VisualShader::_update_shader() const { if (shader_mode != Shader::MODE_PARTICLES) { func_code += "\nvoid " + String(func_name[i]) + "() {\n"; } - insertion_pos.insert(i, code.get_string_length() + func_code.get_string_length()); + insertion_pos.insert(i, shader_code.get_string_length() + func_code.get_string_length()); Error err = _write_node(Type(i), &global_code, &global_code_per_node, &global_code_per_func, func_code, default_tex_params, input_connections, output_connections, NODE_ID_OUTPUT, processed, false, classes); ERR_FAIL_COND(err != OK); @@ -2364,7 +2359,7 @@ void VisualShader::_update_shader() const { } else { func_code += varying_code; func_code += "}\n"; - code += func_code; + shader_code += func_code; } } @@ -2377,65 +2372,65 @@ void VisualShader::_update_shader() const { bool has_process_custom = !code_map[TYPE_PROCESS_CUSTOM].is_empty(); bool has_collide = !code_map[TYPE_COLLIDE].is_empty(); - code += "void start() {\n"; + shader_code += "void start() {\n"; if (has_start || has_start_custom) { - code += " uint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; - code += " vec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; - code += " float __radians;\n"; - code += " vec3 __vec3_buff1;\n"; - code += " vec3 __vec3_buff2;\n"; - code += " float __scalar_buff1;\n"; - code += " float __scalar_buff2;\n"; - code += " int __scalar_ibuff;\n"; - code += " vec4 __vec4_buff;\n"; - code += " vec3 __ndiff = normalize(__diff);\n\n"; + shader_code += " uint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; + shader_code += " vec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; + shader_code += " float __radians;\n"; + shader_code += " vec3 __vec3_buff1;\n"; + shader_code += " vec3 __vec3_buff2;\n"; + shader_code += " float __scalar_buff1;\n"; + shader_code += " float __scalar_buff2;\n"; + shader_code += " int __scalar_ibuff;\n"; + shader_code += " vec4 __vec4_buff;\n"; + shader_code += " vec3 __ndiff = normalize(__diff);\n\n"; } if (has_start) { - code += " {\n"; - code += code_map[TYPE_START].replace("\n ", "\n "); - code += " }\n"; + shader_code += " {\n"; + shader_code += code_map[TYPE_START].replace("\n ", "\n "); + shader_code += " }\n"; if (has_start_custom) { - code += " \n"; + shader_code += " \n"; } } if (has_start_custom) { - code += " {\n"; - code += code_map[TYPE_START_CUSTOM].replace("\n ", "\n "); - code += " }\n"; + shader_code += " {\n"; + shader_code += code_map[TYPE_START_CUSTOM].replace("\n ", "\n "); + shader_code += " }\n"; } - code += "}\n\n"; - code += "void process() {\n"; + shader_code += "}\n\n"; + shader_code += "void process() {\n"; if (has_process || has_process_custom || has_collide) { - code += " uint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; - code += " vec3 __vec3_buff1;\n"; - code += " vec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; - code += " vec3 __ndiff = normalize(__diff);\n\n"; + shader_code += " uint __seed = __hash(NUMBER + uint(1) + RANDOM_SEED);\n"; + shader_code += " vec3 __vec3_buff1;\n"; + shader_code += " vec3 __diff = TRANSFORM[3].xyz - EMISSION_TRANSFORM[3].xyz;\n"; + shader_code += " vec3 __ndiff = normalize(__diff);\n\n"; } - code += " {\n"; + shader_code += " {\n"; String tab = " "; if (has_collide) { - code += " if (COLLIDED) {\n\n"; - code += code_map[TYPE_COLLIDE].replace("\n ", "\n "); + shader_code += " if (COLLIDED) {\n\n"; + shader_code += code_map[TYPE_COLLIDE].replace("\n ", "\n "); if (has_process) { - code += " } else {\n\n"; + shader_code += " } else {\n\n"; tab += " "; } } if (has_process) { - code += code_map[TYPE_PROCESS].replace("\n ", "\n " + tab); + shader_code += code_map[TYPE_PROCESS].replace("\n ", "\n " + tab); } if (has_collide) { - code += " }\n"; + shader_code += " }\n"; } - code += " }\n"; + shader_code += " }\n"; if (has_process_custom) { - code += " {\n\n"; - code += code_map[TYPE_PROCESS_CUSTOM].replace("\n ", "\n "); - code += " }\n"; + shader_code += " {\n\n"; + shader_code += code_map[TYPE_PROCESS_CUSTOM].replace("\n ", "\n "); + shader_code += " }\n"; } - code += "}\n\n"; + shader_code += "}\n\n"; global_compute_code += "float __rand_from_seed(inout uint seed) {\n"; global_compute_code += " int k;\n"; @@ -2504,7 +2499,7 @@ void VisualShader::_update_shader() const { final_code += global_compute_code; final_code += global_code_per_node; final_code += global_expressions; - String tcode = code; + String tcode = shader_code; for (int i = 0; i < TYPE_MAX; i++) { if (!has_func_name(RenderingServer::ShaderMode(shader_mode), func_name[i])) { continue; @@ -2763,6 +2758,9 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "light", "LIGHT" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "light_color", "LIGHT_COLOR" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_position", "LIGHT_POSITION" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_direction", "LIGHT_DIRECTION" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "light_is_directional", "LIGHT_IS_DIRECTIONAL" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "light_energy", "LIGHT_ENERGY" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "light_vertex", "LIGHT_VERTEX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "shadow", "SHADOW_MODULATE" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, @@ -3645,12 +3643,12 @@ String VisualShaderNodeOutput::get_output_port_name(int p_port) const { bool VisualShaderNodeOutput::is_port_separator(int p_index) const { if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_VERTEX) { - String name = get_input_port_name(p_index); - return bool(name == "Model View Matrix"); + String port_name = get_input_port_name(p_index); + return bool(port_name == "Model View Matrix"); } if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_FRAGMENT) { - String name = get_input_port_name(p_index); - return bool(name == "AO" || name == "Normal" || name == "Rim" || name == "Clearcoat" || name == "Anisotropy" || name == "Subsurf Scatter" || name == "Alpha Scissor Threshold"); + String port_name = get_input_port_name(p_index); + return bool(port_name == "AO" || port_name == "Normal" || port_name == "Rim" || port_name == "Clearcoat" || port_name == "Anisotropy" || port_name == "Subsurf Scatter" || port_name == "Alpha Scissor Threshold"); } return false; } @@ -3663,15 +3661,15 @@ String VisualShaderNodeOutput::generate_code(Shader::Mode p_mode, VisualShader:: int idx = 0; int count = 0; - String code; + String shader_code; while (ports[idx].mode != Shader::MODE_MAX) { if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) { if (!p_input_vars[count].is_empty()) { String s = ports[idx].string; if (s.contains(":")) { - code += " " + s.get_slicec(':', 0) + " = " + p_input_vars[count] + "." + s.get_slicec(':', 1) + ";\n"; + shader_code += " " + s.get_slicec(':', 0) + " = " + p_input_vars[count] + "." + s.get_slicec(':', 1) + ";\n"; } else { - code += " " + s + " = " + p_input_vars[count] + ";\n"; + shader_code += " " + s + " = " + p_input_vars[count] + ";\n"; } } count++; @@ -3679,7 +3677,7 @@ String VisualShaderNodeOutput::generate_code(Shader::Mode p_mode, VisualShader:: idx++; } - return code; + return shader_code; } VisualShaderNodeOutput::VisualShaderNodeOutput() { diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 4116eaa196..5ed5f22cd0 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -90,11 +90,10 @@ public: struct Varying { String name; - VaryingMode mode; - VaryingType type; + VaryingMode mode = VARYING_MODE_MAX; + VaryingType type = VARYING_TYPE_MAX; - Varying() { - } + Varying() {} Varying(String p_name, VaryingMode p_mode, VaryingType p_type) : name(p_name), mode(p_mode), type(p_type) {} @@ -157,7 +156,7 @@ private: } }; - Error _write_node(Type p_type, StringBuilder *global_code, StringBuilder *global_code_per_node, HashMap<Type, StringBuilder> *global_code_per_func, StringBuilder &code, Vector<DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, HashSet<int> &processed, bool for_preview, HashSet<StringName> &r_classes) const; + Error _write_node(Type p_type, StringBuilder *p_global_code, StringBuilder *p_global_code_per_node, HashMap<Type, StringBuilder> *p_global_code_per_func, StringBuilder &r_code, Vector<DefaultTextureParam> &r_def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &p_input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &p_output_connections, int p_node, HashSet<int> &r_processed, bool p_for_preview, HashSet<StringName> &r_classes) const; void _input_type_changed(Type p_type, int p_id); bool has_func_name(RenderingServer::ShaderMode p_mode, const String &p_func_name) const; diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index de13912b75..03abac1b3e 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -1642,17 +1642,20 @@ bool VisualShaderNodeLinearSceneDepth::has_output_port_preview(int p_port) const String VisualShaderNodeLinearSceneDepth::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 += " float _log_depth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).x;\n"; - code += " vec3 _depth_ndc = vec3(SCREEN_UV * 2.0 - 1.0, _log_depth);\n"; - code += " vec4 _depth_view = INV_PROJECTION_MATRIX * vec4(_depth_ndc, 1.0);\n"; - code += " _depth_view.xyz /= _depth_view.w;"; - code += vformat(" %s = -_depth_view.z;", p_output_vars[0]); + code += " float __log_depth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).x;\n"; + code += " vec3 __depth_ndc = vec3(SCREEN_UV * 2.0 - 1.0, __log_depth);\n"; + code += " vec4 __depth_view = INV_PROJECTION_MATRIX * vec4(__depth_ndc, 1.0);\n"; + code += " __depth_view.xyz /= __depth_view.w;\n"; + code += vformat(" %s = -__depth_view.z;\n", p_output_vars[0]); + code += " }\n"; return code; } VisualShaderNodeLinearSceneDepth::VisualShaderNodeLinearSceneDepth() { + simple_decl = false; } ////////////// Float Op @@ -3105,9 +3108,9 @@ void VisualShaderNodeUVFunc::set_function(VisualShaderNodeUVFunc::Function p_fun return; } if (p_func == FUNC_PANNING) { - set_input_port_default_value(2, Vector2()); // offset + set_input_port_default_value(2, Vector2(), get_input_port_default_value(2)); // offset } else { // FUNC_SCALING - set_input_port_default_value(2, Vector2(0.5, 0.5)); // pivot + set_input_port_default_value(2, Vector2(0.5, 0.5), get_input_port_default_value(2)); // pivot } func = p_func; emit_changed(); @@ -3204,6 +3207,7 @@ String VisualShaderNodeUVPolarCoord::get_output_port_name(int p_port) const { String VisualShaderNodeUVPolarCoord::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"; String uv; if (p_input_vars[0].is_empty()) { @@ -3220,17 +3224,18 @@ String VisualShaderNodeUVPolarCoord::generate_code(Shader::Mode p_mode, VisualSh String repeat = vformat("%s", p_input_vars[3]); if (p_mode == Shader::MODE_CANVAS_ITEM) { - code += vformat(" vec2 __dir = %s - %s;\n", uv, center); - code += " float __radius = length(__dir) * 2.0;\n"; - code += " float __angle = atan(__dir.y, __dir.x) * 1.0/(PI * 2.0);\n"; - code += vformat(" %s = mod(vec2(__radius * %s, __angle * %s), 1.0);\n", p_output_vars[0], zoom, repeat); + code += vformat(" vec2 __dir = %s - %s;\n", uv, center); + code += " float __radius = length(__dir) * 2.0;\n"; + code += " float __angle = atan(__dir.y, __dir.x) * 1.0 / (PI * 2.0);\n"; + code += vformat(" %s = mod(vec2(__radius * %s, __angle * %s), 1.0);\n", p_output_vars[0], zoom, repeat); } else { - code += vformat(" vec2 __dir = %s - %s;\n", uv, center); - code += " float __radius = length(__dir) * 2.0;\n"; - code += " float __angle = atan(__dir.y, __dir.x) * 1.0/(PI * 2.0);\n"; - code += vformat(" %s = vec2(__radius * %s, __angle * %s);\n", p_output_vars[0], zoom, repeat); + code += vformat(" vec2 __dir = %s - %s;\n", uv, center); + code += " float __radius = length(__dir) * 2.0;\n"; + code += " float __angle = atan(__dir.y, __dir.x) * 1.0 / (PI * 2.0);\n"; + code += vformat(" %s = vec2(__radius * %s, __angle * %s);\n", p_output_vars[0], zoom, repeat); } + code += " }\n"; return code; } @@ -3238,6 +3243,8 @@ VisualShaderNodeUVPolarCoord::VisualShaderNodeUVPolarCoord() { set_input_port_default_value(1, Vector2(0.5, 0.5)); // center set_input_port_default_value(2, 1.0); // zoom set_input_port_default_value(3, 1.0); // repeat + + simple_decl = false; } ////////////// Dot Product @@ -7256,22 +7263,26 @@ bool VisualShaderNodeProximityFade::has_output_port_preview(int p_port) const { String VisualShaderNodeProximityFade::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"; String proximity_fade_distance = vformat("%s", p_input_vars[0]); - code += " float __depth_tex = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r;\n"; + code += " float __depth_tex = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r;\n"; if (!RenderingServer::get_singleton()->is_low_end()) { - code += " vec4 __depth_world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, __depth_tex, 1.0);\n"; + code += " vec4 __depth_world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, __depth_tex, 1.0);\n"; } else { - code += " vec4 __depth_world_pos = INV_PROJECTION_MATRIX * vec4(vec3(SCREEN_UV, __depth_tex) * 2.0 - 1.0, 1.0);\n"; + code += " vec4 __depth_world_pos = INV_PROJECTION_MATRIX * vec4(vec3(SCREEN_UV, __depth_tex) * 2.0 - 1.0, 1.0);\n"; } - code += " __depth_world_pos.xyz /= __depth_world_pos.z;\n"; - code += vformat(" %s = clamp(1.0 - smoothstep(__depth_world_pos.z + %s, __depth_world_pos.z, VERTEX.z), 0.0, 1.0);\n", p_output_vars[0], p_input_vars[0]); + code += " __depth_world_pos.xyz /= __depth_world_pos.w;\n"; + code += vformat(" %s = clamp(1.0 - smoothstep(__depth_world_pos.z + %s, __depth_world_pos.z, VERTEX.z), 0.0, 1.0);\n", p_output_vars[0], p_input_vars[0]); + code += " }\n"; return code; } VisualShaderNodeProximityFade::VisualShaderNodeProximityFade() { set_input_port_default_value(0, 1.0); + + simple_decl = false; } ////////////// Random Range @@ -7416,11 +7427,11 @@ String VisualShaderNodeRemap::get_output_port_name(int p_port) const { 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 += 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]); - + 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]); + code += " }\n"; return code; } @@ -7429,4 +7440,6 @@ VisualShaderNodeRemap::VisualShaderNodeRemap() { set_input_port_default_value(2, 1.0); set_input_port_default_value(3, 0.0); set_input_port_default_value(4, 1.0); + + simple_decl = false; } diff --git a/scene/resources/visual_shader_particle_nodes.cpp b/scene/resources/visual_shader_particle_nodes.cpp index bdfbb59fa6..f125b05a26 100644 --- a/scene/resources/visual_shader_particle_nodes.cpp +++ b/scene/resources/visual_shader_particle_nodes.cpp @@ -460,9 +460,9 @@ void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Vector2> image.instantiate(); if (p_array.size() == 0) { - image->create(1, 1, false, Image::Format::FORMAT_RGBF); + image->initialize_data(1, 1, false, Image::Format::FORMAT_RGBF); } else { - image->create(p_array.size(), 1, false, Image::Format::FORMAT_RGBF); + image->initialize_data(p_array.size(), 1, false, Image::Format::FORMAT_RGBF); } for (int i = 0; i < p_array.size(); i++) { @@ -481,9 +481,9 @@ void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Vector3> image.instantiate(); if (p_array.size() == 0) { - image->create(1, 1, false, Image::Format::FORMAT_RGBF); + image->initialize_data(1, 1, false, Image::Format::FORMAT_RGBF); } else { - image->create(p_array.size(), 1, false, Image::Format::FORMAT_RGBF); + image->initialize_data(p_array.size(), 1, false, Image::Format::FORMAT_RGBF); } for (int i = 0; i < p_array.size(); i++) { @@ -502,9 +502,9 @@ void VisualShaderNodeParticleMeshEmitter::_update_texture(const Vector<Color> &p image.instantiate(); if (p_array.size() == 0) { - image->create(1, 1, false, Image::Format::FORMAT_RGBA8); + image->initialize_data(1, 1, false, Image::Format::FORMAT_RGBA8); } else { - image->create(p_array.size(), 1, false, Image::Format::FORMAT_RGBA8); + image->initialize_data(p_array.size(), 1, false, Image::Format::FORMAT_RGBA8); } for (int i = 0; i < p_array.size(); i++) { @@ -1130,31 +1130,38 @@ VisualShaderNodeParticleAccelerator::VisualShaderNodeParticleAccelerator() { // VisualShaderNodeParticleOutput String VisualShaderNodeParticleOutput::get_caption() const { - if (shader_type == VisualShader::TYPE_START) { - return "StartOutput"; - } else if (shader_type == VisualShader::TYPE_PROCESS) { - return "ProcessOutput"; - } else if (shader_type == VisualShader::TYPE_COLLIDE) { - return "CollideOutput"; - } else if (shader_type == VisualShader::TYPE_START_CUSTOM) { - return "CustomStartOutput"; - } else if (shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { - return "CustomProcessOutput"; + switch (shader_type) { + case VisualShader::TYPE_START: + return "StartOutput"; + case VisualShader::TYPE_PROCESS: + return "ProcessOutput"; + case VisualShader::TYPE_COLLIDE: + return "CollideOutput"; + case VisualShader::TYPE_START_CUSTOM: + return "CustomStartOutput"; + case VisualShader::TYPE_PROCESS_CUSTOM: + return "CustomProcessOutput"; + default: + ERR_PRINT(vformat("Unexpected shader_type %d for VisualShaderNodeParticleOutput.", shader_type)); + return ""; } - return String(); } int VisualShaderNodeParticleOutput::get_input_port_count() const { - if (shader_type == VisualShader::TYPE_START) { - return 8; - } else if (shader_type == VisualShader::TYPE_COLLIDE) { - return 5; - } else if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { - return 6; - } else { // TYPE_PROCESS - return 7; + switch (shader_type) { + case VisualShader::TYPE_START: + return 8; + case VisualShader::TYPE_PROCESS: + return 7; + case VisualShader::TYPE_COLLIDE: + return 5; + case VisualShader::TYPE_START_CUSTOM: + case VisualShader::TYPE_PROCESS_CUSTOM: + return 6; + default: + ERR_PRINT(vformat("Unexpected shader_type %d for VisualShaderNodeParticleOutput.", shader_type)); + return 0; } - return 0; } VisualShaderNodeParticleOutput::PortType VisualShaderNodeParticleOutput::get_input_port_type(int p_port) const { @@ -1284,12 +1291,12 @@ String VisualShaderNodeParticleOutput::get_input_port_name(int p_port) const { bool VisualShaderNodeParticleOutput::is_port_separator(int p_index) const { if (shader_type == VisualShader::TYPE_START || shader_type == VisualShader::TYPE_PROCESS) { - String name = get_input_port_name(p_index); - return bool(name == "Scale"); + String port_name = get_input_port_name(p_index); + return bool(port_name == "Scale"); } if (shader_type == VisualShader::TYPE_START_CUSTOM || shader_type == VisualShader::TYPE_PROCESS_CUSTOM) { - String name = get_input_port_name(p_index); - return bool(name == "Velocity"); + String port_name = get_input_port_name(p_index); + return bool(port_name == "Velocity"); } return false; } @@ -1597,24 +1604,24 @@ String VisualShaderNodeParticleEmit::generate_code(Shader::Mode p_mode, VisualSh flags_arr.push_back("FLAG_EMIT_CUSTOM"); } - String flags; + String flags_str; for (int i = 0; i < flags_arr.size(); i++) { if (i > 0) { - flags += "|"; + flags_str += "|"; } - flags += flags_arr[i]; + flags_str += flags_arr[i]; } - if (flags.is_empty()) { - flags = "uint(0)"; + if (flags_str.is_empty()) { + flags_str = "uint(0)"; } if (!default_condition) { code += " if (" + p_input_vars[0] + ") {\n"; } - code += tab + "emit_subparticle(" + transform + ", " + velocity + ", vec4(" + color + ", " + alpha + "), vec4(" + custom + ", " + custom_alpha + "), " + flags + ");\n"; + code += tab + "emit_subparticle(" + transform + ", " + velocity + ", vec4(" + color + ", " + alpha + "), vec4(" + custom + ", " + custom_alpha + "), " + flags_str + ");\n"; if (!default_condition) { code += " }\n"; diff --git a/scene/theme/theme_db.cpp b/scene/theme/theme_db.cpp index d6e892cd93..1025d40b05 100644 --- a/scene/theme/theme_db.cpp +++ b/scene/theme/theme_db.cpp @@ -52,13 +52,13 @@ void ThemeDB::initialize_theme() { ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/custom_font", PropertyInfo(Variant::STRING, "gui/theme/custom_font", PROPERTY_HINT_FILE, "*.tres,*.res", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); TextServer::FontAntialiasing font_antialiasing = (TextServer::FontAntialiasing)(int)GLOBAL_DEF_RST("gui/theme/default_font_antialiasing", 1); - ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_antialiasing", PropertyInfo(Variant::INT, "gui/theme/default_font_antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD sub-pixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); + ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_antialiasing", PropertyInfo(Variant::INT, "gui/theme/default_font_antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); TextServer::Hinting font_hinting = (TextServer::Hinting)(int)GLOBAL_DEF_RST("gui/theme/default_font_hinting", TextServer::HINTING_LIGHT); ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_hinting", PropertyInfo(Variant::INT, "gui/theme/default_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); TextServer::SubpixelPositioning font_subpixel_positioning = (TextServer::SubpixelPositioning)(int)GLOBAL_DEF_RST("gui/theme/default_font_subpixel_positioning", TextServer::SUBPIXEL_POSITIONING_AUTO); - ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_subpixel_positioning", PropertyInfo(Variant::INT, "gui/theme/default_font_subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); + ProjectSettings::get_singleton()->set_custom_property_info("gui/theme/default_font_subpixel_positioning", PropertyInfo(Variant::INT, "gui/theme/default_font_subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)); const bool font_msdf = GLOBAL_DEF_RST("gui/theme/default_font_multichannel_signed_distance_field", false); const bool font_generate_mipmaps = GLOBAL_DEF_RST("gui/theme/default_font_generate_mipmaps", false); |