diff options
Diffstat (limited to 'scene')
253 files changed, 7392 insertions, 4978 deletions
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index b1b1cb23ed..7ee9861d3f 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -63,9 +63,13 @@ Rect2 AnimatedSprite2D::_edit_get_rect() const { } bool AnimatedSprite2D::_edit_use_rect() const { - if (!frames.is_valid() || !frames->has_animation(animation) || frame < 0 || frame >= frames->get_frame_count(animation)) { + if (frames.is_null() || !frames->has_animation(animation)) { return false; } + if (frame < 0 || frame >= frames->get_frame_count(animation)) { + return false; + } + Ref<Texture2D> t; if (animation) { t = frames->get_frame(animation, frame); @@ -79,7 +83,10 @@ Rect2 AnimatedSprite2D::get_anchorable_rect() const { } Rect2 AnimatedSprite2D::_get_rect() const { - if (!frames.is_valid() || !frames->has_animation(animation) || frame < 0 || frame >= frames->get_frame_count(animation)) { + if (frames.is_null() || !frames->has_animation(animation)) { + return Rect2(); + } + if (frame < 0 || frame >= frames->get_frame_count(animation)) { return Rect2(); } @@ -108,6 +115,7 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &p_property) const { if (!frames.is_valid()) { return; } + if (p_property.name == "animation") { p_property.hint = PROPERTY_HINT_ENUM; List<StringName> names; @@ -137,9 +145,15 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &p_property) const { p_property.hint_string = String(animation) + "," + p_property.hint_string; } } + return; } if (p_property.name == "frame") { + if (playing) { + p_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; + return; + } + p_property.hint = PROPERTY_HINT_RANGE; if (frames->has_animation(animation) && frames->get_frame_count(animation) > 0) { p_property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; @@ -154,54 +168,52 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &p_property) const { void AnimatedSprite2D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_INTERNAL_PROCESS: { - if (frames.is_null()) { + if (frames.is_null() || !frames->has_animation(animation)) { return; } - if (!frames->has_animation(animation)) { - return; - } - if (frame < 0) { - return; + + double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale); + if (speed == 0) { + return; // Do nothing. } + int last_frame = frames->get_frame_count(animation) - 1; double remaining = get_process_delta_time(); - while (remaining) { - double speed = frames->get_animation_speed(animation) * speed_scale; - if (speed == 0) { - return; // Do nothing. - } - if (timeout <= 0) { timeout = _get_frame_duration(); - int fc = frames->get_frame_count(animation); - if ((!backwards && frame >= fc - 1) || (backwards && frame <= 0)) { - if (frames->get_animation_loop(animation)) { - if (backwards) { - frame = fc - 1; - } else { - frame = 0; - } - - emit_signal(SceneStringNames::get_singleton()->animation_finished); - } else { - if (backwards) { + if (!playing_backwards) { + // Forward. + if (frame >= last_frame) { + if (frames->get_animation_loop(animation)) { frame = 0; - } else { - frame = fc - 1; - } - - if (!is_over) { - is_over = true; emit_signal(SceneStringNames::get_singleton()->animation_finished); + } else { + frame = last_frame; + if (!is_over) { + is_over = true; + emit_signal(SceneStringNames::get_singleton()->animation_finished); + } } + } else { + frame++; } } else { - if (backwards) { - frame--; + // Reversed. + if (frame <= 0) { + if (frames->get_animation_loop(animation)) { + frame = last_frame; + emit_signal(SceneStringNames::get_singleton()->animation_finished); + } else { + frame = 0; + if (!is_over) { + is_over = true; + emit_signal(SceneStringNames::get_singleton()->animation_finished); + } + } } else { - frame++; + frame--; } } @@ -217,13 +229,7 @@ void AnimatedSprite2D::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - if (frames.is_null()) { - return; - } - if (frame < 0) { - return; - } - if (!frames->has_animation(animation)) { + if (frames.is_null() || !frames->has_animation(animation)) { return; } @@ -259,14 +265,15 @@ void AnimatedSprite2D::_notification(int p_what) { void AnimatedSprite2D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { if (frames.is_valid()) { - frames->disconnect("changed", callable_mp(this, &AnimatedSprite2D::_res_changed)); + frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed)); } + frames = p_frames; if (frames.is_valid()) { - frames->connect("changed", callable_mp(this, &AnimatedSprite2D::_res_changed)); + frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite2D::_res_changed)); } - if (!frames.is_valid()) { + if (frames.is_null()) { frame = 0; } else { set_frame(frame); @@ -283,7 +290,7 @@ Ref<SpriteFrames> AnimatedSprite2D::get_sprite_frames() const { } void AnimatedSprite2D::set_frame(int p_frame) { - if (!frames.is_valid()) { + if (frames.is_null()) { return; } @@ -314,11 +321,16 @@ int AnimatedSprite2D::get_frame() const { } void AnimatedSprite2D::set_speed_scale(double p_speed_scale) { + if (speed_scale == p_speed_scale) { + return; + } + double elapsed = _get_frame_duration() - timeout; - speed_scale = MAX(p_speed_scale, 0.0f); + speed_scale = p_speed_scale; + playing_backwards = signbit(speed_scale) != backwards; - // We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed + // We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed. _reset_timeout(); timeout -= elapsed; } @@ -378,18 +390,20 @@ void AnimatedSprite2D::set_playing(bool p_playing) { playing = p_playing; _reset_timeout(); set_process_internal(playing); + notify_property_list_changed(); } bool AnimatedSprite2D::is_playing() const { return playing; } -void AnimatedSprite2D::play(const StringName &p_animation, const bool p_backwards) { +void AnimatedSprite2D::play(const StringName &p_animation, bool p_backwards) { backwards = p_backwards; + playing_backwards = signbit(speed_scale) != backwards; if (p_animation) { set_animation(p_animation); - if (frames.is_valid() && backwards && get_frame() == 0) { + if (frames.is_valid() && playing_backwards && get_frame() == 0) { set_frame(frames->get_frame_count(p_animation) - 1); } } @@ -404,7 +418,7 @@ void AnimatedSprite2D::stop() { double AnimatedSprite2D::_get_frame_duration() { if (frames.is_valid() && frames->has_animation(animation)) { - double speed = frames->get_animation_speed(animation) * speed_scale; + double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale); if (speed > 0) { return 1.0 / speed; } @@ -440,11 +454,11 @@ 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 AnimatedSprite to display frames.")); + warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite2D to display frames.")); } return warnings; diff --git a/scene/2d/animated_sprite_2d.h b/scene/2d/animated_sprite_2d.h index 0a19e250d8..11c4adb816 100644 --- a/scene/2d/animated_sprite_2d.h +++ b/scene/2d/animated_sprite_2d.h @@ -39,6 +39,7 @@ class AnimatedSprite2D : public Node2D { Ref<SpriteFrames> frames; bool playing = false; + bool playing_backwards = false; bool backwards = false; StringName animation = "default"; int frame = 0; @@ -81,7 +82,7 @@ public: void set_sprite_frames(const Ref<SpriteFrames> &p_frames); Ref<SpriteFrames> get_sprite_frames() const; - void play(const StringName &p_animation = StringName(), const bool p_backwards = false); + void play(const StringName &p_animation = StringName(), bool p_backwards = false); void stop(); void set_playing(bool p_playing); @@ -108,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/area_2d.cpp b/scene/2d/area_2d.cpp index 3def41eaa5..b3f80b5e43 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -459,6 +459,16 @@ TypedArray<Area2D> Area2D::get_overlapping_areas() const { return ret; } +bool Area2D::has_overlapping_bodies() const { + ERR_FAIL_COND_V_MSG(!monitoring, false, "Can't find overlapping bodies when monitoring is off."); + return !body_map.is_empty(); +} + +bool Area2D::has_overlapping_areas() const { + ERR_FAIL_COND_V_MSG(!monitoring, false, "Can't find overlapping areas when monitoring is off."); + return !area_map.is_empty(); +} + bool Area2D::overlaps_area(Node *p_area) const { ERR_FAIL_NULL_V(p_area, false); HashMap<ObjectID, AreaState>::ConstIterator E = area_map.find(p_area->get_instance_id()); @@ -578,6 +588,9 @@ void Area2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_overlapping_bodies"), &Area2D::get_overlapping_bodies); ClassDB::bind_method(D_METHOD("get_overlapping_areas"), &Area2D::get_overlapping_areas); + ClassDB::bind_method(D_METHOD("has_overlapping_bodies"), &Area2D::has_overlapping_bodies); + ClassDB::bind_method(D_METHOD("has_overlapping_areas"), &Area2D::has_overlapping_areas); + ClassDB::bind_method(D_METHOD("overlaps_body", "body"), &Area2D::overlaps_body); ClassDB::bind_method(D_METHOD("overlaps_area", "area"), &Area2D::overlaps_area); diff --git a/scene/2d/area_2d.h b/scene/2d/area_2d.h index 3d8d77eabb..f70f1dfc3d 100644 --- a/scene/2d/area_2d.h +++ b/scene/2d/area_2d.h @@ -180,6 +180,9 @@ public: TypedArray<Node2D> get_overlapping_bodies() const; //function for script TypedArray<Area2D> get_overlapping_areas() const; //function for script + bool has_overlapping_bodies() const; + bool has_overlapping_areas() const; + bool overlaps_area(Node *p_area) const; bool overlaps_body(Node *p_body) const; 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/camera_2d.cpp b/scene/2d/camera_2d.cpp index a11b2b66bf..aa7df4ea9c 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 { @@ -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..9786b01058 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -296,8 +296,8 @@ bool GPUParticles2D::get_interpolate() const { return interpolate; } -TypedArray<String> GPUParticles2D::get_configuration_warnings() const { - TypedArray<String> warnings = Node2D::get_configuration_warnings(); +PackedStringArray GPUParticles2D::get_configuration_warnings() const { + PackedStringArray 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.")); @@ -353,13 +353,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() { diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index 10ae91775f..d613b4ef51 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -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..6000508f36 100644 --- a/scene/2d/joint_2d.cpp +++ b/scene/2d/joint_2d.cpp @@ -202,8 +202,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..80169bc80c 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -395,8 +395,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 +454,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..f077f7f5e6 100644 --- a/scene/2d/navigation_agent_2d.cpp +++ b/scene/2d/navigation_agent_2d.cpp @@ -178,6 +178,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 +321,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 +331,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 +355,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 +383,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 +406,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 +431,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 +469,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; 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..2a5092216d 100644 --- a/scene/2d/navigation_link_2d.h +++ b/scene/2d/navigation_link_2d.h @@ -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 bd5a01f5a4..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, screen_offset); + l->set_base_offset_and_scale((scroll_ofs + screen_offset * (scale - 1)) / scale, 1.0); } else { - l->set_base_offset_and_scale(ofs, scale, screen_offset); + l->set_base_offset_and_scale(scroll_ofs, scale); } } } diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index f0aad1b8a4..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, screen_offset); + 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, screen_offset); + Vector2 final_ofs = pb->get_final_offset(); + real_t scroll_scale = pb->get_scroll_scale(); + set_base_offset_and_scale(final_ofs, scroll_scale); } } @@ -111,9 +111,7 @@ void ParallaxLayer::_notification(int p_what) { } } -void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale, const Point2 &p_screen_offset) { - screen_offset = p_screen_offset; - +void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale) { if (!is_inside_tree()) { return; } @@ -121,7 +119,7 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s return; } - Point2 new_ofs = (screen_offset + (p_offset - screen_offset) * motion_scale) + motion_offset * p_scale + orig_offset * p_scale; + Point2 new_ofs = p_offset * motion_scale + motion_offset * p_scale + orig_offset * p_scale; if (mirroring.x) { real_t den = mirroring.x * p_scale; @@ -139,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 b4dcf0ea61..6471f56c5c 100644 --- a/scene/2d/parallax_layer.h +++ b/scene/2d/parallax_layer.h @@ -43,8 +43,6 @@ class ParallaxLayer : public Node2D { Vector2 mirroring; void _update_mirroring(); - Point2 screen_offset; - protected: void _notification(int p_what); static void _bind_methods(); @@ -59,9 +57,9 @@ public: void set_mirroring(const Size2 &p_mirroring); Size2 get_mirroring() const; - void set_base_offset_and_scale(const Point2 &p_offset, real_t p_scale, const Point2 &p_screen_offset); + 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..c1044fdf5b 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -256,8 +256,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 714d196779..0f3e6c7529 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -34,8 +34,8 @@ #include "scene/scene_string_names.h" void PhysicsBody2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "distance", "test_only", "safe_margin"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08)); - ClassDB::bind_method(D_METHOD("test_move", "from", "distance", "collision", "safe_margin"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08)); + ClassDB::bind_method(D_METHOD("move_and_collide", "distance", "test_only", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::_move, DEFVAL(false), DEFVAL(0.08), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("test_move", "from", "distance", "collision", "safe_margin", "recovery_as_collision"), &PhysicsBody2D::test_move, DEFVAL(Variant()), DEFVAL(0.08), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &PhysicsBody2D::get_collision_exceptions); ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &PhysicsBody2D::add_collision_exception_with); @@ -54,15 +54,15 @@ PhysicsBody2D::~PhysicsBody2D() { } } -Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_distance, bool p_test_only, real_t p_margin) { +Ref<KinematicCollision2D> PhysicsBody2D::_move(const Vector2 &p_distance, bool p_test_only, real_t p_margin, bool p_recovery_as_collision) { PhysicsServer2D::MotionParameters parameters(get_global_transform(), p_distance, p_margin); - parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. + parameters.recovery_as_collision = p_recovery_as_collision; PhysicsServer2D::MotionResult result; 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; } @@ -128,7 +128,7 @@ bool PhysicsBody2D::move_and_collide(const PhysicsServer2D::MotionParameters &p_ return colliding; } -bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_distance, const Ref<KinematicCollision2D> &r_collision, real_t p_margin) { +bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_distance, const Ref<KinematicCollision2D> &r_collision, real_t p_margin, bool p_recovery_as_collision) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer2D::MotionResult *r = nullptr; @@ -141,7 +141,7 @@ bool PhysicsBody2D::test_move(const Transform2D &p_from, const Vector2 &p_distan } PhysicsServer2D::MotionParameters parameters(p_from, p_distance, p_margin); - parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. + parameters.recovery_as_collision = p_recovery_as_collision; return PhysicsServer2D::get_singleton()->body_test_motion(get_rid(), parameters, r); } @@ -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 eaba9aadad..932ec1de16 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -47,11 +47,11 @@ protected: Ref<KinematicCollision2D> motion_cache; - Ref<KinematicCollision2D> _move(const Vector2 &p_distance, bool p_test_only = false, real_t p_margin = 0.08); + Ref<KinematicCollision2D> _move(const Vector2 &p_distance, bool p_test_only = false, real_t p_margin = 0.08, bool p_recovery_as_collision = false); public: bool move_and_collide(const PhysicsServer2D::MotionParameters &p_parameters, PhysicsServer2D::MotionResult &r_result, bool p_test_only = false, bool p_cancel_sliding = true); - bool test_move(const Transform2D &p_from, const Vector2 &p_distance, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08); + bool test_move(const Transform2D &p_from, const Vector2 &p_distance, const Ref<KinematicCollision2D> &r_collision = Ref<KinematicCollision2D>(), real_t p_margin = 0.08, bool p_recovery_as_collision = false); TypedArray<PhysicsBody2D> get_collision_exceptions(); void add_collision_exception_with(Node *p_node); //must be physicsbody @@ -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..6222b0db14 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -391,8 +391,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.")); diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h index 660e52f189..7b55566b01 100644 --- a/scene/2d/shape_cast_2d.h +++ b/scene/2d/shape_cast_2d.h @@ -117,7 +117,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..f80b2a07c9 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) { @@ -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..26627a4fca 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; } } @@ -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())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat())); - 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(); } @@ -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()); @@ -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(); } } @@ -2794,9 +2819,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 +2837,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 +3640,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; @@ -3819,8 +3844,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; diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index a819eeab71..d468675e91 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; @@ -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 f118080009..c6433e773d 100644 --- a/scene/3d/area_3d.cpp +++ b/scene/3d/area_3d.cpp @@ -489,6 +489,11 @@ TypedArray<Node3D> Area3D::get_overlapping_bodies() const { return ret; } +bool Area3D::has_overlapping_bodies() const { + ERR_FAIL_COND_V_MSG(!monitoring, false, "Can't find overlapping bodies when monitoring is off."); + return !body_map.is_empty(); +} + void Area3D::set_monitorable(bool p_enable) { ERR_FAIL_COND_MSG(locked || (is_inside_tree() && PhysicsServer3D::get_singleton()->is_flushing_queries()), "Function blocked during in/out signal. Use set_deferred(\"monitorable\", true/false)."); @@ -521,6 +526,11 @@ TypedArray<Area3D> Area3D::get_overlapping_areas() const { return ret; } +bool Area3D::has_overlapping_areas() const { + ERR_FAIL_COND_V_MSG(!monitoring, false, "Can't find overlapping areas when monitoring is off."); + return !area_map.is_empty(); +} + bool Area3D::overlaps_area(Node *p_area) const { ERR_FAIL_NULL_V(p_area, false); HashMap<ObjectID, AreaState>::ConstIterator E = area_map.find(p_area->get_instance_id()); @@ -568,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; @@ -686,6 +696,9 @@ void Area3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_overlapping_bodies"), &Area3D::get_overlapping_bodies); ClassDB::bind_method(D_METHOD("get_overlapping_areas"), &Area3D::get_overlapping_areas); + ClassDB::bind_method(D_METHOD("has_overlapping_bodies"), &Area3D::has_overlapping_bodies); + ClassDB::bind_method(D_METHOD("has_overlapping_areas"), &Area3D::has_overlapping_areas); + ClassDB::bind_method(D_METHOD("overlaps_body", "body"), &Area3D::overlaps_body); ClassDB::bind_method(D_METHOD("overlaps_area", "area"), &Area3D::overlaps_area); @@ -698,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); @@ -747,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 48364739b7..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); @@ -201,6 +200,9 @@ public: TypedArray<Node3D> get_overlapping_bodies() const; TypedArray<Area3D> get_overlapping_areas() const; //function for script + bool has_overlapping_bodies() const; + bool has_overlapping_areas() const; + bool overlaps_area(Node *p_area) const; bool overlaps_body(Node *p_body) const; @@ -213,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..40afbdf2ed 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"); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index ef48269544..913cc9fc00 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; @@ -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..a98be62dfa 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 { @@ -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..7de685b469 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; @@ -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..dbbf196f7a 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -269,8 +269,8 @@ bool GPUParticles3D::get_interpolate() const { return interpolate; } -TypedArray<String> GPUParticles3D::get_configuration_warnings() const { - TypedArray<String> warnings = GeometryInstance3D::get_configuration_warnings(); +PackedStringArray GPUParticles3D::get_configuration_warnings() const { + PackedStringArray 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.")); diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 2ad9672474..ef92218e4d 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -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 b0bccc4571..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); @@ -1418,7 +1424,8 @@ float LightmapGI::get_bias() const { } void LightmapGI::set_max_texture_size(int p_size) { - ERR_FAIL_COND(p_size < 2048); + ERR_FAIL_COND_MSG(p_size < 2048, vformat("The LightmapGI maximum texture size supplied (%d) is too small. The minimum allowed value is 2048.", p_size)); + ERR_FAIL_COND_MSG(p_size > 16384, vformat("The LightmapGI maximum texture size supplied (%d) is too large. The maximum allowed value is 16384.", p_size)); max_texture_size = p_size; } @@ -1506,7 +1513,7 @@ void LightmapGI::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size"), "set_max_texture_size", "get_max_texture_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size", PROPERTY_HINT_RANGE, "2048,16384,1"), "set_max_texture_size", "get_max_texture_size"); ADD_GROUP("Environment", "environment_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "environment_mode", PROPERTY_HINT_ENUM, "Disabled,Scene,Custom Sky,Custom Color"), "set_environment_mode", "get_environment_mode"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment_custom_sky", PROPERTY_HINT_RESOURCE_TYPE, "Sky"), "set_environment_custom_sky", "get_environment_custom_sky"); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 0062a4a093..1d282e92c8 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -142,16 +142,16 @@ public: private: BakeQuality bake_quality = BAKE_QUALITY_MEDIUM; bool use_denoiser = true; - int bounces = 1; + int bounces = 3; float bias = 0.0005; int max_texture_size = 16384; bool interior = false; - EnvironmentMode environment_mode = ENVIRONMENT_MODE_DISABLED; + EnvironmentMode environment_mode = ENVIRONMENT_MODE_SCENE; Ref<Sky> environment_custom_sky; - Color environment_custom_color = Color(0.2, 0.7, 1.0); + Color environment_custom_color = Color(1, 1, 1); float environment_custom_energy = 1.0; bool directional = false; - GenerateProbes gen_probes = GENERATE_PROBES_DISABLED; + GenerateProbes gen_probes = GENERATE_PROBES_SUBDIV_8; Ref<CameraAttributes> camera_attributes; Ref<LightmapGIData> light_data; 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..39068fe83c 100644 --- a/scene/3d/navigation_agent_3d.cpp +++ b/scene/3d/navigation_agent_3d.cpp @@ -185,6 +185,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 +337,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 +347,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 +371,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 +398,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 +421,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 +448,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 +486,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; 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..1fc03546fa 100644 --- a/scene/3d/navigation_link_3d.h +++ b/scene/3d/navigation_link_3d.h @@ -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..5f515acead 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -786,6 +786,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,6 +1038,7 @@ 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); @@ -1052,7 +1054,7 @@ void Node3D::_bind_methods() { 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..457f634c34 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -71,8 +71,8 @@ public: }; 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,6 +96,7 @@ 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; 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 8888aa183a..d9fa37ce74 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -34,8 +34,8 @@ #include "scene/scene_string_names.h" void PhysicsBody3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("move_and_collide", "distance", "test_only", "safe_margin", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(1)); - ClassDB::bind_method(D_METHOD("test_move", "from", "distance", "collision", "safe_margin", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("move_and_collide", "distance", "test_only", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::_move, DEFVAL(false), DEFVAL(0.001), DEFVAL(false), DEFVAL(1)); + ClassDB::bind_method(D_METHOD("test_move", "from", "distance", "collision", "safe_margin", "recovery_as_collision", "max_collisions"), &PhysicsBody3D::test_move, DEFVAL(Variant()), DEFVAL(0.001), DEFVAL(false), DEFVAL(1)); ClassDB::bind_method(D_METHOD("set_axis_lock", "axis", "lock"), &PhysicsBody3D::set_axis_lock); ClassDB::bind_method(D_METHOD("get_axis_lock", "axis"), &PhysicsBody3D::get_axis_lock); @@ -91,16 +91,16 @@ void PhysicsBody3D::remove_collision_exception_with(Node *p_node) { PhysicsServer3D::get_singleton()->body_remove_collision_exception(get_rid(), collision_object->get_rid()); } -Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_distance, bool p_test_only, real_t p_margin, int p_max_collisions) { +Ref<KinematicCollision3D> PhysicsBody3D::_move(const Vector3 &p_distance, bool p_test_only, real_t p_margin, bool p_recovery_as_collision, int p_max_collisions) { PhysicsServer3D::MotionParameters parameters(get_global_transform(), p_distance, p_margin); parameters.max_collisions = p_max_collisions; - parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. + parameters.recovery_as_collision = p_recovery_as_collision; PhysicsServer3D::MotionResult result; 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; } @@ -169,7 +169,7 @@ bool PhysicsBody3D::move_and_collide(const PhysicsServer3D::MotionParameters &p_ return colliding; } -bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_distance, const Ref<KinematicCollision3D> &r_collision, real_t p_margin, int p_max_collisions) { +bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_distance, const Ref<KinematicCollision3D> &r_collision, real_t p_margin, bool p_recovery_as_collision, int p_max_collisions) { ERR_FAIL_COND_V(!is_inside_tree(), false); PhysicsServer3D::MotionResult *r = nullptr; @@ -182,7 +182,7 @@ bool PhysicsBody3D::test_move(const Transform3D &p_from, const Vector3 &p_distan } PhysicsServer3D::MotionParameters parameters(p_from, p_distance, p_margin); - parameters.recovery_as_collision = false; // Don't report collisions generated only from recovery. + parameters.recovery_as_collision = p_recovery_as_collision; return PhysicsServer3D::get_singleton()->body_test_motion(get_rid(), parameters, r); } @@ -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 184d8b00d0..4b874b91d9 100644 --- a/scene/3d/physics_body_3d.h +++ b/scene/3d/physics_body_3d.h @@ -50,11 +50,11 @@ protected: uint16_t locked_axis = 0; - Ref<KinematicCollision3D> _move(const Vector3 &p_distance, bool p_test_only = false, real_t p_margin = 0.001, int p_max_collisions = 1); + Ref<KinematicCollision3D> _move(const Vector3 &p_distance, bool p_test_only = false, real_t p_margin = 0.001, bool p_recovery_as_collision = false, int p_max_collisions = 1); public: bool move_and_collide(const PhysicsServer3D::MotionParameters &p_parameters, PhysicsServer3D::MotionResult &r_result, bool p_test_only = false, bool p_cancel_sliding = true); - bool test_move(const Transform3D &p_from, const Vector3 &p_distance, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001, int p_max_collisions = 1); + bool test_move(const Transform3D &p_from, const Vector3 &p_distance, const Ref<KinematicCollision3D> &r_collision = Ref<KinematicCollision3D>(), real_t p_margin = 0.001, bool p_recovery_as_collision = false, int p_max_collisions = 1); void set_axis_lock(PhysicsServer3D::BodyAxis p_axis, bool p_lock); bool get_axis_lock(PhysicsServer3D::BodyAxis p_axis) const; @@ -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/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index 9979052385..ff05e88241 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -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..e7d1a8ec7d 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -167,8 +167,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.")); diff --git a/scene/3d/shape_cast_3d.h b/scene/3d/shape_cast_3d.h index 5bda15e4b0..2526d8d32c 100644 --- a/scene/3d/shape_cast_3d.h +++ b/scene/3d/shape_cast_3d.h @@ -136,7 +136,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 e04e1866db..85b2c5154b 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -45,7 +45,6 @@ void SkinReference::_skin_changed() { } void SkinReference::_bind_methods() { - ClassDB::bind_method(D_METHOD("_skin_changed"), &SkinReference::_skin_changed); ClassDB::bind_method(D_METHOD("get_skeleton"), &SkinReference::get_skeleton); ClassDB::bind_method(D_METHOD("get_skin"), &SkinReference::get_skin); } @@ -1007,7 +1006,7 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) { skin_bindings.insert(skin_ref.operator->()); - skin_ref->skin->connect("changed", Callable(skin_ref.operator->(), "_skin_changed")); + skin_ref->skin->connect("changed", callable_mp(skin_ref.operator->(), &SkinReference::_skin_changed)); _make_dirty(); // Skin needs to be updated, so update skeleton. diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 5e49dfa1f4..c2e9cfcced 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -51,12 +51,14 @@ class SkinReference : public RefCounted { uint64_t skeleton_version = 0; Vector<uint32_t> skin_bone_indices; uint32_t *skin_bone_indices_ptrs = nullptr; - void _skin_changed(); protected: static void _bind_methods(); public: + // Public for use with callable_mp. + void _skin_changed(); + RID get_skeleton() const; Ref<Skin> get_skin() const; ~SkinReference(); 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 4515277dc3..be6eab2178 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -30,7 +30,6 @@ #include "sprite_3d.h" -#include "core/core_string_names.h" #include "scene/scene_string_names.h" Color SpriteBase3D::_get_color_accum() { @@ -58,7 +57,7 @@ void SpriteBase3D::_propagate_color_changed() { } color_dirty = true; - _queue_update(); + _queue_redraw(); for (SpriteBase3D *&E : children) { E->_propagate_color_changed(); @@ -88,9 +87,185 @@ 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_update(); + _queue_redraw(); } bool SpriteBase3D::is_centered() const { @@ -99,7 +274,7 @@ bool SpriteBase3D::is_centered() const { void SpriteBase3D::set_offset(const Point2 &p_offset) { offset = p_offset; - _queue_update(); + _queue_redraw(); } Point2 SpriteBase3D::get_offset() const { @@ -108,7 +283,7 @@ Point2 SpriteBase3D::get_offset() const { void SpriteBase3D::set_flip_h(bool p_flip) { hflip = p_flip; - _queue_update(); + _queue_redraw(); } bool SpriteBase3D::is_flipped_h() const { @@ -117,7 +292,7 @@ bool SpriteBase3D::is_flipped_h() const { void SpriteBase3D::set_flip_v(bool p_flip) { vflip = p_flip; - _queue_update(); + _queue_redraw(); } bool SpriteBase3D::is_flipped_v() const { @@ -127,7 +302,7 @@ bool SpriteBase3D::is_flipped_v() const { void SpriteBase3D::set_modulate(const Color &p_color) { modulate = p_color; _propagate_color_changed(); - _queue_update(); + _queue_redraw(); } Color SpriteBase3D::get_modulate() const { @@ -137,7 +312,7 @@ Color SpriteBase3D::get_modulate() const { void SpriteBase3D::set_render_priority(int p_priority) { ERR_FAIL_COND(p_priority < RS::MATERIAL_RENDER_PRIORITY_MIN || p_priority > RS::MATERIAL_RENDER_PRIORITY_MAX); render_priority = p_priority; - _queue_update(); + _queue_redraw(); } int SpriteBase3D::get_render_priority() const { @@ -146,7 +321,7 @@ int SpriteBase3D::get_render_priority() const { void SpriteBase3D::set_pixel_size(real_t p_amount) { pixel_size = p_amount; - _queue_update(); + _queue_redraw(); } real_t SpriteBase3D::get_pixel_size() const { @@ -156,7 +331,7 @@ real_t SpriteBase3D::get_pixel_size() const { void SpriteBase3D::set_axis(Vector3::Axis p_axis) { ERR_FAIL_INDEX(p_axis, 3); axis = p_axis; - _queue_update(); + _queue_redraw(); } Vector3::Axis SpriteBase3D::get_axis() const { @@ -171,7 +346,8 @@ void SpriteBase3D::_im_update() { //texture->draw_rect_region(ci,dst_rect,src_rect,modulate); } -void SpriteBase3D::_queue_update() { +void SpriteBase3D::_queue_redraw() { + // The 3D equivalent of CanvasItem.queue_redraw(). if (pending_update) { return; } @@ -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, }; @@ -250,7 +426,7 @@ Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const { void SpriteBase3D::set_draw_flag(DrawFlags p_flag, bool p_enable) { ERR_FAIL_INDEX(p_flag, FLAG_MAX); flags[p_flag] = p_enable; - _queue_update(); + _queue_redraw(); } bool SpriteBase3D::get_draw_flag(DrawFlags p_flag) const { @@ -261,7 +437,7 @@ bool SpriteBase3D::get_draw_flag(DrawFlags p_flag) const { void SpriteBase3D::set_alpha_cut_mode(AlphaCutMode p_mode) { ERR_FAIL_INDEX(p_mode, 3); alpha_cut = p_mode; - _queue_update(); + _queue_redraw(); } SpriteBase3D::AlphaCutMode SpriteBase3D::get_alpha_cut_mode() const { @@ -269,9 +445,9 @@ SpriteBase3D::AlphaCutMode SpriteBase3D::get_alpha_cut_mode() const { } void SpriteBase3D::set_billboard_mode(StandardMaterial3D::BillboardMode p_mode) { - ERR_FAIL_INDEX(p_mode, 3); + ERR_FAIL_INDEX(p_mode, 3); // Cannot use BILLBOARD_PARTICLES. billboard_mode = p_mode; - _queue_update(); + _queue_redraw(); } StandardMaterial3D::BillboardMode SpriteBase3D::get_billboard_mode() const { @@ -281,7 +457,7 @@ StandardMaterial3D::BillboardMode SpriteBase3D::get_billboard_mode() const { void SpriteBase3D::set_texture_filter(StandardMaterial3D::TextureFilter p_filter) { if (texture_filter != p_filter) { texture_filter = p_filter; - _queue_update(); + _queue_redraw(); } } @@ -329,7 +505,6 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_rect"), &SpriteBase3D::get_item_rect); ClassDB::bind_method(D_METHOD("generate_triangle_mesh"), &SpriteBase3D::generate_triangle_mesh); - ClassDB::bind_method(D_METHOD("_queue_update"), &SpriteBase3D::_queue_update); ClassDB::bind_method(D_METHOD("_im_update"), &SpriteBase3D::_im_update); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); @@ -368,7 +543,7 @@ SpriteBase3D::SpriteBase3D() { } material = RenderingServer::get_singleton()->material_create(); - // Set defaults for material, names need to match up those in StandardMaterial3D + // Set defaults for material, names need to match up those in StandardMaterial3D. RS::get_singleton()->material_set_param(material, "albedo", Color(1, 1, 1, 1)); RS::get_singleton()->material_set_param(material, "specular", 0.5); RS::get_singleton()->material_set_param(material, "metallic", 0.0); @@ -394,7 +569,7 @@ SpriteBase3D::SpriteBase3D() { mesh_colors.resize(4); mesh_uvs.resize(4); - // create basic mesh and store format information + // Create basic mesh and store format information. for (int i = 0; i < 4; i++) { mesh_normals.write[i] = Vector3(0.0, 0.0, 0.0); mesh_tangents.write[i * 4 + 0] = 0.0; @@ -448,7 +623,7 @@ void Sprite3D::_draw() { if (get_base() != get_mesh()) { set_base(get_mesh()); } - if (!texture.is_valid()) { + if (texture.is_null()) { set_base(RID()); return; } @@ -465,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; - - // 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); + Rect2 dst_rect(dst_offset, frame_size); - 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) { @@ -637,13 +658,14 @@ void Sprite3D::set_texture(const Ref<Texture2D> &p_texture) { return; } if (texture.is_valid()) { - texture->disconnect(CoreStringNames::get_singleton()->changed, Callable(this, "_queue_update")); + texture->disconnect(SceneStringNames::get_singleton()->changed, callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw)); } texture = p_texture; if (texture.is_valid()) { - texture->connect(CoreStringNames::get_singleton()->changed, Callable(this, "_queue_update")); + texture->connect(SceneStringNames::get_singleton()->changed, callable_mp((SpriteBase3D *)this, &Sprite3D::_queue_redraw)); } - _queue_update(); + + _queue_redraw(); emit_signal(SceneStringNames::get_singleton()->texture_changed); } @@ -657,7 +679,7 @@ void Sprite3D::set_region_enabled(bool p_region) { } region = p_region; - _queue_update(); + _queue_redraw(); } bool Sprite3D::is_region_enabled() const { @@ -668,7 +690,7 @@ void Sprite3D::set_region_rect(const Rect2 &p_region_rect) { bool changed = region_rect != p_region_rect; region_rect = p_region_rect; if (region && changed) { - _queue_update(); + _queue_redraw(); } } @@ -681,7 +703,7 @@ void Sprite3D::set_frame(int p_frame) { frame = p_frame; - _queue_update(); + _queue_redraw(); emit_signal(SceneStringNames::get_singleton()->frame_changed); } @@ -704,7 +726,7 @@ Vector2i Sprite3D::get_frame_coords() const { void Sprite3D::set_vframes(int p_amount) { ERR_FAIL_COND(p_amount < 1); vframes = p_amount; - _queue_update(); + _queue_redraw(); notify_property_list_changed(); } @@ -715,7 +737,7 @@ int Sprite3D::get_vframes() const { void Sprite3D::set_hframes(int p_amount) { ERR_FAIL_COND(p_amount < 1); hframes = p_amount; - _queue_update(); + _queue_redraw(); notify_property_list_changed(); } @@ -788,7 +810,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"); @@ -807,22 +829,14 @@ void AnimatedSprite3D::_draw() { set_base(get_mesh()); } - if (frames.is_null()) { - return; - } - - if (frame < 0) { - return; - } - - if (!frames->has_animation(animation)) { + if (frames.is_null() || !frames->has_animation(animation)) { return; } Ref<Texture2D> texture = frames->get_frame(animation, frame); - if (!texture.is_valid()) { + if (texture.is_null()) { set_base(RID()); - return; //no texuture no life + return; } Size2 tsize = texture->get_size(); if (tsize.x == 0 || tsize.y == 0) { @@ -839,164 +853,14 @@ 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 { if (!frames.is_valid()) { return; } + if (p_property.name == "animation") { p_property.hint = PROPERTY_HINT_ENUM; List<StringName> names; @@ -1026,9 +890,15 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &p_property) const { p_property.hint_string = String(animation) + "," + p_property.hint_string; } } + return; } if (p_property.name == "frame") { + if (playing) { + p_property.usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY; + return; + } + p_property.hint = PROPERTY_HINT_RANGE; if (frames->has_animation(animation) && frames->get_frame_count(animation) > 0) { p_property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; @@ -1043,40 +913,57 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &p_property) const { void AnimatedSprite3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_INTERNAL_PROCESS: { - if (frames.is_null()) { - return; - } - if (!frames->has_animation(animation)) { + if (frames.is_null() || !frames->has_animation(animation)) { return; } - if (frame < 0) { - return; + + double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale); + if (speed == 0) { + return; // Do nothing. } + int last_frame = frames->get_frame_count(animation) - 1; double remaining = get_process_delta_time(); - while (remaining) { - double speed = frames->get_animation_speed(animation); - if (speed == 0) { - return; // Do nothing. - } - if (timeout <= 0) { - timeout = 1.0 / speed; - - int fc = frames->get_frame_count(animation); - if (frame >= fc - 1) { - if (frames->get_animation_loop(animation)) { - frame = 0; + timeout = _get_frame_duration(); + + if (!playing_backwards) { + // Forward. + if (frame >= last_frame) { + if (frames->get_animation_loop(animation)) { + frame = 0; + emit_signal(SceneStringNames::get_singleton()->animation_finished); + } else { + frame = last_frame; + if (!is_over) { + is_over = true; + emit_signal(SceneStringNames::get_singleton()->animation_finished); + } + } } else { - frame = fc - 1; + frame++; } - emit_signal(SceneStringNames::get_singleton()->animation_finished); } else { - frame++; + // Reversed. + if (frame <= 0) { + if (frames->get_animation_loop(animation)) { + frame = last_frame; + emit_signal(SceneStringNames::get_singleton()->animation_finished); + } else { + frame = 0; + if (!is_over) { + is_over = true; + emit_signal(SceneStringNames::get_singleton()->animation_finished); + } + } + } else { + frame--; + } } - _queue_update(); + _queue_redraw(); + emit_signal(SceneStringNames::get_singleton()->frame_changed); } @@ -1090,14 +977,15 @@ void AnimatedSprite3D::_notification(int p_what) { void AnimatedSprite3D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { if (frames.is_valid()) { - frames->disconnect("changed", Callable(this, "_res_changed")); + frames->disconnect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed)); } + frames = p_frames; if (frames.is_valid()) { - frames->connect("changed", Callable(this, "_res_changed")); + frames->connect(SceneStringNames::get_singleton()->changed, callable_mp(this, &AnimatedSprite3D::_res_changed)); } - if (!frames.is_valid()) { + if (frames.is_null()) { frame = 0; } else { set_frame(frame); @@ -1105,7 +993,7 @@ void AnimatedSprite3D::set_sprite_frames(const Ref<SpriteFrames> &p_frames) { notify_property_list_changed(); _reset_timeout(); - _queue_update(); + _queue_redraw(); update_configuration_warnings(); } @@ -1114,7 +1002,7 @@ Ref<SpriteFrames> AnimatedSprite3D::get_sprite_frames() const { } void AnimatedSprite3D::set_frame(int p_frame) { - if (!frames.is_valid()) { + if (frames.is_null()) { return; } @@ -1135,7 +1023,8 @@ void AnimatedSprite3D::set_frame(int p_frame) { frame = p_frame; _reset_timeout(); - _queue_update(); + _queue_redraw(); + emit_signal(SceneStringNames::get_singleton()->frame_changed); } @@ -1143,8 +1032,30 @@ int AnimatedSprite3D::get_frame() const { return frame; } +void AnimatedSprite3D::set_speed_scale(double p_speed_scale) { + if (speed_scale == p_speed_scale) { + return; + } + + double elapsed = _get_frame_duration() - timeout; + + speed_scale = p_speed_scale; + playing_backwards = signbit(speed_scale) != backwards; + + // We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed. + _reset_timeout(); + timeout -= elapsed; +} + +double AnimatedSprite3D::get_speed_scale() const { + return speed_scale; +} + Rect2 AnimatedSprite3D::get_item_rect() const { - if (!frames.is_valid() || !frames->has_animation(animation) || frame < 0 || frame >= frames->get_frame_count(animation)) { + if (frames.is_null() || !frames->has_animation(animation)) { + return Rect2(0, 0, 1, 1); + } + if (frame < 0 || frame >= frames->get_frame_count(animation)) { return Rect2(0, 0, 1, 1); } @@ -1171,7 +1082,8 @@ Rect2 AnimatedSprite3D::get_item_rect() const { void AnimatedSprite3D::_res_changed() { set_frame(frame); - _queue_update(); + + _queue_redraw(); } void AnimatedSprite3D::set_playing(bool p_playing) { @@ -1181,16 +1093,25 @@ void AnimatedSprite3D::set_playing(bool p_playing) { playing = p_playing; _reset_timeout(); set_process_internal(playing); + notify_property_list_changed(); } bool AnimatedSprite3D::is_playing() const { return playing; } -void AnimatedSprite3D::play(const StringName &p_animation) { +void AnimatedSprite3D::play(const StringName &p_animation, bool p_backwards) { + backwards = p_backwards; + playing_backwards = signbit(speed_scale) != backwards; + if (p_animation) { set_animation(p_animation); + if (frames.is_valid() && playing_backwards && get_frame() == 0) { + set_frame(frames->get_frame_count(p_animation) - 1); + } } + + is_over = false; set_playing(true); } @@ -1198,24 +1119,28 @@ void AnimatedSprite3D::stop() { set_playing(false); } +double AnimatedSprite3D::_get_frame_duration() { + if (frames.is_valid() && frames->has_animation(animation)) { + double speed = frames->get_animation_speed(animation) * Math::abs(speed_scale); + if (speed > 0) { + return 1.0 / speed; + } + } + return 0.0; +} + void AnimatedSprite3D::_reset_timeout() { if (!playing) { return; } - if (frames.is_valid() && frames->has_animation(animation)) { - float speed = frames->get_animation_speed(animation); - if (speed > 0) { - timeout = 1.0 / speed; - } else { - timeout = 0; - } - } else { - timeout = 0; - } + timeout = _get_frame_duration(); + is_over = false; } void AnimatedSprite3D::set_animation(const StringName &p_animation) { + ERR_FAIL_COND_MSG(frames == nullptr, vformat("There is no animation with name '%s'.", p_animation)); + ERR_FAIL_COND_MSG(!frames->get_animation_names().has(p_animation), vformat("There is no animation with name '%s'.", p_animation)); if (animation == p_animation) { return; } @@ -1224,15 +1149,15 @@ void AnimatedSprite3D::set_animation(const StringName &p_animation) { _reset_timeout(); set_frame(0); notify_property_list_changed(); - _queue_update(); + _queue_redraw(); } 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.")); } @@ -1261,12 +1186,15 @@ void AnimatedSprite3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_playing", "playing"), &AnimatedSprite3D::set_playing); ClassDB::bind_method(D_METHOD("is_playing"), &AnimatedSprite3D::is_playing); - ClassDB::bind_method(D_METHOD("play", "anim"), &AnimatedSprite3D::play, DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("play", "anim", "backwards"), &AnimatedSprite3D::play, DEFVAL(StringName()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("stop"), &AnimatedSprite3D::stop); ClassDB::bind_method(D_METHOD("set_frame", "frame"), &AnimatedSprite3D::set_frame); ClassDB::bind_method(D_METHOD("get_frame"), &AnimatedSprite3D::get_frame); + ClassDB::bind_method(D_METHOD("set_speed_scale", "speed_scale"), &AnimatedSprite3D::set_speed_scale); + ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedSprite3D::get_speed_scale); + ClassDB::bind_method(D_METHOD("_res_changed"), &AnimatedSprite3D::_res_changed); ADD_SIGNAL(MethodInfo("frame_changed")); @@ -1275,6 +1203,7 @@ void AnimatedSprite3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation"); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "set_playing", "is_playing"); } diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index 84244a2476..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; } @@ -106,7 +110,7 @@ protected: uint32_t skin_stride = 0; uint32_t mesh_surface_format = 0; - void _queue_update(); + void _queue_redraw(); public: void set_centered(bool p_center); @@ -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(); @@ -209,20 +210,22 @@ class AnimatedSprite3D : public SpriteBase3D { Ref<SpriteFrames> frames; bool playing = false; + bool playing_backwards = false; + bool backwards = false; StringName animation = "default"; int frame = 0; + float speed_scale = 1.0f; bool centered = false; + bool is_over = false; double timeout = 0.0; void _res_changed(); + double _get_frame_duration(); void _reset_timeout(); - RID last_shader; - RID last_texture; - protected: virtual void _draw() override; static void _bind_methods(); @@ -233,7 +236,7 @@ public: void set_sprite_frames(const Ref<SpriteFrames> &p_frames); Ref<SpriteFrames> get_sprite_frames() const; - void play(const StringName &p_animation = StringName()); + void play(const StringName &p_animation = StringName(), bool p_backwards = false); void stop(); void set_playing(bool p_playing); @@ -245,9 +248,12 @@ public: void set_frame(int p_frame); int get_frame() const; + void set_speed_scale(double p_speed_scale); + double get_speed_scale() const; + 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..2013a93e26 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); } ////////////////////////////////////// diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index db9f68544b..e93ad5ecbf 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -385,8 +385,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.")); diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index 100d8d8836..4755545516 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -186,7 +186,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..f5d30c584f 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,44 @@ 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); + } + } + } 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 +672,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; - - case NOTIFICATION_INTERNAL_PROCESS: { - // set our world origin to our node transform - xr_server->set_world_origin(get_global_transform()); + if (!Engine::get_singleton()->is_editor_hint()) { + origin_nodes.erase(this); - // 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 99f17a1eef..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)); @@ -1175,6 +1175,7 @@ void AnimationNodeBlendTree::_tree_changed() { void AnimationNodeBlendTree::_node_changed(const StringName &p_node) { ERR_FAIL_COND(!nodes.has(p_node)); nodes[p_node].connections.resize(nodes[p_node].node->get_input_count()); + emit_signal(SNAME("node_changed"), p_node); } void AnimationNodeBlendTree::_bind_methods() { @@ -1200,6 +1201,8 @@ void AnimationNodeBlendTree::_bind_methods() { BIND_CONSTANT(CONNECTION_ERROR_NO_OUTPUT); BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE); BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS); + + ADD_SIGNAL(MethodInfo("node_changed", PropertyInfo(Variant::STRING_NAME, "node_name"))); } void AnimationNodeBlendTree::_initialize_node_tree() { 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 096f4edee2..e306d00a51 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); } @@ -650,15 +650,14 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double double c = Math::ease(p_time / first_key_time, transition); Variant first_value = a->track_get_key_value(i, first_key); first_value = _post_process_key_value(a, i, first_value, nc->node); - Variant interp_value; - Variant::interpolate(pa->capture, first_value, c, interp_value); + Variant interp_value = Animation::interpolate_variant(pa->capture, first_value, c); if (pa->accum_pass != accum_pass) { ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX); cache_update_prop[cache_update_prop_size++] = pa; pa->value_accum = interp_value; pa->accum_pass = accum_pass; } else { - Variant::interpolate(pa->value_accum, interp_value, p_interp, pa->value_accum); + pa->value_accum = Animation::interpolate_variant(pa->value_accum, interp_value, p_interp); } continue; //handled @@ -679,7 +678,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double pa->value_accum = value; pa->accum_pass = accum_pass; } else { - Variant::interpolate(pa->value_accum, value, p_interp, pa->value_accum); + pa->value_accum = Animation::interpolate_variant(pa->value_accum, value, p_interp); } } else if (p_is_current && p_delta != 0) { @@ -1270,9 +1269,28 @@ void AnimationPlayer::_animation_set_cache_update() { // If something was modified or removed, caches need to be cleared clear_caches(); } + + emit_signal(SNAME("animation_list_changed")); } 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(); } @@ -1282,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); @@ -1302,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; @@ -1375,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(); @@ -1398,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(); @@ -1437,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) { @@ -1503,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 { @@ -1522,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."); @@ -1537,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; @@ -1570,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) == "") { @@ -1586,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; @@ -1740,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)); @@ -1761,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(); } @@ -1932,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; } @@ -2036,8 +2064,7 @@ 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(); @@ -2150,6 +2177,8 @@ void AnimationPlayer::_bind_methods() { ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING_NAME, "anim_name"))); 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 d06324f0aa..f5c7ad254c 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -97,8 +97,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 +160,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; } @@ -847,6 +847,11 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { return true; } +void AnimationTree::_animation_player_changed() { + emit_signal(SNAME("animation_player_changed")); + _clear_caches(); +} + void AnimationTree::_clear_caches() { for (KeyValue<NodePath, TrackCache *> &K : track_cache) { memdelete(K.value); @@ -1378,8 +1383,13 @@ void AnimationTree::_process_graph(double p_delta) { } t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU); } else { - Variant::sub(value, t->init_value, value); - Variant::blend(t->value, value, blend, t->value); + if (t->init_value.get_type() == Variant::BOOL) { + value = Animation::subtract_variant(value.operator real_t(), t->init_value.operator real_t()); + t->value = Animation::blend_variant(t->value.operator real_t(), value.operator real_t(), blend); + } else { + value = Animation::subtract_variant(value, t->init_value); + t->value = Animation::blend_variant(t->value, value, blend); + } } } else { if (blend < CMP_EPSILON) { @@ -1555,11 +1565,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) { @@ -1698,7 +1704,11 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_VALUE: { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); - t->object->set_indexed(t->subpath, t->value); + if (t->init_value.get_type() == Variant::BOOL) { + t->object->set_indexed(t->subpath, t->value.operator real_t() >= 0.5); + } else { + t->object->set_indexed(t->subpath, t->value); + } } break; case Animation::TYPE_BEZIER: { @@ -1731,13 +1741,14 @@ 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); } void AnimationTree::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + _setup_animation_player(); if (last_animation_player.is_valid()) { Object *player = ObjectDB::get_instance(last_animation_player); if (player) { @@ -1770,8 +1781,43 @@ void AnimationTree::_notification(int p_what) { } } +void AnimationTree::_setup_animation_player() { + if (!is_inside_tree()) { + return; + } + + AnimationPlayer *new_player = nullptr; + if (!animation_player.is_empty()) { + new_player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); + if (new_player && !new_player->is_connected("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed))) { + new_player->connect("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed)); + } + } + + if (new_player) { + if (!last_animation_player.is_valid()) { + // Animation player set newly. + emit_signal(SNAME("animation_player_changed")); + return; + } else if (last_animation_player == new_player->get_instance_id()) { + // Animation player isn't changed. + return; + } + } else if (!last_animation_player.is_valid()) { + // Animation player is being empty. + return; + } + + AnimationPlayer *old_player = Object::cast_to<AnimationPlayer>(ObjectDB::get_instance(last_animation_player)); + if (old_player && old_player->is_connected("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed))) { + old_player->disconnect("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed)); + } + emit_signal(SNAME("animation_player_changed")); +} + void AnimationTree::set_animation_player(const NodePath &p_player) { animation_player = p_player; + _setup_animation_player(); update_configuration_warnings(); } @@ -1799,8 +1845,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.")); @@ -2008,6 +2054,8 @@ void AnimationTree::_bind_methods() { BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); + + ADD_SIGNAL(MethodInfo("animation_player_changed")); } AnimationTree::AnimationTree() { diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index ee0c0303dc..96c1279f82 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -282,6 +282,8 @@ private: bool cache_valid = false; void _node_removed(Node *p_node); + void _setup_animation_player(); + void _animation_player_changed(); void _clear_caches(); bool _update_caches(AnimationPlayer *player); void _process_graph(double p_delta); @@ -340,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; @@ -351,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 5b18d4e457..aa58e1044a 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -32,6 +32,7 @@ #include "scene/animation/easing_equations.h" #include "scene/main/node.h" +#include "scene/resources/animation.h" Tween::interpolater Tween::interpolaters[Tween::TRANS_MAX][Tween::EASE_MAX] = { { &linear::in, &linear::in, &linear::in, &linear::in }, // Linear is the same for each easing. @@ -70,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 { @@ -92,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."); @@ -110,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."); @@ -244,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); @@ -252,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; } @@ -262,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 { @@ -281,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); @@ -314,6 +315,7 @@ bool Tween::step(float p_delta) { running = false; dead = true; emit_signal(SNAME("finished")); + break; } else { emit_signal(SNAME("loop_finished"), loops_done); current_step = 0; @@ -340,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(); } } @@ -357,7 +359,7 @@ Node *Tween::get_bound_node() const { } } -float Tween::get_total_time() const { +double Tween::get_total_time() const { return total_time; } @@ -371,268 +373,18 @@ 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()); -// Helper macro to run equation on sub-elements of the value (e.g. x and y of Vector2). -#define APPLY_EQUATION(element) \ - r.element = run_equation(p_trans, p_ease, p_time, i.element, d.element, p_duration); - - switch (p_initial_val.get_type()) { - case Variant::BOOL: { - return (run_equation(p_trans, p_ease, p_time, p_initial_val, p_delta_val, p_duration)) >= 0.5; - } - - case Variant::INT: { - return (int)run_equation(p_trans, p_ease, p_time, (int)p_initial_val, (int)p_delta_val, p_duration); - } - - case Variant::FLOAT: { - return run_equation(p_trans, p_ease, p_time, (real_t)p_initial_val, (real_t)p_delta_val, p_duration); - } - - case Variant::VECTOR2: { - Vector2 i = p_initial_val; - Vector2 d = p_delta_val; - Vector2 r; - - APPLY_EQUATION(x); - APPLY_EQUATION(y); - return r; - } - - case Variant::VECTOR2I: { - Vector2i i = p_initial_val; - Vector2i d = p_delta_val; - Vector2i r; - - APPLY_EQUATION(x); - APPLY_EQUATION(y); - return r; - } - - case Variant::RECT2: { - Rect2 i = p_initial_val; - Rect2 d = p_delta_val; - Rect2 r; - - APPLY_EQUATION(position.x); - APPLY_EQUATION(position.y); - APPLY_EQUATION(size.x); - APPLY_EQUATION(size.y); - return r; - } - - case Variant::RECT2I: { - Rect2i i = p_initial_val; - Rect2i d = p_delta_val; - Rect2i r; - - APPLY_EQUATION(position.x); - APPLY_EQUATION(position.y); - APPLY_EQUATION(size.x); - APPLY_EQUATION(size.y); - return r; - } - - case Variant::VECTOR3: { - Vector3 i = p_initial_val; - Vector3 d = p_delta_val; - Vector3 r; - - APPLY_EQUATION(x); - APPLY_EQUATION(y); - APPLY_EQUATION(z); - return r; - } - - case Variant::VECTOR3I: { - Vector3i i = p_initial_val; - Vector3i d = p_delta_val; - Vector3i r; - - APPLY_EQUATION(x); - APPLY_EQUATION(y); - APPLY_EQUATION(z); - return r; - } - - case Variant::TRANSFORM2D: { - Transform2D i = p_initial_val; - Transform2D d = p_delta_val; - Transform2D r; - - APPLY_EQUATION(columns[0][0]); - APPLY_EQUATION(columns[0][1]); - APPLY_EQUATION(columns[1][0]); - APPLY_EQUATION(columns[1][1]); - APPLY_EQUATION(columns[2][0]); - APPLY_EQUATION(columns[2][1]); - return r; - } - case Variant::VECTOR4: { - Vector4 i = p_initial_val; - Vector4 d = p_delta_val; - Vector4 r; - - APPLY_EQUATION(x); - APPLY_EQUATION(y); - APPLY_EQUATION(z); - APPLY_EQUATION(w); - return r; - } - - case Variant::QUATERNION: { - Quaternion i = p_initial_val; - Quaternion d = p_delta_val; - Quaternion r = i * d; - r = i.slerp(r, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration)); - return r; - } - - case Variant::AABB: { - AABB i = p_initial_val; - AABB d = p_delta_val; - AABB r; - - APPLY_EQUATION(position.x); - APPLY_EQUATION(position.y); - APPLY_EQUATION(position.z); - APPLY_EQUATION(size.x); - APPLY_EQUATION(size.y); - APPLY_EQUATION(size.z); - return r; - } - - case Variant::BASIS: { - Basis i = p_initial_val; - Basis d = p_delta_val; - Basis r; - - APPLY_EQUATION(rows[0][0]); - APPLY_EQUATION(rows[0][1]); - APPLY_EQUATION(rows[0][2]); - APPLY_EQUATION(rows[1][0]); - APPLY_EQUATION(rows[1][1]); - APPLY_EQUATION(rows[1][2]); - APPLY_EQUATION(rows[2][0]); - APPLY_EQUATION(rows[2][1]); - APPLY_EQUATION(rows[2][2]); - return r; - } - - case Variant::TRANSFORM3D: { - Transform3D i = p_initial_val; - Transform3D d = p_delta_val; - Transform3D r; - - APPLY_EQUATION(basis.rows[0][0]); - APPLY_EQUATION(basis.rows[0][1]); - APPLY_EQUATION(basis.rows[0][2]); - APPLY_EQUATION(basis.rows[1][0]); - APPLY_EQUATION(basis.rows[1][1]); - APPLY_EQUATION(basis.rows[1][2]); - APPLY_EQUATION(basis.rows[2][0]); - APPLY_EQUATION(basis.rows[2][1]); - APPLY_EQUATION(basis.rows[2][2]); - APPLY_EQUATION(origin.x); - APPLY_EQUATION(origin.y); - APPLY_EQUATION(origin.z); - return r; - } - - case Variant::COLOR: { - Color i = p_initial_val; - Color d = p_delta_val; - Color r; - - APPLY_EQUATION(r); - APPLY_EQUATION(g); - APPLY_EQUATION(b); - APPLY_EQUATION(a); - return r; - } - - default: { - return p_initial_val; - } - }; -#undef APPLY_EQUATION -} - -Variant Tween::calculate_delta_value(Variant p_intial_val, Variant p_final_val) { - ERR_FAIL_COND_V_MSG(p_intial_val.get_type() != p_final_val.get_type(), p_intial_val, "Type mismatch between initial and final value: " + Variant::get_type_name(p_intial_val.get_type()) + " and " + Variant::get_type_name(p_final_val.get_type())); - - switch (p_intial_val.get_type()) { - case Variant::BOOL: { - return (int)p_final_val - (int)p_intial_val; - } - - case Variant::RECT2: { - Rect2 i = p_intial_val; - Rect2 f = p_final_val; - return Rect2(f.position - i.position, f.size - i.size); - } - - case Variant::RECT2I: { - Rect2i i = p_intial_val; - Rect2i f = p_final_val; - return Rect2i(f.position - i.position, f.size - i.size); - } - - case Variant::TRANSFORM2D: { - Transform2D i = p_intial_val; - Transform2D f = p_final_val; - return Transform2D(f.columns[0][0] - i.columns[0][0], - f.columns[0][1] - i.columns[0][1], - f.columns[1][0] - i.columns[1][0], - f.columns[1][1] - i.columns[1][1], - f.columns[2][0] - i.columns[2][0], - f.columns[2][1] - i.columns[2][1]); - } - - case Variant::AABB: { - AABB i = p_intial_val; - AABB f = p_final_val; - return AABB(f.position - i.position, f.size - i.size); - } - - case Variant::BASIS: { - Basis i = p_intial_val; - Basis f = p_final_val; - return Basis(f.rows[0][0] - i.rows[0][0], - f.rows[0][1] - i.rows[0][1], - f.rows[0][2] - i.rows[0][2], - f.rows[1][0] - i.rows[1][0], - f.rows[1][1] - i.rows[1][1], - f.rows[1][2] - i.rows[1][2], - f.rows[2][0] - i.rows[2][0], - f.rows[2][1] - i.rows[2][1], - f.rows[2][2] - i.rows[2][2]); - } - - case Variant::TRANSFORM3D: { - Transform3D i = p_intial_val; - Transform3D f = p_final_val; - return Transform3D(f.basis.rows[0][0] - i.basis.rows[0][0], - f.basis.rows[0][1] - i.basis.rows[0][1], - f.basis.rows[0][2] - i.basis.rows[0][2], - f.basis.rows[1][0] - i.basis.rows[1][0], - f.basis.rows[1][1] - i.basis.rows[1][1], - f.basis.rows[1][2] - i.basis.rows[1][2], - f.basis.rows[2][0] - i.basis.rows[2][0], - f.basis.rows[2][1] - i.basis.rows[2][1], - f.basis.rows[2][2] - i.basis.rows[2][2], - f.origin.x - i.origin.x, - f.origin.y - i.origin.y, - f.origin.z - i.origin.z); - } + // Special case for bool. + if (p_initial_val.get_type() == Variant::BOOL) { + return run_equation(p_trans, p_ease, p_time, p_initial_val, p_delta_val, p_duration) >= 0.5; + } - default: { - return Variant::evaluate(Variant::OP_SUBTRACT, p_final_val, p_intial_val); - } - }; + Variant ret = Animation::add_variant(p_initial_val, p_delta_val); + ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration)); + return ret; } void Tween::_bind_methods() { @@ -728,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; } @@ -748,13 +500,13 @@ void PropertyTweener::start() { } if (relative) { - final_val = Variant::evaluate(Variant::Operator::OP_ADD, initial_val, base_final_val); + final_val = Animation::add_variant(initial_val, base_final_val); } - delta_val = tween->calculate_delta_value(initial_val, final_val); + 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; @@ -771,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; @@ -804,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); @@ -822,7 +574,7 @@ void IntervalTweener::start() { finished = false; } -bool IntervalTweener::step(float &r_delta) { +bool IntervalTweener::step(double &r_delta) { if (finished) { return false; } @@ -840,7 +592,7 @@ bool IntervalTweener::step(float &r_delta) { } } -IntervalTweener::IntervalTweener(float p_time) { +IntervalTweener::IntervalTweener(double p_time) { duration = p_time; } @@ -848,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; } @@ -858,7 +610,7 @@ void CallbackTweener::start() { finished = false; } -bool CallbackTweener::step(float &r_delta) { +bool CallbackTweener::step(double &r_delta) { if (finished) { return false; } @@ -894,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; } @@ -914,7 +666,7 @@ void MethodTweener::start() { finished = false; } -bool MethodTweener::step(float &r_delta) { +bool MethodTweener::step(double &r_delta) { if (finished) { return false; } @@ -927,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 { @@ -970,10 +722,10 @@ 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 = tween->calculate_delta_value(p_from, p_to); + delta_val = Animation::subtract_variant(p_to, p_from); final_val = p_to; duration = p_duration; } diff --git a/scene/animation/tween.h b/scene/animation/tween.h index b57ec2e5e7..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,13 +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); - Variant calculate_delta_value(Variant p_intial_val, Variant p_final_val); + 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); @@ -189,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: @@ -209,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; }; @@ -223,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(); @@ -249,7 +248,7 @@ protected: private: Callable callback; - float delay = 0; + double delay = 0; }; class MethodTweener : public Tweener { @@ -258,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..bfc3c25fe6 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -450,9 +450,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)); diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index cf467ceafb..552345e4fe 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) { @@ -264,7 +264,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 +273,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); @@ -291,8 +290,6 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const { return DRAW_NORMAL; } } - - return DRAW_NORMAL; } void BaseButton::set_toggle_mode(bool p_on) { @@ -352,10 +349,6 @@ 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)) { on_action_event(p_event); accept_event(); @@ -392,34 +385,6 @@ 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::_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)); -} - bool BaseButton::_was_pressed_by_mouse() const { return was_mouse_pressed; } @@ -449,9 +414,6 @@ 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); - GDVIRTUAL_BIND(_pressed); GDVIRTUAL_BIND(_toggled, "button_pressed"); @@ -469,7 +431,6 @@ void BaseButton::_bind_methods() { 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::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..7839239800 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -81,7 +81,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,9 +131,6 @@ 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; - BaseButton(); ~BaseButton(); }; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index f6e0e4216d..8a5d04f49c 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -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; } @@ -609,72 +609,74 @@ 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; } @@ -684,51 +686,65 @@ void CodeEdit::_backspace_internal() { 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, 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(); } /* Indent management */ @@ -803,10 +819,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 +836,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; - - 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(); + 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; + } - /* Ignore the last line if the selection is not past the first column. */ - if (get_selection_to_column() == 0) { - selection_offset = 0; - end_line--; - } - } + if (!indent_using_spaces) { + set_line(i, '\t' + line_text); + continue; + } - for (int i = start_line; i <= end_line; i++) { - const String line_text = get_line(i); - if (line_text.size() == 0 && has_selection()) { - 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; } - if (!indent_using_spaces) { - set_line(i, '\t' + line_text); - 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); } - - /* 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 +894,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 +933,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 +1020,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; + + 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); + } + } + } + } - bool first_line = false; - if (!p_split_current_line) { - deselect(); + bool first_line = false; + if (!p_split_current_line) { + deselect(i); - if (p_above) { - if (cl > 0) { - set_caret_line(cl - 1, false); - set_caret_column(get_line(get_caret_line()).length()); + 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 +1249,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 +1558,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(); } @@ -1941,98 +1990,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 +2139,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 +2437,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); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 2065f3e681..09c7ef80bf 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -261,8 +261,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..3a5013dabe 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; @@ -159,7 +159,7 @@ void ColorModeHSV::slider_draw(int p_which) { } 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(margin * -1, 0), Vector2(margin, size.x)), false); return; } else { Color s_col; @@ -174,10 +174,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 +216,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 +230,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 +245,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; @@ -294,10 +293,10 @@ void ColorModeOKHSL::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; @@ -306,7 +305,7 @@ void ColorModeOKHSL::slider_draw(int p_which) { } 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(margin * -1, 0), Vector2(margin, size.x)), false); return; } else { Color s_col; @@ -321,10 +320,10 @@ void ColorModeOKHSL::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); } diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 4a1f2ab7c6..a2c138c0b2 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; @@ -262,6 +281,11 @@ void ColorPicker::_update_controls() { } 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 +354,62 @@ 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 *s = memnew(HSlider); - s->set_v_size_flags(SIZE_SHRINK_CENTER); - s->set_focus_mode(FOCUS_NONE); - gc->add_child(s); + HSlider *slider = memnew(HSlider); + slider->set_v_size_flags(SIZE_SHRINK_CENTER); + slider->set_focus_mode(FOCUS_NONE); + gc->add_child(slider); - 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)); + SpinBox *val = memnew(SpinBox); + slider->share(val); + gc->add_child(val); - s->set_h_size_flags(SIZE_EXPAND_FILL); + LineEdit *vle = val->get_line_edit(); + vle->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter), CONNECT_DEFERRED); + vle->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); + 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->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); - s->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + val->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); + + 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 +432,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 +486,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 +543,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 +587,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 +604,14 @@ 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; } + shape_popup->set_item_checked(current_shape, false); + 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 +625,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,9 +739,31 @@ 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_delete(); + } + 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. @@ -605,6 +784,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_delete(); + 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 +817,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 +904,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 +916,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) { @@ -859,17 +1130,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 +1190,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 +1211,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 +1266,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,12 +1298,31 @@ 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) { erase_preset(p_color); @@ -1035,6 +1331,22 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_c } } +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; @@ -1063,6 +1375,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,9 +1403,7 @@ 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(); } @@ -1159,7 +1473,6 @@ 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); } @@ -1183,9 +1496,16 @@ void ColorPicker::_bind_methods() { 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"); @@ -1213,7 +1533,7 @@ 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); @@ -1226,24 +1546,82 @@ ColorPicker::ColorPicker() : HBoxContainer *hb_smpl = memnew(HBoxContainer); add_child(hb_smpl, false, INTERNAL_MODE_FRONT); + btn_pick = memnew(Button); + 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)); + sample = memnew(TextureRect); hb_smpl->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); + hb_smpl->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)); + + HBoxContainer *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); @@ -1262,16 +1640,10 @@ ColorPicker::ColorPicker() : alpha_label->set_text("A"); HBoxContainer *hhb = memnew(HBoxContainer); + hhb->set_alignment(ALIGNMENT_BEGIN); vbr->add_child(hhb); - 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)); + hhb->add_child(memnew(Label("Hex"))); text_type = memnew(Button); hhb->add_child(text_type); @@ -1286,9 +1658,9 @@ ColorPicker::ColorPicker() : c_text = memnew(LineEdit); hhb->add_child(c_text); - c_text->set_h_size_flags(SIZE_EXPAND_FILL); 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_entered", callable_mp(this, &ColorPicker::_focus_enter), CONNECT_DEFERRED); c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); wheel_edit = memnew(AspectRatioContainer); @@ -1325,16 +1697,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 +1774,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 +1881,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 +1921,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 +1971,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..6c91575893 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(); }; @@ -96,8 +97,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 +117,20 @@ 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; + 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 +149,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; @@ -150,6 +167,8 @@ private: bool spinning = false; bool presets_enabled = true; bool presets_visible = true; + bool line_edit_mouse_release = false; + bool text_changed = false; float h = 0.0; float s = 0.0; @@ -175,8 +194,12 @@ 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(); @@ -185,8 +208,16 @@ private: 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,13 +249,23 @@ 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; 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..565e450d60 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\".")); @@ -1759,6 +1759,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) { @@ -1793,7 +1821,7 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const } } - bool ret; + bool ret = false; if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { return ret; } @@ -2225,7 +2253,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 { @@ -2873,8 +2913,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 +3161,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); @@ -3194,6 +3237,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..e526690cbe 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. @@ -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; @@ -487,6 +490,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/graph_edit.cpp b/scene/gui/graph_edit.cpp index 69512903b4..caf8db4515 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,6 +190,14 @@ void GraphEditMinimap::_adjust_graph_scroll(const Vector2 &p_offset) { ge->set_scroll_ofs(p_offset + graph_offset - camera_size / 2); } +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.")); + + return warnings; +} + Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { if (is_node_connected(p_from, p_from_port, p_to, p_to_port)) { return OK; @@ -1377,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++) { @@ -1467,7 +1475,7 @@ 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; + bool valid = false; if (GDVIRTUAL_CALL(_is_node_hover_valid, p_from, p_from_port, p_to, p_to_port, valid)) { return valid; } diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 9371ed3df4..101087bdbd 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -287,6 +287,8 @@ protected: GDVIRTUAL4R(bool, _is_node_hover_valid, StringName, int, StringName, int); public: + 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); void disconnect_node(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..223428906a 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,7 +728,7 @@ 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; @@ -778,7 +766,7 @@ 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; @@ -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()) { @@ -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)) { 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..65670b7786 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -971,7 +971,18 @@ void LineEdit::_notification(int p_what) { } 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); } @@ -1047,8 +1058,8 @@ void LineEdit::_notification(int p_what) { 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(); @@ -2164,6 +2175,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 +2203,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))); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index a4d5205f81..861c7f273b 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -199,8 +199,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 +238,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(); 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..2cbece69f2 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) { @@ -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..9a411ef7ed 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); @@ -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..2d2b3e413d 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.")); diff --git a/scene/gui/range.h b/scene/gui/range.h index 87bd0d88af..19452243cf 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -100,7 +100,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_label.cpp b/scene/gui/rich_text_label.cpp index c936fe9738..c96d3c763d 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) { @@ -1181,17 +1180,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 +1205,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 +1220,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 +1236,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 +1255,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 +1263,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); @@ -1281,7 +1283,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // 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); @@ -1664,7 +1666,7 @@ int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const { r = m; } } - return l; + return MIN(l, (int)main->lines.size() - 1); } _FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const { @@ -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; } @@ -2188,6 +2190,75 @@ RichTextLabel::ItemFont *RichTextLabel::_find_font(Item *p_item) { while (fontitem) { if (fontitem->type == ITEM_FONT) { ItemFont *fi = static_cast<ItemFont *>(fontitem); + switch (fi->def_font) { + case NORMAL_FONT: { + 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: { + 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: { + 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: { + 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: { + 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; + } return fi; } @@ -2885,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); @@ -2898,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; @@ -2910,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(); + } } } @@ -3004,6 +3096,33 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font _add_item(item, false); } +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_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); +} + void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -3020,31 +3139,31 @@ void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) { void RichTextLabel::push_normal() { ERR_FAIL_COND(theme_cache.normal_font.is_null()); - push_font(theme_cache.normal_font, theme_cache.normal_font_size); + _push_def_font(NORMAL_FONT); } void RichTextLabel::push_bold() { ERR_FAIL_COND(theme_cache.bold_font.is_null()); - push_font(theme_cache.bold_font, theme_cache.bold_font_size); + _push_def_font(BOLD_FONT); } void RichTextLabel::push_bold_italics() { ERR_FAIL_COND(theme_cache.bold_italics_font.is_null()); - push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size); + _push_def_font(BOLD_ITALICS_FONT); } void RichTextLabel::push_italics() { ERR_FAIL_COND(theme_cache.italics_font.is_null()); - push_font(theme_cache.italics_font, theme_cache.italics_font_size); + _push_def_font(ITALICS_FONT); } void RichTextLabel::push_mono() { ERR_FAIL_COND(theme_cache.mono_font.is_null()); - push_font(theme_cache.mono_font, theme_cache.mono_font_size); + _push_def_font(MONO_FONT); } void RichTextLabel::push_font_size(int p_font_size) { @@ -3201,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); } @@ -3508,21 +3630,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 } @@ -3531,8 +3653,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; } @@ -3578,36 +3700,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(); @@ -3619,15 +3741,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; @@ -3635,9 +3757,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use bold font in_bold = true; if (in_italics) { - push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size); + _push_def_font(BOLD_ITALICS_FONT); } else { - push_font(theme_cache.bold_font, theme_cache.bold_font_size); + _push_def_font(BOLD_FONT); } pos = brk_end + 1; tag_stack.push_front(tag); @@ -3645,15 +3767,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use italics font in_italics = true; if (in_bold) { - push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size); + _push_def_font(BOLD_ITALICS_FONT); } else { - push_font(theme_cache.italics_font, theme_cache.italics_font_size); + _push_def_font(ITALICS_FONT); } pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "code") { //use monospace font - push_font(theme_cache.mono_font, theme_cache.mono_font_size); + _push_def_font(MONO_FONT); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("table=")) { @@ -3869,7 +3991,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) { @@ -3895,24 +4017,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,9 +4100,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); @@ -4025,6 +4147,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) { @@ -4053,7 +4187,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; @@ -4078,24 +4212,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("="); @@ -4105,11 +4236,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()); @@ -4125,9 +4266,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) { @@ -4135,7 +4288,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(); @@ -4189,7 +4343,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"); @@ -4230,7 +4391,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); @@ -4247,7 +4414,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); @@ -4264,7 +4437,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); @@ -4364,6 +4543,8 @@ int RichTextLabel::get_visible_paragraph_count() const { if (!is_visible()) { return 0; } + + const_cast<RichTextLabel *>(this)->_validate_line_caches(); return visible_paragraph_count; } @@ -4392,6 +4573,8 @@ void RichTextLabel::scroll_to_line(int p_line) { } float RichTextLabel::get_line_offset(int p_line) { + _validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -4409,6 +4592,8 @@ float RichTextLabel::get_line_offset(int p_line) { } float RichTextLabel::get_paragraph_offset(int p_paragraph) { + _validate_line_caches(); + int to_line = main->first_invalid_line.load(); if (0 <= p_paragraph && p_paragraph < to_line) { return main->lines[p_paragraph].offset.y; @@ -4417,6 +4602,8 @@ float RichTextLabel::get_paragraph_offset(int p_paragraph) { } int RichTextLabel::get_line_count() const { + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -4430,6 +4617,8 @@ int RichTextLabel::get_visible_line_count() const { if (!is_visible()) { return 0; } + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + return visible_line_count; } @@ -4512,19 +4701,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); @@ -4540,9 +4729,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) { @@ -4631,7 +4820,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) { @@ -4643,10 +4835,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]; @@ -4666,7 +4858,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); } } } @@ -4678,23 +4870,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) { @@ -4728,12 +4920,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() { @@ -4742,10 +4934,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); } } @@ -4844,7 +5036,14 @@ void RichTextLabel::set_use_bbcode(bool p_enable) { } use_bbcode = p_enable; notify_property_list_changed(); - set_text(text); + + const String current_text = text; + if (use_bbcode) { + parse_bbcode(current_text); + } else { // raw text + clear(); + add_text(current_text); + } } bool RichTextLabel::is_using_bbcode() const { @@ -4852,25 +5051,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) { @@ -5005,7 +5204,12 @@ int RichTextLabel::get_content_height() const { int to_line = main->first_invalid_line.load(); if (to_line) { MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); - total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation; + if (theme_cache.line_separation < 0) { + // Do not apply to the last line to avoid cutting text. + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + (main->lines[to_line - 1].text_buf->get_line_count() - 1) * theme_cache.line_separation; + } else { + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation; + } } return total_height; } @@ -5038,7 +5242,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); @@ -5298,6 +5502,8 @@ int RichTextLabel::get_visible_characters() const { } int RichTextLabel::get_character_line(int p_char) { + _validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -5318,6 +5524,8 @@ int RichTextLabel::get_character_line(int p_char) { } int RichTextLabel::get_character_paragraph(int p_char) { + _validate_line_caches(); + int para_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -5349,6 +5557,8 @@ int RichTextLabel::get_total_character_count() const { } int RichTextLabel::get_total_glyph_count() const { + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + int tg = 0; Item *it = main; while (it) { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 8bc28a9ecf..04a682349d 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -82,6 +82,15 @@ public: MENU_SELECT_ALL, }; + enum DefaultFont { + NORMAL_FONT, + BOLD_FONT, + ITALICS_FONT, + BOLD_ITALICS_FONT, + MONO_FONT, + CUSTOM_FONT, + }; + protected: virtual void _update_theme_item_cache() override; void _notification(int p_what); @@ -178,7 +187,10 @@ 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; } }; @@ -272,6 +284,7 @@ private: struct ItemFX : public Item { double elapsed_time = 0.f; + bool connected = true; }; struct ItemShake : public ItemFX { @@ -288,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))); } }; @@ -556,10 +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_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); @@ -579,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..193c6f1ce7 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()); } } diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index c12ac115b7..a6aa4707ff 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -162,7 +162,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { 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_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); drag_touching_deaccel = false; beyond_deadzone = false; time_since_motion = 0; @@ -501,8 +501,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/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 f45d132a66..ab4808d312 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -519,12 +519,12 @@ void TabContainer::_refresh_tab_names() { } void TabContainer::add_child_notify(Node *p_child) { + Container::add_child_notify(p_child); + if (p_child == tab_bar) { return; } - Container::add_child_notify(p_child); - Control *c = Object::cast_to<Control>(p_child); if (!c || c->is_set_as_top_level()) { return; @@ -838,7 +838,7 @@ Size2 TabContainer::get_minimum_size() const { } Vector<Control *> controls = _get_tab_controls(); - int max_control_height = 0; + Size2 largest_child_min_size; for (int i = 0; i < controls.size(); i++) { Control *c = controls[i]; @@ -847,13 +847,14 @@ Size2 TabContainer::get_minimum_size() const { } Size2 cms = c->get_combined_minimum_size(); - ms.x = MAX(ms.x, cms.x); - max_control_height = MAX(max_control_height, cms.y); + largest_child_min_size.x = MAX(largest_child_min_size.x, cms.x); + largest_child_min_size.y = MAX(largest_child_min_size.y, cms.y); } - ms.y += max_control_height; + ms.y += largest_child_min_size.y; Size2 panel_ms = theme_cache.panel_style->get_minimum_size(); - ms.x = MAX(ms.x, panel_ms.x); + + ms.x = MAX(ms.x, largest_child_min_size.x + panel_ms.x); ms.y += panel_ms.y; return ms; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 318447ecd8..90974e31df 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); } } @@ -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 { @@ -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 && override_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,15 @@ 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()) { + 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 +1905,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 +1933,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 +1951,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 +1987,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. @@ -2129,8 +2192,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 +2217,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 +2229,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 overlaping 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 +2659,147 @@ 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(); + start_action(EditAction::ACTION_DELETE); + Vector<int> carets_to_remove; - if (caret.line == text.size() - 1 && caret.column == curline_len) { - return; // Last line, last column: Nothing to do. - } - - 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); } - _remove_text(caret.line, caret.column, 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]); + } + + // 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); } } @@ -2676,7 +2919,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 +2946,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 +2988,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 +3006,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 +3225,7 @@ void TextEdit::clear() { void TextEdit::_clear() { if (editable && undo_enabled) { + remove_secondary_carets(); _move_caret_document_start(false); begin_complex_operation(); @@ -2990,13 +3241,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 +3261,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 +3317,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 +3391,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 +3562,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 +3738,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; @@ -3545,25 +3844,33 @@ 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; } + _push_current_op(); if (undo_stack_pos == nullptr) { @@ -3589,10 +3896,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 +3926,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 +4115,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 +4143,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 +4218,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 +4240,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 +4263,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 +4350,233 @@ 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; } -Point2 TextEdit::get_caret_draw_pos() const { - return caret.draw_pos; +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::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { +void TextEdit::remove_caret(int p_caret) { + ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed."); + ERR_FAIL_INDEX(p_caret, carets.size()); + 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(); +} + +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(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, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); if (setting_caret_line) { return; } @@ -4052,10 +4605,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 +4619,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 +4636,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 +4667,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 +4722,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(); } } @@ -4180,22 +4747,24 @@ 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) { - selection.selecting_mode = p_mode; +void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) { + ERR_FAIL_INDEX(p_caret, carets.size()); + + 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 +4775,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 +4796,44 @@ 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(); - return; - } + for (int c = 0; c < carets.size(); c++) { + if (p_caret != -1 && p_caret != c) { + continue; + } - 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 (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; } - } - // No word found. - if (begin == 0 && end == 0) { - return; - } + 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(caret.line, begin, caret.line, end); - /* Move the caret to the end of the word for easier editing. */ - set_caret_column(end, false); + 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::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 +4862,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; + } + + 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; - 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); + 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 +5098,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 +5177,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 +5216,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 +5259,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 +5297,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 +5352,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 +5825,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 +5834,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 +5880,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 +5927,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 +5951,30 @@ 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("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); @@ -5335,27 +5995,27 @@ void TextEdit::_bind_methods() { 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("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 +6038,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 +6073,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); @@ -5527,6 +6190,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 +6276,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); + + if (cc == 0 && cl == 0) { + continue; + } - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); + int prev_line = cc ? cl : cl - 1; + int prev_column = cc ? (cc - 1) : (text[cl - 1].length()); - merge_gutters(prev_line, cl); + 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); + 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); - 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(); } -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 overlaping carets. + // We don't need to worry about selections as that is caught before this entire section. + for (int j = i - 1; j >= 0; j--) { + if (get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + } + } + + clipboard += text[cl]; + if (p_caret == -1 && caret_idx != 0) { + clipboard += "\n"; + } - // 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; + 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; + } + + 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); + 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 +6624,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 +6729,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 +6752,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 +6765,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 +6778,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 +6805,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 +6824,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 +6839,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 +6868,7 @@ void TextEdit::_update_selection_mode_word() { queue_redraw(); click_select_held->start(); + merge_overlapping_carets(); } void TextEdit::_update_selection_mode_line() { @@ -6124,21 +6878,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 +6901,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 +6951,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 +7007,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 +7018,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 +7055,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 +7078,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 +7160,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 +7182,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 +7329,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 +7353,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 +7361,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 +7380,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 +7397,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 +7450,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 +7512,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..e4af621b73 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,28 +434,10 @@ 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; @@ -428,6 +447,7 @@ private: Color selection_color = Color(1, 1, 1); bool override_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; @@ -583,6 +605,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 +640,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 +729,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 +738,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 +752,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 +790,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 +808,30 @@ 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; + + 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_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; + bool is_caret_visible(int p_caret = 0) const; + Point2 get_caret_draw_pos(int p_caret = 0) const; - void set_caret_column(int p_col, bool p_adjust_viewport = true); - int get_caret_column() 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; - int get_caret_wrap_index() 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; - String get_word_under_caret() 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 +846,27 @@ 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 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 +886,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 +918,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 +1001,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/tree.cpp b/scene/gui/tree.cpp index 237c78407b..6f9a9a5141 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; @@ -5026,6 +5089,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; @@ -5129,6 +5200,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 +5217,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..f994a5cec1 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; @@ -712,6 +719,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/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/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..7e2c82c88d 100644 --- a/scene/main/multiplayer_api.cpp +++ b/scene/main/multiplayer_api.cpp @@ -372,7 +372,7 @@ 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; + int ret = FAILED; if (GDVIRTUAL_CALL(_rpc, p_peer_id, p_obj, p_method, args, ret)) { return (Error)ret; } @@ -380,7 +380,7 @@ Error MultiplayerAPIExtension::rpcp(Object *p_obj, int p_peer_id, const StringNa } int MultiplayerAPIExtension::get_remote_sender_id() { - int id; + int id = 0; if (GDVIRTUAL_CALL(_get_remote_sender_id, id)) { return id; } @@ -388,7 +388,7 @@ int MultiplayerAPIExtension::get_remote_sender_id() { } Error MultiplayerAPIExtension::object_configuration_add(Object *p_object, Variant p_config) { - int err; + int err = ERR_UNAVAILABLE; if (GDVIRTUAL_CALL(_object_configuration_add, p_object, p_config, err)) { return (Error)err; } @@ -396,7 +396,7 @@ Error MultiplayerAPIExtension::object_configuration_add(Object *p_object, Varian } Error MultiplayerAPIExtension::object_configuration_remove(Object *p_object, Variant p_config) { - int err; + int err = ERR_UNAVAILABLE; if (GDVIRTUAL_CALL(_object_configuration_remove, p_object, p_config, err)) { return (Error)err; } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 29f4d4fb1c..2ea45df309 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(); @@ -1411,14 +1409,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 +1464,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 +1481,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; } @@ -1904,9 +1892,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 +2119,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 +2389,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()) { @@ -2640,21 +2628,19 @@ 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(); + PackedStringArray warnings = get_configuration_warnings(); String all_warnings = String(); for (int i = 0; i < warnings.size(); i++) { if (i > 0) { @@ -2662,7 +2648,7 @@ String Node::get_configuration_warnings_as_string() const { } // 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 +2757,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); diff --git a/scene/main/node.h b/scene/main/node.h index 39225b1358..c8c8c395ce 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; @@ -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(); @@ -529,4 +530,8 @@ Error Node::rpc_id(int p_peer_id, const StringName &p_method, VarArgs... p_args) return rpcp(p_peer_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args)); } +// Add these macro to your class's 'get_configuration_warnings' function to have warnings show up in the scene tree inspector. +#define DEPRECATED_NODE_WARNING warnings.push_back(RTR("This node is marked as deprecated and will be removed in future versions.\nPlease check the Godot documentation for information about migration.")); +#define EXPERIMENTAL_NODE_WARNING warnings.push_back(RTR("This node is marked as experimental and may be subject to removal or major changes in future versions.")); + #endif // NODE_H diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index c18aa5aaa2..270e5b7025 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); } } @@ -207,15 +208,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 +254,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])) { 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])) { 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 +307,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])) { 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])) { 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 +358,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])) { 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])) { 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); } } } @@ -510,7 +511,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 +543,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,13 +847,13 @@ 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]; + for (int i = 0; i < gr_node_count; i++) { + Node *n = gr_nodes[i]; if (call_lock && call_skip.has(n)) { continue; } @@ -865,7 +866,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,17 +891,19 @@ 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<Node *> no_context_nodes; + + for (int i = gr_node_count - 1; i >= 0; i--) { if (p_viewport->is_input_handled()) { break; } - Node *n = nodes[i]; + Node *n = gr_nodes[i]; if (call_lock && call_skip.has(n)) { continue; } @@ -913,9 +916,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_nodes.append(n); + 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 +941,10 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal } } + for (Node *n : no_context_nodes) { + n->_call_shortcut_input(p_input); + } + call_lock--; if (call_lock == 0) { call_skip.clear(); @@ -1085,6 +1105,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(); } } @@ -1389,6 +1410,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..a460e40597 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -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 5295de5c09..a2963eadd5 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); @@ -499,6 +499,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 +635,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; @@ -795,11 +802,20 @@ void Viewport::_set_size(const Size2i &p_size, const Size2i &p_size_2d_override, stretch_transform = p_stretch_transform; to_screen_rect = p_to_screen_rect; - if (p_allocated) { - RS::get_singleton()->viewport_set_size(viewport, size.width, size.height); - } else { - RS::get_singleton()->viewport_set_size(viewport, 0, 0); - } +#ifndef _3D_DISABLED + if (!use_xr) { +#endif + + if (p_allocated) { + RS::get_singleton()->viewport_set_size(viewport, size.width, size.height); + } else { + RS::get_singleton()->viewport_set_size(viewport, 0, 0); + } + +#ifndef _3D_DISABLED + } // if (!use_xr) +#endif + _update_global_transform(); update_configuration_warnings(); @@ -813,6 +829,19 @@ void Viewport::_set_size(const Size2i &p_size, const Size2i &p_size_2d_override, } Size2i Viewport::_get_size() const { +#ifndef _3D_DISABLED + if (use_xr) { + if (XRServer::get_singleton() != nullptr) { + Ref<XRInterface> xr_interface = XRServer::get_singleton()->get_primary_interface(); + if (xr_interface.is_valid() && xr_interface->is_initialized()) { + Size2 xr_size = xr_interface->get_render_target_size(); + return (Size2i)xr_size; + } + } + return Size2i(); + } +#endif // _3D_DISABLED + return size; } @@ -1133,8 +1162,6 @@ void Viewport::_gui_cancel_tooltip() { } if (gui.tooltip_popup) { gui.tooltip_popup->queue_delete(); - gui.tooltip_popup = nullptr; - gui.tooltip_label = nullptr; } } @@ -1197,8 +1224,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) { @@ -1230,6 +1255,7 @@ 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; @@ -1464,7 +1490,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) { @@ -1554,7 +1579,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_preview_id = ObjectID(); } _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - // Change mouse accordingly. + get_base_window()->update_mouse_cursor_shape(); } _gui_cancel_tooltip(); @@ -1575,7 +1600,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.dragging = false; gui.drag_mouse_over = nullptr; _propagate_viewport_notification(this, NOTIFICATION_DRAG_END); - // Change mouse accordingly. + get_base_window()->update_mouse_cursor_shape(); } gui.mouse_focus_mask &= ~mouse_button_to_mask(mb->get_button_index()); // Remove from mask. @@ -1618,8 +1643,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(); @@ -2733,6 +2756,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; @@ -2777,7 +2805,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); } @@ -2850,8 +2878,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.")); @@ -3612,9 +3640,20 @@ void Viewport::_propagate_exit_world_3d(Node *p_node) { } void Viewport::set_use_xr(bool p_use_xr) { - use_xr = p_use_xr; + if (use_xr != p_use_xr) { + use_xr = p_use_xr; + + RS::get_singleton()->viewport_set_use_xr(viewport, use_xr); - RS::get_singleton()->viewport_set_use_xr(viewport, use_xr); + if (!use_xr) { + // Set viewport to previous size when exiting XR. + if (size_allocated) { + RS::get_singleton()->viewport_set_size(viewport, size.width, size.height); + } else { + RS::get_singleton()->viewport_set_size(viewport, 0, 0); + } + } + } } bool Viewport::is_using_xr() { @@ -3690,6 +3729,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); @@ -4017,16 +4057,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. @@ -4102,7 +4136,7 @@ Transform2D SubViewport::_stretch_transform() { Transform2D transform = Transform2D(); 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); } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index afea3ea56c..6f67649ea3 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; @@ -568,7 +569,7 @@ public: bool is_input_disabled() const; Vector2 get_mouse_position() const; - virtual void warp_mouse(const Vector2 &p_position); + void warp_mouse(const Vector2 &p_position); void set_physics_object_picking(bool p_enable); bool get_physics_object_picking(); @@ -581,7 +582,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; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 04f56bb874..8fb497113d 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; @@ -636,9 +632,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 +647,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 +655,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 +733,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 +745,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); @@ -986,18 +982,6 @@ DisplayServer::WindowID Window::get_window_id() const { return window_id; } -void Window::warp_mouse(const Vector2 &p_position) { - Transform2D xform = get_screen_transform(); - Vector2 gpos = xform.xform(p_position); - - if (transient_parent && !transient_parent->is_embedding_subwindows()) { - Transform2D window_trans = Transform2D().translated(get_position() + (transient_parent->get_visible_rect().size - transient_parent->get_real_size())); - gpos = window_trans.xform(gpos); - } - - Input::get_singleton()->warp_mouse(gpos); -} - void Window::set_wrap_controls(bool p_enable) { wrap_controls = p_enable; if (wrap_controls) { @@ -1153,7 +1137,7 @@ void Window::popup_centered_clamped(const Size2i &p_size, float p_fallback_ratio Rect2 parent_rect; if (is_embedded()) { - parent_rect = get_parent_viewport()->get_visible_rect(); + parent_rect = _get_embedder()->get_visible_rect(); } else { DisplayServer::WindowID parent_id = get_parent_visible_window()->get_window_id(); int parent_screen = DisplayServer::get_singleton()->window_get_current_screen(parent_id); @@ -1179,7 +1163,7 @@ void Window::popup_centered(const Size2i &p_minsize) { Rect2 parent_rect; if (is_embedded()) { - parent_rect = get_parent_viewport()->get_visible_rect(); + parent_rect = _get_embedder()->get_visible_rect(); } else { DisplayServer::WindowID parent_id = get_parent_visible_window()->get_window_id(); int parent_screen = DisplayServer::get_singleton()->window_get_current_screen(parent_id); @@ -1207,7 +1191,7 @@ void Window::popup_centered_ratio(float p_ratio) { Rect2 parent_rect; if (is_embedded()) { - parent_rect = get_parent_viewport()->get_visible_rect(); + parent_rect = _get_embedder()->get_visible_rect(); } else { DisplayServer::WindowID parent_id = get_parent_visible_window()->get_window_id(); int parent_screen = DisplayServer::get_singleton()->window_get_current_screen(parent_id); @@ -1260,6 +1244,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); } @@ -1282,17 +1279,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) { @@ -1560,9 +1557,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; @@ -1794,6 +1791,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); diff --git a/scene/main/window.h b/scene/main/window.h index 8113117103..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); @@ -254,8 +258,6 @@ public: void set_use_font_oversampling(bool p_oversampling); bool is_using_font_oversampling() const; - void warp_mouse(const Vector2 &p_position) override; - void set_wrap_controls(bool p_enable); bool is_wrapping_controls() const; void child_controls_changed(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 72c57f1bfc..e536aeee51 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). diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 9d5bc18c96..dfd9c6eb2f 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) { @@ -2317,9 +2315,7 @@ Quaternion Animation::_interpolate(const Quaternion &p_a, const Quaternion &p_b, } Variant Animation::_interpolate(const Variant &p_a, const Variant &p_b, real_t p_c) const { - Variant dst; - Variant::interpolate(p_a, p_b, p_c, dst); - return dst; + return interpolate_variant(p_a, p_b, p_c); } real_t Animation::_interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) const { @@ -5243,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]; @@ -5388,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]; @@ -5460,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]; @@ -5496,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]; @@ -5563,6 +5551,466 @@ bool Animation::_fetch_compressed_by_index(uint32_t p_compressed_track, int p_in return false; } +// Helper math functions for Variant. +Variant Animation::add_variant(const Variant &a, const Variant &b) { + if (a.get_type() != b.get_type()) { + return a; + } + + switch (a.get_type()) { + case Variant::NIL: { + return Variant(); + } + case Variant::BOOL: { + return (a.operator real_t()) + (b.operator real_t()); // It is cast for interpolation. + } + case Variant::RECT2: { + const Rect2 ra = a.operator Rect2(); + const Rect2 rb = b.operator Rect2(); + return Rect2(ra.position + rb.position, ra.size + rb.size); + } + case Variant::RECT2I: { + const Rect2i ra = a.operator Rect2i(); + const Rect2i rb = b.operator Rect2i(); + return Rect2i(ra.position + rb.position, ra.size + rb.size); + } + case Variant::PLANE: { + const Plane pa = a.operator Plane(); + const Plane pb = b.operator Plane(); + return Plane(pa.normal + pb.normal, pa.d + pb.d); + } + case Variant::AABB: { + const ::AABB aa = a.operator ::AABB(); + const ::AABB ab = b.operator ::AABB(); + return ::AABB(aa.position + ab.position, aa.size + ab.size); + } + case Variant::QUATERNION: { + return (a.operator Quaternion()) * (b.operator Quaternion()); + } + case Variant::TRANSFORM2D: { + return (a.operator Transform2D()) * (b.operator Transform2D()); + } + case Variant::TRANSFORM3D: { + return (a.operator Transform3D()) * (b.operator Transform3D()); + } + default: { + return Variant::evaluate(Variant::OP_ADD, a, b); + } + } +} + +Variant Animation::subtract_variant(const Variant &a, const Variant &b) { + if (a.get_type() != b.get_type()) { + return a; + } + + switch (a.get_type()) { + case Variant::NIL: { + return Variant(); + } + case Variant::BOOL: { + return (a.operator real_t()) - (b.operator real_t()); // It is cast for interpolation. + } + case Variant::RECT2: { + const Rect2 ra = a.operator Rect2(); + const Rect2 rb = b.operator Rect2(); + return Rect2(ra.position - rb.position, ra.size - rb.size); + } + case Variant::RECT2I: { + const Rect2i ra = a.operator Rect2i(); + const Rect2i rb = b.operator Rect2i(); + return Rect2i(ra.position - rb.position, ra.size - rb.size); + } + case Variant::PLANE: { + const Plane pa = a.operator Plane(); + const Plane pb = b.operator Plane(); + return Plane(pa.normal - pb.normal, pa.d - pb.d); + } + case Variant::AABB: { + const ::AABB aa = a.operator ::AABB(); + const ::AABB ab = b.operator ::AABB(); + return ::AABB(aa.position - ab.position, aa.size - ab.size); + } + case Variant::QUATERNION: { + return (b.operator Quaternion()).inverse() * (a.operator Quaternion()); + } + case Variant::TRANSFORM2D: { + return (b.operator Transform2D()).inverse() * (a.operator Transform2D()); + } + case Variant::TRANSFORM3D: { + return (b.operator Transform3D()).inverse() * (a.operator Transform3D()); + } + default: { + return Variant::evaluate(Variant::OP_SUBTRACT, a, b); + } + } +} + +Variant Animation::blend_variant(const Variant &a, const Variant &b, float c) { + if (a.get_type() != b.get_type()) { + if (a.is_num() && b.is_num()) { + real_t va = a; + real_t vb = b; + return va + vb * c; + } + return a; + } + + switch (a.get_type()) { + case Variant::NIL: { + return Variant(); + } + case Variant::INT: { + return int((a.operator int64_t()) + (b.operator int64_t()) * c + 0.5); + } + case Variant::FLOAT: { + return (a.operator double()) + (b.operator double()) * c; + } + case Variant::VECTOR2: { + return (a.operator Vector2()) + (b.operator Vector2()) * c; + } + case Variant::VECTOR2I: { + const Vector2i va = a.operator Vector2i(); + const Vector2i vb = b.operator Vector2i(); + return Vector2i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5)); + } + case Variant::RECT2: { + const Rect2 ra = a.operator Rect2(); + const Rect2 rb = b.operator Rect2(); + return Rect2(ra.position + rb.position * c, ra.size + rb.size * c); + } + case Variant::RECT2I: { + const Rect2i ra = a.operator Rect2i(); + const Rect2i rb = b.operator Rect2i(); + return Rect2i(int32_t(ra.position.x + rb.position.x * c + 0.5), int32_t(ra.position.y + rb.position.y * c + 0.5), int32_t(ra.size.x + rb.size.x * c + 0.5), int32_t(ra.size.y + rb.size.y * c + 0.5)); + } + case Variant::VECTOR3: { + return (a.operator Vector3()) + (b.operator Vector3()) * c; + } + case Variant::VECTOR3I: { + const Vector3i va = a.operator Vector3i(); + const Vector3i vb = b.operator Vector3i(); + return Vector3i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5)); + } + case Variant::VECTOR4: { + return (a.operator Vector4()) + (b.operator Vector4()) * c; + } + case Variant::VECTOR4I: { + const Vector4i va = a.operator Vector4i(); + const Vector4i vb = b.operator Vector4i(); + return Vector4i(int32_t(va.x + vb.x * c + 0.5), int32_t(va.y + vb.y * c + 0.5), int32_t(va.z + vb.z * c + 0.5), int32_t(va.w + vb.w * c + 0.5)); + } + case Variant::PLANE: { + const Plane pa = a.operator Plane(); + const Plane pb = b.operator Plane(); + return Plane(pa.normal + pb.normal * c, pa.d + pb.d * c); + } + case Variant::COLOR: { + return (a.operator Color()) + (b.operator Color()) * c; + } + case Variant::AABB: { + const ::AABB aa = a.operator ::AABB(); + const ::AABB ab = b.operator ::AABB(); + return ::AABB(aa.position + ab.position * c, aa.size + ab.size * c); + } + case Variant::BASIS: { + return (a.operator Basis()) + (b.operator Basis()) * c; + } + case Variant::QUATERNION: { + return (a.operator Quaternion()) * Quaternion().slerp((b.operator Quaternion()), c); + } + case Variant::TRANSFORM2D: { + return (a.operator Transform2D()) * Transform2D().interpolate_with((b.operator Transform2D()), c); + } + case Variant::TRANSFORM3D: { + return (a.operator Transform3D()) * Transform3D().interpolate_with((b.operator Transform3D()), c); + } + default: { + return c < 0.5 ? a : b; + } + } +} + +Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float c) { + if (a.get_type() != b.get_type()) { + if (a.is_num() && b.is_num()) { + real_t va = a; + real_t vb = b; + return va + (vb - va) * c; + } + return a; + } + + switch (a.get_type()) { + case Variant::NIL: { + return Variant(); + } + case Variant::INT: { + const int64_t va = a.operator int64_t(); + return int(va + ((b.operator int64_t()) - va) * c); + } + case Variant::FLOAT: { + const real_t va = a.operator real_t(); + return va + ((b.operator real_t()) - va) * c; + } + case Variant::VECTOR2: { + return (a.operator Vector2()).lerp(b.operator Vector2(), c); + } + case Variant::VECTOR2I: { + const Vector2i va = a.operator Vector2i(); + const Vector2i vb = b.operator Vector2i(); + return Vector2i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c)); + } + case Variant::RECT2: { + const Rect2 ra = a.operator Rect2(); + const Rect2 rb = b.operator Rect2(); + return Rect2(ra.position.lerp(rb.position, c), ra.size.lerp(rb.size, c)); + } + case Variant::RECT2I: { + const Rect2i ra = a.operator Rect2i(); + const Rect2i rb = b.operator Rect2i(); + return Rect2i(int32_t(ra.position.x + (rb.position.x - ra.position.x) * c), int32_t(ra.position.y + (rb.position.y - ra.position.y) * c), int32_t(ra.size.x + (rb.size.x - ra.size.x) * c), int32_t(ra.size.y + (rb.size.y - ra.size.y) * c)); + } + case Variant::VECTOR3: { + return (a.operator Vector3()).lerp(b.operator Vector3(), c); + } + case Variant::VECTOR3I: { + const Vector3i va = a.operator Vector3i(); + const Vector3i vb = b.operator Vector3i(); + return Vector3i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c)); + } + case Variant::VECTOR4: { + return (a.operator Vector4()).lerp(b.operator Vector4(), c); + } + case Variant::VECTOR4I: { + const Vector4i va = a.operator Vector4i(); + const Vector4i vb = b.operator Vector4i(); + return Vector4i(int32_t(va.x + (vb.x - va.x) * c), int32_t(va.y + (vb.y - va.y) * c), int32_t(va.z + (vb.z - va.z) * c), int32_t(va.w + (vb.w - va.w) * c)); + } + case Variant::PLANE: { + const Plane pa = a.operator Plane(); + const Plane pb = b.operator Plane(); + return Plane(pa.normal.lerp(pb.normal, c), pa.d + (pb.d - pa.d) * c); + } + case Variant::COLOR: { + return (a.operator Color()).lerp(b.operator Color(), c); + } + case Variant::AABB: { + const ::AABB aa = a.operator ::AABB(); + const ::AABB ab = b.operator ::AABB(); + return ::AABB(aa.position.lerp(ab.position, c), aa.size.lerp(ab.size, c)); + } + case Variant::BASIS: { + return (a.operator Basis()).lerp(b.operator Basis(), c); + } + case Variant::QUATERNION: { + return (a.operator Quaternion()).slerp(b.operator Quaternion(), c); + } + case Variant::TRANSFORM2D: { + return (a.operator Transform2D()).interpolate_with(b.operator Transform2D(), c); + } + case Variant::TRANSFORM3D: { + return (a.operator Transform3D()).interpolate_with(b.operator Transform3D(), c); + } + case Variant::STRING: { + // This is pretty funny and bizarre, but artists like to use it for typewriter effects. + const String sa = a.operator String(); + const String sb = b.operator String(); + String dst; + int sa_len = sa.length(); + int sb_len = sb.length(); + int csize = sa_len + (sb_len - sa_len) * c; + if (csize == 0) { + return ""; + } + dst.resize(csize + 1); + dst[csize] = 0; + int split = csize / 2; + + for (int i = 0; i < csize; i++) { + char32_t chr = ' '; + + if (i < split) { + if (i < sa.length()) { + chr = sa[i]; + } else if (i < sb.length()) { + chr = sb[i]; + } + + } else { + if (i < sb.length()) { + chr = sb[i]; + } else if (i < sa.length()) { + chr = sa[i]; + } + } + + dst[i] = chr; + } + + 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) { + 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(); + + Variant va; + for (int32_t i = 0; i < sz; i++) { + va = interpolate_variant(ar[i], br[i], c); + vw[i] = va; + } + } + return v; + } + } + 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) { + 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(); + + Variant va; + for (int64_t i = 0; i < sz; i++) { + va = interpolate_variant(ar[i], br[i], c); + vw[i] = va; + } + } + return v; + } + } + 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) { + 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(); + + Variant va; + for (int i = 0; i < sz; i++) { + va = interpolate_variant(ar[i], br[i], c); + vw[i] = va; + } + } + return v; + } + } + 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) { + 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(); + + Variant va; + for (int i = 0; i < sz; i++) { + va = interpolate_variant(ar[i], br[i], c); + vw[i] = va; + } + } + return v; + } + } + 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) { + 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(); + + for (int i = 0; i < sz; i++) { + vw[i] = ar[i].lerp(br[i], c); + } + } + return v; + } + } + 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) { + 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(); + + for (int i = 0; i < sz; i++) { + vw[i] = ar[i].lerp(br[i], c); + } + } + return v; + } + } + 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) { + 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(); + + for (int i = 0; i < sz; i++) { + vw[i] = ar[i].lerp(br[i], c); + } + } + return v; + } + } + default: { + return c < 0.5 ? a : b; + } + } +} + Animation::Animation() {} Animation::~Animation() { diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 46a88df130..49c8fa4c22 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -496,6 +496,12 @@ public: void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3); void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests + // Helper math functions for Variant. + static Variant add_variant(const Variant &a, const Variant &b); + static Variant subtract_variant(const Variant &a, const Variant &b); + static Variant blend_variant(const Variant &a, const Variant &b, float c); + static Variant interpolate_variant(const Variant &a, const Variant &b, float c); + Animation(); ~Animation(); }; 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/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..49b78a091d 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -1667,6 +1667,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 +1679,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/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/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 869d582935..c9d92cea3f 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; @@ -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,7 +880,12 @@ 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"]); 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/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 189d8d5502..cbecab62b3 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); @@ -554,7 +561,6 @@ Font::Font() { } Font::~Font() { - reset_state(); } /*************************************************************************/ @@ -1059,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. @@ -1076,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. @@ -1102,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(); @@ -1181,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(); @@ -1807,8 +1813,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; } @@ -2433,11 +2439,10 @@ int32_t FontFile::get_glyph_index(int p_size, char32_t p_char, char32_t p_variat } FontFile::FontFile() { - /* NOP */ } FontFile::~FontFile() { - reset_state(); + _clear_cache(); } /*************************************************************************/ @@ -2688,7 +2693,6 @@ FontVariation::FontVariation() { } FontVariation::~FontVariation() { - reset_state(); } /*************************************************************************/ @@ -3081,5 +3085,4 @@ SystemFont::SystemFont() { } SystemFont::~SystemFont() { - reset_state(); } diff --git a/scene/resources/importer_mesh.cpp b/scene/resources/importer_mesh.cpp index 0afca95de0..b728c24e0d 100644 --- a/scene/resources/importer_mesh.cpp +++ b/scene/resources/importer_mesh.cpp @@ -254,7 +254,20 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma mesh.unref(); } -void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle) { +#define VERTEX_SKIN_FUNC(bone_count, vert_idx, read_array, write_array, transform_array, bone_array, weight_array) \ + Vector3 transformed_vert = Vector3(); \ + 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]; \ + if (w < FLT_EPSILON) { \ + continue; \ + } \ + ERR_FAIL_INDEX(bone_idx, static_cast<int>(transform_array.size())); \ + transformed_vert += transform_array[bone_idx].xform(read_array[vert_idx]) * w; \ + } \ + write_array[vert_idx] = transformed_vert; + +void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array) { if (!SurfaceTool::simplify_scale_func) { return; } @@ -265,6 +278,12 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli return; } + LocalVector<Transform3D> bone_transform_vector; + for (int i = 0; i < p_bone_transform_array.size(); i++) { + ERR_FAIL_COND(p_bone_transform_array[i].get_type() != Variant::TRANSFORM3D); + bone_transform_vector.push_back(p_bone_transform_array[i]); + } + for (int i = 0; i < surfaces.size(); i++) { if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) { continue; @@ -276,6 +295,8 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli Vector<Vector3> normals = surfaces[i].arrays[RS::ARRAY_NORMAL]; Vector<Vector2> uvs = surfaces[i].arrays[RS::ARRAY_TEX_UV]; Vector<Vector2> uv2s = surfaces[i].arrays[RS::ARRAY_TEX_UV2]; + Vector<int> bones = surfaces[i].arrays[RS::ARRAY_BONES]; + Vector<float> weights = surfaces[i].arrays[RS::ARRAY_WEIGHTS]; unsigned int index_count = indices.size(); unsigned int vertex_count = vertices.size(); @@ -301,6 +322,22 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli } } + if (bones.size() > 0 && weights.size() && bone_transform_vector.size() > 0) { + Vector3 *vertices_ptrw = vertices.ptrw(); + + // Apply bone transforms to regular surface. + unsigned int bone_weight_length = surfaces[i].flags & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4; + + const int *bo = bones.ptr(); + const float *we = weights.ptr(); + + for (unsigned int j = 0; j < vertex_count; j++) { + VERTEX_SKIN_FUNC(bone_weight_length, j, vertices_ptr, vertices_ptrw, bone_transform_vector, bo, we) + } + + vertices_ptr = vertices.ptr(); + } + float normal_merge_threshold = Math::cos(Math::deg_to_rad(p_normal_merge_angle)); float normal_pre_split_threshold = Math::cos(Math::deg_to_rad(MIN(180.0f, p_normal_split_angle * 2.0f))); float normal_split_threshold = Math::cos(Math::deg_to_rad(p_normal_split_angle)); @@ -792,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"]; @@ -811,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); } } } @@ -1246,7 +1283,7 @@ void ImporterMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name); ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material); - ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle"), &ImporterMesh::generate_lods); + ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::generate_lods); ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>())); ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear); diff --git a/scene/resources/importer_mesh.h b/scene/resources/importer_mesh.h index dce2638c19..088a77edd1 100644 --- a/scene/resources/importer_mesh.h +++ b/scene/resources/importer_mesh.h @@ -112,7 +112,7 @@ public: void set_surface_material(int p_surface, const Ref<Material> &p_material); - void generate_lods(float p_normal_merge_angle, float p_normal_split_angle); + void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array); void create_shadow_mesh(); Ref<ImporterMesh> get_shadow_mesh() const; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 448ff74a53..8ae217dd1f 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -156,17 +156,7 @@ Material::~Material() { bool ShaderMaterial::_set(const StringName &p_name, const Variant &p_value) { if (shader.is_valid()) { - StringName pr = shader->remap_uniform(p_name); - if (!pr) { - String n = p_name; - if (n.find("shader_parameter/") == 0) { //backwards compatibility - pr = n.replace_first("shader_parameter/", ""); - } else if (n.find("shader_uniform/") == 0) { //backwards compatibility - pr = n.replace_first("shader_uniform/", ""); - } else if (n.find("param/") == 0) { //backwards compatibility - pr = n.substr(6, n.length()); - } - } + StringName pr = shader->remap_parameter(p_name); if (pr) { set_shader_parameter(pr, p_value); return true; @@ -178,25 +168,9 @@ bool ShaderMaterial::_set(const StringName &p_name, const Variant &p_value) { bool ShaderMaterial::_get(const StringName &p_name, Variant &r_ret) const { if (shader.is_valid()) { - StringName pr = shader->remap_uniform(p_name); - if (!pr) { - String n = p_name; - if (n.find("shader_parameter/") == 0) { //backwards compatibility - pr = n.replace_first("shader_parameter/", ""); - } else if (n.find("shader_uniform/") == 0) { //backwards compatibility - pr = n.replace_first("shader_uniform/", ""); - } else if (n.find("param/") == 0) { //backwards compatibility - pr = n.substr(6, n.length()); - } - } - + StringName pr = shader->remap_parameter(p_name); if (pr) { - HashMap<StringName, Variant>::ConstIterator E = param_cache.find(pr); - if (E) { - r_ret = E->value; - } else { - r_ret = Variant(); - } + r_ret = get_shader_parameter(pr); return true; } } @@ -238,6 +212,7 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const { PropertyInfo info; info.usage = PROPERTY_USAGE_GROUP; info.name = last_group.capitalize(); + info.hint_string = "shader_parameter/"; List<PropertyInfo> none_subgroup; none_subgroup.push_back(info); @@ -252,6 +227,7 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const { PropertyInfo info; info.usage = PROPERTY_USAGE_SUBGROUP; info.name = last_subgroup.capitalize(); + info.hint_string = "shader_parameter/"; List<PropertyInfo> subgroup; subgroup.push_back(info); @@ -271,39 +247,42 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const { PropertyInfo info; info.usage = PROPERTY_USAGE_GROUP; - info.name = "Shader Param"; + info.name = "Shader Parameters"; + info.hint_string = "shader_parameter/"; groups["<None>"]["<None>"].push_back(info); } PropertyInfo info = E->get(); - info.name = info.name; + info.name = "shader_parameter/" + info.name; groups[last_group][last_subgroup].push_back(info); } - // Sort groups alphabetically. - List<UniformProp> props; + List<String> group_names; for (HashMap<String, HashMap<String, List<PropertyInfo>>>::Iterator group = groups.begin(); group; ++group) { - for (HashMap<String, List<PropertyInfo>>::Iterator subgroup = group->value.begin(); subgroup; ++subgroup) { - for (List<PropertyInfo>::Element *item = subgroup->value.front(); item; item = item->next()) { - if (subgroup->key == "<None>") { - props.push_back({ group->key, item->get() }); - } else { - props.push_back({ group->key + "::" + subgroup->key, item->get() }); - } - } - } + group_names.push_back(group->key); } - props.sort_custom<UniformPropComparator>(); + group_names.sort(); - for (List<UniformProp>::Element *E = props.front(); E; E = E->next()) { - p_list->push_back(E->get().info); + for (const String &group_name : group_names) { + List<String> subgroup_names; + HashMap<String, List<PropertyInfo>> &subgroups = groups[group_name]; + for (HashMap<String, List<PropertyInfo>>::Iterator subgroup = subgroups.begin(); subgroup; ++subgroup) { + subgroup_names.push_back(subgroup->key); + } + subgroup_names.sort(); + for (const String &subgroup_name : subgroup_names) { + List<PropertyInfo> &prop_infos = subgroups[subgroup_name]; + for (List<PropertyInfo>::Element *item = prop_infos.front(); item; item = item->next()) { + p_list->push_back(item->get()); + } + } } } } bool ShaderMaterial::_property_can_revert(const StringName &p_name) const { if (shader.is_valid()) { - StringName pr = shader->remap_uniform(p_name); + StringName pr = shader->remap_parameter(p_name); if (pr) { Variant default_value = RenderingServer::get_singleton()->shader_get_parameter_default(shader->get_rid(), pr); Variant current_value; @@ -316,7 +295,7 @@ bool ShaderMaterial::_property_can_revert(const StringName &p_name) const { bool ShaderMaterial::_property_get_revert(const StringName &p_name, Variant &r_property) const { if (shader.is_valid()) { - StringName pr = shader->remap_uniform(p_name); + StringName pr = shader->remap_parameter(p_name); if (pr) { r_property = RenderingServer::get_singleton()->shader_get_parameter_default(shader->get_rid(), pr); return true; @@ -924,6 +903,7 @@ void BaseMaterial3D::_update_shader() { if (flags[FLAG_BILLBOARD_KEEP_SCALE]) { code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } + code += " MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);\n"; } break; case BILLBOARD_FIXED_Y: { code += " MODELVIEW_MATRIX = VIEW_MATRIX * mat4(vec4(normalize(cross(vec3(0.0, 1.0, 0.0), INV_VIEW_MATRIX[2].xyz)), 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(normalize(cross(INV_VIEW_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))), 0.0), MODEL_MATRIX[3]);\n"; @@ -931,6 +911,7 @@ void BaseMaterial3D::_update_shader() { if (flags[FLAG_BILLBOARD_KEEP_SCALE]) { code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } + code += " MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);\n"; } break; case BILLBOARD_PARTICLES: { //make billboard @@ -939,6 +920,8 @@ void BaseMaterial3D::_update_shader() { code += " mat_world = mat_world * mat4(vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; //set modelview code += " MODELVIEW_MATRIX = VIEW_MATRIX * mat_world;\n"; + //set modelview normal + code += " MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);\n"; //handle animation code += " float h_frames = float(particles_anim_h_frames);\n"; @@ -949,7 +932,7 @@ void BaseMaterial3D::_update_shader() { code += " particle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n"; code += " } else {\n"; code += " particle_frame = mod(particle_frame, particle_total_frames);\n"; - code += " }"; + code += " }\n"; code += " UV /= vec2(h_frames, v_frames);\n"; code += " UV += vec2(mod(particle_frame, h_frames) / h_frames, floor((particle_frame + 0.5) / h_frames) / v_frames);\n"; } break; @@ -2365,7 +2348,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(); @@ -2641,7 +2624,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); @@ -2825,7 +2808,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"); @@ -3140,8 +3123,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 6c81293ee3..b3c2159e70 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -84,17 +84,6 @@ class ShaderMaterial : public Material { HashMap<StringName, Variant> param_cache; - struct UniformProp { - String str; - PropertyInfo info; - }; - - struct UniformPropComparator { - bool operator()(const UniformProp &p_a, const UniformProp &p_b) const { - return p_a.str.naturalnocasecmp_to(p_b.str) < 0; - } - }; - protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -730,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..706db3af16 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) { @@ -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..eb83a37c7b 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -771,6 +771,7 @@ 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); @@ -789,7 +790,7 @@ 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)); point++; @@ -1078,7 +1079,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) @@ -2443,17 +2444,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..ee61f0ac55 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -262,6 +262,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. */ diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 0d798d2e27..85b538b1d9 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 } } @@ -1114,10 +1121,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 +1168,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 +1251,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 +1295,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 +1370,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 +1403,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 +1451,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.h b/scene/resources/shader.h index d267e6520e..57be142a95 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -87,15 +87,44 @@ public: virtual bool is_text_shader() const; - _FORCE_INLINE_ StringName remap_uniform(const StringName &p_uniform) const { + // Finds the shader parameter name for the given property name, which should start with "shader_parameter/". + _FORCE_INLINE_ StringName remap_parameter(const StringName &p_property) const { if (params_cache_dirty) { get_shader_uniform_list(nullptr); } - const HashMap<StringName, StringName>::Iterator E = params_cache.find(p_uniform); - if (E) { - return E->value; + String n = p_property; + + // Backwards compatibility with old shader parameter names. + // Note: The if statements are important to make sure we are only replacing text exactly at index 0. + if (n.find("param/") == 0) { + n = n.replace_first("param/", "shader_parameter/"); + } + if (n.find("shader_param/") == 0) { + n = n.replace_first("shader_param/", "shader_parameter/"); + } + if (n.find("shader_uniform/") == 0) { + n = n.replace_first("shader_uniform/", "shader_parameter/"); + } + + { + // Additional backwards compatibility for projects between #62972 and #64092 (about a month of v4.0 development). + // These projects did not have any prefix for shader uniforms due to a bug. + // This code should be removed during beta or rc of 4.0. + const HashMap<StringName, StringName>::Iterator E = params_cache.find(n); + if (E) { + return E->value; + } + } + + if (n.begins_with("shader_parameter/")) { + n = n.replace_first("shader_parameter/", ""); + const HashMap<StringName, StringName>::Iterator E = params_cache.find(n); + if (E) { + return E->value; + } } + return StringName(); } diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp index 885cf0f1b8..0ae0e31120 100644 --- a/scene/resources/skeleton_modification_2d.cpp +++ b/scene/resources/skeleton_modification_2d.cpp @@ -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_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_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/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp index 068c756849..56234a8a14 100644 --- a/scene/resources/skeleton_modification_stack_2d.cpp +++ b/scene/resources/skeleton_modification_stack_2d.cpp @@ -182,11 +182,11 @@ void SkeletonModificationStack2D::delete_modification(int p_mod_idx) { void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod) { ERR_FAIL_INDEX(p_mod_idx, modifications.size()); - if (p_mod == nullptr) { - modifications.insert(p_mod_idx, nullptr); + if (p_mod.is_null()) { + modifications.write[p_mod_idx] = Ref<SkeletonModification2D>(); } else { + modifications.write[p_mod_idx] = p_mod; p_mod->_setup_modification(this); - modifications.insert(p_mod_idx, p_mod); } #ifdef TOOLS_ENABLED diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp index 1367ea86dd..61a0350440 100644 --- a/scene/resources/skeleton_profile.cpp +++ b/scene/resources/skeleton_profile.cpp @@ -566,7 +566,7 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[14].bone_name = "LeftThumbMetacarpal"; bones.write[14].bone_parent = "LeftHand"; - bones.write[14].reference_pose = Transform3D(0, -0.577, 0.816, 0.707, 0.577, 0.408, -0.707, 0.577, 0.408, -0.025, 0, 0); + bones.write[14].reference_pose = Transform3D(0, -0.577, 0.816, 0, 0.816, 0.577, -1, 0, 0, -0.025, 0.025, 0); bones.write[14].handle_offset = Vector2(0.4, 0.8); bones.write[14].group = "LeftHand"; @@ -686,7 +686,7 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() { bones.write[33].bone_name = "RightThumbMetacarpal"; bones.write[33].bone_parent = "RightHand"; - bones.write[33].reference_pose = Transform3D(0, 0.577, -0.816, -0.707, 0.577, 0.408, 0.707, 0.577, 0.408, 0.025, 0, 0); + bones.write[33].reference_pose = Transform3D(0, 0.577, -0.816, 0, 0.816, 0.577, 1, 0, 0, 0.025, 0.025, 0); bones.write[33].handle_offset = Vector2(0.6, 0.8); bones.write[33].group = "RightHand"; 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.cpp b/scene/resources/sky_material.cpp index fc999d5fcb..d21f04fab8 100644 --- a/scene/resources/sky_material.cpp +++ b/scene/resources/sky_material.cpp @@ -34,7 +34,7 @@ #include "core/version.h" Mutex ProceduralSkyMaterial::shader_mutex; -RID ProceduralSkyMaterial::shader; +RID ProceduralSkyMaterial::shader_cache[2]; void ProceduralSkyMaterial::set_sky_top_color(const Color &p_sky_top) { sky_top_color = p_sky_top; @@ -147,7 +147,11 @@ float ProceduralSkyMaterial::get_sun_curve() const { void ProceduralSkyMaterial::set_use_debanding(bool p_use_debanding) { use_debanding = p_use_debanding; - RS::get_singleton()->material_set_param(_get_material(), "use_debanding", use_debanding); + _update_shader(); + // Only set if shader already compiled + if (shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); + } } bool ProceduralSkyMaterial::get_use_debanding() const { @@ -161,7 +165,8 @@ Shader::Mode ProceduralSkyMaterial::get_shader_mode() const { RID ProceduralSkyMaterial::get_rid() const { _update_shader(); if (!shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader); + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(use_debanding)]); + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); shader_set = true; } return _get_material(); @@ -169,7 +174,7 @@ RID ProceduralSkyMaterial::get_rid() const { RID ProceduralSkyMaterial::get_shader_rid() const { _update_shader(); - return shader; + return shader_cache[int(use_debanding)]; } void ProceduralSkyMaterial::_validate_property(PropertyInfo &p_property) const { @@ -241,21 +246,24 @@ void ProceduralSkyMaterial::_bind_methods() { } void ProceduralSkyMaterial::cleanup_shader() { - if (shader.is_valid()) { - RS::get_singleton()->free(shader); + if (shader_cache[0].is_valid()) { + RS::get_singleton()->free(shader_cache[0]); + RS::get_singleton()->free(shader_cache[1]); } } void ProceduralSkyMaterial::_update_shader() { shader_mutex.lock(); - if (shader.is_null()) { - shader = RS::get_singleton()->shader_create(); + if (shader_cache[0].is_null()) { + for (int i = 0; i < 2; i++) { + shader_cache[i] = RS::get_singleton()->shader_create(); - // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). - RS::get_singleton()->shader_set_code(shader, R"( + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( // NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s ProceduralSkyMaterial. shader_type sky; +%s uniform vec4 sky_top_color : source_color = vec4(0.385, 0.454, 0.55, 1.0); uniform vec4 sky_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); @@ -269,14 +277,6 @@ uniform float ground_curve : hint_range(0, 1) = 0.02; uniform float ground_energy = 1.0; uniform float sun_angle_max = 30.0; uniform float sun_curve : hint_range(0, 1) = 0.15; -uniform bool use_debanding = true; - -// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare -vec3 interleaved_gradient_noise(vec2 pos) { - const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f); - float res = fract(magic.z * fract(dot(pos, magic.xy))) * 2.0 - 1.0; - return vec3(res, -res, res) / 255.0; -} void sky() { float v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0)); @@ -332,11 +332,10 @@ void sky() { ground *= ground_energy; COLOR = mix(ground, sky, step(0.0, EYEDIR.y)); - if (use_debanding) { - COLOR += interleaved_gradient_noise(FRAGCOORD.xy); - } } -)"); +)", + i ? "render_mode use_debanding;" : "")); + } } shader_mutex.unlock(); } @@ -546,7 +545,11 @@ float PhysicalSkyMaterial::get_energy_multiplier() const { void PhysicalSkyMaterial::set_use_debanding(bool p_use_debanding) { use_debanding = p_use_debanding; - RS::get_singleton()->material_set_param(_get_material(), "use_debanding", use_debanding); + _update_shader(); + // Only set if shader already compiled + if (shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); + } } bool PhysicalSkyMaterial::get_use_debanding() const { @@ -570,7 +573,8 @@ Shader::Mode PhysicalSkyMaterial::get_shader_mode() const { RID PhysicalSkyMaterial::get_rid() const { _update_shader(); if (!shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader); + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(use_debanding)]); + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); shader_set = true; } return _get_material(); @@ -578,7 +582,7 @@ RID PhysicalSkyMaterial::get_rid() const { RID PhysicalSkyMaterial::get_shader_rid() const { _update_shader(); - return shader; + return shader_cache[int(use_debanding)]; } void PhysicalSkyMaterial::_validate_property(PropertyInfo &p_property) const { @@ -588,7 +592,7 @@ void PhysicalSkyMaterial::_validate_property(PropertyInfo &p_property) const { } Mutex PhysicalSkyMaterial::shader_mutex; -RID PhysicalSkyMaterial::shader; +RID PhysicalSkyMaterial::shader_cache[2]; void PhysicalSkyMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rayleigh_coefficient", "rayleigh"), &PhysicalSkyMaterial::set_rayleigh_coefficient); @@ -642,21 +646,24 @@ void PhysicalSkyMaterial::_bind_methods() { } void PhysicalSkyMaterial::cleanup_shader() { - if (shader.is_valid()) { - RS::get_singleton()->free(shader); + if (shader_cache[0].is_valid()) { + RS::get_singleton()->free(shader_cache[0]); + RS::get_singleton()->free(shader_cache[1]); } } void PhysicalSkyMaterial::_update_shader() { shader_mutex.lock(); - if (shader.is_null()) { - shader = RS::get_singleton()->shader_create(); + if (shader_cache[0].is_null()) { + for (int i = 0; i < 2; i++) { + shader_cache[i] = RS::get_singleton()->shader_create(); - // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). - RS::get_singleton()->shader_set_code(shader, R"( + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( // NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PhysicalSkyMaterial. shader_type sky; +%s uniform float rayleigh : hint_range(0, 64) = 2.0; uniform vec4 rayleigh_color : source_color = vec4(0.3, 0.405, 0.6, 1.0); @@ -668,7 +675,6 @@ uniform float turbidity : hint_range(0, 1000) = 10.0; uniform float sun_disk_scale : hint_range(0, 360) = 1.0; uniform vec4 ground_color : source_color = vec4(0.1, 0.07, 0.034, 1.0); uniform float exposure : hint_range(0, 128) = 1.0; -uniform bool use_debanding = true; uniform sampler2D night_sky : source_color, hint_default_black; @@ -683,13 +689,6 @@ float henyey_greenstein(float cos_theta, float g) { return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5)); } -// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare -vec3 interleaved_gradient_noise(vec2 pos) { - const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f); - float res = fract(magic.z * fract(dot(pos, magic.xy))) * 2.0 - 1.0; - return vec3(res, -res, res) / 255.0; -} - void sky() { if (LIGHT0_ENABLED) { float zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 ); @@ -737,16 +736,15 @@ void sky() { vec3 color = Lin + L0; COLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade)))); COLOR *= exposure; - if (use_debanding) { - COLOR += interleaved_gradient_noise(FRAGCOORD.xy); - } } else { // There is no sun, so display night_sky and nothing else. COLOR = texture(night_sky, SKY_COORDS).xyz; COLOR *= exposure; } } -)"); +)", + i ? "render_mode use_debanding;" : "")); + } } shader_mutex.unlock(); diff --git a/scene/resources/sky_material.h b/scene/resources/sky_material.h index b517fd806b..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); @@ -55,7 +55,7 @@ private: bool use_debanding = true; static Mutex shader_mutex; - static RID shader; + static RID shader_cache[2]; static void _update_shader(); mutable bool shader_set = false; @@ -160,7 +160,7 @@ class PhysicalSkyMaterial : public Material { private: static Mutex shader_mutex; - static RID shader; + static RID shader_cache[2]; float rayleigh = 0.0f; Color rayleigh_color; 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/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/texture.cpp b/scene/resources/texture.cpp index d53dc1a8fc..3a2b0ed9cb 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)) { @@ -739,7 +740,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 +766,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 +1206,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 +1285,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 +1488,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(); } @@ -2325,11 +2333,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 +2364,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 +3113,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..3caf6484d9 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,14 +3779,14 @@ 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(); @@ -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/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..640f6dfcb7 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -955,7 +955,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 +1148,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 +1189,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 +1263,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 +1271,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 +1312,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 +1325,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 +1352,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 +1366,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 +1386,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 +1396,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 +1408,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 +1434,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 +1468,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 +1483,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 +1509,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 +1579,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 +1592,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 +1610,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 +1631,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 +1644,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 +1657,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 +1825,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 +1902,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 +1947,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 +1991,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 +2070,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 +2102,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 +2339,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 +2363,7 @@ void VisualShader::_update_shader() const { } else { func_code += varying_code; func_code += "}\n"; - code += func_code; + shader_code += func_code; } } @@ -2377,65 +2376,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 +2503,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 +2762,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 +3647,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 +3665,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 +3681,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..a4c958d402 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -7264,7 +7264,7 @@ String VisualShaderNodeProximityFade::generate_code(Shader::Mode p_mode, VisualS } else { 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 += " __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]); return code; 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"; |