diff options
Diffstat (limited to 'scene')
309 files changed, 14969 insertions, 3379 deletions
diff --git a/scene/2d/animated_sprite.cpp b/scene/2d/animated_sprite.cpp index 824f50495b..e3d1592be0 100644 --- a/scene/2d/animated_sprite.cpp +++ b/scene/2d/animated_sprite.cpp @@ -59,15 +59,36 @@ bool AnimatedSprite::_edit_use_pivot() const { } Rect2 AnimatedSprite::_edit_get_rect() const { + return _get_rect(); +} + +bool AnimatedSprite::_edit_use_rect() const { + if (!frames.is_valid() || !frames->has_animation(animation) || frame < 0 || frame >= frames->get_frame_count(animation)) { + return false; + } + Ref<Texture> t; + if (animation) + t = frames->get_frame(animation, frame); + if (t.is_null()) + return false; + + return true; +} + +Rect2 AnimatedSprite::get_anchorable_rect() const { + return _get_rect(); +} + +Rect2 AnimatedSprite::_get_rect() const { if (!frames.is_valid() || !frames->has_animation(animation) || frame < 0 || frame >= frames->get_frame_count(animation)) { - return Node2D::_edit_get_rect(); + return Rect2(); } Ref<Texture> t; if (animation) t = frames->get_frame(animation, frame); if (t.is_null()) - return Node2D::_edit_get_rect(); + return Rect2(); Size2 s = t->get_size(); Point2 ofs = offset; @@ -347,7 +368,7 @@ void AnimatedSprite::_notification(int p_what) { if (frame < 0) return; - float speed = frames->get_animation_speed(animation); + float speed = frames->get_animation_speed(animation) * speed_scale; if (speed == 0) return; //do nothing @@ -357,7 +378,7 @@ void AnimatedSprite::_notification(int p_what) { if (timeout <= 0) { - timeout = 1.0 / speed; + timeout = _get_frame_duration(); int fc = frames->get_frame_count(animation); if (frame >= fc - 1) { @@ -477,6 +498,22 @@ int AnimatedSprite::get_frame() const { return frame; } +void AnimatedSprite::set_speed_scale(float p_speed_scale) { + + float elapsed = _get_frame_duration() - timeout; + + speed_scale = MAX(p_speed_scale, 0.0f); + + // We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed + _reset_timeout(); + timeout -= elapsed; +} + +float AnimatedSprite::get_speed_scale() const { + + return speed_scale; +} + void AnimatedSprite::set_centered(bool p_center) { centered = p_center; @@ -560,21 +597,22 @@ bool AnimatedSprite::is_playing() const { return playing; } +float AnimatedSprite::_get_frame_duration() { + if (frames.is_valid() && frames->has_animation(animation)) { + float speed = frames->get_animation_speed(animation) * speed_scale; + if (speed > 0) { + return 1.0 / speed; + } + } + return 0.0; +} + void AnimatedSprite::_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(); } void AnimatedSprite::set_animation(const StringName &p_animation) { @@ -632,6 +670,9 @@ void AnimatedSprite::_bind_methods() { ClassDB::bind_method(D_METHOD("set_frame", "frame"), &AnimatedSprite::set_frame); ClassDB::bind_method(D_METHOD("get_frame"), &AnimatedSprite::get_frame); + ClassDB::bind_method(D_METHOD("set_speed_scale", "speed_scale"), &AnimatedSprite::set_speed_scale); + ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimatedSprite::get_speed_scale); + ClassDB::bind_method(D_METHOD("_res_changed"), &AnimatedSprite::_res_changed); ADD_SIGNAL(MethodInfo("frame_changed")); @@ -640,6 +681,7 @@ void AnimatedSprite::_bind_methods() { ADD_PROPERTYNZ(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_PROPERTYNZ(PropertyInfo(Variant::INT, "frame", PROPERTY_HINT_SPRITE_FRAME), "set_frame", "get_frame"); + ADD_PROPERTYNO(PropertyInfo(Variant::REAL, "speed_scale"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "playing"), "_set_playing", "_is_playing"); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); @@ -654,6 +696,7 @@ AnimatedSprite::AnimatedSprite() { vflip = false; frame = 0; + speed_scale = 1.0f; playing = false; animation = "default"; timeout = 0; diff --git a/scene/2d/animated_sprite.h b/scene/2d/animated_sprite.h index a38adf792c..806052a696 100644 --- a/scene/2d/animated_sprite.h +++ b/scene/2d/animated_sprite.h @@ -129,6 +129,7 @@ class AnimatedSprite : public Node2D { bool playing; StringName animation; int frame; + float speed_scale; bool centered; Point2 offset; @@ -140,9 +141,11 @@ class AnimatedSprite : public Node2D { void _res_changed(); + float _get_frame_duration(); void _reset_timeout(); void _set_playing(bool p_playing); bool _is_playing() const; + Rect2 _get_rect() const; protected: static void _bind_methods(); @@ -157,6 +160,9 @@ public: virtual Point2 _edit_get_pivot() const; virtual bool _edit_use_pivot() const; virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; + + virtual Rect2 get_anchorable_rect() const; void set_sprite_frames(const Ref<SpriteFrames> &p_frames); Ref<SpriteFrames> get_sprite_frames() const; @@ -171,6 +177,9 @@ public: void set_frame(int p_frame); int get_frame() const; + void set_speed_scale(float p_speed_scale); + float get_speed_scale() const; + void set_centered(bool p_center); bool is_centered() const; diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index bb914b90fc..c375374dce 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -666,11 +666,11 @@ void Area2D::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine"), "set_space_override_mode", "get_space_override_mode"); ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "gravity_point"), "set_gravity_is_point", "is_gravity_a_point"); - ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "gravity_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001"), "set_gravity_distance_scale", "get_gravity_distance_scale"); + ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "gravity_distance_scale", PROPERTY_HINT_EXP_RANGE, "0,1024,0.001,or_greater"), "set_gravity_distance_scale", "get_gravity_distance_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity_vec"), "set_gravity_vector", "get_gravity_vector"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "gravity", PROPERTY_HINT_RANGE, "-1024,1024,0.001"), "set_gravity", "get_gravity"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_linear_damp", "get_linear_damp"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_angular_damp", "get_angular_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_linear_damp", "get_linear_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_angular_damp", "get_angular_damp"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority"); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "monitoring"), "set_monitoring", "is_monitoring"); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable"); diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index f998f23d3b..6e77369d65 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -36,11 +36,8 @@ void AudioStreamPlayer2D::_mix_audio() { - if (!stream_playback.is_valid()) { - return; - } - - if (!active) { + if (!stream_playback.is_valid() || !active || + (stream_paused && stream_paused_fade <= 0.f)) { return; } @@ -53,8 +50,10 @@ void AudioStreamPlayer2D::_mix_audio() { AudioFrame *buffer = mix_buffer.ptrw(); int buffer_size = mix_buffer.size(); - //mix - stream_playback->mix(buffer, pitch_scale, buffer_size); + // Mix if we're not paused or we're fading out + if (!stream_paused || stream_paused_fade > 0.f) { + stream_playback->mix(buffer, pitch_scale, buffer_size); + } //write all outputs for (int i = 0; i < output_count; i++) { @@ -86,6 +85,13 @@ void AudioStreamPlayer2D::_mix_audio() { AudioFrame vol_inc = (current.vol - prev_outputs[i].vol) / float(buffer_size); AudioFrame vol = current.vol; + if (stream_paused) { + vol = vol * stream_paused_fade; + if (stream_paused_fade > 0.f) { + stream_paused_fade -= 0.1f; + } + } + int cc = AudioServer::get_singleton()->get_channel_count(); if (cc == 1) { @@ -142,6 +148,17 @@ void AudioStreamPlayer2D::_notification(int p_what) { AudioServer::get_singleton()->remove_callback(_mix_audios, this); } + if (p_what == NOTIFICATION_PAUSED) { + if (!can_process()) { + // Node can't process so we start fading out to silence + set_stream_paused(true); + } + } + + if (p_what == NOTIFICATION_UNPAUSED) { + set_stream_paused(false); + } + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { //update anything related to position first, if possible of course @@ -418,6 +435,19 @@ uint32_t AudioStreamPlayer2D::get_area_mask() const { return area_mask; } +void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { + + if (p_pause != stream_paused) { + stream_paused = p_pause; + stream_paused_fade = stream_paused ? 1.f : 0.f; + } +} + +bool AudioStreamPlayer2D::get_stream_paused() const { + + return stream_paused; +} + void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer2D::set_stream); @@ -454,6 +484,9 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_area_mask", "mask"), &AudioStreamPlayer2D::set_area_mask); ClassDB::bind_method(D_METHOD("get_area_mask"), &AudioStreamPlayer2D::get_area_mask); + ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer2D::set_stream_paused); + ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer2D::get_stream_paused); + ClassDB::bind_method(D_METHOD("_bus_layout_changed"), &AudioStreamPlayer2D::_bus_layout_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -461,8 +494,9 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,32,0.01"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_RANGE, "1,65536,1"), "set_max_distance", "get_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation", PROPERTY_HINT_EXP_EASING), "set_attenuation", "get_attenuation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_EXP_RANGE, "1,4096,1,or_greater"), "set_max_distance", "get_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); @@ -483,6 +517,8 @@ AudioStreamPlayer2D::AudioStreamPlayer2D() { setplay = -1; output_ready = false; area_mask = 1; + stream_paused = false; + stream_paused_fade = 0.f; AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); } diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index 9ae8e3a518..b508de3171 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -71,7 +71,9 @@ private: float volume_db; float pitch_scale; + float stream_paused_fade; bool autoplay; + bool stream_paused; StringName bus; void _mix_audio(); @@ -123,6 +125,9 @@ public: void set_area_mask(uint32_t p_mask); uint32_t get_area_mask() const; + void set_stream_paused(bool p_pause); + bool get_stream_paused() const; + AudioStreamPlayer2D(); ~AudioStreamPlayer2D(); }; diff --git a/scene/2d/back_buffer_copy.cpp b/scene/2d/back_buffer_copy.cpp index 446b367be0..e06c30ec6b 100644 --- a/scene/2d/back_buffer_copy.cpp +++ b/scene/2d/back_buffer_copy.cpp @@ -55,6 +55,15 @@ Rect2 BackBufferCopy::_edit_get_rect() const { return rect; } +bool BackBufferCopy::_edit_use_rect() const { + return true; +} + +Rect2 BackBufferCopy::get_anchorable_rect() const { + + return rect; +} + void BackBufferCopy::set_rect(const Rect2 &p_rect) { rect = p_rect; diff --git a/scene/2d/back_buffer_copy.h b/scene/2d/back_buffer_copy.h index b786c2b828..b1ee12544b 100644 --- a/scene/2d/back_buffer_copy.h +++ b/scene/2d/back_buffer_copy.h @@ -54,9 +54,11 @@ protected: public: Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; void set_rect(const Rect2 &p_rect); Rect2 get_rect() const; + Rect2 get_anchorable_rect() const; void set_copy_mode(CopyMode p_mode); CopyMode get_copy_mode() const; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index d172da5bd9..3b86ca76ea 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -91,8 +91,8 @@ Transform2D Camera2D::get_camera_transform() { if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) { if (h_drag_enabled && !Engine::get_singleton()->is_editor_hint()) { - camera_pos.x = MIN(camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_LEFT])); - camera_pos.x = MAX(camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * drag_margin[MARGIN_RIGHT])); + camera_pos.x = MIN(camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * zoom.x * drag_margin[MARGIN_LEFT])); + camera_pos.x = MAX(camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * zoom.x * drag_margin[MARGIN_RIGHT])); } else { if (h_ofs < 0) { @@ -104,8 +104,8 @@ Transform2D Camera2D::get_camera_transform() { if (v_drag_enabled && !Engine::get_singleton()->is_editor_hint()) { - camera_pos.y = MIN(camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_TOP])); - camera_pos.y = MAX(camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM])); + camera_pos.y = MIN(camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * zoom.y * drag_margin[MARGIN_TOP])); + camera_pos.y = MAX(camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * zoom.y * drag_margin[MARGIN_BOTTOM])); } else { diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index 5cca5705a0..47326b9be2 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -94,6 +94,7 @@ void CanvasItemMaterial::_update_shader() { case BLEND_MODE_SUB: code += "blend_sub"; break; case BLEND_MODE_MUL: code += "blend_mul"; break; case BLEND_MODE_PREMULT_ALPHA: code += "blend_premul_alpha"; break; + case BLEND_MODE_DISABLED: code += "blend_disabled"; break; } switch (light_mode) { @@ -245,6 +246,14 @@ CanvasItemMaterial::~CanvasItemMaterial() { /////////////////////////////////////////////////////////////////// +bool CanvasItem::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + if (_edit_use_rect()) { + return _edit_get_rect().has_point(p_point); + } else { + return p_point.length() < p_tolerance; + } +} + bool CanvasItem::is_visible_in_tree() const { if (!is_inside_tree()) @@ -312,11 +321,6 @@ void CanvasItem::hide() { _change_notify("visible"); } -Size2 CanvasItem::_edit_get_minimum_size() const { - - return Size2(-1, -1); //no limit -} - void CanvasItem::_update_callback() { if (!is_inside_tree()) { @@ -412,7 +416,7 @@ void CanvasItem::_enter_canvas() { RID canvas; if (canvas_layer) - canvas = canvas_layer->get_world_2d()->get_canvas(); + canvas = canvas_layer->get_canvas(); else canvas = get_viewport()->find_world_2d()->get_canvas(); @@ -821,6 +825,12 @@ float CanvasItem::draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const void CanvasItem::_notify_transform(CanvasItem *p_node) { + /* This check exists to avoid re-propagating the transform + * notification down the tree on dirty nodes. It provides + * optimization by avoiding redundancy (nodes are dirty, will get the + * notification anyway). + */ + if (/*p_node->xform_change.in_list() &&*/ p_node->global_invalid) { return; //nothing to do } @@ -854,7 +864,7 @@ RID CanvasItem::get_canvas() const { ERR_FAIL_COND_V(!is_inside_tree(), RID()); if (canvas_layer) - return canvas_layer->get_world_2d()->get_canvas(); + return canvas_layer->get_canvas(); else return get_viewport()->find_world_2d()->get_canvas(); } @@ -875,9 +885,7 @@ Ref<World2D> CanvasItem::get_world_2d() const { CanvasItem *tl = get_toplevel(); - if (tl->canvas_layer) { - return tl->canvas_layer->get_world_2d(); - } else if (tl->get_viewport()) { + if (tl->get_viewport()) { return tl->get_viewport()->find_world_2d(); } else { return Ref<World2D>(); @@ -976,11 +984,11 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("_edit_set_position", "position"), &CanvasItem::_edit_set_position); ClassDB::bind_method(D_METHOD("_edit_get_position"), &CanvasItem::_edit_get_position); - ClassDB::bind_method(D_METHOD("_edit_use_position"), &CanvasItem::_edit_use_position); + ClassDB::bind_method(D_METHOD("_edit_set_scale", "scale"), &CanvasItem::_edit_set_scale); + ClassDB::bind_method(D_METHOD("_edit_get_scale"), &CanvasItem::_edit_get_scale); ClassDB::bind_method(D_METHOD("_edit_set_rect", "rect"), &CanvasItem::_edit_set_rect); ClassDB::bind_method(D_METHOD("_edit_get_rect"), &CanvasItem::_edit_get_rect); ClassDB::bind_method(D_METHOD("_edit_use_rect"), &CanvasItem::_edit_use_rect); - ClassDB::bind_method(D_METHOD("_edit_get_item_and_children_rect"), &CanvasItem::_edit_get_item_and_children_rect); ClassDB::bind_method(D_METHOD("_edit_set_rotation", "degrees"), &CanvasItem::_edit_set_rotation); ClassDB::bind_method(D_METHOD("_edit_get_rotation"), &CanvasItem::_edit_get_rotation); ClassDB::bind_method(D_METHOD("_edit_use_rotation"), &CanvasItem::_edit_use_rotation); @@ -1091,6 +1099,7 @@ void CanvasItem::_bind_methods() { BIND_ENUM_CONSTANT(BLEND_MODE_SUB); BIND_ENUM_CONSTANT(BLEND_MODE_MUL); BIND_ENUM_CONSTANT(BLEND_MODE_PREMULT_ALPHA); + BIND_ENUM_CONSTANT(BLEND_MODE_DISABLED); BIND_CONSTANT(NOTIFICATION_TRANSFORM_CHANGED); BIND_CONSTANT(NOTIFICATION_DRAW); @@ -1160,21 +1169,6 @@ int CanvasItem::get_canvas_layer() const { return 0; } -Rect2 CanvasItem::_edit_get_item_and_children_rect() const { - - Rect2 rect = _edit_get_rect(); - - for (int i = 0; i < get_child_count(); i++) { - CanvasItem *c = Object::cast_to<CanvasItem>(get_child(i)); - if (c) { - Rect2 sir = c->get_transform().xform(c->_edit_get_item_and_children_rect()); - rect = rect.merge(sir); - } - } - - return rect; -} - CanvasItem::CanvasItem() : xform_change(this) { diff --git a/scene/2d/canvas_item.h b/scene/2d/canvas_item.h index 980fcb4109..1e6a251c9c 100644 --- a/scene/2d/canvas_item.h +++ b/scene/2d/canvas_item.h @@ -54,7 +54,8 @@ public: BLEND_MODE_ADD, BLEND_MODE_SUB, BLEND_MODE_MUL, - BLEND_MODE_PREMULT_ALPHA + BLEND_MODE_PREMULT_ALPHA, + BLEND_MODE_DISABLED }; enum LightMode { @@ -145,7 +146,8 @@ public: BLEND_MODE_ADD, BLEND_MODE_SUB, BLEND_MODE_MUL, - BLEND_MODE_PREMULT_ALPHA + BLEND_MODE_PREMULT_ALPHA, + BLEND_MODE_DISABLED }; private: @@ -220,32 +222,36 @@ public: /* EDITOR */ + // Select the node + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; + + // Save and restore a CanvasItem state virtual void _edit_set_state(const Dictionary &p_state){}; virtual Dictionary _edit_get_state() const { return Dictionary(); }; - // Used to move/select the node - virtual void _edit_set_position(const Point2 &p_position){}; - virtual Point2 _edit_get_position() const { return Point2(); }; - virtual bool _edit_use_position() const { return false; }; + // Used to move the node + virtual void _edit_set_position(const Point2 &p_position) = 0; + virtual Point2 _edit_get_position() const = 0; - // Used to resize/move/select the node - virtual void _edit_set_rect(const Rect2 &p_rect){}; - virtual Rect2 _edit_get_rect() const { return Rect2(-32, -32, 64, 64); }; - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return _edit_get_rect().has_point(p_point); } - Rect2 _edit_get_item_and_children_rect() const; - virtual bool _edit_use_rect() const { return false; }; + // Used to scale the node + virtual void _edit_set_scale(const Size2 &p_scale) = 0; + virtual Size2 _edit_get_scale() const = 0; // Used to rotate the node + virtual bool _edit_use_rotation() const { return false; }; virtual void _edit_set_rotation(float p_rotation){}; virtual float _edit_get_rotation() const { return 0.0; }; - virtual bool _edit_use_rotation() const { return false; }; + + // Used to resize/move the node + virtual bool _edit_use_rect() const { return false; }; // MAYBE REPLACE BY A _edit_get_editmode() + virtual void _edit_set_rect(const Rect2 &p_rect){}; + virtual Rect2 _edit_get_rect() const { return Rect2(0, 0, 0, 0); }; + virtual Size2 _edit_get_minimum_size() const { return Size2(-1, -1); }; // LOOKS WEIRD // Used to set a pivot + virtual bool _edit_use_pivot() const { return false; }; virtual void _edit_set_pivot(const Point2 &p_pivot){}; virtual Point2 _edit_get_pivot() const { return Point2(); }; - virtual bool _edit_use_pivot() const { return false; }; - - virtual Size2 _edit_get_minimum_size() const; /* VISIBILITY */ @@ -308,7 +314,9 @@ public: virtual Transform2D get_global_transform_with_canvas() const; CanvasItem *get_toplevel() const; - _FORCE_INLINE_ RID get_canvas_item() const { return canvas_item; } + _FORCE_INLINE_ RID get_canvas_item() const { + return canvas_item; + } void set_block_transform_notify(bool p_enable); bool is_block_transform_notify_enabled() const; @@ -338,6 +346,9 @@ public: void set_notify_transform(bool p_enable); bool is_transform_notification_enabled() const; + // Used by control nodes to retreive the parent's anchorable area + virtual Rect2 get_anchorable_rect() const { return Rect2(0, 0, 0, 0); }; + int get_canvas_layer() const; CanvasItem(); diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index 978fb379ac..9d2a83fda7 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -175,7 +175,8 @@ void CollisionPolygon2D::_notification(int p_what) { Vector2 p = polygon[i]; Vector2 n = polygon[(i + 1) % polygon.size()]; - draw_line(p, n, Color(0.9, 0.2, 0.0, 0.8), 3); + // draw line with width <= 1, so it does not scale with zoom and break pixel exact editing + draw_line(p, n, Color(0.9, 0.2, 0.0, 0.8), 1); } #define DEBUG_DECOMPOSE #if defined(TOOLS_ENABLED) && defined(DEBUG_DECOMPOSE) @@ -263,6 +264,10 @@ Rect2 CollisionPolygon2D::_edit_get_rect() const { return aabb; } +bool CollisionPolygon2D::_edit_use_rect() const { + return true; +} + bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return Geometry::is_point_in_polygon(p_point, Variant(polygon)); diff --git a/scene/2d/collision_polygon_2d.h b/scene/2d/collision_polygon_2d.h index 4dafe7d1da..412a923292 100644 --- a/scene/2d/collision_polygon_2d.h +++ b/scene/2d/collision_polygon_2d.h @@ -73,6 +73,7 @@ public: Vector<Point2> get_polygon() const; virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; virtual String get_configuration_warning() const; diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index 0eeb6dafe5..83ef4df8f4 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -173,11 +173,6 @@ Ref<Shape2D> CollisionShape2D::get_shape() const { return shape; } -Rect2 CollisionShape2D::_edit_get_rect() const { - - return rect; -} - bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { if (!shape.is_valid()) diff --git a/scene/2d/collision_shape_2d.h b/scene/2d/collision_shape_2d.h index cdff595828..ed2c09d53d 100644 --- a/scene/2d/collision_shape_2d.h +++ b/scene/2d/collision_shape_2d.h @@ -54,7 +54,6 @@ protected: static void _bind_methods(); public: - virtual Rect2 _edit_get_rect() const; virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; void set_shape(const Ref<Shape2D> &p_shape); diff --git a/scene/2d/joints_2d.cpp b/scene/2d/joints_2d.cpp index 329382c034..7d5360c0e4 100644 --- a/scene/2d/joints_2d.cpp +++ b/scene/2d/joints_2d.cpp @@ -158,8 +158,8 @@ void Joint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint2D::set_exclude_nodes_from_collision); ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint2D::get_exclude_nodes_from_collision); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a"), "set_node_a", "get_node_a"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b"), "set_node_b", "get_node_b"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_a", "get_node_a"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_b", "get_node_b"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0,0.9,0.001"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_collision"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision"); } diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 1220ff299c..f93c7d1f79 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -59,7 +59,19 @@ bool Light2D::_edit_use_pivot() const { Rect2 Light2D::_edit_get_rect() const { if (texture.is_null()) - return Node2D::_edit_get_rect(); + return Rect2(); + + Size2 s = texture->get_size() * _scale; + return Rect2(texture_offset - s / 2.0, s); +} + +bool Light2D::_edit_use_rect() const { + return !texture.is_null(); +} + +Rect2 Light2D::get_anchorable_rect() const { + if (texture.is_null()) + return Rect2(); Size2 s = texture->get_size() * _scale; return Rect2(texture_offset - s / 2.0, s); diff --git a/scene/2d/light_2d.h b/scene/2d/light_2d.h index 16d8c485d4..40469cfbc8 100644 --- a/scene/2d/light_2d.h +++ b/scene/2d/light_2d.h @@ -92,6 +92,9 @@ public: virtual Point2 _edit_get_pivot() const; virtual bool _edit_use_pivot() const; virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; + + virtual Rect2 get_anchorable_rect() const; void set_enabled(bool p_enabled); bool is_enabled() const; diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index d4481583fb..c9e5d0f1bc 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -107,12 +107,12 @@ OccluderPolygon2D::~OccluderPolygon2D() { VS::get_singleton()->free(occ_polygon); } -#ifdef DEBUG_ENABLED void LightOccluder2D::_poly_changed() { +#ifdef DEBUG_ENABLED update(); -} #endif +} void LightOccluder2D::_notification(int p_what) { @@ -221,9 +221,7 @@ void LightOccluder2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_occluder_light_mask", "mask"), &LightOccluder2D::set_occluder_light_mask); ClassDB::bind_method(D_METHOD("get_occluder_light_mask"), &LightOccluder2D::get_occluder_light_mask); -#ifdef DEBUG_ENABLED ClassDB::bind_method("_poly_changed", &LightOccluder2D::_poly_changed); -#endif ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "occluder", PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D"), "set_occluder_polygon", "get_occluder_polygon"); ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask"); diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index 5e16351a6f..d59c9100b0 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -78,9 +78,7 @@ class LightOccluder2D : public Node2D { int mask; Ref<OccluderPolygon2D> occluder_polygon; -#ifdef DEBUG_ENABLED void _poly_changed(); -#endif protected: void _notification(int p_what); diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index f0ab9652b4..3e61dd05f4 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -62,6 +62,10 @@ Rect2 Line2D::_edit_get_rect() const { return aabb; } +bool Line2D::_edit_use_rect() const { + return true; +} + bool Line2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { const real_t d = _width / 2 + p_tolerance; @@ -252,18 +256,22 @@ void Line2D::_draw() { lb.sharp_limit = _sharp_limit; lb.width = _width; - lb.build(); - RID texture_rid; - if (_texture.is_valid()) + if (_texture.is_valid()) { texture_rid = (**_texture).get_rid(); + lb.tile_aspect = _texture->get_size().aspect(); + } + + lb.build(); + VS::get_singleton()->canvas_item_add_triangle_array( get_canvas_item(), lb.indices, lb.vertices, lb.colors, - lb.uvs, + lb.uvs, Vector<int>(), Vector<float>(), + texture_rid); // DEBUG diff --git a/scene/2d/line_2d.h b/scene/2d/line_2d.h index 0eba024555..24c48982cd 100644 --- a/scene/2d/line_2d.h +++ b/scene/2d/line_2d.h @@ -59,6 +59,7 @@ public: Line2D(); virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; void set_points(const PoolVector<Vector2> &p_points); diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index b1a072729f..845788bada 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -101,6 +101,7 @@ LineBuilder::LineBuilder() { round_precision = 8; begin_cap_mode = Line2D::LINE_CAP_NONE; end_cap_mode = Line2D::LINE_CAP_NONE; + tile_aspect = 1.f; _interpolate_color = false; _last_index[0] = 0; @@ -111,6 +112,7 @@ void LineBuilder::clear_output() { vertices.clear(); colors.clear(); indices.clear(); + uvs.clear(); } void LineBuilder::build() { @@ -121,6 +123,8 @@ void LineBuilder::build() { return; } + ERR_FAIL_COND(tile_aspect <= 0.f); + const float hw = width / 2.f; const float hw_sq = hw * hw; const float sharp_limit_sq = sharp_limit * sharp_limit; @@ -164,7 +168,7 @@ void LineBuilder::build() { current_distance1 = current_distance0; } else if (begin_cap_mode == Line2D::LINE_CAP_ROUND) { if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx0 = 0.5f; + uvx0 = 0.5f / tile_aspect; } new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, 1.f, 1.f)); total_distance += width; @@ -286,8 +290,8 @@ void LineBuilder::build() { color1 = gradient->get_color_at_offset(current_distance1 / total_distance); } if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx0 = current_distance0 / width; - uvx1 = current_distance1 / width; + uvx0 = current_distance0 / (width * tile_aspect); + uvx1 = current_distance1 / (width * tile_aspect); } strip_add_quad(pos_up1, pos_down1, color1, uvx1); @@ -373,7 +377,7 @@ void LineBuilder::build() { color1 = gradient->get_color(gradient->get_points_count() - 1); } if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx1 = current_distance1 / width; + uvx1 = current_distance1 / (width * tile_aspect); } strip_add_quad(pos_up1, pos_down1, color1, uvx1); diff --git a/scene/2d/line_builder.h b/scene/2d/line_builder.h index daa2ed7c24..b1c62f84e2 100644 --- a/scene/2d/line_builder.h +++ b/scene/2d/line_builder.h @@ -50,6 +50,7 @@ public: Line2D::LineTextureMode texture_mode; float sharp_limit; int round_precision; + float tile_aspect; // w/h // TODO offset_joints option (offers alternative implementation of round joints) // TODO Move in a struct and reference it diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp index adbb227d0c..9f21fe1a1f 100644 --- a/scene/2d/mesh_instance_2d.cpp +++ b/scene/2d/mesh_instance_2d.cpp @@ -1,3 +1,33 @@ +/*************************************************************************/ +/* mesh_instance_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + #include "mesh_instance_2d.h" void MeshInstance2D::_notification(int p_what) { diff --git a/scene/2d/mesh_instance_2d.h b/scene/2d/mesh_instance_2d.h index d1d1ade0ae..c9889c1c03 100644 --- a/scene/2d/mesh_instance_2d.h +++ b/scene/2d/mesh_instance_2d.h @@ -1,3 +1,33 @@ +/*************************************************************************/ +/* mesh_instance_2d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + #ifndef MESH_INSTANCE_2D_H #define MESH_INSTANCE_2D_H diff --git a/scene/2d/navigation2d.cpp b/scene/2d/navigation2d.cpp index 4737e14111..16e0386952 100644 --- a/scene/2d/navigation2d.cpp +++ b/scene/2d/navigation2d.cpp @@ -671,7 +671,7 @@ Object *Navigation2D::get_closest_point_owner(const Vector2 &p_point) { if (Geometry::is_point_in_triangle(p_point, _get_vertex(p.edges[0].point), _get_vertex(p.edges[i - 1].point), _get_vertex(p.edges[i].point))) { - E->get().owner; + return E->get().owner; } } } diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 95e24505be..3813bd96fe 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -58,16 +58,39 @@ void Node2D::_edit_set_state(const Dictionary &p_state) { } void Node2D::_edit_set_position(const Point2 &p_position) { - pos = p_position; - _update_transform(); - _change_notify("position"); + set_position(p_position); } Point2 Node2D::_edit_get_position() const { return pos; } +void Node2D::_edit_set_scale(const Size2 &p_scale) { + set_scale(p_scale); +} + +Size2 Node2D::_edit_get_scale() const { + return _scale; +} + +void Node2D::_edit_set_rotation(float p_rotation) { + angle = p_rotation; + _update_transform(); + _change_notify("rotation"); + _change_notify("rotation_degrees"); +} + +float Node2D::_edit_get_rotation() const { + return angle; +} + +bool Node2D::_edit_use_rotation() const { + return true; +} + void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) { + ERR_FAIL_COND(!_edit_use_rect()); + Rect2 r = _edit_get_rect(); Vector2 zero_offset; @@ -83,7 +106,7 @@ void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) { if (r.size.y != 0) new_scale.y = p_edit_rect.size.y / r.size.y; - Point2 new_pos = p_edit_rect.position + p_edit_rect.size * zero_offset; //p_edit_rect.pos - r.pos; + Point2 new_pos = p_edit_rect.position + p_edit_rect.size * zero_offset; Transform2D postxf; postxf.set_rotation_and_scale(angle, _scale); @@ -97,25 +120,6 @@ void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) { _change_notify("position"); } -bool Node2D::_edit_use_rect() const { - return true; -} - -void Node2D::_edit_set_rotation(float p_rotation) { - angle = p_rotation; - _update_transform(); - _change_notify("rotation"); - _change_notify("rotation_degrees"); -} - -float Node2D::_edit_get_rotation() const { - return angle; -} - -bool Node2D::_edit_use_rotation() const { - return true; -} - void Node2D::_update_xform_values() { pos = _mat.elements[2]; @@ -444,10 +448,10 @@ void Node2D::_bind_methods() { ADD_PROPERTYNO(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform", PROPERTY_HINT_NONE, "", 0), "set_transform", "get_transform"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "global_position", PROPERTY_HINT_NONE, "", 0), "set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", 0), "set_global_position", "get_global_position"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "global_rotation", PROPERTY_HINT_NONE, "", 0), "set_global_rotation", "get_global_rotation"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "global_rotation_degrees", PROPERTY_HINT_NONE, "", 0), "set_global_rotation_degrees", "get_global_rotation_degrees"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "global_scale", PROPERTY_HINT_NONE, "", 0), "set_global_scale", "get_global_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_scale", PROPERTY_HINT_NONE, "", 0), "set_global_scale", "get_global_scale"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_transform", PROPERTY_HINT_NONE, "", 0), "set_global_transform", "get_global_transform"); ADD_GROUP("Z Index", ""); diff --git a/scene/2d/node_2d.h b/scene/2d/node_2d.h index f817b214f8..725686cdf8 100644 --- a/scene/2d/node_2d.h +++ b/scene/2d/node_2d.h @@ -62,12 +62,16 @@ public: virtual void _edit_set_position(const Point2 &p_position); virtual Point2 _edit_get_position() const; - virtual void _edit_set_rect(const Rect2 &p_edit_rect); - virtual bool _edit_use_rect() const; + + virtual void _edit_set_scale(const Size2 &p_scale); + virtual Size2 _edit_get_scale() const; + virtual void _edit_set_rotation(float p_rotation); virtual float _edit_get_rotation() const; virtual bool _edit_use_rotation() const; + virtual void _edit_set_rect(const Rect2 &p_edit_rect); + void set_position(const Point2 &p_pos); void set_rotation(float p_radians); void set_rotation_degrees(float p_degrees); diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 050f98b02b..2ac6c76032 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -72,7 +72,7 @@ void ParallaxLayer::_update_mirroring() { ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent()); if (pb) { - RID c = pb->get_world_2d()->get_canvas(); + RID c = pb->get_canvas(); RID ci = get_canvas_item(); Point2 mirrorScale = mirroring * get_scale(); VisualServer::get_singleton()->canvas_set_item_mirroring(c, ci, mirrorScale); @@ -120,7 +120,6 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, float p_sc if (mirroring.x) { double den = mirroring.x * p_scale; - double before = new_ofs.x; new_ofs.x -= den * ceil(new_ofs.x / den); } diff --git a/scene/2d/particles_2d.cpp b/scene/2d/particles_2d.cpp index 7eaa5bb88c..1da1d44b17 100644 --- a/scene/2d/particles_2d.cpp +++ b/scene/2d/particles_2d.cpp @@ -367,9 +367,9 @@ void Particles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("restart"), &Particles2D::restart); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,100000,1"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed_scale", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_speed_scale", "get_speed_scale"); diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 052a0ac026..658b998d17 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -57,6 +57,10 @@ Rect2 Path2D::_edit_get_rect() const { return aabb; } +bool Path2D::_edit_use_rect() const { + return true; +} + bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { for (int i = 0; i < curve->get_point_count(); i++) { @@ -92,7 +96,7 @@ void Path2D::_notification(int p_what) { #else const float line_width = 2; #endif - const Color color = Color(0.5, 0.6, 1.0, 0.7); + const Color color = Color(1.0, 1.0, 1.0, 1.0); for (int i = 0; i < curve->get_point_count(); i++) { @@ -147,6 +151,7 @@ void Path2D::_bind_methods() { Path2D::Path2D() { set_curve(Ref<Curve2D>(memnew(Curve2D))); //create one by default + set_self_modulate(Color(0.5, 0.6, 1.0, 0.7)); } ///////////////////////////////////////////////////////////////////////////////// @@ -294,7 +299,7 @@ void PathFollow2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_lookahead", "lookahead"), &PathFollow2D::set_lookahead); ClassDB::bind_method(D_METHOD("get_lookahead"), &PathFollow2D::get_lookahead); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_EXP_RANGE, "0,10000,0.01,or_greater"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "h_offset"), "set_h_offset", "get_h_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_offset"), "set_v_offset", "get_v_offset"); diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index 735d289d74..64696442c3 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -48,6 +48,7 @@ protected: public: virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; void set_curve(const Ref<Curve2D> &p_curve); diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index e63dc4e515..81ed3c63c3 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -30,7 +30,7 @@ #include "polygon_2d.h" #include "core/math/geometry.h" - +#include "skeleton_2d.h" Dictionary Polygon2D::_edit_get_state() const { Dictionary state = Node2D::_edit_get_state(); state["offset"] = offset; @@ -73,6 +73,10 @@ Rect2 Polygon2D::_edit_get_rect() const { return item_rect; } +bool Polygon2D::_edit_use_rect() const { + return true; +} + bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return Geometry::is_point_in_polygon(p_point - get_offset(), Variant(polygon)); @@ -87,8 +91,20 @@ void Polygon2D::_notification(int p_what) { if (polygon.size() < 3) return; + Skeleton2D *skeleton_node = NULL; + if (has_node(skeleton)) { + skeleton_node = Object::cast_to<Skeleton2D>(get_node(skeleton)); + } + + if (skeleton_node) + VS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), skeleton_node->get_skeleton()); + else + VS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID()); + Vector<Vector2> points; Vector<Vector2> uvs; + Vector<int> bones; + Vector<float> weights; points.resize(polygon.size()); @@ -176,6 +192,70 @@ void Polygon2D::_notification(int p_what) { } } + if (!invert && bone_weights.size()) { + //a skeleton is set! fill indices and weights + int vc = points.size(); + bones.resize(vc * 4); + weights.resize(vc * 4); + + int *bonesw = bones.ptrw(); + float *weightsw = weights.ptrw(); + + for (int i = 0; i < vc * 4; i++) { + bonesw[i] = 0; + weightsw[i] = 0; + } + + for (int i = 0; i < bone_weights.size(); i++) { + if (bone_weights[i].weights.size() != points.size()) { + continue; //different number of vertices, sorry not using. + } + if (!skeleton_node->has_node(bone_weights[i].path)) { + continue; //node does not exist + } + Bone2D *bone = Object::cast_to<Bone2D>(skeleton_node->get_node(bone_weights[i].path)); + if (!bone) { + continue; + } + + int bone_index = bone->get_index_in_skeleton(); + PoolVector<float>::Read r = bone_weights[i].weights.read(); + for (int j = 0; j < vc; j++) { + if (r[j] == 0.0) + continue; //weight is unpainted, skip + //find an index with a weight + for (int k = 0; k < 4; k++) { + if (weightsw[j * 4 + k] < r[j]) { + //this is less than this weight, insert weight! + for (int l = 3; l > k; l--) { + weightsw[j * 4 + l] = weightsw[j * 4 + l - 1]; + bonesw[j * 4 + l] = bonesw[j * 4 + l - 1]; + } + weightsw[j * 4 + k] = r[j]; + bonesw[j * 4 + k] = bone_index; + break; + } + } + } + } + + //normalize the weights + for (int i = 0; i < vc; i++) { + float tw = 0; + for (int j = 0; j < 4; j++) { + tw += weightsw[i * 4 + j]; + } + if (tw == 0) + continue; //unpainted, do nothing + + //normalize + for (int j = 0; j < 4; j++) { + weightsw[i * 4 + j] /= tw; + // print_line("point " + itos(i) + " idx " + itos(j) + " index: " + itos(bonesw[i * 4 + j]) + " weight: " + rtos(weightsw[i * 4 + j])); + } + } + } + Vector<Color> colors; int color_len = vertex_colors.size(); colors.resize(len); @@ -192,7 +272,81 @@ void Polygon2D::_notification(int p_what) { // Vector<int> indices = Geometry::triangulate_polygon(points); // VS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, texture.is_valid() ? texture->get_rid() : RID()); - VS::get_singleton()->canvas_item_add_polygon(get_canvas_item(), points, colors, uvs, texture.is_valid() ? texture->get_rid() : RID(), RID(), antialiased); + if (invert || splits.size() == 0) { + Vector<int> indices = Geometry::triangulate_polygon(points); + VS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID()); + } else { + //use splits + Vector<int> loop; + int sc = splits.size(); + PoolVector<int>::Read r = splits.read(); + int last = points.size(); + + Vector<Vector<int> > loops; + + for (int i = 0; i < last; i++) { + + int split; + int min_end = -1; + + do { + + loop.push_back(i); + + split = -1; + int end = -1; + + for (int j = 0; j < sc; j += 2) { + if (r[j + 1] >= last) + continue; //no longer valid + if (min_end != -1 && r[j + 1] >= min_end) + continue; + if (r[j] == i) { + if (split == -1 || r[j + 1] > end) { + split = r[j]; + end = r[j + 1]; + } + } + } + + if (split != -1) { + for (int j = end; j < last; j++) { + loop.push_back(j); + } + loops.push_back(loop); + last = end + 1; + loop.clear(); + min_end = end; //avoid this split from repeating + } + + } while (split != -1); + } + + if (loop.size()) { + loops.push_back(loop); + } + + Vector<int> indices; + + for (int i = 0; i < loops.size(); i++) { + Vector<int> loop = loops[i]; + Vector<Vector2> vertices; + vertices.resize(loop.size()); + for (int j = 0; j < vertices.size(); j++) { + vertices[j] = points[loop[j]]; + } + Vector<int> sub_indices = Geometry::triangulate_polygon(vertices); + int from = indices.size(); + indices.resize(from + sub_indices.size()); + for (int j = 0; j < sub_indices.size(); j++) { + indices[from + j] = loop[sub_indices[j]]; + } + } + + //print_line("loops: " + itos(loops.size()) + " indices: " + itos(indices.size())); + + VS::get_singleton()->canvas_item_add_triangle_array(get_canvas_item(), indices, points, colors, uvs, bones, weights, texture.is_valid() ? texture->get_rid() : RID()); + } } break; } @@ -220,6 +374,18 @@ PoolVector<Vector2> Polygon2D::get_uv() const { return uv; } +void Polygon2D::set_splits(const PoolVector<int> &p_splits) { + + ERR_FAIL_COND(p_splits.size() & 1); //splits should be multiple of 2 + splits = p_splits; + update(); +} + +PoolVector<int> Polygon2D::get_splits() const { + + return splits; +} + void Polygon2D::set_color(const Color &p_color) { color = p_color; @@ -341,6 +507,74 @@ Vector2 Polygon2D::get_offset() const { return offset; } +void Polygon2D::add_bone(const NodePath &p_path, const PoolVector<float> &p_weights) { + + Bone bone; + bone.path = p_path; + bone.weights = p_weights; + bone_weights.push_back(bone); +} +int Polygon2D::get_bone_count() const { + return bone_weights.size(); +} +NodePath Polygon2D::get_bone_path(int p_index) const { + ERR_FAIL_INDEX_V(p_index, bone_weights.size(), NodePath()); + return bone_weights[p_index].path; +} +PoolVector<float> Polygon2D::get_bone_weights(int p_index) const { + + ERR_FAIL_INDEX_V(p_index, bone_weights.size(), PoolVector<float>()); + return bone_weights[p_index].weights; +} +void Polygon2D::erase_bone(int p_idx) { + + ERR_FAIL_INDEX(p_idx, bone_weights.size()); + bone_weights.remove(p_idx); +} + +void Polygon2D::clear_bones() { + bone_weights.clear(); +} + +void Polygon2D::set_bone_weights(int p_index, const PoolVector<float> &p_weights) { + ERR_FAIL_INDEX(p_index, bone_weights.size()); + bone_weights[p_index].weights = p_weights; + update(); +} +void Polygon2D::set_bone_path(int p_index, const NodePath &p_path) { + ERR_FAIL_INDEX(p_index, bone_weights.size()); + bone_weights[p_index].path = p_path; + update(); +} + +Array Polygon2D::_get_bones() const { + Array bones; + for (int i = 0; i < get_bone_count(); i++) { + bones.push_back(get_bone_path(i)); + bones.push_back(get_bone_weights(i)); + } + return bones; +} +void Polygon2D::_set_bones(const Array &p_bones) { + + ERR_FAIL_COND(p_bones.size() & 1); + clear_bones(); + for (int i = 0; i < p_bones.size(); i += 2) { + add_bone(p_bones[i], p_bones[i + 1]); + } +} + +void Polygon2D::set_skeleton(const NodePath &p_skeleton) { + if (skeleton == p_skeleton) + return; + skeleton = p_skeleton; + update(); +} + +NodePath Polygon2D::get_skeleton() const { + return skeleton; +} + void Polygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &Polygon2D::set_polygon); @@ -352,6 +586,9 @@ void Polygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_color", "color"), &Polygon2D::set_color); ClassDB::bind_method(D_METHOD("get_color"), &Polygon2D::get_color); + ClassDB::bind_method(D_METHOD("set_splits", "splits"), &Polygon2D::set_splits); + ClassDB::bind_method(D_METHOD("get_splits"), &Polygon2D::get_splits); + ClassDB::bind_method(D_METHOD("set_vertex_colors", "vertex_colors"), &Polygon2D::set_vertex_colors); ClassDB::bind_method(D_METHOD("get_vertex_colors"), &Polygon2D::get_vertex_colors); @@ -382,8 +619,24 @@ void Polygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_offset", "offset"), &Polygon2D::set_offset); ClassDB::bind_method(D_METHOD("get_offset"), &Polygon2D::get_offset); + ClassDB::bind_method(D_METHOD("add_bone", "path", "weights"), &Polygon2D::add_bone); + ClassDB::bind_method(D_METHOD("get_bone_count"), &Polygon2D::get_bone_count); + ClassDB::bind_method(D_METHOD("get_bone_path", "index"), &Polygon2D::get_bone_path); + ClassDB::bind_method(D_METHOD("get_bone_weights", "index"), &Polygon2D::get_bone_weights); + ClassDB::bind_method(D_METHOD("erase_bone", "index"), &Polygon2D::erase_bone); + ClassDB::bind_method(D_METHOD("clear_bones"), &Polygon2D::clear_bones); + ClassDB::bind_method(D_METHOD("set_bone_path", "index", "path"), &Polygon2D::set_bone_path); + ClassDB::bind_method(D_METHOD("set_bone_weights", "index", "weights"), &Polygon2D::set_bone_weights); + + ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &Polygon2D::set_skeleton); + ClassDB::bind_method(D_METHOD("get_skeleton"), &Polygon2D::get_skeleton); + + ClassDB::bind_method(D_METHOD("_set_bones", "bones"), &Polygon2D::_set_bones); + ClassDB::bind_method(D_METHOD("_get_bones"), &Polygon2D::_get_bones); + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "uv"), "set_uv", "get_uv"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "splits"), "set_splits", "get_splits"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "vertex_colors"), "set_vertex_colors", "get_vertex_colors"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); @@ -395,10 +648,14 @@ void Polygon2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_scale"), "set_texture_scale", "get_texture_scale"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation_degrees", PROPERTY_HINT_RANGE, "-1440,1440,0.1"), "set_texture_rotation_degrees", "get_texture_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation", PROPERTY_HINT_NONE, "", 0), "set_texture_rotation", "get_texture_rotation"); + ADD_GROUP("Skeleton", ""); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton2D"), "set_skeleton", "get_skeleton"); ADD_GROUP("Invert", "invert_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_enable"), "set_invert", "get_invert"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "invert_border", PROPERTY_HINT_RANGE, "0.1,16384,0.1"), "set_invert_border", "get_invert_border"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_bones", "_get_bones"); } Polygon2D::Polygon2D() { diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h index 66f44cb3f1..575f71d74a 100644 --- a/scene/2d/polygon_2d.h +++ b/scene/2d/polygon_2d.h @@ -40,6 +40,15 @@ class Polygon2D : public Node2D { PoolVector<Vector2> polygon; PoolVector<Vector2> uv; PoolVector<Color> vertex_colors; + PoolVector<int> splits; + + struct Bone { + NodePath path; + PoolVector<float> weights; + }; + + Vector<Bone> bone_weights; + Color color; Ref<Texture> texture; Size2 tex_scale; @@ -54,6 +63,11 @@ class Polygon2D : public Node2D { mutable bool rect_cache_dirty; mutable Rect2 item_rect; + NodePath skeleton; + + Array _get_bones() const; + void _set_bones(const Array &p_bones); + protected: void _notification(int p_what); static void _bind_methods(); @@ -66,6 +80,7 @@ public: virtual Point2 _edit_get_pivot() const; virtual bool _edit_use_pivot() const; virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; @@ -75,6 +90,9 @@ public: void set_uv(const PoolVector<Vector2> &p_uv); PoolVector<Vector2> get_uv() const; + void set_splits(const PoolVector<int> &p_uv); + PoolVector<int> get_splits() const; + void set_color(const Color &p_color); Color get_color() const; @@ -108,6 +126,18 @@ public: void set_offset(const Vector2 &p_offset); Vector2 get_offset() const; + void add_bone(const NodePath &p_path = NodePath(), const PoolVector<float> &p_weights = PoolVector<float>()); + int get_bone_count() const; + NodePath get_bone_path(int p_index) const; + PoolVector<float> get_bone_weights(int p_index) const; + void erase_bone(int p_idx); + void clear_bones(); + void set_bone_weights(int p_index, const PoolVector<float> &p_weights); + void set_bone_path(int p_index, const NodePath &p_path); + + void set_skeleton(const NodePath &p_skeleton); + NodePath get_skeleton() const; + Polygon2D(); }; diff --git a/scene/2d/position_2d.cpp b/scene/2d/position_2d.cpp index 37f6aaa2d6..64d23719e7 100644 --- a/scene/2d/position_2d.cpp +++ b/scene/2d/position_2d.cpp @@ -44,6 +44,10 @@ Rect2 Position2D::_edit_get_rect() const { return Rect2(Point2(-10, -10), Size2(20, 20)); } +bool Position2D::_edit_use_rect() const { + return false; +} + void Position2D::_notification(int p_what) { switch (p_what) { diff --git a/scene/2d/position_2d.h b/scene/2d/position_2d.h index 6f6a34c452..bff474cccd 100644 --- a/scene/2d/position_2d.h +++ b/scene/2d/position_2d.h @@ -44,6 +44,7 @@ protected: public: virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; Position2D(); }; diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 5a563143ad..255d2d38d5 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -101,7 +101,7 @@ void RayCast2D::set_enabled(bool p_enabled) { enabled = p_enabled; if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) - set_physics_process(p_enabled); + set_physics_process_internal(p_enabled); if (!p_enabled) collided = false; } @@ -141,9 +141,9 @@ void RayCast2D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { if (enabled && !Engine::get_singleton()->is_editor_hint()) - set_physics_process(true); + set_physics_process_internal(true); else - set_physics_process(false); + set_physics_process_internal(false); if (Object::cast_to<CollisionObject2D>(get_parent())) { if (exclude_parent_body) @@ -155,7 +155,7 @@ void RayCast2D::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { if (enabled) - set_physics_process(false); + set_physics_process_internal(false); } break; @@ -183,7 +183,7 @@ void RayCast2D::_notification(int p_what) { } break; - case NOTIFICATION_PHYSICS_PROCESS: { + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (!enabled) break; diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index da764e032b..63c3d78dfd 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -201,7 +201,7 @@ void RemoteTransform2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_update_scale", "update_remote_scale"), &RemoteTransform2D::set_update_scale); ClassDB::bind_method(D_METHOD("get_update_scale"), &RemoteTransform2D::get_update_scale); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path"), "set_remote_node", "get_remote_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_remote_node", "get_remote_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_coordinates"), "set_use_global_coordinates", "get_use_global_coordinates"); ADD_GROUP("Update", "update_"); diff --git a/scene/2d/screen_button.cpp b/scene/2d/screen_button.cpp index b6cb734cee..45f63fd5bf 100644 --- a/scene/2d/screen_button.cpp +++ b/scene/2d/screen_button.cpp @@ -324,13 +324,19 @@ void TouchScreenButton::_release(bool p_exiting_tree) { } Rect2 TouchScreenButton::_edit_get_rect() const { - - if (texture.is_null()) - return Rect2(0, 0, 1, 1); - /* if (texture.is_null()) return CanvasItem::_edit_get_rect(); - */ + + return Rect2(Size2(), texture->get_size()); +} + +bool TouchScreenButton::_edit_use_rect() const { + return !texture.is_null(); +} + +Rect2 TouchScreenButton::get_anchorable_rect() const { + if (texture.is_null()) + return CanvasItem::get_anchorable_rect(); return Rect2(Size2(), texture->get_size()); } diff --git a/scene/2d/screen_button.h b/scene/2d/screen_button.h index e6f2a2f3cd..3e8adc2e5e 100644 --- a/scene/2d/screen_button.h +++ b/scene/2d/screen_button.h @@ -103,7 +103,9 @@ public: bool is_pressed() const; - Rect2 _edit_get_rect() const; + virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; + virtual Rect2 get_anchorable_rect() const; TouchScreenButton(); }; diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 705e82bcbb..8ceffb3c27 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -1,3 +1,33 @@ +/*************************************************************************/ +/* skeleton_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + #include "skeleton_2d.h" void Bone2D::_notification(int p_what) { @@ -12,6 +42,8 @@ void Bone2D::_notification(int p_what) { break; if (!Object::cast_to<Bone2D>(parent)) break; //skeletons must be chained to Bone2Ds. + + parent = parent->get_parent(); } if (skeleton) { @@ -26,6 +58,11 @@ void Bone2D::_notification(int p_what) { skeleton->_make_transform_dirty(); } } + if (p_what == NOTIFICATION_MOVED_IN_PARENT) { + if (skeleton) { + skeleton->_make_bone_setup_dirty(); + } + } if (p_what == NOTIFICATION_EXIT_TREE) { if (skeleton) { @@ -46,12 +83,22 @@ void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rest", "rest"), &Bone2D::set_rest); ClassDB::bind_method(D_METHOD("get_rest"), &Bone2D::get_rest); ClassDB::bind_method(D_METHOD("apply_rest"), &Bone2D::apply_rest); + ClassDB::bind_method(D_METHOD("get_skeleton_rest"), &Bone2D::get_skeleton_rest); + ClassDB::bind_method(D_METHOD("get_index_in_skeleton"), &Bone2D::get_index_in_skeleton); + + ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length); + ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length); + + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_length", PROPERTY_HINT_RANGE, "1,1024,1"), "set_default_length", "get_default_length"); } void Bone2D::set_rest(const Transform2D &p_rest) { rest = p_rest; if (skeleton) skeleton->_make_bone_setup_dirty(); + + update_configuration_warning(); } Transform2D Bone2D::get_rest() const { @@ -71,22 +118,54 @@ void Bone2D::apply_rest() { set_transform(rest); } +void Bone2D::set_default_length(float p_length) { + + default_length = p_length; +} + +float Bone2D::get_default_length() const { + return default_length; +} + +int Bone2D::get_index_in_skeleton() const { + ERR_FAIL_COND_V(!skeleton, -1); + skeleton->_update_bone_setup(); + return skeleton_index; +} String Bone2D::get_configuration_warning() const { + + String warning = Node2D::get_configuration_warning(); if (!skeleton) { + if (warning != String()) { + warning += "\n"; + } if (parent_bone) { - return TTR("This Bone2D chain should end at a Skeleton2D node."); + warning += TTR("This Bone2D chain should end at a Skeleton2D node."); } else { - return TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."); + warning += TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."); + } + } + + if (rest == Transform2D(0, 0, 0, 0, 0, 0)) { + if (warning != String()) { + warning += "\n"; } + warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."); } - return Node2D::get_configuration_warning(); + return warning; } Bone2D::Bone2D() { skeleton = NULL; parent_bone = NULL; + skeleton_index = -1; + default_length = 16; set_notify_local_transform(true); + //this is a clever hack so the bone knows no rest has been set yet, allowing to show an error. + for (int i = 0; i < 3; i++) { + rest[i] = Vector2(0, 0); + } } ////////////////////////////////////// @@ -112,7 +191,14 @@ void Skeleton2D::_update_bone_setup() { bones.sort(); //sorty so they are always in the same order/index for (int i = 0; i < bones.size(); i++) { - bones[i].rest_inverse = bones[i].bone->get_skeleton_rest(); //bind pose + bones[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose + bones[i].bone->skeleton_index = i; + Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent()); + if (parent_bone) { + bones[i].parent_index = parent_bone->skeleton_index; + } else { + bones[i].parent_index = -1; + } } transform_dirty = true; @@ -140,13 +226,20 @@ void Skeleton2D::_update_transform() { transform_dirty = false; - Transform2D global_xform = get_global_transform(); - Transform2D global_xform_inverse = global_xform.affine_inverse(); + for (int i = 0; i < bones.size(); i++) { + + ERR_CONTINUE(bones[i].parent_index >= i); + if (bones[i].parent_index >= 0) { + bones[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform(); + } else { + bones[i].accum_transform = bones[i].bone->get_transform(); + } + } for (int i = 0; i < bones.size(); i++) { - Transform2D final_xform = bones[i].rest_inverse * bones[i].bone->get_relative_transform_to_parent(this); - VS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, global_xform * (final_xform * global_xform_inverse)); + Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse; + VS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform); } } @@ -177,10 +270,12 @@ void Skeleton2D::_notification(int p_what) { _update_bone_setup(); if (transform_dirty) _update_transform(); + + request_ready(); } if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - _make_transform_dirty(); + VS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); } } @@ -201,6 +296,7 @@ void Skeleton2D::_bind_methods() { Skeleton2D::Skeleton2D() { bone_setup_dirty = true; transform_dirty = true; + skeleton = VS::get_singleton()->skeleton_create(); } diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 49199f684f..9d0a061457 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -1,3 +1,33 @@ +/*************************************************************************/ +/* skeleton_2d.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + #ifndef SKELETON_2D_H #define SKELETON_2D_H @@ -8,9 +38,14 @@ class Skeleton2D; class Bone2D : public Node2D { GDCLASS(Bone2D, Node2D) + friend class Skeleton2D; + Bone2D *parent_bone; Skeleton2D *skeleton; Transform2D rest; + float default_length; + + int skeleton_index; protected: void _notification(int p_what); @@ -24,6 +59,11 @@ public: String get_configuration_warning() const; + void set_default_length(float p_length); + float get_default_length() const; + + int get_index_in_skeleton() const; + Bone2D(); }; @@ -37,6 +77,8 @@ class Skeleton2D : public Node2D { return p_bone.bone->is_greater_than(bone); } Bone2D *bone; + int parent_index; + Transform2D accum_transform; Transform2D rest_inverse; }; diff --git a/scene/2d/sprite.cpp b/scene/2d/sprite.cpp index 796969be1e..ebe0e81f6e 100644 --- a/scene/2d/sprite.cpp +++ b/scene/2d/sprite.cpp @@ -58,33 +58,48 @@ bool Sprite::_edit_use_pivot() const { return true; } +Rect2 Sprite::_edit_get_rect() const { + return get_rect(); +} + +bool Sprite::_edit_use_rect() const { + if (texture.is_null()) + return false; + + return true; +} + +Rect2 Sprite::get_anchorable_rect() const { + return get_rect(); +} + void Sprite::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip) const { - Size2 s; - r_filter_clip = false; + Rect2 base_rect; if (region) { - - s = region_rect.size; - r_src_rect = region_rect; r_filter_clip = region_filter_clip; + base_rect = region_rect; } else { - s = Size2(texture->get_size()); - s = s / Size2(hframes, vframes); - - r_src_rect.size = s; - r_src_rect.position.x += float(frame % hframes) * s.x; - r_src_rect.position.y += float(frame / hframes) * s.y; + r_filter_clip = false; + base_rect = Rect2(0, 0, texture->get_width(), texture->get_height()); } - Point2 ofs = offset; + Size2 frame_size = base_rect.size / Size2(hframes, vframes); + Point2 frame_offset = Point2(frame % hframes, frame / hframes); + frame_offset *= frame_size; + + r_src_rect.size = frame_size; + r_src_rect.position = base_rect.position + frame_offset; + + Point2 dest_offset = offset; if (centered) - ofs -= s / 2; + dest_offset -= frame_size / 2; if (Engine::get_singleton()->get_use_pixel_snap()) { - ofs = ofs.floor(); + dest_offset = dest_offset.floor(); } - r_dst_rect = Rect2(ofs, s); + r_dst_rect = Rect2(dest_offset, frame_size); if (hflip) r_dst_rect.size.x = -r_dst_rect.size.x; @@ -121,7 +136,15 @@ void Sprite::set_texture(const Ref<Texture> &p_texture) { if (p_texture == texture) return; + + if (texture.is_valid()) + texture->remove_change_receptor(this); + texture = p_texture; + + if (texture.is_valid()) + texture->add_change_receptor(this); + update(); emit_signal("texture_changed"); item_rect_changed(); @@ -281,15 +304,65 @@ bool Sprite::_edit_is_selected_on_click(const Point2 &p_point, double p_toleranc Rect2 src_rect, dst_rect; bool filter_clip; _get_rects(src_rect, dst_rect, filter_clip); + dst_rect.size = dst_rect.size.abs(); if (!dst_rect.has_point(p_point)) return false; - Vector2 q = ((p_point - dst_rect.position) / dst_rect.size) * src_rect.size + src_rect.position; + Vector2 q = (p_point - dst_rect.position) / dst_rect.size; + if (hflip) + q.x = 1.0f - q.x; + if (vflip) + q.y = 1.0f - q.y; + q = q * src_rect.size + src_rect.position; + + Ref<Image> image; + Ref<AtlasTexture> atlasTexture = texture; + if (atlasTexture.is_null()) { + image = texture->get_data(); + } else { + ERR_FAIL_COND_V(atlasTexture->get_atlas().is_null(), false); + + image = atlasTexture->get_atlas()->get_data(); + + Rect2 region = atlasTexture->get_region(); + Rect2 margin = atlasTexture->get_margin(); + + q -= margin.position; + + if ((q.x > region.size.width) || (q.y > region.size.height)) { + return false; + } + + q += region.position; + } - Ref<Image> image = texture->get_data(); ERR_FAIL_COND_V(image.is_null(), false); + if (image->is_compressed()) { + return dst_rect.has_point(p_point); + } + bool is_repeat = texture->get_flags() & Texture::FLAG_REPEAT; + bool is_mirrored_repeat = texture->get_flags() & Texture::FLAG_MIRRORED_REPEAT; + if (is_repeat) { + int mirror_x = 0; + int mirror_y = 0; + if (is_mirrored_repeat) { + mirror_x = (int)(q.x / texture->get_size().width); + mirror_y = (int)(q.y / texture->get_size().height); + } + q.x = Math::fmod(q.x, texture->get_size().width); + q.y = Math::fmod(q.y, texture->get_size().height); + if (mirror_x % 2 == 1) { + q.x = texture->get_size().width - q.x - 1; + } + if (mirror_y % 2 == 1) { + q.y = texture->get_size().height - q.y - 1; + } + } else { + q.x = MIN(q.x, texture->get_size().width - 1); + q.y = MIN(q.y, texture->get_size().height - 1); + } image->lock(); const Color c = image->get_pixel((int)q.x, (int)q.y); image->unlock(); @@ -301,21 +374,17 @@ Rect2 Sprite::get_rect() const { if (texture.is_null()) return Rect2(0, 0, 1, 1); - /* - if (texture.is_null()) - return CanvasItem::_edit_get_rect(); - */ Size2i s; if (region) { - s = region_rect.size; } else { s = texture->get_size(); - s = s / Point2(hframes, vframes); } + s = s / Point2(hframes, vframes); + Point2 ofs = offset; if (centered) ofs -= s / 2; @@ -336,6 +405,15 @@ void Sprite::_validate_property(PropertyInfo &property) const { } } +void Sprite::_changed_callback(Object *p_changed, const char *p_prop) { + + // Changes to the texture need to trigger an update to make + // the editor redraw the sprite with the updated texture. + if (texture.is_valid() && texture.ptr() == p_changed) { + update(); + } +} + void Sprite::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &Sprite::set_texture); @@ -410,3 +488,8 @@ Sprite::Sprite() { vframes = 1; hframes = 1; } + +Sprite::~Sprite() { + if (texture.is_valid()) + texture->remove_change_receptor(this); +} diff --git a/scene/2d/sprite.h b/scene/2d/sprite.h index dd3719099f..0a5ff002cd 100644 --- a/scene/2d/sprite.h +++ b/scene/2d/sprite.h @@ -64,6 +64,8 @@ protected: virtual void _validate_property(PropertyInfo &property) const; + virtual void _changed_callback(Object *p_changed, const char *p_prop); + public: virtual Dictionary _edit_get_state() const; virtual void _edit_set_state(const Dictionary &p_state); @@ -72,7 +74,9 @@ public: virtual Point2 _edit_get_pivot() const; virtual bool _edit_use_pivot() const; virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; - virtual Rect2 _edit_get_rect() const { return get_rect(); } + + virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; void set_texture(const Ref<Texture> &p_texture); Ref<Texture> get_texture() const; @@ -111,8 +115,10 @@ public: int get_hframes() const; Rect2 get_rect() const; + virtual Rect2 get_anchorable_rect() const; Sprite(); + ~Sprite(); }; #endif // SPRITE_H diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 2aa55e2825..9a343ca0f0 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -61,6 +61,7 @@ void TileMap::_notification(int p_what) { } pending_update = true; + _recreate_quadrants(); _update_dirty_quadrants(); RID space = get_world_2d()->get_space(); _update_quadrant_transform(); @@ -142,16 +143,20 @@ void TileMap::_update_quadrant_transform() { void TileMap::set_tileset(const Ref<TileSet> &p_tileset) { - if (tile_set.is_valid()) + if (tile_set.is_valid()) { tile_set->disconnect("changed", this, "_recreate_quadrants"); + tile_set->remove_change_receptor(this); + } _clear_quadrants(); tile_set = p_tileset; - if (tile_set.is_valid()) + if (tile_set.is_valid()) { tile_set->connect("changed", this, "_recreate_quadrants"); - else + tile_set->add_change_receptor(this); + } else { clear(); + } _recreate_quadrants(); emit_signal("settings_changed"); @@ -261,12 +266,18 @@ void TileMap::_update_dirty_quadrants() { SceneTree *st = SceneTree::get_singleton(); Color debug_collision_color; + Color debug_navigation_color; bool debug_shapes = st && st->is_debugging_collisions_hint(); if (debug_shapes) { debug_collision_color = st->get_debug_collisions_color(); } + bool debug_navigation = st && st->is_debugging_navigation_hint(); + if (debug_navigation) { + debug_navigation_color = st->get_debug_navigation_color(); + } + while (dirty_quadrant_list.first()) { Quadrant &q = *dirty_quadrant_list.first()->self(); @@ -294,6 +305,7 @@ void TileMap::_update_dirty_quadrants() { } q.occluder_instances.clear(); Ref<ShaderMaterial> prev_material; + int prev_z_index; RID prev_canvas_item; RID prev_debug_canvas_item; @@ -314,11 +326,12 @@ void TileMap::_update_dirty_quadrants() { continue; Ref<ShaderMaterial> mat = tile_set->tile_get_material(c.id); + int z_index = tile_set->tile_get_z_index(c.id); RID canvas_item; RID debug_canvas_item; - if (prev_canvas_item == RID() || prev_material != mat) { + if (prev_canvas_item == RID() || prev_material != mat || prev_z_index != z_index) { canvas_item = vs->canvas_item_create(); if (mat.is_valid()) @@ -329,6 +342,7 @@ void TileMap::_update_dirty_quadrants() { xform.set_origin(q.pos); vs->canvas_item_set_transform(canvas_item, xform); vs->canvas_item_set_light_mask(canvas_item, get_light_mask()); + vs->canvas_item_set_z_index(canvas_item, z_index); q.canvas_items.push_back(canvas_item); @@ -344,6 +358,7 @@ void TileMap::_update_dirty_quadrants() { prev_canvas_item = canvas_item; prev_material = mat; + prev_z_index = z_index; } else { canvas_item = prev_canvas_item; @@ -451,10 +466,12 @@ void TileMap::_update_dirty_quadrants() { Transform2D xform; xform.set_origin(offset.floor()); - Vector2 shape_ofs = tile_set->tile_get_shape_offset(c.id, i); + Vector2 shape_ofs = shapes[i].shape_transform.get_origin(); _fix_cell_transform(xform, c, shape_ofs + center_ofs, s); + xform *= shapes[i].shape_transform.untranslated(); + if (debug_canvas_item.is_valid()) { vs->canvas_item_add_set_transform(debug_canvas_item, xform); shape->draw(debug_canvas_item, debug_collision_color); @@ -493,6 +510,55 @@ void TileMap::_update_dirty_quadrants() { np.id = pid; np.xform = xform; q.navpoly_ids[E->key()] = np; + + if (debug_navigation) { + RID debug_navigation_item = vs->canvas_item_create(); + vs->canvas_item_set_parent(debug_navigation_item, canvas_item); + vs->canvas_item_set_z_as_relative_to_parent(debug_navigation_item, false); + vs->canvas_item_set_z_index(debug_navigation_item, VS::CANVAS_ITEM_Z_MAX - 2); // Display one below collision debug + + if (debug_navigation_item.is_valid()) { + PoolVector<Vector2> navigation_polygon_vertices = navpoly->get_vertices(); + int vsize = navigation_polygon_vertices.size(); + + if (vsize > 2) { + Vector<Color> colors; + Vector<Vector2> vertices; + vertices.resize(vsize); + colors.resize(vsize); + { + PoolVector<Vector2>::Read vr = navigation_polygon_vertices.read(); + for (int i = 0; i < vsize; i++) { + vertices[i] = vr[i]; + colors[i] = debug_navigation_color; + } + } + + Vector<int> indices; + + for (int i = 0; i < navpoly->get_polygon_count(); i++) { + Vector<int> polygon = navpoly->get_polygon(i); + + for (int j = 2; j < polygon.size(); j++) { + + int kofs[3] = { 0, j - 1, j }; + for (int k = 0; k < 3; k++) { + + int idx = polygon[kofs[k]]; + ERR_FAIL_INDEX(idx, vsize); + indices.push_back(idx); + } + } + } + Transform2D navxform; + navxform.set_origin(offset.floor()); + _fix_cell_transform(navxform, c, npoly_ofs + center_ofs, s); + + vs->canvas_item_set_transform(debug_navigation_item, navxform); + vs->canvas_item_add_triangle_array(debug_navigation_item, indices, vertices, colors); + } + } + } } } @@ -642,7 +708,7 @@ void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) { rect_cache_dirty = true; } -void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q) { +void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update) { Quadrant &q = Q->get(); if (!q.dirty_list.in_list()) @@ -653,7 +719,10 @@ void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q) { pending_update = true; if (!is_inside_tree()) return; - call_deferred("_update_dirty_quadrants"); + + if (update) { + _update_dirty_quadrants(); + } } void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose) { @@ -661,6 +730,11 @@ void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_ set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose); } +void TileMap::set_celld(const Vector2 &p_pos, const Dictionary &p_data) { + + set_cell(p_pos.x, p_pos.y, p_data["id"], p_data["flip_h"], p_data["flip_y"], p_data["transpose"], p_data["auto_coord"]); +} + void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose, Vector2 p_autotile_coord) { PosKey pk(p_x, p_y); @@ -781,16 +855,37 @@ void TileMap::update_cell_bitmask(int p_x, int p_y) { if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { mask |= TileSet::BIND_BOTTOMRIGHT; } - } else if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_3X3) { - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { - mask |= TileSet::BIND_TOPLEFT; + } else { + if (tile_set->autotile_get_bitmask_mode(id) == TileSet::BITMASK_3X3_MINIMAL) { + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { + mask |= TileSet::BIND_TOPLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { + mask |= TileSet::BIND_TOPRIGHT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { + mask |= TileSet::BIND_BOTTOMLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { + mask |= TileSet::BIND_BOTTOMRIGHT; + } + } else { + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y - 1))) { + mask |= TileSet::BIND_TOPLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1))) { + mask |= TileSet::BIND_TOPRIGHT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1))) { + mask |= TileSet::BIND_BOTTOMLEFT; + } + if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1))) { + mask |= TileSet::BIND_BOTTOMRIGHT; + } } if (tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1))) { mask |= TileSet::BIND_TOP; } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y - 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { - mask |= TileSet::BIND_TOPRIGHT; - } if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { mask |= TileSet::BIND_LEFT; } @@ -798,15 +893,9 @@ void TileMap::update_cell_bitmask(int p_x, int p_y) { if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { mask |= TileSet::BIND_RIGHT; } - if (tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x - 1, p_y))) { - mask |= TileSet::BIND_BOTTOMLEFT; - } if (tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1))) { mask |= TileSet::BIND_BOTTOM; } - if (tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x, p_y + 1)) && tile_set->is_tile_bound(id, get_cell(p_x + 1, p_y))) { - mask |= TileSet::BIND_BOTTOMRIGHT; - } } Vector2 coord = tile_set->autotile_get_subtile_for_bitmask(id, mask, this, Vector2(p_x, p_y)); E->get().autotile_coord_x = (int)coord.x; @@ -830,6 +919,16 @@ void TileMap::update_dirty_bitmask() { } } +void TileMap::fix_invalid_tiles() { + + for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { + + if (!tile_set->has_tile(get_cell(E->key().x, E->key().y))) { + set_cell(E->key().x, E->key().y, INVALID_CELL); + } + } +} + int TileMap::get_cell(int p_x, int p_y) const { PosKey pk(p_x, p_y); @@ -888,6 +987,14 @@ void TileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord) c.autotile_coord_x = p_coord.x; c.autotile_coord_y = p_coord.y; tile_map[pk] = c; + + PosKey qk(p_x / _get_quadrant_size(), p_y / _get_quadrant_size()); + Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); + + if (!Q) + return; + + _make_quadrant_dirty(Q); } Vector2 TileMap::get_cell_autotile_coord(int p_x, int p_y) const { @@ -917,8 +1024,9 @@ void TileMap::_recreate_quadrants() { } Q->get().cells.insert(E->key()); - _make_quadrant_dirty(Q); + _make_quadrant_dirty(Q, false); } + _update_dirty_quadrants(); } void TileMap::_clear_quadrants() { @@ -1044,12 +1152,6 @@ PoolVector<int> TileMap::_get_tile_data() const { return data; } -Rect2 TileMap::_edit_get_rect() const { - - const_cast<TileMap *>(this)->_update_dirty_quadrants(); - return rect_cache; -} - void TileMap::set_collision_layer(uint32_t p_layer) { collision_layer = p_layer; @@ -1509,12 +1611,14 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell", "x", "y", "tile", "flip_x", "flip_y", "transpose", "autotile_coord"), &TileMap::set_cell, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(Vector2())); ClassDB::bind_method(D_METHOD("set_cellv", "position", "tile", "flip_x", "flip_y", "transpose"), &TileMap::set_cellv, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_celld", "data"), &TileMap::set_celld); ClassDB::bind_method(D_METHOD("get_cell", "x", "y"), &TileMap::get_cell); ClassDB::bind_method(D_METHOD("get_cellv", "position"), &TileMap::get_cellv); ClassDB::bind_method(D_METHOD("is_cell_x_flipped", "x", "y"), &TileMap::is_cell_x_flipped); ClassDB::bind_method(D_METHOD("is_cell_y_flipped", "x", "y"), &TileMap::is_cell_y_flipped); ClassDB::bind_method(D_METHOD("is_cell_transposed", "x", "y"), &TileMap::is_cell_transposed); + ClassDB::bind_method(D_METHOD("fix_invalid_tiles"), &TileMap::fix_invalid_tiles); ClassDB::bind_method(D_METHOD("clear"), &TileMap::clear); ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMap::get_used_cells); @@ -1573,6 +1677,12 @@ void TileMap::_bind_methods() { BIND_ENUM_CONSTANT(TILE_ORIGIN_BOTTOM_LEFT); } +void TileMap::_changed_callback(Object *p_changed, const char *p_prop) { + if (tile_set.is_valid() && tile_set.ptr() == p_changed) { + emit_signal("settings_changed"); + } +} + TileMap::TileMap() { rect_cache_dirty = true; @@ -1601,5 +1711,8 @@ TileMap::TileMap() { TileMap::~TileMap() { + if (tile_set.is_valid()) + tile_set->remove_change_receptor(this); + clear(); } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 973e527b42..79d79ca59f 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -188,7 +188,7 @@ private: Map<PosKey, Quadrant>::Element *_create_quadrant(const PosKey &p_qk); void _erase_quadrant(Map<PosKey, Quadrant>::Element *Q); - void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q); + void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update = true); void _recreate_quadrants(); void _clear_quadrants(); void _update_dirty_quadrants(); @@ -217,6 +217,8 @@ protected: void _notification(int p_what); static void _bind_methods(); + virtual void _changed_callback(Object *p_changed, const char *p_prop); + public: enum { INVALID_CELL = -1 @@ -239,11 +241,10 @@ public: void set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord); Vector2 get_cell_autotile_coord(int p_x, int p_y) const; + void set_celld(const Vector2 &p_pos, const Dictionary &p_data); void set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false); int get_cellv(const Vector2 &p_pos) const; - Rect2 _edit_get_rect() const; - void make_bitmask_area_dirty(const Vector2 &p_pos); void update_bitmask_area(const Vector2 &p_pos); void update_bitmask_region(const Vector2 &p_start = Vector2(), const Vector2 &p_end = Vector2()); @@ -308,6 +309,7 @@ public: void set_clip_uv(bool p_enable); bool get_clip_uv() const; + void fix_invalid_tiles(); void clear(); TileMap(); diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp index 4b38534d97..ddca97e60a 100644 --- a/scene/2d/visibility_notifier_2d.cpp +++ b/scene/2d/visibility_notifier_2d.cpp @@ -89,6 +89,10 @@ Rect2 VisibilityNotifier2D::_edit_get_rect() const { return rect; } +bool VisibilityNotifier2D::_edit_use_rect() const { + return true; +} + Rect2 VisibilityNotifier2D::get_rect() const { return rect; diff --git a/scene/2d/visibility_notifier_2d.h b/scene/2d/visibility_notifier_2d.h index 93a35a709e..c4e12dfa22 100644 --- a/scene/2d/visibility_notifier_2d.h +++ b/scene/2d/visibility_notifier_2d.h @@ -56,6 +56,7 @@ protected: public: virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; void set_rect(const Rect2 &p_rect); Rect2 get_rect() const; diff --git a/scene/3d/area.cpp b/scene/3d/area.cpp index 21f471039f..6ea980ec97 100644 --- a/scene/3d/area.cpp +++ b/scene/3d/area.cpp @@ -711,7 +711,7 @@ void Area::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "space_override", PROPERTY_HINT_ENUM, "Disabled,Combine,Combine-Replace,Replace,Replace-Combine"), "set_space_override_mode", "get_space_override_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gravity_point"), "set_gravity_is_point", "is_gravity_a_point"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "gravity_distance_scale", PROPERTY_HINT_RANGE, "0,1024,0.001"), "set_gravity_distance_scale", "get_gravity_distance_scale"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "gravity_distance_scale", PROPERTY_HINT_EXP_RANGE, "0,1024,0.001,or_greater"), "set_gravity_distance_scale", "get_gravity_distance_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity_vec"), "set_gravity_vector", "get_gravity_vector"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "gravity", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_gravity", "get_gravity"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "0,1024,0.001"), "set_linear_damp", "get_linear_damp"); diff --git a/scene/3d/arvr_nodes.cpp b/scene/3d/arvr_nodes.cpp index 001c58ea76..4bff26a200 100644 --- a/scene/3d/arvr_nodes.cpp +++ b/scene/3d/arvr_nodes.cpp @@ -73,7 +73,10 @@ Vector3 ARVRCamera::project_local_ray_normal(const Point2 &p_pos) const { ERR_FAIL_NULL_V(arvr_server, Vector3()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector3()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::project_local_ray_normal(p_pos); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -98,7 +101,10 @@ Point2 ARVRCamera::unproject_position(const Vector3 &p_pos) const { ERR_FAIL_NULL_V(arvr_server, Vector2()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector2()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::unproject_position(p_pos); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -127,7 +133,10 @@ Vector3 ARVRCamera::project_position(const Point2 &p_point) const { ERR_FAIL_NULL_V(arvr_server, Vector3()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector3()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::project_position(p_point); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -157,7 +166,10 @@ Vector<Plane> ARVRCamera::get_frustum() const { ERR_FAIL_NULL_V(arvr_server, Vector<Plane>()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector<Plane>()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::get_frustum(); + } ERR_FAIL_COND_V(!is_inside_world(), Vector<Plane>()); diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index c2a50ec7bb..f1da375451 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -35,11 +35,8 @@ #include "scene/main/viewport.h" void AudioStreamPlayer3D::_mix_audio() { - if (!stream_playback.is_valid()) { - return; - } - - if (!active) { + if (!stream_playback.is_valid() || !active || + (stream_paused && stream_paused_fade <= 0.f)) { return; } @@ -54,8 +51,9 @@ void AudioStreamPlayer3D::_mix_audio() { AudioFrame *buffer = mix_buffer.ptrw(); int buffer_size = mix_buffer.size(); - //mix - if (output_count > 0 || out_of_range_mode == OUT_OF_RANGE_MIX) { + // Mix if we're not paused or we're fading out + if ((output_count > 0 || out_of_range_mode == OUT_OF_RANGE_MIX) && + (!stream_paused || stream_paused_fade > 0.f)) { float output_pitch_scale = 0.0; if (output_count) { @@ -108,6 +106,13 @@ void AudioStreamPlayer3D::_mix_audio() { AudioFrame vol_inc = (current.vol[k] - prev_outputs[i].vol[k]) / float(buffer_size); AudioFrame vol = current.vol[k]; + if (stream_paused) { + vol = vol * stream_paused_fade; + if (stream_paused_fade > 0.f) { + stream_paused_fade -= 0.1f; + } + } + AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(current.bus_index, k); current.filter.set_mode(AudioFilterSW::HIGHSHELF); @@ -237,6 +242,18 @@ void AudioStreamPlayer3D::_notification(int p_what) { AudioServer::get_singleton()->remove_callback(_mix_audios, this); } + + if (p_what == NOTIFICATION_PAUSED) { + if (!can_process()) { + // Node can't process so we start fading out to silence + set_stream_paused(true); + } + } + + if (p_what == NOTIFICATION_UNPAUSED) { + set_stream_paused(false); + } + if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { @@ -825,6 +842,19 @@ AudioStreamPlayer3D::DopplerTracking AudioStreamPlayer3D::get_doppler_tracking() return doppler_tracking; } +void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { + + if (p_pause != stream_paused) { + stream_paused = p_pause; + stream_paused_fade = stream_paused ? 1.f : 0.f; + } +} + +bool AudioStreamPlayer3D::get_stream_paused() const { + + return stream_paused; +} + void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer3D::set_stream); @@ -888,6 +918,9 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_doppler_tracking", "mode"), &AudioStreamPlayer3D::set_doppler_tracking); ClassDB::bind_method(D_METHOD("get_doppler_tracking"), &AudioStreamPlayer3D::get_doppler_tracking); + ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer3D::set_stream_paused); + ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer3D::get_stream_paused); + ClassDB::bind_method(D_METHOD("_bus_layout_changed"), &AudioStreamPlayer3D::_bus_layout_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); @@ -898,7 +931,8 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,32,0.01"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_RANGE, "0,65536,1"), "set_max_distance", "get_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_EXP_RANGE, "0,4096,1,or_greater"), "set_max_distance", "get_max_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "out_of_range_mode", PROPERTY_HINT_ENUM, "Mix,Pause"), "set_out_of_range_mode", "get_out_of_range_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); @@ -949,6 +983,8 @@ AudioStreamPlayer3D::AudioStreamPlayer3D() { attenuation_filter_db = -24; out_of_range_mode = OUT_OF_RANGE_MIX; doppler_tracking = DOPPLER_TRACKING_DISABLED; + stream_paused = false; + stream_paused_fade = 0.f; velocity_tracker.instance(); AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index 1fcb83cf21..cab1ff121a 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -107,7 +107,9 @@ private: float unit_size; float max_db; float pitch_scale; + float stream_paused_fade; bool autoplay; + bool stream_paused; StringName bus; void _mix_audio(); @@ -199,6 +201,9 @@ public: void set_doppler_tracking(DopplerTracking p_tracking); DopplerTracking get_doppler_tracking() const; + void set_stream_paused(bool p_pause); + bool get_stream_paused() const; + AudioStreamPlayer3D(); ~AudioStreamPlayer3D(); }; diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp index 6998b34cfd..e11e8abe5b 100644 --- a/scene/3d/camera.cpp +++ b/scene/3d/camera.cpp @@ -201,7 +201,7 @@ void Camera::make_current() { //get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,camera_group,"_camera_make_current",this); } -void Camera::clear_current() { +void Camera::clear_current(bool p_enable_next) { current = false; if (!is_inside_tree()) @@ -209,7 +209,10 @@ void Camera::clear_current() { if (get_viewport()->get_camera() == this) { get_viewport()->_camera_set(NULL); - get_viewport()->_camera_make_next_current(this); + + if (p_enable_next) { + get_viewport()->_camera_make_next_current(this); + } } } @@ -439,7 +442,7 @@ void Camera::_bind_methods() { ClassDB::bind_method(D_METHOD("set_perspective", "fov", "z_near", "z_far"), &Camera::set_perspective); ClassDB::bind_method(D_METHOD("set_orthogonal", "size", "z_near", "z_far"), &Camera::set_orthogonal); ClassDB::bind_method(D_METHOD("make_current"), &Camera::make_current); - ClassDB::bind_method(D_METHOD("clear_current"), &Camera::clear_current); + ClassDB::bind_method(D_METHOD("clear_current", "enable_next"), &Camera::clear_current, DEFVAL(true)); ClassDB::bind_method(D_METHOD("set_current"), &Camera::set_current); ClassDB::bind_method(D_METHOD("is_current"), &Camera::is_current); ClassDB::bind_method(D_METHOD("get_camera_transform"), &Camera::get_camera_transform); @@ -477,8 +480,8 @@ void Camera::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "fov", PROPERTY_HINT_RANGE, "1,179,0.1"), "set_fov", "get_fov"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "size", PROPERTY_HINT_RANGE, "0.1,16384,0.01"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "near"), "set_znear", "get_znear"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "far"), "set_zfar", "get_zfar"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "near", PROPERTY_HINT_EXP_RANGE, "0.1,8192,0.1,or_greater"), "set_znear", "get_znear"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "far", PROPERTY_HINT_EXP_RANGE, "0.1,8192,0.1,or_greater"), "set_zfar", "get_zfar"); BIND_ENUM_CONSTANT(PROJECTION_PERSPECTIVE); BIND_ENUM_CONSTANT(PROJECTION_ORTHOGONAL); diff --git a/scene/3d/camera.h b/scene/3d/camera.h index e2679870de..1b506e0c4f 100644 --- a/scene/3d/camera.h +++ b/scene/3d/camera.h @@ -113,7 +113,7 @@ public: void set_projection(Camera::Projection p_mode); void make_current(); - void clear_current(); + void clear_current(bool p_enable_next = true); void set_current(bool p_current); bool is_current() const; @@ -132,9 +132,9 @@ public: virtual Transform get_camera_transform() const; - Vector3 project_ray_normal(const Point2 &p_pos) const; + virtual Vector3 project_ray_normal(const Point2 &p_pos) const; virtual Vector3 project_ray_origin(const Point2 &p_pos) const; - Vector3 project_local_ray_normal(const Point2 &p_pos) const; + virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const; virtual Point2 unproject_position(const Vector3 &p_pos) const; bool is_position_behind(const Vector3 &p_pos) const; virtual Vector3 project_position(const Point2 &p_point) const; diff --git a/scene/3d/collision_polygon.cpp b/scene/3d/collision_polygon.cpp index 3a77360bc2..379dd21c39 100644 --- a/scene/3d/collision_polygon.cpp +++ b/scene/3d/collision_polygon.cpp @@ -173,6 +173,9 @@ String CollisionPolygon::get_configuration_warning() const { return String(); } +bool CollisionPolygon::_is_editable_3d_polygon() const { + return true; +} void CollisionPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CollisionPolygon::set_depth); @@ -184,6 +187,8 @@ void CollisionPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &CollisionPolygon::set_disabled); ClassDB::bind_method(D_METHOD("is_disabled"), &CollisionPolygon::is_disabled); + ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CollisionPolygon::_is_editable_3d_polygon); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "depth"), "set_depth", "get_depth"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); diff --git a/scene/3d/collision_polygon.h b/scene/3d/collision_polygon.h index 971c67f1ad..f1f137c9c5 100644 --- a/scene/3d/collision_polygon.h +++ b/scene/3d/collision_polygon.h @@ -53,6 +53,8 @@ protected: void _update_in_shape_owner(bool p_xform_only = false); + bool _is_editable_3d_polygon() const; + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/3d/interpolated_camera.cpp b/scene/3d/interpolated_camera.cpp index 9865fe156a..ffa283f634 100644 --- a/scene/3d/interpolated_camera.cpp +++ b/scene/3d/interpolated_camera.cpp @@ -38,10 +38,10 @@ void InterpolatedCamera::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { if (Engine::get_singleton()->is_editor_hint() && enabled) - set_physics_process(false); + set_process_internal(false); } break; - case NOTIFICATION_PROCESS: { + case NOTIFICATION_INTERNAL_PROCESS: { if (!enabled) break; @@ -111,9 +111,9 @@ void InterpolatedCamera::set_interpolation_enabled(bool p_enable) { if (p_enable) { if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) return; - set_process(true); + set_process_internal(true); } else - set_process(false); + set_process_internal(false); } bool InterpolatedCamera::is_interpolation_enabled() const { diff --git a/scene/3d/light.cpp b/scene/3d/light.cpp index 240bd631a1..7c42638107 100644 --- a/scene/3d/light.cpp +++ b/scene/3d/light.cpp @@ -375,7 +375,7 @@ void DirectionalLight::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_normal_bias", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_SHADOW_NORMAL_BIAS); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_bias_split_scale", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_BIAS_SPLIT_SCALE); ADD_PROPERTY(PropertyInfo(Variant::INT, "directional_shadow_depth_range", PROPERTY_HINT_ENUM, "Stable,Optimized"), "set_shadow_depth_range", "get_shadow_depth_range"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_max_distance", PROPERTY_HINT_EXP_RANGE, "0,8192,0.1,or_greater"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE); BIND_ENUM_CONSTANT(SHADOW_ORTHOGONAL); BIND_ENUM_CONSTANT(SHADOW_PARALLEL_2_SPLITS); @@ -428,8 +428,8 @@ void OmniLight::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shadow_detail"), &OmniLight::get_shadow_detail); ADD_GROUP("Omni", "omni_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_range", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_RANGE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_range", PROPERTY_HINT_EXP_RANGE, "0,4096,0.1,or_greater"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_mode", PROPERTY_HINT_ENUM, "Dual Paraboloid,Cube"), "set_shadow_mode", "get_shadow_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_detail", PROPERTY_HINT_ENUM, "Vertical,Horizontal"), "set_shadow_detail", "get_shadow_detail"); @@ -450,8 +450,8 @@ OmniLight::OmniLight() : void SpotLight::_bind_methods() { ADD_GROUP("Spot", "spot_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_range", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_RANGE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_range", PROPERTY_HINT_EXP_RANGE, "0,4096,0.1,or_greater"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.1"), "set_param", "get_param", PARAM_SPOT_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_SPOT_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION); } diff --git a/scene/3d/mesh_instance.cpp b/scene/3d/mesh_instance.cpp index 80bae911d4..e836a6154a 100644 --- a/scene/3d/mesh_instance.cpp +++ b/scene/3d/mesh_instance.cpp @@ -371,7 +371,7 @@ void MeshInstance::_bind_methods() { ClassDB::set_method_flags("MeshInstance", "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_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton"), "set_skeleton_path", "get_skeleton_path"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton"), "set_skeleton_path", "get_skeleton_path"); } MeshInstance::MeshInstance() { diff --git a/scene/3d/navigation.cpp b/scene/3d/navigation.cpp index e7ab6cde8a..77bf703706 100644 --- a/scene/3d/navigation.cpp +++ b/scene/3d/navigation.cpp @@ -35,6 +35,7 @@ void Navigation::_navmesh_link(int p_id) { ERR_FAIL_COND(!navmesh_map.has(p_id)); NavMesh &nm = navmesh_map[p_id]; ERR_FAIL_COND(nm.linked); + ERR_FAIL_COND(nm.navmesh.is_null()); PoolVector<Vector3> vertices = nm.navmesh->get_vertices(); int len = vertices.size(); diff --git a/scene/3d/particles.cpp b/scene/3d/particles.cpp index 8617bbc2f6..2b3a62fcdc 100644 --- a/scene/3d/particles.cpp +++ b/scene/3d/particles.cpp @@ -324,11 +324,11 @@ void Particles::_bind_methods() { ClassDB::bind_method(D_METHOD("capture_aabb"), &Particles::capture_aabb); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,100000,1"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_EXP_RANGE, "1,1000000,1"), "set_amount", "get_amount"); ADD_GROUP("Time", ""); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01"), "set_lifetime", "get_lifetime"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime", PROPERTY_HINT_EXP_RANGE, "0.01,600.0,0.01"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "preprocess", PROPERTY_HINT_EXP_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed_scale", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); @@ -849,9 +849,9 @@ void ParticlesMaterial::_update_shader() { code += " vec4(1.250, -1.050, -0.203, 0.0),\n"; code += " vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_s;\n"; if (color_ramp.is_valid()) { - code += " COLOR = textureLod(color_ramp,vec2(CUSTOM.y,0.0),0.0) * hue_rot_mat;\n"; + code += " COLOR = hue_rot_mat * textureLod(color_ramp,vec2(CUSTOM.y,0.0),0.0);\n"; } else { - code += " COLOR = color_value * hue_rot_mat;\n"; + code += " COLOR = hue_rot_mat * color_value;\n"; } if (emission_color_texture.is_valid() && emission_shape >= EMISSION_SHAPE_POINTS) { code += " COLOR*= texelFetch(emission_texture_color,emission_tex_ofs,0);\n"; @@ -1028,8 +1028,6 @@ void ParticlesMaterial::set_param(Parameter p_param, float p_value) { case PARAM_ANIM_OFFSET: { VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset, p_value); } break; - case PARAM_MAX: { - }; } } float ParticlesMaterial::get_param(Parameter p_param) const { @@ -1082,8 +1080,6 @@ void ParticlesMaterial::set_param_randomness(Parameter p_param, float p_value) { case PARAM_ANIM_OFFSET: { VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_random, p_value); } break; - case PARAM_MAX: { - }; } } float ParticlesMaterial::get_param_randomness(Parameter p_param) const { @@ -1160,8 +1156,6 @@ void ParticlesMaterial::set_param_texture(Parameter p_param, const Ref<Texture> case PARAM_ANIM_OFFSET: { VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->anim_offset_texture, p_texture); } break; - case PARAM_MAX: { - }; } _queue_shader_change(); @@ -1233,28 +1227,19 @@ void ParticlesMaterial::set_emission_box_extents(Vector3 p_extents) { void ParticlesMaterial::set_emission_point_texture(const Ref<Texture> &p_points) { emission_point_texture = p_points; - RID texture; - if (p_points.is_valid()) - texture = p_points->get_rid(); - VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_points, texture); + VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_points, p_points); } void ParticlesMaterial::set_emission_normal_texture(const Ref<Texture> &p_normals) { emission_normal_texture = p_normals; - RID texture; - if (p_normals.is_valid()) - texture = p_normals->get_rid(); - VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, texture); + VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_normal, p_normals); } void ParticlesMaterial::set_emission_color_texture(const Ref<Texture> &p_colors) { emission_color_texture = p_colors; - RID texture; - if (p_colors.is_valid()) - texture = p_colors->get_rid(); - VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, texture); + VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_texture_color, p_colors); _queue_shader_change(); } @@ -1316,10 +1301,7 @@ void ParticlesMaterial::set_trail_size_modifier(const Ref<CurveTexture> &p_trail curve->ensure_default_setup(); } - RID texture; - if (p_trail_size_modifier.is_valid()) - texture = p_trail_size_modifier->get_rid(); - VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->trail_size_modifier, texture); + VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->trail_size_modifier, curve); _queue_shader_change(); } @@ -1331,10 +1313,7 @@ Ref<CurveTexture> ParticlesMaterial::get_trail_size_modifier() const { void ParticlesMaterial::set_trail_color_modifier(const Ref<GradientTexture> &p_trail_color_modifier) { trail_color_modifier = p_trail_color_modifier; - RID texture; - if (p_trail_color_modifier.is_valid()) - texture = p_trail_color_modifier->get_rid(); - VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->trail_color_modifier, texture); + VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->trail_color_modifier, p_trail_color_modifier); _queue_shader_change(); } @@ -1465,7 +1444,7 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trail_color_modifier", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_trail_color_modifier", "get_trail_color_modifier"); ADD_GROUP("Emission Shape", "emission_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,or_greater"), "set_emission_sphere_radius", "get_emission_sphere_radius"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_point_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_point_texture", "get_emission_point_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_normal_texture", "get_emission_normal_texture"); @@ -1481,38 +1460,38 @@ void ParticlesMaterial::_bind_methods() { ADD_GROUP("Gravity", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("Initial Velocity", "initial_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "initial_velocity", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_INITIAL_LINEAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "initial_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_INITIAL_LINEAR_VELOCITY); ADD_GROUP("Angular Velocity", "angular_"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-360,360,0.01"), "set_param", "get_param", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGULAR_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGULAR_VELOCITY); ADD_GROUP("Orbit Velocity", "orbit_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "orbit_velocity_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ORBIT_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ORBIT_VELOCITY); ADD_GROUP("Linear Accel", "linear_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01"), "set_param", "get_param", PARAM_LINEAR_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_LINEAR_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_LINEAR_ACCEL); ADD_GROUP("Radial Accel", "radial_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01"), "set_param", "get_param", PARAM_RADIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radial_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "radial_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_RADIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_RADIAL_ACCEL); ADD_GROUP("Tangential Accel", "tangential_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel", PROPERTY_HINT_RANGE, "-100,100,0.01,or_lesser,or_greater"), "set_param", "get_param", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param", "get_param", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING); ADD_GROUP("Angle", ""); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1"), "set_param", "get_param", PARAM_ANGLE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angle", PROPERTY_HINT_RANGE, "-720,720,0.1,or_lesser,or_greater"), "set_param", "get_param", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angle_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE); ADD_GROUP("Scale", ""); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,1000,0.01"), "set_param", "get_param", PARAM_SCALE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param", "get_param", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "scale_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_SCALE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE); ADD_GROUP("Color", ""); @@ -1524,7 +1503,7 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "hue_variation_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_HUE_VARIATION); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_HUE_VARIATION); ADD_GROUP("Animation", "anim_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_param", "get_param", PARAM_ANIM_SPEED); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_param", "get_param", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_speed_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_SPEED); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anim_offset", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_ANIM_OFFSET); diff --git a/scene/3d/path.cpp b/scene/3d/path.cpp index 7ac7f74bb0..9acaa15641 100644 --- a/scene/3d/path.cpp +++ b/scene/3d/path.cpp @@ -40,6 +40,9 @@ void Path::_curve_changed() { if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) update_gizmo(); + if (is_inside_tree()) { + emit_signal("curve_changed"); + } } void Path::set_curve(const Ref<Curve3D> &p_curve) { @@ -68,6 +71,8 @@ void Path::_bind_methods() { ClassDB::bind_method(D_METHOD("_curve_changed"), &Path::_curve_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve3D"), "set_curve", "get_curve"); + + ADD_SIGNAL(MethodInfo("curve_changed")); } Path::Path() { @@ -225,7 +230,7 @@ void PathFollow::_bind_methods() { ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow::set_loop); ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow::has_loop); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_EXP_RANGE, "0,10000,0.01,or_greater"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "h_offset"), "set_h_offset", "get_h_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_offset"), "set_v_offset", "get_v_offset"); @@ -324,3 +329,219 @@ PathFollow::PathFollow() { cubic = true; loop = true; } + +////////////// + +void OrientedPathFollow::_update_transform() { + + if (!path) + return; + + Ref<Curve3D> c = path->get_curve(); + if (!c.is_valid()) + return; + + int count = c->get_point_count(); + if (count < 2) + return; + + if (delta_offset == 0) { + return; + } + + float offset = get_offset(); + float bl = c->get_baked_length(); + float bi = c->get_bake_interval(); + float o = offset; + float o_next = offset + bi; + + if (has_loop()) { + o = Math::fposmod(o, bl); + o_next = Math::fposmod(o_next, bl); + } else if (o_next >= bl) { + o = bl - bi; + o_next = bl; + } + + bool cubic = get_cubic_interpolation(); + Vector3 pos = c->interpolate_baked(o, cubic); + Vector3 forward = c->interpolate_baked(o_next, cubic) - pos; + + if (forward.length_squared() < CMP_EPSILON2) + forward = Vector3(0, 0, 1); + else + forward.normalize(); + + Vector3 up = c->interpolate_baked_up_vector(o, true); + + if (o_next < o) { + Vector3 up1 = c->interpolate_baked_up_vector(o_next, true); + Vector3 axis = up.cross(up1); + + if (axis.length_squared() < CMP_EPSILON2) + axis = forward; + else + axis.normalize(); + + up.rotate(axis, up.angle_to(up1) * 0.5f); + } + + Transform t = get_transform(); + Vector3 scale = t.basis.get_scale(); + + Vector3 sideways = up.cross(forward).normalized(); + up = forward.cross(sideways).normalized(); + + t.basis.set(sideways, up, forward); + t.basis.scale_local(scale); + + t.origin = pos + sideways * get_h_offset() + up * get_v_offset(); + + set_transform(t); +} + +void OrientedPathFollow::_notification(int p_what) { + + switch (p_what) { + + case NOTIFICATION_ENTER_TREE: { + + Node *parent = get_parent(); + if (parent) { + path = Object::cast_to<Path>(parent); + if (path) { + _update_transform(); + } + } + + } break; + case NOTIFICATION_EXIT_TREE: { + + path = NULL; + } break; + } +} + +void OrientedPathFollow::set_cubic_interpolation(bool p_enable) { + + cubic = p_enable; +} + +bool OrientedPathFollow::get_cubic_interpolation() const { + + return cubic; +} + +void OrientedPathFollow::_validate_property(PropertyInfo &property) const { + + if (property.name == "offset") { + + float max = 10000; + if (path && path->get_curve().is_valid()) + max = path->get_curve()->get_baked_length(); + + property.hint_string = "0," + rtos(max) + ",0.01"; + } +} + +void OrientedPathFollow::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_offset", "offset"), &OrientedPathFollow::set_offset); + ClassDB::bind_method(D_METHOD("get_offset"), &OrientedPathFollow::get_offset); + + ClassDB::bind_method(D_METHOD("set_h_offset", "h_offset"), &OrientedPathFollow::set_h_offset); + ClassDB::bind_method(D_METHOD("get_h_offset"), &OrientedPathFollow::get_h_offset); + + ClassDB::bind_method(D_METHOD("set_v_offset", "v_offset"), &OrientedPathFollow::set_v_offset); + ClassDB::bind_method(D_METHOD("get_v_offset"), &OrientedPathFollow::get_v_offset); + + ClassDB::bind_method(D_METHOD("set_unit_offset", "unit_offset"), &OrientedPathFollow::set_unit_offset); + ClassDB::bind_method(D_METHOD("get_unit_offset"), &OrientedPathFollow::get_unit_offset); + + ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enable"), &OrientedPathFollow::set_cubic_interpolation); + ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &OrientedPathFollow::get_cubic_interpolation); + + ClassDB::bind_method(D_METHOD("set_loop", "loop"), &OrientedPathFollow::set_loop); + ClassDB::bind_method(D_METHOD("has_loop"), &OrientedPathFollow::has_loop); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "h_offset"), "set_h_offset", "get_h_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_offset"), "set_v_offset", "get_v_offset"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); +} + +void OrientedPathFollow::set_offset(float p_offset) { + delta_offset = p_offset - offset; + offset = p_offset; + + if (path) + _update_transform(); + _change_notify("offset"); + _change_notify("unit_offset"); +} + +void OrientedPathFollow::set_h_offset(float p_h_offset) { + + h_offset = p_h_offset; + if (path) + _update_transform(); +} + +float OrientedPathFollow::get_h_offset() const { + + return h_offset; +} + +void OrientedPathFollow::set_v_offset(float p_v_offset) { + + v_offset = p_v_offset; + if (path) + _update_transform(); +} + +float OrientedPathFollow::get_v_offset() const { + + return v_offset; +} + +float OrientedPathFollow::get_offset() const { + + return offset; +} + +void OrientedPathFollow::set_unit_offset(float p_unit_offset) { + + if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) + set_offset(p_unit_offset * path->get_curve()->get_baked_length()); +} + +float OrientedPathFollow::get_unit_offset() const { + + if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) + return get_offset() / path->get_curve()->get_baked_length(); + else + return 0; +} + +void OrientedPathFollow::set_loop(bool p_loop) { + + loop = p_loop; +} + +bool OrientedPathFollow::has_loop() const { + + return loop; +} + +OrientedPathFollow::OrientedPathFollow() { + + offset = 0; + delta_offset = 0; + h_offset = 0; + v_offset = 0; + path = NULL; + cubic = true; + loop = true; +} diff --git a/scene/3d/path.h b/scene/3d/path.h index 2ed686ac3c..f73bf17dfe 100644 --- a/scene/3d/path.h +++ b/scene/3d/path.h @@ -111,4 +111,47 @@ public: VARIANT_ENUM_CAST(PathFollow::RotationMode); +class OrientedPathFollow : public Spatial { + + GDCLASS(OrientedPathFollow, Spatial); + +private: + Path *path; + real_t delta_offset; // change in offset since last _update_transform + real_t offset; + real_t h_offset; + real_t v_offset; + bool cubic; + bool loop; + + void _update_transform(); + +protected: + virtual void _validate_property(PropertyInfo &property) const; + + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_offset(float p_offset); + float get_offset() const; + + void set_h_offset(float p_h_offset); + float get_h_offset() const; + + void set_v_offset(float p_v_offset); + float get_v_offset() const; + + void set_unit_offset(float p_unit_offset); + float get_unit_offset() const; + + void set_loop(bool p_loop); + bool has_loop() const; + + void set_cubic_interpolation(bool p_enable); + bool get_cubic_interpolation() const; + + OrientedPathFollow(); +}; + #endif // PATH_H diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index ff4a807de0..e851c8d643 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -34,6 +34,10 @@ #include "method_bind_ext.gen.inc" #include "scene/scene_string_names.h" +#ifdef TOOLS_ENABLED +#include "editor/plugins/spatial_editor_plugin.h" +#endif + void PhysicsBody::_notification(int p_what) { /* @@ -781,7 +785,7 @@ String RigidBody::get_configuration_warning() const { String warning = CollisionObject::get_configuration_warning(); - if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(0).length() - 1.0) > 0.05)) { + if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.basis.get_axis(0).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(1).length() - 1.0) > 0.05 || ABS(t.basis.get_axis(2).length() - 1.0) > 0.05)) { if (warning != String()) { warning += "\n"; } @@ -975,7 +979,7 @@ bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_in return colliding; } -Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction, bool p_infinite_inertia, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle) { +Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle, bool p_infinite_inertia) { Vector3 lv = p_linear_velocity; @@ -1124,7 +1128,7 @@ Ref<KinematicCollision> KinematicBody::_get_slide_collision(int p_bounce) { void KinematicBody::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia"), &KinematicBody::_move, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "infinite_inertia", "slope_stop_min_velocity", "max_slides", "floor_max_angle"), &KinematicBody::move_and_slide, DEFVAL(Vector3(0, 0, 0)), DEFVAL(true), DEFVAL(0.05), DEFVAL(4), DEFVAL(Math::deg2rad((float)45))); + ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "slope_stop_min_velocity", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody::move_and_slide, DEFVAL(Vector3(0, 0, 0)), DEFVAL(0.05), DEFVAL(4), DEFVAL(Math::deg2rad((float)45)), DEFVAL(true)); ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia"), &KinematicBody::test_move); @@ -1266,3 +1270,1112 @@ KinematicCollision::KinematicCollision() { collision.local_shape = 0; owner = NULL; } + +/////////////////////////////////////// + +bool PhysicalBone::JointData::_set(const StringName &p_name, const Variant &p_value, RID j) { + return false; +} + +bool PhysicalBone::JointData::_get(const StringName &p_name, Variant &r_ret) const { + return false; +} + +void PhysicalBone::JointData::_get_property_list(List<PropertyInfo> *p_list) const { +} + +bool PhysicalBone::PinJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { + if (JointData::_set(p_name, p_value, j)) { + return true; + } + + if ("joint_constraints/bias" == p_name) { + bias = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->pin_joint_set_param(j, PhysicsServer::PIN_JOINT_BIAS, bias); + + } else if ("joint_constraints/damping" == p_name) { + damping = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->pin_joint_set_param(j, PhysicsServer::PIN_JOINT_DAMPING, damping); + + } else if ("joint_constraints/impulse_clamp" == p_name) { + impulse_clamp = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->pin_joint_set_param(j, PhysicsServer::PIN_JOINT_IMPULSE_CLAMP, impulse_clamp); + + } else { + return false; + } + + return true; +} + +bool PhysicalBone::PinJointData::_get(const StringName &p_name, Variant &r_ret) const { + if (JointData::_get(p_name, r_ret)) { + return true; + } + + if ("joint_constraints/bias" == p_name) { + r_ret = bias; + } else if ("joint_constraints/damping" == p_name) { + r_ret = damping; + } else if ("joint_constraints/impulse_clamp" == p_name) { + r_ret = impulse_clamp; + } else { + return false; + } + + return true; +} + +void PhysicalBone::PinJointData::_get_property_list(List<PropertyInfo> *p_list) const { + JointData::_get_property_list(p_list); + + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/damping", PROPERTY_HINT_RANGE, "0.01,8.0,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/impulse_clamp", PROPERTY_HINT_RANGE, "0.0,64.0,0.01")); +} + +bool PhysicalBone::ConeJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { + if (JointData::_set(p_name, p_value, j)) { + return true; + } + + if ("joint_constraints/swing_span" == p_name) { + swing_span = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->cone_twist_joint_set_param(j, PhysicsServer::CONE_TWIST_JOINT_SWING_SPAN, swing_span); + + } else if ("joint_constraints/twist_span" == p_name) { + twist_span = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->cone_twist_joint_set_param(j, PhysicsServer::CONE_TWIST_JOINT_TWIST_SPAN, twist_span); + + } else if ("joint_constraints/bias" == p_name) { + bias = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->cone_twist_joint_set_param(j, PhysicsServer::CONE_TWIST_JOINT_BIAS, bias); + + } else if ("joint_constraints/softness" == p_name) { + softness = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->cone_twist_joint_set_param(j, PhysicsServer::CONE_TWIST_JOINT_SOFTNESS, softness); + + } else if ("joint_constraints/relaxation" == p_name) { + relaxation = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->cone_twist_joint_set_param(j, PhysicsServer::CONE_TWIST_JOINT_RELAXATION, relaxation); + + } else { + return false; + } + + return true; +} + +bool PhysicalBone::ConeJointData::_get(const StringName &p_name, Variant &r_ret) const { + if (JointData::_get(p_name, r_ret)) { + return true; + } + + if ("joint_constraints/swing_span" == p_name) { + r_ret = Math::rad2deg(swing_span); + } else if ("joint_constraints/twist_span" == p_name) { + r_ret = Math::rad2deg(twist_span); + } else if ("joint_constraints/bias" == p_name) { + r_ret = bias; + } else if ("joint_constraints/softness" == p_name) { + r_ret = softness; + } else if ("joint_constraints/relaxation" == p_name) { + r_ret = relaxation; + } else { + return false; + } + + return true; +} + +void PhysicalBone::ConeJointData::_get_property_list(List<PropertyInfo> *p_list) const { + JointData::_get_property_list(p_list); + + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/swing_span", PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/twist_span", PROPERTY_HINT_RANGE, "-40000,40000,0.1,or_lesser,or_greater")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/bias", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/relaxation", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); +} + +bool PhysicalBone::HingeJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { + if (JointData::_set(p_name, p_value, j)) { + return true; + } + + if ("joint_constraints/angular_limit_enabled" == p_name) { + angular_limit_enabled = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->hinge_joint_set_flag(j, PhysicsServer::HINGE_JOINT_FLAG_USE_LIMIT, angular_limit_enabled); + + } else if ("joint_constraints/angular_limit_upper" == p_name) { + angular_limit_upper = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->hinge_joint_set_param(j, PhysicsServer::HINGE_JOINT_LIMIT_UPPER, angular_limit_upper); + + } else if ("joint_constraints/angular_limit_lower" == p_name) { + angular_limit_lower = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->hinge_joint_set_param(j, PhysicsServer::HINGE_JOINT_LIMIT_LOWER, angular_limit_lower); + + } else if ("joint_constraints/angular_limit_bias" == p_name) { + angular_limit_bias = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->hinge_joint_set_param(j, PhysicsServer::HINGE_JOINT_LIMIT_BIAS, angular_limit_bias); + + } else if ("joint_constraints/angular_limit_softness" == p_name) { + angular_limit_softness = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->hinge_joint_set_param(j, PhysicsServer::HINGE_JOINT_LIMIT_SOFTNESS, angular_limit_softness); + + } else if ("joint_constraints/angular_limit_relaxation" == p_name) { + angular_limit_relaxation = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->hinge_joint_set_param(j, PhysicsServer::HINGE_JOINT_LIMIT_RELAXATION, angular_limit_relaxation); + + } else { + return false; + } + + return true; +} + +bool PhysicalBone::HingeJointData::_get(const StringName &p_name, Variant &r_ret) const { + if (JointData::_get(p_name, r_ret)) { + return true; + } + + if ("joint_constraints/angular_limit_enabled" == p_name) { + r_ret = angular_limit_enabled; + } else if ("joint_constraints/angular_limit_upper" == p_name) { + r_ret = Math::rad2deg(angular_limit_upper); + } else if ("joint_constraints/angular_limit_lower" == p_name) { + r_ret = Math::rad2deg(angular_limit_lower); + } else if ("joint_constraints/angular_limit_bias" == p_name) { + r_ret = angular_limit_bias; + } else if ("joint_constraints/angular_limit_softness" == p_name) { + r_ret = angular_limit_softness; + } else if ("joint_constraints/angular_limit_relaxation" == p_name) { + r_ret = angular_limit_relaxation; + } else { + return false; + } + + return true; +} + +void PhysicalBone::HingeJointData::_get_property_list(List<PropertyInfo> *p_list) const { + JointData::_get_property_list(p_list); + + p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/angular_limit_enabled")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_upper", PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_lower", PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_softness", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_relaxation", PROPERTY_HINT_RANGE, "0.01,16,0.01")); +} + +bool PhysicalBone::SliderJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { + if (JointData::_set(p_name, p_value, j)) { + return true; + } + + if ("joint_constraints/linear_limit_upper" == p_name) { + linear_limit_upper = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_UPPER, linear_limit_upper); + + } else if ("joint_constraints/linear_limit_lower" == p_name) { + linear_limit_lower = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_LOWER, linear_limit_lower); + + } else if ("joint_constraints/linear_limit_softness" == p_name) { + linear_limit_softness = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS, linear_limit_softness); + + } else if ("joint_constraints/linear_limit_restitution" == p_name) { + linear_limit_restitution = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION, linear_limit_restitution); + + } else if ("joint_constraints/linear_limit_damping" == p_name) { + linear_limit_damping = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_DAMPING, linear_limit_restitution); + + } else if ("joint_constraints/angular_limit_upper" == p_name) { + angular_limit_upper = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_UPPER, angular_limit_upper); + + } else if ("joint_constraints/angular_limit_lower" == p_name) { + angular_limit_lower = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_LOWER, angular_limit_lower); + + } else if ("joint_constraints/angular_limit_softness" == p_name) { + angular_limit_softness = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS, angular_limit_softness); + + } else if ("joint_constraints/angular_limit_restitution" == p_name) { + angular_limit_restitution = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS, angular_limit_softness); + + } else if ("joint_constraints/angular_limit_damping" == p_name) { + angular_limit_damping = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->slider_joint_set_param(j, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING, angular_limit_damping); + + } else { + return false; + } + + return true; +} + +bool PhysicalBone::SliderJointData::_get(const StringName &p_name, Variant &r_ret) const { + if (JointData::_get(p_name, r_ret)) { + return true; + } + + if ("joint_constraints/linear_limit_upper" == p_name) { + r_ret = linear_limit_upper; + } else if ("joint_constraints/linear_limit_lower" == p_name) { + r_ret = linear_limit_lower; + } else if ("joint_constraints/linear_limit_softness" == p_name) { + r_ret = linear_limit_softness; + } else if ("joint_constraints/linear_limit_restitution" == p_name) { + r_ret = linear_limit_restitution; + } else if ("joint_constraints/linear_limit_damping" == p_name) { + r_ret = linear_limit_damping; + } else if ("joint_constraints/angular_limit_upper" == p_name) { + r_ret = Math::rad2deg(angular_limit_upper); + } else if ("joint_constraints/angular_limit_lower" == p_name) { + r_ret = Math::rad2deg(angular_limit_lower); + } else if ("joint_constraints/angular_limit_softness" == p_name) { + r_ret = angular_limit_softness; + } else if ("joint_constraints/angular_limit_restitution" == p_name) { + r_ret = angular_limit_restitution; + } else if ("joint_constraints/angular_limit_damping" == p_name) { + r_ret = angular_limit_damping; + } else { + return false; + } + + return true; +} + +void PhysicalBone::SliderJointData::_get_property_list(List<PropertyInfo> *p_list) const { + JointData::_get_property_list(p_list); + + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/linear_limit_upper")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/linear_limit_lower")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/linear_limit_softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/linear_limit_restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/linear_limit_damping", PROPERTY_HINT_RANGE, "0,16.0,0.01")); + + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_upper", PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_lower", PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_softness", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_restitution", PROPERTY_HINT_RANGE, "0.01,16.0,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/angular_limit_damping", PROPERTY_HINT_RANGE, "0,16.0,0.01")); +} + +bool PhysicalBone::SixDOFJointData::_set(const StringName &p_name, const Variant &p_value, RID j) { + if (JointData::_set(p_name, p_value, j)) { + return true; + } + + String path = p_name; + + Vector3::Axis axis; + { + const String axis_s = path.get_slicec('/', 1); + if ("x" == axis_s) { + axis = Vector3::AXIS_X; + } else if ("y" == axis_s) { + axis = Vector3::AXIS_Y; + } else if ("z" == axis_s) { + axis = Vector3::AXIS_Z; + } else { + return false; + } + } + + String var_name = path.get_slicec('/', 2); + + if ("linear_limit_enabled" == var_name) { + axis_data[axis].linear_limit_enabled = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_flag(j, axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, axis_data[axis].linear_limit_enabled); + + } else if ("linear_limit_upper" == var_name) { + axis_data[axis].linear_limit_upper = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_LINEAR_UPPER_LIMIT, axis_data[axis].linear_limit_upper); + + } else if ("linear_limit_lower" == var_name) { + axis_data[axis].linear_limit_lower = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_LINEAR_LOWER_LIMIT, axis_data[axis].linear_limit_lower); + + } else if ("linear_limit_softness" == var_name) { + axis_data[axis].linear_limit_softness = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS, axis_data[axis].linear_limit_softness); + + } else if ("linear_restitution" == var_name) { + axis_data[axis].linear_restitution = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_LINEAR_RESTITUTION, axis_data[axis].linear_restitution); + + } else if ("linear_damping" == var_name) { + axis_data[axis].linear_damping = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_LINEAR_DAMPING, axis_data[axis].linear_damping); + + } else if ("angular_limit_enabled" == var_name) { + axis_data[axis].angular_limit_enabled = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_flag(j, axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT, axis_data[axis].angular_limit_enabled); + + } else if ("angular_limit_upper" == var_name) { + axis_data[axis].angular_limit_upper = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_ANGULAR_UPPER_LIMIT, axis_data[axis].angular_limit_upper); + + } else if ("angular_limit_lower" == var_name) { + axis_data[axis].angular_limit_lower = Math::deg2rad(real_t(p_value)); + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_ANGULAR_LOWER_LIMIT, axis_data[axis].angular_limit_lower); + + } else if ("angular_limit_softness" == var_name) { + axis_data[axis].angular_limit_softness = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS, axis_data[axis].angular_limit_softness); + + } else if ("angular_restitution" == var_name) { + axis_data[axis].angular_restitution = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_ANGULAR_RESTITUTION, axis_data[axis].angular_restitution); + + } else if ("angular_damping" == var_name) { + axis_data[axis].angular_damping = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_ANGULAR_DAMPING, axis_data[axis].angular_damping); + + } else if ("erp" == var_name) { + axis_data[axis].erp = p_value; + if (j.is_valid()) + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(j, axis, PhysicsServer::G6DOF_JOINT_ANGULAR_ERP, axis_data[axis].erp); + + } else { + return false; + } + + return true; +} + +bool PhysicalBone::SixDOFJointData::_get(const StringName &p_name, Variant &r_ret) const { + if (JointData::_get(p_name, r_ret)) { + return true; + } + + String path = p_name; + + int axis; + { + const String axis_s = path.get_slicec('/', 1); + if ("x" == axis_s) { + axis = 0; + } else if ("y" == axis_s) { + axis = 1; + } else if ("z" == axis_s) { + axis = 2; + } else { + return false; + } + } + + String var_name = path.get_slicec('/', 2); + + if ("linear_limit_enabled" == var_name) { + r_ret = axis_data[axis].linear_limit_enabled; + } else if ("linear_limit_upper" == var_name) { + r_ret = axis_data[axis].linear_limit_upper; + } else if ("linear_limit_lower" == var_name) { + r_ret = axis_data[axis].linear_limit_lower; + } else if ("linear_limit_softness" == var_name) { + r_ret = axis_data[axis].linear_limit_softness; + } else if ("linear_restitution" == var_name) { + r_ret = axis_data[axis].linear_restitution; + } else if ("linear_damping" == var_name) { + r_ret = axis_data[axis].linear_damping; + } else if ("angular_limit_enabled" == var_name) { + r_ret = axis_data[axis].angular_limit_enabled; + } else if ("angular_limit_upper" == var_name) { + r_ret = Math::rad2deg(axis_data[axis].angular_limit_upper); + } else if ("angular_limit_lower" == var_name) { + r_ret = Math::rad2deg(axis_data[axis].angular_limit_lower); + } else if ("angular_limit_softness" == var_name) { + r_ret = axis_data[axis].angular_limit_softness; + } else if ("angular_restitution" == var_name) { + r_ret = axis_data[axis].angular_restitution; + } else if ("angular_damping" == var_name) { + r_ret = axis_data[axis].angular_damping; + } else if ("erp" == var_name) { + r_ret = axis_data[axis].erp; + } else { + return false; + } + + return true; +} + +void PhysicalBone::SixDOFJointData::_get_property_list(List<PropertyInfo> *p_list) const { + const StringName axis_names[] = { "x", "y", "z" }; + for (int i = 0; i < 3; ++i) { + p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/" + axis_names[i] + "/linear_limit_enabled")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/linear_limit_upper")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/linear_limit_lower")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/linear_limit_softness", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/linear_restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/linear_damping", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::BOOL, "joint_constraints/" + axis_names[i] + "/angular_limit_enabled")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/angular_limit_upper", PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/angular_limit_lower", PROPERTY_HINT_RANGE, "-180,180,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/angular_limit_softness", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/angular_restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/angular_damping", PROPERTY_HINT_RANGE, "0.01,16,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, "joint_constraints/" + axis_names[i] + "/erp")); + } +} + +bool PhysicalBone::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "bone_name") { + set_bone_name(p_value); + return true; + } + + if (joint_data) { + if (joint_data->_set(p_name, p_value)) { +#ifdef TOOLS_ENABLED + if (get_gizmo().is_valid()) + get_gizmo()->redraw(); +#endif + return true; + } + } + + return false; +} + +bool PhysicalBone::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "bone_name") { + r_ret = get_bone_name(); + return true; + } + + if (joint_data) { + return joint_data->_get(p_name, r_ret); + } + + return false; +} + +void PhysicalBone::_get_property_list(List<PropertyInfo> *p_list) const { + + Skeleton *parent = find_skeleton_parent(get_parent()); + + if (parent) { + + String names; + for (int i = 0; i < parent->get_bone_count(); i++) { + if (i > 0) + names += ","; + names += parent->get_bone_name(i); + } + + p_list->push_back(PropertyInfo(Variant::STRING, "bone_name", PROPERTY_HINT_ENUM, names)); + } else { + + p_list->push_back(PropertyInfo(Variant::STRING, "bone_name")); + } + + if (joint_data) { + joint_data->_get_property_list(p_list); + } +} + +void PhysicalBone::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + parent_skeleton = find_skeleton_parent(get_parent()); + update_bone_id(); + reset_to_rest_position(); + _reset_physics_simulation_state(); + break; + case NOTIFICATION_EXIT_TREE: + if (parent_skeleton) { + if (-1 != bone_id) { + parent_skeleton->unbind_physical_bone_from_bone(bone_id); + } + } + parent_skeleton = NULL; + update_bone_id(); + break; + case NOTIFICATION_TRANSFORM_CHANGED: + if (Engine::get_singleton()->is_editor_hint()) { + + update_offset(); + } + break; + } +} + +void PhysicalBone::_direct_state_changed(Object *p_state) { + + if (!simulate_physics) { + return; + } + + /// Update bone transform + + PhysicsDirectBodyState *state; + +#ifdef DEBUG_ENABLED + state = Object::cast_to<PhysicsDirectBodyState>(p_state); +#else + state = (PhysicsDirectBodyState *)p_state; //trust it +#endif + + Transform global_transform(state->get_transform()); + + set_ignore_transform_notification(true); + set_global_transform(global_transform); + set_ignore_transform_notification(false); + + // Update skeleton + if (parent_skeleton) { + if (-1 != bone_id) { + parent_skeleton->set_bone_global_pose(bone_id, parent_skeleton->get_global_transform().affine_inverse() * (global_transform * body_offset_inverse)); + } + } +} + +void PhysicalBone::_bind_methods() { + ClassDB::bind_method(D_METHOD("_direct_state_changed"), &PhysicalBone::_direct_state_changed); + + ClassDB::bind_method(D_METHOD("set_joint_type", "joint_type"), &PhysicalBone::set_joint_type); + ClassDB::bind_method(D_METHOD("get_joint_type"), &PhysicalBone::get_joint_type); + + ClassDB::bind_method(D_METHOD("set_joint_offset", "offset"), &PhysicalBone::set_joint_offset); + ClassDB::bind_method(D_METHOD("get_joint_offset"), &PhysicalBone::get_joint_offset); + + ClassDB::bind_method(D_METHOD("set_body_offset", "offset"), &PhysicalBone::set_body_offset); + ClassDB::bind_method(D_METHOD("get_body_offset"), &PhysicalBone::get_body_offset); + + ClassDB::bind_method(D_METHOD("is_static_body"), &PhysicalBone::is_static_body); + + ClassDB::bind_method(D_METHOD("get_simulate_physics"), &PhysicalBone::get_simulate_physics); + + ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBone::is_simulating_physics); + + ClassDB::bind_method(D_METHOD("set_mass", "mass"), &PhysicalBone::set_mass); + ClassDB::bind_method(D_METHOD("get_mass"), &PhysicalBone::get_mass); + + ClassDB::bind_method(D_METHOD("set_weight", "weight"), &PhysicalBone::set_weight); + ClassDB::bind_method(D_METHOD("get_weight"), &PhysicalBone::get_weight); + + ClassDB::bind_method(D_METHOD("set_friction", "friction"), &PhysicalBone::set_friction); + ClassDB::bind_method(D_METHOD("get_friction"), &PhysicalBone::get_friction); + + ClassDB::bind_method(D_METHOD("set_bounce", "bounce"), &PhysicalBone::set_bounce); + ClassDB::bind_method(D_METHOD("get_bounce"), &PhysicalBone::get_bounce); + + ClassDB::bind_method(D_METHOD("set_gravity_scale", "gravity_scale"), &PhysicalBone::set_gravity_scale); + ClassDB::bind_method(D_METHOD("get_gravity_scale"), &PhysicalBone::get_gravity_scale); + + ADD_GROUP("Joint", "joint_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "joint_type", PROPERTY_HINT_ENUM, "None,PinJoint,ConeJoint,HingeJoint,SliderJoint,6DOFJoint"), "set_joint_type", "get_joint_type"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "joint_offset"), "set_joint_offset", "get_joint_offset"); + + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "body_offset"), "set_body_offset", "get_body_offset"); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "mass", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "weight", PROPERTY_HINT_EXP_RANGE, "0.01,65535,0.01"), "set_weight", "get_weight"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_friction", "get_friction"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_bounce", "get_bounce"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "gravity_scale", PROPERTY_HINT_RANGE, "-10,10,0.01"), "set_gravity_scale", "get_gravity_scale"); + + BIND_ENUM_CONSTANT(JOINT_TYPE_NONE); + BIND_ENUM_CONSTANT(JOINT_TYPE_PIN); + BIND_ENUM_CONSTANT(JOINT_TYPE_CONE); + BIND_ENUM_CONSTANT(JOINT_TYPE_HINGE); + BIND_ENUM_CONSTANT(JOINT_TYPE_SLIDER); + BIND_ENUM_CONSTANT(JOINT_TYPE_6DOF); +} + +Skeleton *PhysicalBone::find_skeleton_parent(Node *p_parent) { + if (!p_parent) { + return NULL; + } + Skeleton *s = Object::cast_to<Skeleton>(p_parent); + return s ? s : find_skeleton_parent(p_parent->get_parent()); +} + +void PhysicalBone::_fix_joint_offset() { + // Clamp joint origin to bone origin + if (parent_skeleton) { + joint_offset.origin = body_offset.affine_inverse().origin; + } +} + +void PhysicalBone::_reload_joint() { + + if (joint.is_valid()) { + PhysicsServer::get_singleton()->free(joint); + joint = RID(); + } + + if (!parent_skeleton) { + return; + } + + PhysicalBone *body_a = parent_skeleton->get_physical_bone_parent(bone_id); + if (!body_a) { + return; + } + + Transform joint_transf = get_global_transform() * joint_offset; + Transform local_a = body_a->get_global_transform().affine_inverse() * joint_transf; + local_a.orthonormalize(); + + switch (get_joint_type()) { + case JOINT_TYPE_PIN: { + + joint = PhysicsServer::get_singleton()->joint_create_pin(body_a->get_rid(), local_a.origin, get_rid(), joint_offset.origin); + const PinJointData *pjd(static_cast<const PinJointData *>(joint_data)); + PhysicsServer::get_singleton()->pin_joint_set_param(joint, PhysicsServer::PIN_JOINT_BIAS, pjd->bias); + PhysicsServer::get_singleton()->pin_joint_set_param(joint, PhysicsServer::PIN_JOINT_DAMPING, pjd->damping); + PhysicsServer::get_singleton()->pin_joint_set_param(joint, PhysicsServer::PIN_JOINT_IMPULSE_CLAMP, pjd->impulse_clamp); + + } break; + case JOINT_TYPE_CONE: { + + joint = PhysicsServer::get_singleton()->joint_create_cone_twist(body_a->get_rid(), local_a, get_rid(), joint_offset); + const ConeJointData *cjd(static_cast<const ConeJointData *>(joint_data)); + PhysicsServer::get_singleton()->cone_twist_joint_set_param(joint, PhysicsServer::CONE_TWIST_JOINT_SWING_SPAN, cjd->swing_span); + PhysicsServer::get_singleton()->cone_twist_joint_set_param(joint, PhysicsServer::CONE_TWIST_JOINT_TWIST_SPAN, cjd->twist_span); + PhysicsServer::get_singleton()->cone_twist_joint_set_param(joint, PhysicsServer::CONE_TWIST_JOINT_BIAS, cjd->bias); + PhysicsServer::get_singleton()->cone_twist_joint_set_param(joint, PhysicsServer::CONE_TWIST_JOINT_SOFTNESS, cjd->softness); + PhysicsServer::get_singleton()->cone_twist_joint_set_param(joint, PhysicsServer::CONE_TWIST_JOINT_RELAXATION, cjd->relaxation); + + } break; + case JOINT_TYPE_HINGE: { + + joint = PhysicsServer::get_singleton()->joint_create_hinge(body_a->get_rid(), local_a, get_rid(), joint_offset); + const HingeJointData *hjd(static_cast<const HingeJointData *>(joint_data)); + PhysicsServer::get_singleton()->hinge_joint_set_flag(joint, PhysicsServer::HINGE_JOINT_FLAG_USE_LIMIT, hjd->angular_limit_enabled); + PhysicsServer::get_singleton()->hinge_joint_set_param(joint, PhysicsServer::HINGE_JOINT_LIMIT_UPPER, hjd->angular_limit_upper); + PhysicsServer::get_singleton()->hinge_joint_set_param(joint, PhysicsServer::HINGE_JOINT_LIMIT_LOWER, hjd->angular_limit_lower); + PhysicsServer::get_singleton()->hinge_joint_set_param(joint, PhysicsServer::HINGE_JOINT_LIMIT_BIAS, hjd->angular_limit_bias); + PhysicsServer::get_singleton()->hinge_joint_set_param(joint, PhysicsServer::HINGE_JOINT_LIMIT_SOFTNESS, hjd->angular_limit_softness); + PhysicsServer::get_singleton()->hinge_joint_set_param(joint, PhysicsServer::HINGE_JOINT_LIMIT_RELAXATION, hjd->angular_limit_relaxation); + + } break; + case JOINT_TYPE_SLIDER: { + + joint = PhysicsServer::get_singleton()->joint_create_slider(body_a->get_rid(), local_a, get_rid(), joint_offset); + const SliderJointData *sjd(static_cast<const SliderJointData *>(joint_data)); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_UPPER, sjd->linear_limit_upper); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_LOWER, sjd->linear_limit_lower); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_SOFTNESS, sjd->linear_limit_softness); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_RESTITUTION, sjd->linear_limit_restitution); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_LINEAR_LIMIT_DAMPING, sjd->linear_limit_restitution); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_UPPER, sjd->angular_limit_upper); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_LOWER, sjd->angular_limit_lower); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS, sjd->angular_limit_softness); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_SOFTNESS, sjd->angular_limit_softness); + PhysicsServer::get_singleton()->slider_joint_set_param(joint, PhysicsServer::SLIDER_JOINT_ANGULAR_LIMIT_DAMPING, sjd->angular_limit_damping); + + } break; + case JOINT_TYPE_6DOF: { + + joint = PhysicsServer::get_singleton()->joint_create_generic_6dof(body_a->get_rid(), local_a, get_rid(), joint_offset); + const SixDOFJointData *g6dofjd(static_cast<const SixDOFJointData *>(joint_data)); + for (int axis = 0; axis < 3; ++axis) { + PhysicsServer::get_singleton()->generic_6dof_joint_set_flag(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, g6dofjd->axis_data[axis].linear_limit_enabled); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_LINEAR_UPPER_LIMIT, g6dofjd->axis_data[axis].linear_limit_upper); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_LINEAR_LOWER_LIMIT, g6dofjd->axis_data[axis].linear_limit_lower); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS, g6dofjd->axis_data[axis].linear_limit_softness); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_LINEAR_RESTITUTION, g6dofjd->axis_data[axis].linear_restitution); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_LINEAR_DAMPING, g6dofjd->axis_data[axis].linear_damping); + PhysicsServer::get_singleton()->generic_6dof_joint_set_flag(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT, g6dofjd->axis_data[axis].angular_limit_enabled); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_ANGULAR_UPPER_LIMIT, g6dofjd->axis_data[axis].angular_limit_upper); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_ANGULAR_LOWER_LIMIT, g6dofjd->axis_data[axis].angular_limit_lower); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS, g6dofjd->axis_data[axis].angular_limit_softness); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_ANGULAR_RESTITUTION, g6dofjd->axis_data[axis].angular_restitution); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_ANGULAR_DAMPING, g6dofjd->axis_data[axis].angular_damping); + PhysicsServer::get_singleton()->generic_6dof_joint_set_param(joint, static_cast<Vector3::Axis>(axis), PhysicsServer::G6DOF_JOINT_ANGULAR_ERP, g6dofjd->axis_data[axis].erp); + } + + } break; + } +} + +void PhysicalBone::_on_bone_parent_changed() { + _reload_joint(); +} + +void PhysicalBone::_set_gizmo_move_joint(bool p_move_joint) { +#ifdef TOOLS_ENABLED + gizmo_move_joint = p_move_joint; + SpatialEditor::get_singleton()->update_transform_gizmo(); +#endif +} + +#ifdef TOOLS_ENABLED +Transform PhysicalBone::get_global_gizmo_transform() const { + return gizmo_move_joint ? get_global_transform() * joint_offset : get_global_transform(); +} + +Transform PhysicalBone::get_local_gizmo_transform() const { + return gizmo_move_joint ? get_transform() * joint_offset : get_transform(); +} +#endif + +const PhysicalBone::JointData *PhysicalBone::get_joint_data() const { + return joint_data; +} + +Skeleton *PhysicalBone::find_skeleton_parent() { + return find_skeleton_parent(this); +} + +void PhysicalBone::set_joint_type(JointType p_joint_type) { + + if (p_joint_type == get_joint_type()) + return; + + memdelete(joint_data); + joint_data = NULL; + switch (p_joint_type) { + case JOINT_TYPE_PIN: + joint_data = memnew(PinJointData); + break; + case JOINT_TYPE_CONE: + joint_data = memnew(ConeJointData); + break; + case JOINT_TYPE_HINGE: + joint_data = memnew(HingeJointData); + break; + case JOINT_TYPE_SLIDER: + joint_data = memnew(SliderJointData); + break; + case JOINT_TYPE_6DOF: + joint_data = memnew(SixDOFJointData); + break; + } + + _reload_joint(); + +#ifdef TOOLS_ENABLED + _change_notify(); + if (get_gizmo().is_valid()) + get_gizmo()->redraw(); +#endif +} + +PhysicalBone::JointType PhysicalBone::get_joint_type() const { + return joint_data ? joint_data->get_joint_type() : JOINT_TYPE_NONE; +} + +void PhysicalBone::set_joint_offset(const Transform &p_offset) { + joint_offset = p_offset; + + _fix_joint_offset(); + + set_ignore_transform_notification(true); + reset_to_rest_position(); + set_ignore_transform_notification(false); + +#ifdef TOOLS_ENABLED + if (get_gizmo().is_valid()) + get_gizmo()->redraw(); +#endif +} + +const Transform &PhysicalBone::get_body_offset() const { + return body_offset; +} + +void PhysicalBone::set_body_offset(const Transform &p_offset) { + body_offset = p_offset; + body_offset_inverse = body_offset.affine_inverse(); + + _fix_joint_offset(); + + set_ignore_transform_notification(true); + reset_to_rest_position(); + set_ignore_transform_notification(false); + +#ifdef TOOLS_ENABLED + if (get_gizmo().is_valid()) + get_gizmo()->redraw(); +#endif +} + +const Transform &PhysicalBone::get_joint_offset() const { + return joint_offset; +} + +void PhysicalBone::set_static_body(bool p_static) { + + static_body = p_static; + + set_as_toplevel(!static_body); + + _reset_physics_simulation_state(); +} + +bool PhysicalBone::is_static_body() { + return static_body; +} + +void PhysicalBone::set_simulate_physics(bool p_simulate) { + if (simulate_physics == p_simulate) { + return; + } + + simulate_physics = p_simulate; + _reset_physics_simulation_state(); +} + +bool PhysicalBone::get_simulate_physics() { + return simulate_physics; +} + +bool PhysicalBone::is_simulating_physics() { + return _internal_simulate_physics && !_internal_static_body; +} + +void PhysicalBone::set_bone_name(const String &p_name) { + + bone_name = p_name; + bone_id = -1; + + update_bone_id(); + reset_to_rest_position(); +} + +const String &PhysicalBone::get_bone_name() const { + + return bone_name; +} + +void PhysicalBone::set_mass(real_t p_mass) { + + ERR_FAIL_COND(p_mass <= 0); + mass = p_mass; + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_MASS, mass); +} + +real_t PhysicalBone::get_mass() const { + + return mass; +} + +void PhysicalBone::set_weight(real_t p_weight) { + + set_mass(p_weight / real_t(GLOBAL_DEF("physics/3d/default_gravity", 9.8))); +} + +real_t PhysicalBone::get_weight() const { + + return mass * real_t(GLOBAL_DEF("physics/3d/default_gravity", 9.8)); +} + +void PhysicalBone::set_friction(real_t p_friction) { + + ERR_FAIL_COND(p_friction < 0 || p_friction > 1); + + friction = p_friction; + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_FRICTION, friction); +} + +real_t PhysicalBone::get_friction() const { + + return friction; +} + +void PhysicalBone::set_bounce(real_t p_bounce) { + + ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); + + bounce = p_bounce; + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_BOUNCE, bounce); +} +real_t PhysicalBone::get_bounce() const { + + return bounce; +} + +void PhysicalBone::set_gravity_scale(real_t p_gravity_scale) { + + gravity_scale = p_gravity_scale; + PhysicsServer::get_singleton()->body_set_param(get_rid(), PhysicsServer::BODY_PARAM_GRAVITY_SCALE, gravity_scale); +} + +real_t PhysicalBone::get_gravity_scale() const { + + return gravity_scale; +} + +PhysicalBone::PhysicalBone() : + PhysicsBody(PhysicsServer::BODY_MODE_STATIC), +#ifdef TOOLS_ENABLED + gizmo_move_joint(false), +#endif + joint_data(NULL), + static_body(false), + simulate_physics(false), + _internal_static_body(false), + _internal_simulate_physics(false), + bone_id(-1), + parent_skeleton(NULL), + bone_name(""), + bounce(0), + mass(1), + friction(1), + gravity_scale(1) { + + set_static_body(static_body); + _reset_physics_simulation_state(); +} + +PhysicalBone::~PhysicalBone() { + memdelete(joint_data); +} + +void PhysicalBone::update_bone_id() { + if (!parent_skeleton) { + return; + } + + const int new_bone_id = parent_skeleton->find_bone(bone_name); + + if (new_bone_id != bone_id) { + if (-1 != bone_id) { + // Assert the unbind from old node + parent_skeleton->unbind_physical_bone_from_bone(bone_id); + parent_skeleton->unbind_child_node_from_bone(bone_id, this); + } + + bone_id = new_bone_id; + + parent_skeleton->bind_physical_bone_to_bone(bone_id, this); + + _fix_joint_offset(); + _internal_static_body = !static_body; // Force staticness reset + _reset_staticness_state(); + } +} + +void PhysicalBone::update_offset() { +#ifdef TOOLS_ENABLED + if (parent_skeleton) { + + Transform bone_transform(parent_skeleton->get_global_transform()); + if (-1 != bone_id) + bone_transform *= parent_skeleton->get_bone_global_pose(bone_id); + + if (gizmo_move_joint) { + bone_transform *= body_offset; + set_joint_offset(bone_transform.affine_inverse() * get_global_transform()); + } else { + set_body_offset(bone_transform.affine_inverse() * get_global_transform()); + } + } +#endif +} + +void PhysicalBone::reset_to_rest_position() { + if (parent_skeleton) { + if (-1 == bone_id) { + set_global_transform(parent_skeleton->get_global_transform() * body_offset); + } else { + set_global_transform(parent_skeleton->get_global_transform() * parent_skeleton->get_bone_global_pose(bone_id) * body_offset); + } + } +} + +void PhysicalBone::_reset_physics_simulation_state() { + if (simulate_physics && !static_body) { + _start_physics_simulation(); + } else { + _stop_physics_simulation(); + } + + _reset_staticness_state(); +} + +void PhysicalBone::_reset_staticness_state() { + + if (parent_skeleton && -1 != bone_id) { + if (static_body && simulate_physics) { // With this check I'm sure the position of this body is updated only when it's necessary + + if (_internal_static_body) { + return; + } + + parent_skeleton->bind_child_node_to_bone(bone_id, this); + _internal_static_body = true; + } else { + + if (!_internal_static_body) { + return; + } + + parent_skeleton->unbind_child_node_from_bone(bone_id, this); + _internal_static_body = false; + } + } +} + +void PhysicalBone::_start_physics_simulation() { + if (_internal_simulate_physics || !parent_skeleton) { + return; + } + reset_to_rest_position(); + PhysicsServer::get_singleton()->body_set_mode(get_rid(), PhysicsServer::BODY_MODE_RIGID); + PhysicsServer::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer()); + PhysicsServer::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask()); + PhysicsServer::get_singleton()->body_set_force_integration_callback(get_rid(), this, "_direct_state_changed"); + parent_skeleton->set_bone_ignore_animation(bone_id, true); + _internal_simulate_physics = true; +} + +void PhysicalBone::_stop_physics_simulation() { + if (!_internal_simulate_physics || !parent_skeleton) { + return; + } + PhysicsServer::get_singleton()->body_set_mode(get_rid(), PhysicsServer::BODY_MODE_STATIC); + PhysicsServer::get_singleton()->body_set_collision_layer(get_rid(), 0); + PhysicsServer::get_singleton()->body_set_collision_mask(get_rid(), 0); + PhysicsServer::get_singleton()->body_set_force_integration_callback(get_rid(), NULL, ""); + parent_skeleton->set_bone_ignore_animation(bone_id, false); + _internal_simulate_physics = false; +} diff --git a/scene/3d/physics_body.h b/scene/3d/physics_body.h index ffdc9ab309..0190dcbfc3 100644 --- a/scene/3d/physics_body.h +++ b/scene/3d/physics_body.h @@ -33,6 +33,7 @@ #include "scene/3d/collision_object.h" #include "servers/physics_server.h" +#include "skeleton.h" #include "vset.h" class PhysicsBody : public CollisionObject { @@ -302,7 +303,7 @@ public: void set_safe_margin(float p_margin); float get_safe_margin() const; - Vector3 move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction = Vector3(0, 0, 0), bool p_infinite_inertia = true, float p_slope_stop_min_velocity = 0.05, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45)); + Vector3 move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction = Vector3(0, 0, 0), float p_slope_stop_min_velocity = 0.05, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45), bool p_infinite_inertia = true); bool is_on_floor() const; bool is_on_wall() const; bool is_on_ceiling() const; @@ -342,4 +343,267 @@ public: KinematicCollision(); }; +class PhysicalBone : public PhysicsBody { + + GDCLASS(PhysicalBone, PhysicsBody); + +public: + enum JointType { + JOINT_TYPE_NONE, + JOINT_TYPE_PIN, + JOINT_TYPE_CONE, + JOINT_TYPE_HINGE, + JOINT_TYPE_SLIDER, + JOINT_TYPE_6DOF + }; + + struct JointData { + virtual JointType get_joint_type() { return JOINT_TYPE_NONE; } + + /// "j" is used to set the parameter inside the PhysicsServer + virtual bool _set(const StringName &p_name, const Variant &p_value, RID j = RID()); + virtual bool _get(const StringName &p_name, Variant &r_ret) const; + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + }; + + struct PinJointData : public JointData { + virtual JointType get_joint_type() { return JOINT_TYPE_PIN; } + + virtual bool _set(const StringName &p_name, const Variant &p_value, RID j = RID()); + virtual bool _get(const StringName &p_name, Variant &r_ret) const; + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + + real_t bias; + real_t damping; + real_t impulse_clamp; + + PinJointData() : + bias(0.3), + damping(1.), + impulse_clamp(0) {} + }; + + struct ConeJointData : public JointData { + virtual JointType get_joint_type() { return JOINT_TYPE_CONE; } + + virtual bool _set(const StringName &p_name, const Variant &p_value, RID j = RID()); + virtual bool _get(const StringName &p_name, Variant &r_ret) const; + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + + real_t swing_span; + real_t twist_span; + real_t bias; + real_t softness; + real_t relaxation; + + ConeJointData() : + swing_span(Math_PI * 0.25), + twist_span(Math_PI), + bias(0.3), + softness(0.8), + relaxation(1.) {} + }; + + struct HingeJointData : public JointData { + virtual JointType get_joint_type() { return JOINT_TYPE_HINGE; } + + virtual bool _set(const StringName &p_name, const Variant &p_value, RID j = RID()); + virtual bool _get(const StringName &p_name, Variant &r_ret) const; + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + + bool angular_limit_enabled; + real_t angular_limit_upper; + real_t angular_limit_lower; + real_t angular_limit_bias; + real_t angular_limit_softness; + real_t angular_limit_relaxation; + + HingeJointData() : + angular_limit_enabled(false), + angular_limit_upper(Math_PI * 0.5), + angular_limit_lower(-Math_PI * 0.5), + angular_limit_bias(0.3), + angular_limit_softness(0.9), + angular_limit_relaxation(1.) {} + }; + + struct SliderJointData : public JointData { + virtual JointType get_joint_type() { return JOINT_TYPE_SLIDER; } + + virtual bool _set(const StringName &p_name, const Variant &p_value, RID j = RID()); + virtual bool _get(const StringName &p_name, Variant &r_ret) const; + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + + real_t linear_limit_upper; + real_t linear_limit_lower; + real_t linear_limit_softness; + real_t linear_limit_restitution; + real_t linear_limit_damping; + real_t angular_limit_upper; + real_t angular_limit_lower; + real_t angular_limit_softness; + real_t angular_limit_restitution; + real_t angular_limit_damping; + + SliderJointData() : + linear_limit_upper(1.), + linear_limit_lower(-1.), + linear_limit_softness(1.), + linear_limit_restitution(0.7), + linear_limit_damping(1.), + angular_limit_upper(0), + angular_limit_lower(0), + angular_limit_softness(1.), + angular_limit_restitution(0.7), + angular_limit_damping(1.) {} + }; + + struct SixDOFJointData : public JointData { + struct SixDOFAxisData { + bool linear_limit_enabled; + real_t linear_limit_upper; + real_t linear_limit_lower; + real_t linear_limit_softness; + real_t linear_restitution; + real_t linear_damping; + bool angular_limit_enabled; + real_t angular_limit_upper; + real_t angular_limit_lower; + real_t angular_limit_softness; + real_t angular_restitution; + real_t angular_damping; + real_t erp; + + SixDOFAxisData() : + linear_limit_enabled(true), + linear_limit_upper(0), + linear_limit_lower(0), + linear_limit_softness(0.7), + linear_restitution(0.5), + linear_damping(1.), + angular_limit_enabled(true), + angular_limit_upper(0), + angular_limit_lower(0), + angular_limit_softness(0.5), + angular_restitution(0), + angular_damping(1.), + erp(0.5) {} + }; + + virtual JointType get_joint_type() { return JOINT_TYPE_6DOF; } + + virtual bool _set(const StringName &p_name, const Variant &p_value, RID j = RID()); + virtual bool _get(const StringName &p_name, Variant &r_ret) const; + virtual void _get_property_list(List<PropertyInfo> *p_list) const; + + SixDOFAxisData axis_data[3]; + + SixDOFJointData() {} + }; + +private: +#ifdef TOOLS_ENABLED + // if false gizmo move body + bool gizmo_move_joint; +#endif + + JointData *joint_data; + Transform joint_offset; + RID joint; + + Skeleton *parent_skeleton; + Transform body_offset; + Transform body_offset_inverse; + bool static_body; + bool _internal_static_body; + bool simulate_physics; + bool _internal_simulate_physics; + int bone_id; + + String bone_name; + real_t bounce; + real_t mass; + real_t friction; + real_t gravity_scale; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + void _notification(int p_what); + void _direct_state_changed(Object *p_state); + + static void _bind_methods(); + +private: + static Skeleton *find_skeleton_parent(Node *p_parent); + void _fix_joint_offset(); + void _reload_joint(); + +public: + void _on_bone_parent_changed(); + void _set_gizmo_move_joint(bool p_move_joint); + +public: +#ifdef TOOLS_ENABLED + virtual Transform get_global_gizmo_transform() const; + virtual Transform get_local_gizmo_transform() const; +#endif + + const JointData *get_joint_data() const; + Skeleton *find_skeleton_parent(); + + int get_bone_id() const { return bone_id; } + + void set_joint_type(JointType p_joint_type); + JointType get_joint_type() const; + + void set_joint_offset(const Transform &p_offset); + const Transform &get_joint_offset() const; + + void set_body_offset(const Transform &p_offset); + const Transform &get_body_offset() const; + + void set_static_body(bool p_static); + bool is_static_body(); + + void set_simulate_physics(bool p_simulate); + bool get_simulate_physics(); + bool is_simulating_physics(); + + void set_bone_name(const String &p_name); + const String &get_bone_name() const; + + void set_mass(real_t p_mass); + real_t get_mass() const; + + void set_weight(real_t p_weight); + real_t get_weight() const; + + void set_friction(real_t p_friction); + real_t get_friction() const; + + void set_bounce(real_t p_bounce); + real_t get_bounce() const; + + void set_gravity_scale(real_t p_gravity_scale); + real_t get_gravity_scale() const; + + PhysicalBone(); + ~PhysicalBone(); + +private: + void update_bone_id(); + void update_offset(); + void reset_to_rest_position(); + + void _reset_physics_simulation_state(); + void _reset_staticness_state(); + + void _start_physics_simulation(); + void _stop_physics_simulation(); +}; + +VARIANT_ENUM_CAST(PhysicalBone::JointType); + #endif // PHYSICS_BODY__H diff --git a/scene/3d/physics_joint.cpp b/scene/3d/physics_joint.cpp index 2e9f1a241a..7988c43eab 100644 --- a/scene/3d/physics_joint.cpp +++ b/scene/3d/physics_joint.cpp @@ -154,8 +154,8 @@ void Joint::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint::set_exclude_nodes_from_collision); ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint::get_exclude_nodes_from_collision); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_a"), "set_node_a", "get_node_a"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_b"), "set_node_b", "get_node_b"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject"), "set_node_a", "get_node_a"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject"), "set_node_b", "get_node_b"); ADD_PROPERTY(PropertyInfo(Variant::INT, "solver/priority", PROPERTY_HINT_RANGE, "1,8,1"), "set_solver_priority", "get_solver_priority"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision/exclude_nodes"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision"); @@ -260,7 +260,7 @@ void HingeJoint::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_lower_limit", "lower_limit"), &HingeJoint::_set_lower_limit); ClassDB::bind_method(D_METHOD("_get_lower_limit"), &HingeJoint::_get_lower_limit); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "params/bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01"), "set_param", "get_param", PARAM_BIAS); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "params/bias", PROPERTY_HINT_RANGE, "0.00,0.99,0.01"), "set_param", "get_param", PARAM_BIAS); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit/enable"), "set_flag", "get_flag", FLAG_USE_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit/upper", PROPERTY_HINT_RANGE, "-180,180,0.1"), "_set_upper_limit", "_get_upper_limit"); @@ -270,7 +270,7 @@ void HingeJoint::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_limit/relaxation", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param", "get_param", PARAM_LIMIT_RELAXATION); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "motor/enable"), "set_flag", "get_flag", FLAG_ENABLE_MOTOR); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/target_velocity", PROPERTY_HINT_RANGE, "0.01,4096,0.01"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/target_velocity", PROPERTY_HINT_RANGE, "-200,200,0.01,or_greater,or_lesser"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/max_impulse", PROPERTY_HINT_RANGE, "0.01,1024,0.01"), "set_param", "get_param", PARAM_MOTOR_MAX_IMPULSE); BIND_ENUM_CONSTANT(PARAM_BIAS); @@ -716,6 +716,9 @@ void Generic6DOFJoint::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_x/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_x/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_x/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_x", "get_param_x", PARAM_LINEAR_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_LINEAR_MOTOR); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_motor_x/target_velocity"), "set_param_x", "get_param_x", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_motor_x/force_limit"), "set_param_x", "get_param_x", PARAM_LINEAR_MOTOR_FORCE_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_x/enabled"), "set_flag_x", "get_flag_x", FLAG_ENABLE_ANGULAR_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit_x/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_hi_limit_x", "_get_angular_hi_limit_x"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit_x/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_lo_limit_x", "_get_angular_lo_limit_x"); @@ -734,6 +737,9 @@ void Generic6DOFJoint::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_y/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_y/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_y/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_y", "get_param_y", PARAM_LINEAR_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_LINEAR_MOTOR); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_motor_y/target_velocity"), "set_param_y", "get_param_y", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_motor_y/force_limit"), "set_param_y", "get_param_y", PARAM_LINEAR_MOTOR_FORCE_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_y/enabled"), "set_flag_y", "get_flag_y", FLAG_ENABLE_ANGULAR_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit_y/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_hi_limit_y", "_get_angular_hi_limit_y"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit_y/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_lo_limit_y", "_get_angular_lo_limit_y"); @@ -752,6 +758,9 @@ void Generic6DOFJoint::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_z/softness", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_LIMIT_SOFTNESS); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_z/restitution", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_RESTITUTION); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_limit_z/damping", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param_z", "get_param_z", PARAM_LINEAR_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "linear_motor_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_LINEAR_MOTOR); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_motor_z/target_velocity"), "set_param_z", "get_param_z", PARAM_LINEAR_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "linear_motor_z/force_limit"), "set_param_z", "get_param_z", PARAM_LINEAR_MOTOR_FORCE_LIMIT); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit_z/enabled"), "set_flag_z", "get_flag_z", FLAG_ENABLE_ANGULAR_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit_z/upper_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_hi_limit_z", "_get_angular_hi_limit_z"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit_z/lower_angle", PROPERTY_HINT_RANGE, "-180,180,0.01"), "_set_angular_lo_limit_z", "_get_angular_lo_limit_z"); @@ -769,6 +778,8 @@ void Generic6DOFJoint::_bind_methods() { BIND_ENUM_CONSTANT(PARAM_LINEAR_LIMIT_SOFTNESS); BIND_ENUM_CONSTANT(PARAM_LINEAR_RESTITUTION); BIND_ENUM_CONSTANT(PARAM_LINEAR_DAMPING); + BIND_ENUM_CONSTANT(PARAM_LINEAR_MOTOR_TARGET_VELOCITY); + BIND_ENUM_CONSTANT(PARAM_LINEAR_MOTOR_FORCE_LIMIT); BIND_ENUM_CONSTANT(PARAM_ANGULAR_LOWER_LIMIT); BIND_ENUM_CONSTANT(PARAM_ANGULAR_UPPER_LIMIT); BIND_ENUM_CONSTANT(PARAM_ANGULAR_LIMIT_SOFTNESS); @@ -783,6 +794,7 @@ void Generic6DOFJoint::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_ENABLE_LINEAR_LIMIT); BIND_ENUM_CONSTANT(FLAG_ENABLE_ANGULAR_LIMIT); BIND_ENUM_CONSTANT(FLAG_ENABLE_MOTOR); + BIND_ENUM_CONSTANT(FLAG_ENABLE_LINEAR_MOTOR); BIND_ENUM_CONSTANT(FLAG_MAX); } @@ -912,6 +924,8 @@ Generic6DOFJoint::Generic6DOFJoint() { set_param_x(PARAM_LINEAR_LIMIT_SOFTNESS, 0.7); set_param_x(PARAM_LINEAR_RESTITUTION, 0.5); set_param_x(PARAM_LINEAR_DAMPING, 1.0); + set_param_x(PARAM_LINEAR_MOTOR_TARGET_VELOCITY, 0); + set_param_x(PARAM_LINEAR_MOTOR_FORCE_LIMIT, 0); set_param_x(PARAM_ANGULAR_LOWER_LIMIT, 0); set_param_x(PARAM_ANGULAR_UPPER_LIMIT, 0); set_param_x(PARAM_ANGULAR_LIMIT_SOFTNESS, 0.5f); @@ -925,12 +939,15 @@ Generic6DOFJoint::Generic6DOFJoint() { set_flag_x(FLAG_ENABLE_ANGULAR_LIMIT, true); set_flag_x(FLAG_ENABLE_LINEAR_LIMIT, true); set_flag_x(FLAG_ENABLE_MOTOR, false); + set_flag_x(FLAG_ENABLE_LINEAR_MOTOR, false); set_param_y(PARAM_LINEAR_LOWER_LIMIT, 0); set_param_y(PARAM_LINEAR_UPPER_LIMIT, 0); set_param_y(PARAM_LINEAR_LIMIT_SOFTNESS, 0.7); set_param_y(PARAM_LINEAR_RESTITUTION, 0.5); set_param_y(PARAM_LINEAR_DAMPING, 1.0); + set_param_y(PARAM_LINEAR_MOTOR_TARGET_VELOCITY, 0); + set_param_y(PARAM_LINEAR_MOTOR_FORCE_LIMIT, 0); set_param_y(PARAM_ANGULAR_LOWER_LIMIT, 0); set_param_y(PARAM_ANGULAR_UPPER_LIMIT, 0); set_param_y(PARAM_ANGULAR_LIMIT_SOFTNESS, 0.5f); @@ -944,12 +961,15 @@ Generic6DOFJoint::Generic6DOFJoint() { set_flag_y(FLAG_ENABLE_ANGULAR_LIMIT, true); set_flag_y(FLAG_ENABLE_LINEAR_LIMIT, true); set_flag_y(FLAG_ENABLE_MOTOR, false); + set_flag_y(FLAG_ENABLE_LINEAR_MOTOR, false); set_param_z(PARAM_LINEAR_LOWER_LIMIT, 0); set_param_z(PARAM_LINEAR_UPPER_LIMIT, 0); set_param_z(PARAM_LINEAR_LIMIT_SOFTNESS, 0.7); set_param_z(PARAM_LINEAR_RESTITUTION, 0.5); set_param_z(PARAM_LINEAR_DAMPING, 1.0); + set_param_z(PARAM_LINEAR_MOTOR_TARGET_VELOCITY, 0); + set_param_z(PARAM_LINEAR_MOTOR_FORCE_LIMIT, 0); set_param_z(PARAM_ANGULAR_LOWER_LIMIT, 0); set_param_z(PARAM_ANGULAR_UPPER_LIMIT, 0); set_param_z(PARAM_ANGULAR_LIMIT_SOFTNESS, 0.5f); @@ -963,4 +983,5 @@ Generic6DOFJoint::Generic6DOFJoint() { set_flag_z(FLAG_ENABLE_ANGULAR_LIMIT, true); set_flag_z(FLAG_ENABLE_LINEAR_LIMIT, true); set_flag_z(FLAG_ENABLE_MOTOR, false); + set_flag_z(FLAG_ENABLE_LINEAR_MOTOR, false); } diff --git a/scene/3d/physics_joint.h b/scene/3d/physics_joint.h index 000109ac55..37870d6f30 100644 --- a/scene/3d/physics_joint.h +++ b/scene/3d/physics_joint.h @@ -249,6 +249,8 @@ public: PARAM_LINEAR_LIMIT_SOFTNESS = PhysicsServer::G6DOF_JOINT_LINEAR_LIMIT_SOFTNESS, PARAM_LINEAR_RESTITUTION = PhysicsServer::G6DOF_JOINT_LINEAR_RESTITUTION, PARAM_LINEAR_DAMPING = PhysicsServer::G6DOF_JOINT_LINEAR_DAMPING, + PARAM_LINEAR_MOTOR_TARGET_VELOCITY = PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY, + PARAM_LINEAR_MOTOR_FORCE_LIMIT = PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT, PARAM_ANGULAR_LOWER_LIMIT = PhysicsServer::G6DOF_JOINT_ANGULAR_LOWER_LIMIT, PARAM_ANGULAR_UPPER_LIMIT = PhysicsServer::G6DOF_JOINT_ANGULAR_UPPER_LIMIT, PARAM_ANGULAR_LIMIT_SOFTNESS = PhysicsServer::G6DOF_JOINT_ANGULAR_LIMIT_SOFTNESS, @@ -265,6 +267,7 @@ public: FLAG_ENABLE_LINEAR_LIMIT = PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_LIMIT, FLAG_ENABLE_ANGULAR_LIMIT = PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT, FLAG_ENABLE_MOTOR = PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_MOTOR, + FLAG_ENABLE_LINEAR_MOTOR = PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR, FLAG_MAX = PhysicsServer::G6DOF_JOINT_FLAG_MAX }; diff --git a/scene/3d/ray_cast.cpp b/scene/3d/ray_cast.cpp index dd5ae8a999..7f83e2c3ea 100644 --- a/scene/3d/ray_cast.cpp +++ b/scene/3d/ray_cast.cpp @@ -103,7 +103,7 @@ void RayCast::set_enabled(bool p_enabled) { enabled = p_enabled; if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) - set_physics_process(p_enabled); + set_physics_process_internal(p_enabled); if (!p_enabled) collided = false; @@ -150,12 +150,12 @@ void RayCast::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { if (enabled && !Engine::get_singleton()->is_editor_hint()) { - set_physics_process(true); + set_physics_process_internal(true); if (get_tree()->is_debugging_collisions_hint()) _update_debug_shape(); } else - set_physics_process(false); + set_physics_process_internal(false); if (Object::cast_to<CollisionObject>(get_parent())) { if (exclude_parent_body) @@ -168,14 +168,14 @@ void RayCast::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { if (enabled) { - set_physics_process(false); + set_physics_process_internal(false); } if (debug_shape) _clear_debug_shape(); } break; - case NOTIFICATION_PHYSICS_PROCESS: { + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (!enabled) break; diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index 2178da02b5..4d50945062 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -240,9 +240,9 @@ void ReflectionProbe::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Once,Always"), "set_update_mode", "get_update_mode"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "intensity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_intensity", "get_intensity"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_RANGE, "0,16384,0.1"), "set_max_distance", "get_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "extents"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "origin_offset"), "set_origin_offset", "get_origin_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_EXP_RANGE, "0,16384,0.1,or_greater"), "set_max_distance", "get_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset"), "set_origin_offset", "get_origin_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "box_projection"), "set_enable_box_projection", "is_box_projection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_shadows"), "set_enable_shadows", "are_shadows_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); diff --git a/scene/3d/remote_transform.cpp b/scene/3d/remote_transform.cpp index afb85f7314..2156e24cd0 100644 --- a/scene/3d/remote_transform.cpp +++ b/scene/3d/remote_transform.cpp @@ -194,7 +194,7 @@ void RemoteTransform::_bind_methods() { ClassDB::bind_method(D_METHOD("set_update_scale", "update_remote_scale"), &RemoteTransform::set_update_scale); ClassDB::bind_method(D_METHOD("get_update_scale"), &RemoteTransform::get_update_scale); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path"), "set_remote_node", "get_remote_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Spatial"), "set_remote_node", "get_remote_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_coordinates"), "set_use_global_coordinates", "get_use_global_coordinates"); ADD_GROUP("Update", "update_"); diff --git a/scene/3d/scenario_fx.cpp b/scene/3d/scenario_fx.cpp index 02768ac91f..26cbfc0b11 100644 --- a/scene/3d/scenario_fx.cpp +++ b/scene/3d/scenario_fx.cpp @@ -79,7 +79,11 @@ Ref<Environment> WorldEnvironment::get_environment() const { String WorldEnvironment::get_configuration_warning() const { - if (/*!is_visible_in_tree() ||*/ !is_inside_tree() || !environment.is_valid()) + if (!environment.is_valid()) { + return TTR("WorldEnvironment needs an Environment resource."); + } + + if (/*!is_visible_in_tree() ||*/ !is_inside_tree()) return String(); List<Node *> nodes; @@ -89,6 +93,11 @@ String WorldEnvironment::get_configuration_warning() const { return TTR("Only one WorldEnvironment is allowed per scene (or set of instanced scenes)."); } + // Commenting this warning for now, I think it makes no sense. If anyone can figure out what its supposed to do, feedback welcome. Else it should be deprecated. + //if (environment.is_valid() && get_viewport() && !get_viewport()->get_camera() && environment->get_background() != Environment::BG_CANVAS) { + // return TTR("This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set this environment's Background Mode to Canvas (for 2D scenes)."); + //} + return String(); } diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index d3a13c741e..8d91b6f09f 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -33,6 +33,7 @@ #include "message_queue.h" #include "core/project_settings.h" +#include "scene/3d/physics_body.h" #include "scene/resources/surface_tool.h" bool Skeleton::_set(const StringName &p_path, const Variant &p_value) { @@ -329,7 +330,7 @@ void Skeleton::add_bone(const String &p_name) { _make_dirty(); update_gizmo(); } -int Skeleton::find_bone(String p_name) const { +int Skeleton::find_bone(const String &p_name) const { for (int i = 0; i < bones.size(); i++) { @@ -346,6 +347,19 @@ String Skeleton::get_bone_name(int p_bone) const { return bones[p_bone].name; } +bool Skeleton::is_bone_parent_of(int p_bone, int p_parent_bone_id) const { + + int parent_of_bone = get_bone_parent(p_bone); + + if (-1 == parent_of_bone) + return false; + + if (parent_of_bone == p_parent_bone_id) + return true; + + return is_bone_parent_of(parent_of_bone, p_parent_bone_id); +} + int Skeleton::get_bone_count() const { return bones.size(); @@ -377,6 +391,17 @@ void Skeleton::unparent_bone_and_rest(int p_bone) { _make_dirty(); } +void Skeleton::set_bone_ignore_animation(int p_bone, bool p_ignore) { + ERR_FAIL_INDEX(p_bone, bones.size()); + bones[p_bone].ignore_animation = p_ignore; +} + +bool Skeleton::is_bone_ignore_animation(int p_bone) const { + + ERR_FAIL_INDEX_V(p_bone, bones.size(), false); + return bones[p_bone].ignore_animation; +} + void Skeleton::set_bone_disable_rest(int p_bone, bool p_disable) { ERR_FAIL_INDEX(p_bone, bones.size()); @@ -522,6 +547,154 @@ void Skeleton::localize_rests() { } } +#ifndef _3D_DISABLED + +void Skeleton::bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone) { + ERR_FAIL_INDEX(p_bone, bones.size()); + ERR_FAIL_COND(bones[p_bone].physical_bone); + ERR_FAIL_COND(!p_physical_bone); + bones[p_bone].physical_bone = p_physical_bone; + + _rebuild_physical_bones_cache(); +} + +void Skeleton::unbind_physical_bone_from_bone(int p_bone) { + ERR_FAIL_INDEX(p_bone, bones.size()); + bones[p_bone].physical_bone = NULL; + + _rebuild_physical_bones_cache(); +} + +PhysicalBone *Skeleton::get_physical_bone(int p_bone) { + ERR_FAIL_INDEX_V(p_bone, bones.size(), NULL); + + return bones[p_bone].physical_bone; +} + +PhysicalBone *Skeleton::get_physical_bone_parent(int p_bone) { + ERR_FAIL_INDEX_V(p_bone, bones.size(), NULL); + + if (bones[p_bone].cache_parent_physical_bone) { + return bones[p_bone].cache_parent_physical_bone; + } + + return _get_physical_bone_parent(p_bone); +} + +PhysicalBone *Skeleton::_get_physical_bone_parent(int p_bone) { + ERR_FAIL_INDEX_V(p_bone, bones.size(), NULL); + + const int parent_bone = bones[p_bone].parent; + if (0 > parent_bone) { + return NULL; + } + + PhysicalBone *pb = bones[parent_bone].physical_bone; + if (pb) { + return pb; + } else { + return get_physical_bone_parent(parent_bone); + } +} + +void Skeleton::_rebuild_physical_bones_cache() { + const int b_size = bones.size(); + for (int i = 0; i < b_size; ++i) { + bones[i].cache_parent_physical_bone = _get_physical_bone_parent(i); + if (bones[i].physical_bone) + bones[i].physical_bone->_on_bone_parent_changed(); + } +} + +void _pb_stop_simulation(Node *p_node) { + + for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { + _pb_stop_simulation(p_node->get_child(i)); + } + + PhysicalBone *pb = Object::cast_to<PhysicalBone>(p_node); + if (pb) { + pb->set_simulate_physics(false); + pb->set_static_body(false); + } +} + +void Skeleton::physical_bones_stop_simulation() { + _pb_stop_simulation(this); +} + +void _pb_start_simulation(const Skeleton *p_skeleton, Node *p_node, const Vector<int> &p_sim_bones) { + + for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { + _pb_start_simulation(p_skeleton, p_node->get_child(i), p_sim_bones); + } + + PhysicalBone *pb = Object::cast_to<PhysicalBone>(p_node); + if (pb) { + bool sim = false; + for (int i = p_sim_bones.size() - 1; 0 <= i; --i) { + if (p_sim_bones[i] == pb->get_bone_id() || p_skeleton->is_bone_parent_of(pb->get_bone_id(), p_sim_bones[i])) { + sim = true; + break; + } + } + + pb->set_simulate_physics(true); + if (sim) { + pb->set_static_body(false); + } else { + pb->set_static_body(true); + } + } +} + +void Skeleton::physical_bones_start_simulation_on(const Array &p_bones) { + + Vector<int> sim_bones; + if (p_bones.size() <= 0) { + sim_bones.push_back(0); // if no bones is specified, activate ragdoll on full body + } else { + sim_bones.resize(p_bones.size()); + int c = 0; + for (int i = sim_bones.size() - 1; 0 <= i; --i) { + if (Variant::STRING == p_bones.get(i).get_type()) { + int bone_id = find_bone(p_bones.get(i)); + if (bone_id != -1) + sim_bones[c++] = bone_id; + } + } + sim_bones.resize(c); + } + + _pb_start_simulation(this, this, sim_bones); +} + +void _physical_bones_add_remove_collision_exception(bool p_add, Node *p_node, RID p_exception) { + + for (int i = p_node->get_child_count() - 1; 0 <= i; --i) { + _physical_bones_add_remove_collision_exception(p_add, p_node->get_child(i), p_exception); + } + + CollisionObject *co = Object::cast_to<CollisionObject>(p_node); + if (co) { + if (p_add) { + PhysicsServer::get_singleton()->body_add_collision_exception(co->get_rid(), p_exception); + } else { + PhysicsServer::get_singleton()->body_remove_collision_exception(co->get_rid(), p_exception); + } + } +} + +void Skeleton::physical_bones_add_collision_exception(RID p_exception) { + _physical_bones_add_remove_collision_exception(true, this, p_exception); +} + +void Skeleton::physical_bones_remove_collision_exception(RID p_exception) { + _physical_bones_add_remove_collision_exception(false, this, p_exception); +} + +#endif // _3D_DISABLED + void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton::add_bone); @@ -558,6 +731,15 @@ void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_transform", "bone_idx"), &Skeleton::get_bone_transform); +#ifndef _3D_DISABLED + + ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &Skeleton::physical_bones_stop_simulation); + ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &Skeleton::physical_bones_start_simulation_on, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton::physical_bones_add_collision_exception); + ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton::physical_bones_remove_collision_exception); + +#endif // _3D_DISABLED + BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } @@ -570,6 +752,5 @@ Skeleton::Skeleton() { } Skeleton::~Skeleton() { - VisualServer::get_singleton()->free(skeleton); } diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index d693670055..9672acb57a 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -37,6 +37,11 @@ /** @author Juan Linietsky <reduzio@gmail.com> */ + +#ifndef _3D_DISABLED +class PhysicalBone; +#endif // _3D_DISABLED + class Skeleton : public Spatial { GDCLASS(Skeleton, Spatial); @@ -48,6 +53,8 @@ class Skeleton : public Spatial { bool enabled; int parent; + bool ignore_animation; + bool disable_rest; Transform rest; Transform rest_global_inverse; @@ -60,13 +67,23 @@ class Skeleton : public Spatial { Transform transform_final; +#ifndef _3D_DISABLED + PhysicalBone *physical_bone; + PhysicalBone *cache_parent_physical_bone; +#endif // _3D_DISABLED + List<uint32_t> nodes_bound; Bone() { parent = -1; enabled = true; + ignore_animation = false; custom_pose_enable = false; disable_rest = false; +#ifndef _3D_DISABLED + physical_bone = NULL; + cache_parent_physical_bone = NULL; +#endif // _3D_DISABLED } }; @@ -110,14 +127,19 @@ public: // skeleton creation api void add_bone(const String &p_name); - int find_bone(String p_name) const; + int find_bone(const String &p_name) const; String get_bone_name(int p_bone) const; + bool is_bone_parent_of(int p_bone_id, int p_parent_bone_id) const; + void set_bone_parent(int p_bone, int p_parent); int get_bone_parent(int p_bone) const; void unparent_bone_and_rest(int p_bone); + void set_bone_ignore_animation(int p_bone, bool p_ignore); + bool is_bone_ignore_animation(int p_bone) const; + void set_bone_disable_rest(int p_bone, bool p_disable); bool is_bone_rest_disabled(int p_bone) const; @@ -149,6 +171,28 @@ public: void localize_rests(); // used for loaders and tools +#ifndef _3D_DISABLED + // Physical bone API + + void bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone); + void unbind_physical_bone_from_bone(int p_bone); + + PhysicalBone *get_physical_bone(int p_bone); + PhysicalBone *get_physical_bone_parent(int p_bone); + +private: + /// This is a slow API os it's cached + PhysicalBone *_get_physical_bone_parent(int p_bone); + void _rebuild_physical_bones_cache(); + +public: + void physical_bones_stop_simulation(); + void physical_bones_start_simulation_on(const Array &p_bones); + void physical_bones_add_collision_exception(RID p_exception); + void physical_bones_remove_collision_exception(RID p_exception); +#endif // _3D_DISABLED + +public: Skeleton(); ~Skeleton(); }; diff --git a/scene/3d/spatial.cpp b/scene/3d/spatial.cpp index 721641e09b..748aa8aad4 100644 --- a/scene/3d/spatial.cpp +++ b/scene/3d/spatial.cpp @@ -85,9 +85,7 @@ void Spatial::_notify_dirty() { } void Spatial::_update_local_transform() const { - data.local_transform.basis = Basis(); - data.local_transform.basis.scale(data.scale); - data.local_transform.basis.rotate(data.rotation); + data.local_transform.basis.set_euler_scale(data.rotation, data.scale); data.dirty &= ~DIRTY_LOCAL; } @@ -188,7 +186,9 @@ void Spatial::_notification(int p_what) { if (data.gizmo.is_valid()) { data.gizmo->create(); if (data.gizmo->can_draw()) { - data.gizmo->redraw(); + if (is_visible_in_tree()) { + data.gizmo->redraw(); + } } data.gizmo->transform(); } @@ -286,6 +286,16 @@ Transform Spatial::get_global_transform() const { return data.global_transform; } +#ifdef TOOLS_ENABLED +Transform Spatial::get_global_gizmo_transform() const { + return get_global_transform(); +} + +Transform Spatial::get_local_gizmo_transform() const { + return get_transform(); +} +#endif + Spatial *Spatial::get_parent_spatial() const { return data.parent; @@ -409,7 +419,9 @@ void Spatial::set_gizmo(const Ref<SpatialGizmo> &p_gizmo) { data.gizmo->create(); if (data.gizmo->can_draw()) { - data.gizmo->redraw(); + if (is_visible_in_tree()) { + data.gizmo->redraw(); + } } data.gizmo->transform(); } @@ -428,10 +440,9 @@ Ref<SpatialGizmo> Spatial::get_gizmo() const { #endif } -#ifdef TOOLS_ENABLED - void Spatial::_update_gizmo() { +#ifdef TOOLS_ENABLED if (!is_inside_world()) return; data.gizmo_dirty = false; @@ -443,8 +454,10 @@ void Spatial::_update_gizmo() { data.gizmo->clear(); } } +#endif } +#ifdef TOOLS_ENABLED void Spatial::set_disable_gizmo(bool p_enabled) { data.gizmo_disabled = p_enabled; @@ -724,9 +737,7 @@ void Spatial::_bind_methods() { ClassDB::bind_method(D_METHOD("is_set_as_toplevel"), &Spatial::is_set_as_toplevel); ClassDB::bind_method(D_METHOD("get_world"), &Spatial::get_world); -#ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("_update_gizmo"), &Spatial::_update_gizmo); -#endif ClassDB::bind_method(D_METHOD("update_gizmo"), &Spatial::update_gizmo); ClassDB::bind_method(D_METHOD("set_gizmo", "gizmo"), &Spatial::set_gizmo); @@ -788,9 +799,7 @@ void Spatial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale"); ADD_GROUP("Visibility", ""); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); -#ifdef TOOLS_ENABLED ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "SpatialGizmo", 0), "set_gizmo", "get_gizmo"); -#endif ADD_SIGNAL(MethodInfo("visibility_changed")); } diff --git a/scene/3d/spatial.h b/scene/3d/spatial.h index 518bba9a51..a43bed3e4a 100644 --- a/scene/3d/spatial.h +++ b/scene/3d/spatial.h @@ -100,10 +100,8 @@ class Spatial : public Node { #endif } data; -#ifdef TOOLS_ENABLED void _update_gizmo(); -#endif void _notify_dirty(); void _propagate_transform_changed(Spatial *p_origin); @@ -147,6 +145,11 @@ public: Transform get_transform() const; Transform get_global_transform() const; +#ifdef TOOLS_ENABLED + virtual Transform get_global_gizmo_transform() const; + virtual Transform get_local_gizmo_transform() const; +#endif + void set_as_toplevel(bool p_enabled); bool is_set_as_toplevel() const; diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 232855c978..036a748c83 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -185,6 +185,9 @@ void SpriteBase3D::_queue_update() { if (pending_update) return; + triangle_mesh.unref(); + update_gizmo(); + pending_update = true; call_deferred(SceneStringNames::get_singleton()->_im_update); } @@ -198,6 +201,66 @@ PoolVector<Face3> SpriteBase3D::get_faces(uint32_t p_usage_flags) const { return PoolVector<Face3>(); } +Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const { + if (triangle_mesh.is_valid()) + return triangle_mesh; + + PoolVector<Vector3> faces; + faces.resize(6); + PoolVector<Vector3>::Write facesw = faces.write(); + + Rect2 final_rect = get_item_rect(); + + if (final_rect.size.x == 0 || final_rect.size.y == 0) + return Ref<TriangleMesh>(); + + float 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, + + }; + + 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++) { + if (axis == Vector3::AXIS_Y) { + vertices[i].y = -vertices[i].y; + } else if (axis == Vector3::AXIS_X) { + vertices[i].x = -vertices[i].x; + } + } + } + + static const int indices[6] = { + 0, 1, 2, + 0, 2, 3 + }; + + for (int j = 0; j < 6; j++) { + int i = indices[j]; + Vector3 vtx; + vtx[x_axis] = vertices[i][0]; + vtx[y_axis] = vertices[i][1]; + facesw[j] = vtx; + } + + facesw = PoolVector<Vector3>::Write(); + + triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); + triangle_mesh->create(faces); + + return triangle_mesh; +} + void SpriteBase3D::set_draw_flag(DrawFlags p_flag, bool p_enable) { ERR_FAIL_INDEX(p_flag, FLAG_MAX); @@ -255,6 +318,7 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_alpha_cut_mode"), &SpriteBase3D::get_alpha_cut_mode); 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); @@ -366,6 +430,16 @@ void Sprite3D::_draw() { final_rect.position * pixel_size, }; + + Vector2 src_tsize = Vector2(texture->get_width(), texture->get_height()); + + // Properly setup UVs for impostor textures (AtlasTexture). + Ref<AtlasTexture> atlas_tex = texture; + if (atlas_tex != NULL) { + 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 / tsize, (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize, @@ -656,6 +730,16 @@ void AnimatedSprite3D::_draw() { final_rect.position * pixel_size, }; + + Vector2 src_tsize = Vector2(texture->get_width(), texture->get_height()); + + // Properly setup UVs for impostor textures (AtlasTexture). + Ref<AtlasTexture> atlas_tex = texture; + if (atlas_tex != NULL) { + 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 / tsize, (final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize, @@ -881,7 +965,7 @@ Rect2 AnimatedSprite3D::get_item_rect() const { return Rect2(0, 0, 1, 1); Size2i s = t->get_size(); - Point2 ofs = offset; + Point2 ofs = get_offset(); if (centered) ofs -= s / 2; diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index 23e1d96b4b..a4705a8970 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -38,6 +38,8 @@ class SpriteBase3D : public GeometryInstance { GDCLASS(SpriteBase3D, GeometryInstance); + mutable Ref<TriangleMesh> triangle_mesh; //cached + public: enum DrawFlags { FLAG_TRANSPARENT, @@ -133,6 +135,7 @@ public: virtual AABB get_aabb() const; virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + Ref<TriangleMesh> generate_triangle_mesh() const; SpriteBase3D(); ~SpriteBase3D(); @@ -192,7 +195,6 @@ class AnimatedSprite3D : public SpriteBase3D { int frame; bool centered; - Point2 offset; float timeout; diff --git a/scene/3d/vehicle_body.cpp b/scene/3d/vehicle_body.cpp index ed3bde9504..385956dc16 100644 --- a/scene/3d/vehicle_body.cpp +++ b/scene/3d/vehicle_body.cpp @@ -524,7 +524,7 @@ void VehicleBody::_update_suspension(PhysicsDirectBodyState *s) { //bilateral constraint between two dynamic objects void VehicleBody::_resolve_single_bilateral(PhysicsDirectBodyState *s, const Vector3 &pos1, - PhysicsBody *body2, const Vector3 &pos2, const Vector3 &normal, real_t &impulse) { + PhysicsBody *body2, const Vector3 &pos2, const Vector3 &normal, real_t &impulse, real_t p_rollInfluence) { real_t normalLenSqr = normal.length_squared(); //ERR_FAIL_COND( normalLenSqr < real_t(1.1)); @@ -582,8 +582,15 @@ void VehicleBody::_resolve_single_bilateral(PhysicsDirectBodyState *s, const Vec rel_vel = normal.dot(vel); - //TODO: move this into proper structure - real_t contactDamping = real_t(0.4); + // !BAS! We had this set to 0.4, in bullet its 0.2 + real_t contactDamping = real_t(0.2); + + if (p_rollInfluence > 0.0) { + // !BAS! But seeing we apply this frame by frame, makes more sense to me to make this time based + // keeping in mind our anti roll factor if it is set + contactDamping = s->get_step() / p_rollInfluence; + } + #define ONLY_USE_LINEAR_MASS #ifdef ONLY_USE_LINEAR_MASS real_t massTerm = real_t(1.) / ((1.0 / mass) + b2invmass); @@ -668,13 +675,8 @@ void VehicleBody::_update_friction(PhysicsDirectBodyState *s) { m_forwardImpulse.resize(numWheel); m_sideImpulse.resize(numWheel); - int numWheelsOnGround = 0; - //collapse all those loops into one! for (int i = 0; i < wheels.size(); i++) { - VehicleWheel &wheelInfo = *wheels[i]; - if (wheelInfo.m_raycastInfo.m_isInContact) - numWheelsOnGround++; m_sideImpulse[i] = real_t(0.); m_forwardImpulse[i] = real_t(0.); } @@ -704,7 +706,7 @@ void VehicleBody::_update_friction(PhysicsDirectBodyState *s) { _resolve_single_bilateral(s, wheelInfo.m_raycastInfo.m_contactPointWS, wheelInfo.m_raycastInfo.m_groundObject, wheelInfo.m_raycastInfo.m_contactPointWS, - m_axle[i], m_sideImpulse[i]); + m_axle[i], m_sideImpulse[i], wheelInfo.m_rollInfluence); m_sideImpulse[i] *= sideFrictionStiffness2; } @@ -925,7 +927,7 @@ void VehicleBody::_bind_methods() { ClassDB::bind_method(D_METHOD("get_steering"), &VehicleBody::get_steering); ADD_GROUP("Motion", ""); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "engine_force", PROPERTY_HINT_RANGE, "0.00,1024.0,0.01"), "set_engine_force", "get_engine_force"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "engine_force", PROPERTY_HINT_RANGE, "0.00,1024.0,0.01,or_greater"), "set_engine_force", "get_engine_force"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "brake", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_brake", "get_brake"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01"), "set_steering", "get_steering"); } diff --git a/scene/3d/vehicle_body.h b/scene/3d/vehicle_body.h index 7810a42e8a..1ac3693cc4 100644 --- a/scene/3d/vehicle_body.h +++ b/scene/3d/vehicle_body.h @@ -168,7 +168,7 @@ class VehicleBody : public RigidBody { btVehicleWheelContactPoint(PhysicsDirectBodyState *s, PhysicsBody *body1, const Vector3 &frictionPosWorld, const Vector3 &frictionDirectionWorld, real_t maxImpulse); }; - void _resolve_single_bilateral(PhysicsDirectBodyState *s, const Vector3 &pos1, PhysicsBody *body2, const Vector3 &pos2, const Vector3 &normal, real_t &impulse); + void _resolve_single_bilateral(PhysicsDirectBodyState *s, const Vector3 &pos1, PhysicsBody *body2, const Vector3 &pos2, const Vector3 &normal, real_t &impulse, real_t p_rollInfluence); real_t _calc_rolling_friction(btVehicleWheelContactPoint &contactPoint); void _update_friction(PhysicsDirectBodyState *s); diff --git a/scene/3d/voxel_light_baker.cpp b/scene/3d/voxel_light_baker.cpp index d389b69ef3..670df5cc7f 100644 --- a/scene/3d/voxel_light_baker.cpp +++ b/scene/3d/voxel_light_baker.cpp @@ -2316,13 +2316,10 @@ Ref<MultiMesh> VoxelLightBaker::create_debug_multimesh(DebugMode p_mode) { PoolVector<Vector3> vertices; PoolVector<Color> colors; - - int vtx_idx = 0; #define ADD_VTX(m_idx) \ ; \ vertices.push_back(face_points[m_idx]); \ - colors.push_back(Color(1, 1, 1, 1)); \ - vtx_idx++; + colors.push_back(Color(1, 1, 1, 1)); for (int i = 0; i < 6; i++) { @@ -2338,9 +2335,9 @@ Ref<MultiMesh> VoxelLightBaker::create_debug_multimesh(DebugMode p_mode) { for (int k = 0; k < 3; k++) { if (i < 3) - face_points[j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + face_points[j][(i + k) % 3] = v[k]; else - face_points[3 - j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + face_points[3 - j][(i + k) % 3] = -v[k]; } } diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp new file mode 100644 index 0000000000..d3d2870c3f --- /dev/null +++ b/scene/animation/animation_blend_space_1d.cpp @@ -0,0 +1,294 @@ +#include "animation_blend_space_1d.h" + +void AnimationNodeBlendSpace1D::set_tree(AnimationTree *p_player) { + + AnimationRootNode::set_tree(p_player); + + for(int i=0;i<blend_points_used;i++) { + blend_points[i].node->set_tree(p_player); + } + +} + +void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("blend_point_")) { + String left = property.name.get_slicec('/', 0); + int idx = left.get_slicec('_', 2).to_int(); + if (idx >= blend_points_used) { + property.usage = 0; + } + } + AnimationRootNode::_validate_property(property); +} + +void AnimationNodeBlendSpace1D::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace1D::set_blend_point_position); + ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace1D::get_blend_point_position); + ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace1D::set_blend_point_node); + ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace1D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace1D::remove_blend_point); + ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace1D::get_blend_point_count); + + ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace1D::set_min_space); + ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace1D::get_min_space); + + ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace1D::set_max_space); + ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace1D::get_max_space); + + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace1D::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace1D::get_snap); + + ClassDB::bind_method(D_METHOD("set_blend_pos", "pos"), &AnimationNodeBlendSpace1D::set_blend_pos); + ClassDB::bind_method(D_METHOD("get_blend_pos"), &AnimationNodeBlendSpace1D::get_blend_pos); + + ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label); + ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label); + + ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point); + + for (int i = 0; i < MAX_BLEND_POINTS; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "_add_blend_point", "get_blend_point_node", i); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i); + } + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "blend_pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_pos", "get_blend_pos"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_value_label", "get_value_label"); +} + +void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index) { + ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); + + if (p_at_index == -1 || p_at_index == blend_points_used) { + p_at_index = blend_points_used; + } else { + for (int i = blend_points_used - 1; i > p_at_index; i++) { + blend_points[i] = blend_points[i - 1]; + } + } + + blend_points[p_at_index].node = p_node; + blend_points[p_at_index].position = p_position; + + blend_points[p_at_index].node->set_parent(this); + blend_points[p_at_index].node->set_tree(get_tree()); + + blend_points_used++; +} + +void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) { + ERR_FAIL_INDEX(p_point, blend_points_used); + + blend_points[p_point].position = p_position; +} + +void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) { + ERR_FAIL_INDEX(p_point, blend_points_used); + ERR_FAIL_COND(p_node.is_null()); + + if (blend_points[p_point].node.is_valid()) { + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + } + + blend_points[p_point].node = p_node; + blend_points[p_point].node->set_parent(this); + blend_points[p_point].node->set_tree(get_tree()); +} + +float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, 0); + return blend_points[p_point].position; +} + +Ref<AnimationRootNode> AnimationNodeBlendSpace1D::get_blend_point_node(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>()); + return blend_points[p_point].node; +} + +void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { + ERR_FAIL_INDEX(p_point, blend_points_used); + + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + + for (int i = p_point; i < blend_points_used - 1; i++) { + blend_points[i] = blend_points[i + 1]; + } + + blend_points_used--; +} + +int AnimationNodeBlendSpace1D::get_blend_point_count() const { + + return blend_points_used; +} + +void AnimationNodeBlendSpace1D::set_min_space(float p_min) { + min_space = p_min; + + if (min_space >= max_space) { + min_space = max_space - 1; + } +} + +float AnimationNodeBlendSpace1D::get_min_space() const { + return min_space; +} + +void AnimationNodeBlendSpace1D::set_max_space(float p_max) { + max_space = p_max; + + if (max_space <= min_space) { + max_space = min_space + 1; + } +} + +float AnimationNodeBlendSpace1D::get_max_space() const { + return max_space; +} + +void AnimationNodeBlendSpace1D::set_snap(float p_snap) { + snap = p_snap; +} + +float AnimationNodeBlendSpace1D::get_snap() const { + return snap; +} + +void AnimationNodeBlendSpace1D::set_blend_pos(float p_pos) { + blend_pos = p_pos; +} + +float AnimationNodeBlendSpace1D::get_blend_pos() const { + return blend_pos; +} + +void AnimationNodeBlendSpace1D::set_value_label(const String &p_label) { + value_label = p_label; +} + +String AnimationNodeBlendSpace1D::get_value_label() const { + return value_label; +} + +void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) { + if (p_index == blend_points_used) { + add_blend_point(p_node, 0); + } else { + set_blend_point_node(p_index, p_node); + } +} + +float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) { + + if (blend_points_used == 0) { + return 0.0; + } + + if (blend_points_used == 1) { + // only one point available, just play that animation + return blend_node(blend_points[0].node, p_time, p_seek, 1.0, FILTER_IGNORE, false); + } + + float weights[MAX_BLEND_POINTS] = {}; + + int point_lower = -1; + float pos_lower = 0.0; + int point_higher = -1; + float pos_higher = 0.0; + + // find the closest two points to blend between + for (int i = 0; i < blend_points_used; i++) { + + float pos = blend_points[i].position; + + if (pos <= blend_pos) { + if (point_lower == -1) { + point_lower = i; + pos_lower = pos; + } else if ((blend_pos - pos) < (blend_pos - pos_lower)) { + point_lower = i; + pos_lower = pos; + } + } else { + if (point_higher == -1) { + point_higher = i; + pos_higher = pos; + } else if ((pos - blend_pos) < (pos_higher - blend_pos)) { + point_higher = i; + pos_higher = pos; + } + } + } + + // fill in weights + + if (point_lower == -1) { + // we are on the left side, no other point to the left + // we just play the next point. + + weights[point_higher] = 1.0; + } else if (point_higher == -1) { + // we are on the right side, no other point to the right + // we just play the previous point + + weights[point_lower] = 1.0; + } else { + + // we are between two points. + // figure out weights, then blend the animations + + float distance_between_points = pos_higher - pos_lower; + + float current_pos_inbetween = blend_pos - pos_lower; + + float blend_percentage = current_pos_inbetween / distance_between_points; + + float blend_lower = 1.0 - blend_percentage; + float blend_higher = blend_percentage; + + weights[point_lower] = blend_lower; + weights[point_higher] = blend_higher; + } + + // actually blend the animations now + + float max_time_remaining = 0.0; + + for (int i = 0; i < blend_points_used; i++) { + float remaining = blend_node(blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false); + + max_time_remaining = MAX(max_time_remaining, remaining); + } + + return max_time_remaining; +} + +String AnimationNodeBlendSpace1D::get_caption() const { + return "BlendSpace1D"; +} + +AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() { + + blend_points_used = 0; + max_space = 1; + min_space = -1; + + snap = 0.1; + value_label = "value"; +} + +AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() { + + for (int i = 0; i < blend_points_used; i++) { + blend_points[i].node->set_parent(this); + blend_points[i].node->set_tree(get_tree()); + } +} diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h new file mode 100644 index 0000000000..774894ef4b --- /dev/null +++ b/scene/animation/animation_blend_space_1d.h @@ -0,0 +1,71 @@ +#ifndef ANIMATION_BLEND_SPACE_1D_H +#define ANIMATION_BLEND_SPACE_1D_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeBlendSpace1D : public AnimationRootNode { + GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode) + + enum { + MAX_BLEND_POINTS = 64 + }; + + struct BlendPoint { + Ref<AnimationRootNode> node; + float position; + }; + + BlendPoint blend_points[MAX_BLEND_POINTS]; + int blend_points_used; + + float blend_pos; + + float max_space; + float min_space; + + float snap; + + String value_label; + + void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node); + +protected: + virtual void _validate_property(PropertyInfo &property) const; + static void _bind_methods(); + +public: + + virtual void set_tree(AnimationTree *p_player); + + void add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index = -1); + void set_blend_point_position(int p_point, float p_position); + void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node); + + float get_blend_point_position(int p_point) const; + Ref<AnimationRootNode> get_blend_point_node(int p_point) const; + void remove_blend_point(int p_point); + int get_blend_point_count() const; + + void set_min_space(float p_min); + float get_min_space() const; + + void set_max_space(float p_max); + float get_max_space() const; + + void set_snap(float p_snap); + float get_snap() const; + + void set_blend_pos(float p_pos); + float get_blend_pos() const; + + void set_value_label(const String &p_label); + String get_value_label() const; + + float process(float p_time, bool p_seek); + String get_caption() const; + + AnimationNodeBlendSpace1D(); + ~AnimationNodeBlendSpace1D(); +}; + +#endif // ANIMATION_BLEND_SPACE_1D_H diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp new file mode 100644 index 0000000000..82db647124 --- /dev/null +++ b/scene/animation/animation_blend_space_2d.cpp @@ -0,0 +1,567 @@ +#include "animation_blend_space_2d.h" +#include "math/delaunay.h" + +void AnimationNodeBlendSpace2D::set_tree(AnimationTree *p_player) { + AnimationRootNode::set_tree(p_player); + + for(int i=0;i<blend_points_used;i++) { + blend_points[i].node->set_tree(p_player); + } +} + + +void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index) { + ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); + + if (p_at_index == -1 || p_at_index == blend_points_used) { + p_at_index = blend_points_used; + } else { + for (int i = blend_points_used - 1; i > p_at_index; i--) { + blend_points[i] = blend_points[i - 1]; + } + for (int i = 0; i < triangles.size(); i++) { + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] >= p_at_index) { + triangles[i].points[j]++; + } + } + } + } + blend_points[p_at_index].node = p_node; + blend_points[p_at_index].position = p_position; + + blend_points[p_at_index].node->set_parent(this); + blend_points[p_at_index].node->set_tree(get_tree()); + blend_points_used++; + + if (auto_triangles) { + trianges_dirty = true; + } +} + +void AnimationNodeBlendSpace2D::set_blend_point_position(int p_point, const Vector2 &p_position) { + ERR_FAIL_INDEX(p_point, blend_points_used); + blend_points[p_point].position = p_position; + if (auto_triangles) { + trianges_dirty = true; + } +} +void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) { + ERR_FAIL_INDEX(p_point, blend_points_used); + ERR_FAIL_COND(p_node.is_null()); + + if (blend_points[p_point].node.is_valid()) { + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + } + blend_points[p_point].node = p_node; + blend_points[p_point].node->set_parent(this); + blend_points[p_point].node->set_tree(get_tree()); +} +Vector2 AnimationNodeBlendSpace2D::get_blend_point_position(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, Vector2()); + return blend_points[p_point].position; +} +Ref<AnimationRootNode> AnimationNodeBlendSpace2D::get_blend_point_node(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>()); + return blend_points[p_point].node; +} +void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { + ERR_FAIL_INDEX(p_point, blend_points_used); + + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + + for (int i = 0; i < triangles.size(); i++) { + bool erase = false; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] == p_point) { + erase = true; + break; + } else if (triangles[i].points[j] > p_point) { + triangles[i].points[j]--; + } + } + if (erase) { + triangles.remove(i); + + i--; + } + } + + for (int i = p_point; i < blend_points_used - 1; i++) { + blend_points[i] = blend_points[i + 1]; + } + blend_points_used--; +} + +int AnimationNodeBlendSpace2D::get_blend_point_count() const { + + return blend_points_used; +} + +bool AnimationNodeBlendSpace2D::has_triangle(int p_x, int p_y, int p_z) const { + + ERR_FAIL_INDEX_V(p_x, blend_points_used, false); + ERR_FAIL_INDEX_V(p_y, blend_points_used, false); + ERR_FAIL_INDEX_V(p_z, blend_points_used, false); + + BlendTriangle t; + t.points[0] = p_x; + t.points[1] = p_y; + t.points[2] = p_z; + + SortArray<int> sort; + sort.sort(t.points, 3); + + for (int i = 0; i < triangles.size(); i++) { + bool all_equal = true; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] != t.points[j]) { + all_equal = false; + break; + } + } + if (all_equal) + return true; + } + + return false; +} + +void AnimationNodeBlendSpace2D::add_triangle(int p_x, int p_y, int p_z, int p_at_index) { + + ERR_FAIL_INDEX(p_x, blend_points_used); + ERR_FAIL_INDEX(p_y, blend_points_used); + ERR_FAIL_INDEX(p_z, blend_points_used); + + _update_triangles(); + + BlendTriangle t; + t.points[0] = p_x; + t.points[1] = p_y; + t.points[2] = p_z; + + SortArray<int> sort; + sort.sort(t.points, 3); + + for (int i = 0; i < triangles.size(); i++) { + bool all_equal = true; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] != t.points[j]) { + all_equal = false; + break; + } + } + ERR_FAIL_COND(all_equal); + } + + if (p_at_index == -1 || p_at_index == triangles.size()) { + triangles.push_back(t); + } else { + triangles.insert(p_at_index, t); + } +} +int AnimationNodeBlendSpace2D::get_triangle_point(int p_triangle, int p_point) { + + _update_triangles(); + + ERR_FAIL_INDEX_V(p_point, 3, -1); + ERR_FAIL_INDEX_V(p_triangle, triangles.size(), -1); + return triangles[p_triangle].points[p_point]; +} +void AnimationNodeBlendSpace2D::remove_triangle(int p_triangle) { + ERR_FAIL_INDEX(p_triangle, triangles.size()); + + triangles.remove(p_triangle); +} + +int AnimationNodeBlendSpace2D::get_triangle_count() const { + return triangles.size(); +} + +void AnimationNodeBlendSpace2D::set_min_space(const Vector2 &p_min) { + + min_space = p_min; + if (min_space.x >= max_space.x) { + min_space.x = max_space.x - 1; + } + if (min_space.y >= max_space.y) { + min_space.y = max_space.y - 1; + } +} +Vector2 AnimationNodeBlendSpace2D::get_min_space() const { + return min_space; +} + +void AnimationNodeBlendSpace2D::set_max_space(const Vector2 &p_max) { + + max_space = p_max; + if (max_space.x <= min_space.x) { + max_space.x = min_space.x + 1; + } + if (max_space.y <= min_space.y) { + max_space.y = min_space.y + 1; + } +} +Vector2 AnimationNodeBlendSpace2D::get_max_space() const { + return max_space; +} + +void AnimationNodeBlendSpace2D::set_snap(const Vector2 &p_snap) { + snap = p_snap; +} +Vector2 AnimationNodeBlendSpace2D::get_snap() const { + return snap; +} + +void AnimationNodeBlendSpace2D::set_blend_position(const Vector2 &p_pos) { + blend_pos = p_pos; +} +Vector2 AnimationNodeBlendSpace2D::get_blend_position() const { + return blend_pos; +} + +void AnimationNodeBlendSpace2D::set_x_label(const String &p_label) { + x_label = p_label; +} +String AnimationNodeBlendSpace2D::get_x_label() const { + return x_label; +} + +void AnimationNodeBlendSpace2D::set_y_label(const String &p_label) { + y_label = p_label; +} +String AnimationNodeBlendSpace2D::get_y_label() const { + return y_label; +} + +void AnimationNodeBlendSpace2D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) { + if (p_index == blend_points_used) { + add_blend_point(p_node, Vector2()); + } else { + set_blend_point_node(p_index, p_node); + } +} + +void AnimationNodeBlendSpace2D::_set_triangles(const Vector<int> &p_triangles) { + + if (auto_triangles) + return; + ERR_FAIL_COND(p_triangles.size() % 3 != 0); + for (int i = 0; i < p_triangles.size(); i += 3) { + add_triangle(p_triangles[i + 0], p_triangles[i + 1], p_triangles[i + 2]); + } +} + +Vector<int> AnimationNodeBlendSpace2D::_get_triangles() const { + + Vector<int> t; + if (auto_triangles && trianges_dirty) + return t; + + t.resize(triangles.size() * 3); + for (int i = 0; i < triangles.size(); i++) { + t[i * 3 + 0] = triangles[i].points[0]; + t[i * 3 + 1] = triangles[i].points[1]; + t[i * 3 + 2] = triangles[i].points[2]; + } + return t; +} + +void AnimationNodeBlendSpace2D::_update_triangles() { + + if (!auto_triangles || !trianges_dirty) + return; + + trianges_dirty = false; + triangles.clear(); + if (blend_points_used < 3) + return; + + Vector<Vector2> points; + points.resize(blend_points_used); + for (int i = 0; i < blend_points_used; i++) { + points[i] = blend_points[i].position; + } + + Vector<Delaunay2D::Triangle> triangles = 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]); + } +} + +Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) { + + _update_triangles(); + + if (triangles.size() == 0) + return Vector2(); + + Vector2 best_point; + bool first = true; + + for (int i = 0; i < triangles.size(); i++) { + Vector2 points[3]; + for (int j = 0; j < 3; j++) { + points[j] = get_blend_point_position(get_triangle_point(i, j)); + } + + if (Geometry::is_point_in_triangle(p_point, points[0], points[1], points[2])) { + + return p_point; + } + + for (int j = 0; j < 3; j++) { + Vector2 s[2] = { + points[j], + points[(j + 1) % 3] + }; + Vector2 closest = Geometry::get_closest_point_to_segment_2d(p_point, s); + if (first || closest.distance_to(p_point) < best_point.distance_to(p_point)) { + best_point = closest; + first = false; + } + } + } + + return best_point; +} + +void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) { + + if (p_pos.distance_squared_to(p_points[0]) < CMP_EPSILON2) { + r_weights[0] = 1; + r_weights[1] = 0; + r_weights[2] = 0; + return; + } + if (p_pos.distance_squared_to(p_points[1]) < CMP_EPSILON2) { + r_weights[0] = 0; + r_weights[1] = 1; + r_weights[2] = 0; + return; + } + if (p_pos.distance_squared_to(p_points[2]) < CMP_EPSILON2) { + r_weights[0] = 0; + r_weights[1] = 0; + r_weights[2] = 1; + return; + } + + Vector2 v0 = p_points[1] - p_points[0]; + Vector2 v1 = p_points[2] - p_points[0]; + Vector2 v2 = p_pos - p_points[0]; + + float d00 = v0.dot(v0); + float d01 = v0.dot(v1); + float d11 = v1.dot(v1); + float d20 = v2.dot(v0); + float d21 = v2.dot(v1); + float denom = (d00 * d11 - d01 * d01); + if (denom == 0) { + r_weights[0] = 1; + r_weights[1] = 0; + r_weights[2] = 0; + return; + } + float v = (d11 * d20 - d01 * d21) / denom; + float w = (d00 * d21 - d01 * d20) / denom; + float u = 1.0f - v - w; + + r_weights[0] = u; + r_weights[1] = v; + r_weights[2] = w; +} + +float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) { + + _update_triangles(); + + if (triangles.size() == 0) + return 0; + + Vector2 best_point; + bool first = true; + int blend_triangle = -1; + float blend_weights[3] = { 0, 0, 0 }; + + for (int i = 0; i < triangles.size(); i++) { + Vector2 points[3]; + for (int j = 0; j < 3; j++) { + points[j] = get_blend_point_position(get_triangle_point(i, j)); + } + + if (Geometry::is_point_in_triangle(blend_pos, points[0], points[1], points[2])) { + + blend_triangle = i; + _blend_triangle(blend_pos, points, blend_weights); + break; + } + + for (int j = 0; j < 3; j++) { + Vector2 s[2] = { + points[j], + points[(j + 1) % 3] + }; + Vector2 closest = Geometry::get_closest_point_to_segment_2d(blend_pos, s); + if (first || closest.distance_to(blend_pos) < best_point.distance_to(blend_pos)) { + best_point = closest; + blend_triangle = i; + first = false; + float d = s[0].distance_to(s[1]); + if (d == 0.0) { + blend_weights[j] = 1.0; + blend_weights[(j + 1) % 3] = 0.0; + blend_weights[(j + 2) % 3] = 0.0; + } else { + float c = s[0].distance_to(closest) / d; + + blend_weights[j] = 1.0 - c; + blend_weights[(j + 1) % 3] = c; + blend_weights[(j + 2) % 3] = 0.0; + } + } + } + } + + ERR_FAIL_COND_V(blend_triangle == -1, 0); //should never reach here + + int triangle_points[3]; + for (int j = 0; j < 3; j++) { + triangle_points[j] = get_triangle_point(blend_triangle, j); + } + + first = true; + float mind; + for (int i = 0; i < blend_points_used; i++) { + + bool found = false; + for (int j = 0; j < 3; j++) { + if (i == triangle_points[j]) { + //blend with the given weight + float t = blend_node(blend_points[i].node, p_time, p_seek, blend_weights[j], FILTER_IGNORE, false); + if (first || t < mind) { + mind = t; + first = false; + } + found = true; + break; + } + } + + if (!found) { + //ignore + blend_node(blend_points[i].node, p_time, p_seek, 0, FILTER_IGNORE, false); + } + } + return mind; +} + +String AnimationNodeBlendSpace2D::get_caption() const { + return "BlendSpace2D"; +} + +void AnimationNodeBlendSpace2D::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("blend_point_")) { + String left = property.name.get_slicec('/', 0); + int idx = left.get_slicec('_', 2).to_int(); + if (idx >= blend_points_used) { + property.usage = 0; + } + } + AnimationRootNode::_validate_property(property); +} + +void AnimationNodeBlendSpace2D::set_auto_triangles(bool p_enable) { + auto_triangles = p_enable; + if (auto_triangles) { + trianges_dirty = true; + } +} + +bool AnimationNodeBlendSpace2D::get_auto_triangles() const { + return auto_triangles; +} + +void AnimationNodeBlendSpace2D::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position); + ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace2D::get_blend_point_position); + ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace2D::set_blend_point_node); + ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace2D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace2D::remove_blend_point); + ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace2D::get_blend_point_count); + + ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace2D::add_triangle, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace2D::get_triangle_point); + ClassDB::bind_method(D_METHOD("remove_triangle", "triangle"), &AnimationNodeBlendSpace2D::remove_triangle); + ClassDB::bind_method(D_METHOD("get_triangle_count"), &AnimationNodeBlendSpace2D::get_triangle_count); + + ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace2D::set_min_space); + ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace2D::get_min_space); + + ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace2D::set_max_space); + ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace2D::get_max_space); + + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace2D::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace2D::get_snap); + + ClassDB::bind_method(D_METHOD("set_blend_position", "pos"), &AnimationNodeBlendSpace2D::set_blend_position); + ClassDB::bind_method(D_METHOD("get_blend_position"), &AnimationNodeBlendSpace2D::get_blend_position); + + ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace2D::set_x_label); + ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace2D::get_x_label); + + ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace2D::set_y_label); + ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace2D::get_y_label); + + ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace2D::_add_blend_point); + + ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace2D::_set_triangles); + ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace2D::_get_triangles); + + ClassDB::bind_method(D_METHOD("set_auto_triangles", "enable"), &AnimationNodeBlendSpace2D::set_auto_triangles); + ClassDB::bind_method(D_METHOD("get_auto_triangles"), &AnimationNodeBlendSpace2D::get_auto_triangles); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_auto_triangles", "get_auto_triangles"); + + for (int i = 0; i < MAX_BLEND_POINTS; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "_add_blend_point", "get_blend_point_node", i); + ADD_PROPERTYI(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i); + } + + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles"); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "blend_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_position", "get_blend_position"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_x_label", "get_x_label"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_y_label", "get_y_label"); +} + +AnimationNodeBlendSpace2D::AnimationNodeBlendSpace2D() { + + auto_triangles = true; + blend_points_used = 0; + max_space = Vector2(1, 1); + min_space = Vector2(-1, -1); + snap = Vector2(0.1, 0.1); + x_label = "x"; + y_label = "y"; + trianges_dirty = false; +} + +AnimationNodeBlendSpace2D::~AnimationNodeBlendSpace2D() { + + for (int i = 0; i < blend_points_used; i++) { + blend_points[i].node->set_parent(this); + blend_points[i].node->set_tree(get_tree()); + } +} diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h new file mode 100644 index 0000000000..4778299df1 --- /dev/null +++ b/scene/animation/animation_blend_space_2d.h @@ -0,0 +1,97 @@ +#ifndef ANIMATION_BLEND_SPACE_2D_H +#define ANIMATION_BLEND_SPACE_2D_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeBlendSpace2D : public AnimationRootNode { + GDCLASS(AnimationNodeBlendSpace2D, AnimationRootNode) + + enum { + MAX_BLEND_POINTS = 64 + }; + + struct BlendPoint { + Ref<AnimationRootNode> node; + Vector2 position; + }; + + BlendPoint blend_points[MAX_BLEND_POINTS]; + int blend_points_used; + + struct BlendTriangle { + int points[3]; + }; + + Vector<BlendTriangle> triangles; + + Vector2 blend_pos; + Vector2 max_space; + Vector2 min_space; + Vector2 snap; + String x_label; + String y_label; + + void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node); + void _set_triangles(const Vector<int> &p_triangles); + Vector<int> _get_triangles() const; + + void _blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights); + + bool auto_triangles; + bool trianges_dirty; + + void _update_triangles(); + +protected: + virtual void _validate_property(PropertyInfo &property) const; + static void _bind_methods(); + +public: + + virtual void set_tree(AnimationTree *p_player); + + void add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index = -1); + void set_blend_point_position(int p_point, const Vector2 &p_position); + void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node); + Vector2 get_blend_point_position(int p_point) const; + Ref<AnimationRootNode> get_blend_point_node(int p_point) const; + void remove_blend_point(int p_point); + int get_blend_point_count() const; + + bool has_triangle(int p_x, int p_y, int p_z) const; + void add_triangle(int p_x, int p_y, int p_z, int p_at_index = -1); + int get_triangle_point(int p_triangle, int p_point); + void remove_triangle(int p_triangle); + int get_triangle_count() const; + + void set_min_space(const Vector2 &p_min); + Vector2 get_min_space() const; + + void set_max_space(const Vector2 &p_max); + Vector2 get_max_space() const; + + void set_snap(const Vector2 &p_snap); + Vector2 get_snap() const; + + void set_blend_position(const Vector2 &p_pos); + Vector2 get_blend_position() const; + + void set_x_label(const String &p_label); + String get_x_label() const; + + void set_y_label(const String &p_label); + String get_y_label() const; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + Vector2 get_closest_point(const Vector2 &p_point); + + void set_auto_triangles(bool p_enable); + bool get_auto_triangles() const; + + AnimationNodeBlendSpace2D(); + ~AnimationNodeBlendSpace2D(); +}; + +#endif // ANIMATION_BLEND_SPACE_2D_H diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp new file mode 100644 index 0000000000..6dcd5ca8ea --- /dev/null +++ b/scene/animation/animation_blend_tree.cpp @@ -0,0 +1,1170 @@ +#include "animation_blend_tree.h" +#include "scene/scene_string_names.h" + +void AnimationNodeAnimation::set_animation(const StringName &p_name) { + animation = p_name; +} + +StringName AnimationNodeAnimation::get_animation() const { + return animation; +} + +float AnimationNodeAnimation::get_playback_time() const { + return time; +} + +void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const { + + if (property.name == "animation") { + AnimationTree *gp = get_tree(); + if (gp && gp->has_node(gp->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + String anims; + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (E != names.front()) { + anims += ","; + } + anims += String(E->get()); + } + if (anims != String()) { + property.hint = PROPERTY_HINT_ENUM; + property.hint_string = anims; + } + } + } + } + + AnimationRootNode::_validate_property(property); +} + +float AnimationNodeAnimation::process(float p_time, bool p_seek) { + + AnimationPlayer *ap = get_player(); + ERR_FAIL_COND_V(!ap, 0); + + Ref<Animation> anim = ap->get_animation(animation); + if (!anim.is_valid()) { + + Ref<AnimationNodeBlendTree> tree = get_parent(); + if (tree.is_valid()) { + String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), name, animation)); + + } else { + make_invalid(vformat(RTR("Animation not found: '%s'"), animation)); + } + + return 0; + } + + if (p_seek) { + time = p_time; + step = 0; + } else { + time = MAX(0, time + p_time); + step = p_time; + } + + float anim_size = anim->get_length(); + + if (anim->has_loop()) { + + if (anim_size) { + time = Math::fposmod(time, anim_size); + } + + } else if (time > anim_size) { + + time = anim_size; + } + + blend_animation(animation, time, step, p_seek, 1.0); + + return anim_size - time; +} + +String AnimationNodeAnimation::get_caption() const { + return "Animation"; +} + +void AnimationNodeAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation); + ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation); + + ClassDB::bind_method(D_METHOD("get_playback_time"), &AnimationNodeAnimation::get_playback_time); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation"); +} + +AnimationNodeAnimation::AnimationNodeAnimation() { + last_version = 0; + skip = false; + time = 0; + step = 0; +} + +//////////////////////////////////////////////////////// + +void AnimationNodeOneShot::set_fadein_time(float p_time) { + + fade_in = p_time; +} + +void AnimationNodeOneShot::set_fadeout_time(float p_time) { + + fade_out = p_time; +} + +float AnimationNodeOneShot::get_fadein_time() const { + + return fade_in; +} +float AnimationNodeOneShot::get_fadeout_time() const { + + return fade_out; +} + +void AnimationNodeOneShot::set_autorestart(bool p_active) { + + autorestart = p_active; +} +void AnimationNodeOneShot::set_autorestart_delay(float p_time) { + + autorestart_delay = p_time; +} +void AnimationNodeOneShot::set_autorestart_random_delay(float p_time) { + + autorestart_random_delay = p_time; +} + +bool AnimationNodeOneShot::has_autorestart() const { + + return autorestart; +} +float AnimationNodeOneShot::get_autorestart_delay() const { + + return autorestart_delay; +} +float AnimationNodeOneShot::get_autorestart_random_delay() const { + + return autorestart_random_delay; +} + +void AnimationNodeOneShot::set_mix_mode(MixMode p_mix) { + + mix = p_mix; +} +AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const { + + return mix; +} + +void AnimationNodeOneShot::start() { + active = true; + do_start = true; +} +void AnimationNodeOneShot::stop() { + active = false; +} +bool AnimationNodeOneShot::is_active() const { + + return active; +} + +String AnimationNodeOneShot::get_caption() const { + return "OneShot"; +} + +bool AnimationNodeOneShot::has_filter() const { + return true; +} + +float AnimationNodeOneShot::process(float p_time, bool p_seek) { + + if (!active) { + //make it as if this node doesn't exist, pass input 0 by. + return blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + } + + bool os_seek = p_seek; + + if (p_seek) + time = p_time; + if (do_start) { + time = 0; + os_seek = true; + } + + float blend; + + if (time < fade_in) { + + if (fade_in > 0) + blend = time / fade_in; + else + blend = 0; //wtf + + } else if (!do_start && remaining < fade_out) { + + if (fade_out) + blend = (remaining / fade_out); + else + blend = 1.0; + } else + blend = 1.0; + + float main_rem; + if (mix == MIX_MODE_ADD) { + main_rem = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + } else { + main_rem = blend_input(0, p_time, p_seek, 1.0 - blend, FILTER_BLEND, !sync); + } + + float os_rem = blend_input(1, os_seek ? time : p_time, os_seek, blend, FILTER_PASS, false); + + if (do_start) { + remaining = os_rem; + do_start = false; + } + + if (!p_seek) { + time += p_time; + remaining = os_rem; + if (remaining <= 0) + active = false; + } + + return MAX(main_rem, remaining); +} +void AnimationNodeOneShot::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeOneShot::is_using_sync() const { + + return sync; +} + +void AnimationNodeOneShot::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_fadein_time", "time"), &AnimationNodeOneShot::set_fadein_time); + ClassDB::bind_method(D_METHOD("get_fadein_time"), &AnimationNodeOneShot::get_fadein_time); + + ClassDB::bind_method(D_METHOD("set_fadeout_time", "time"), &AnimationNodeOneShot::set_fadeout_time); + ClassDB::bind_method(D_METHOD("get_fadeout_time"), &AnimationNodeOneShot::get_fadeout_time); + + ClassDB::bind_method(D_METHOD("set_autorestart", "enable"), &AnimationNodeOneShot::set_autorestart); + ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::has_autorestart); + + ClassDB::bind_method(D_METHOD("set_autorestart_delay", "enable"), &AnimationNodeOneShot::set_autorestart_delay); + ClassDB::bind_method(D_METHOD("get_autorestart_delay"), &AnimationNodeOneShot::get_autorestart_delay); + + ClassDB::bind_method(D_METHOD("set_autorestart_random_delay", "enable"), &AnimationNodeOneShot::set_autorestart_random_delay); + ClassDB::bind_method(D_METHOD("get_autorestart_random_delay"), &AnimationNodeOneShot::get_autorestart_random_delay); + + ClassDB::bind_method(D_METHOD("set_mix_mode", "mode"), &AnimationNodeOneShot::set_mix_mode); + ClassDB::bind_method(D_METHOD("get_mix_mode"), &AnimationNodeOneShot::get_mix_mode); + + ClassDB::bind_method(D_METHOD("start"), &AnimationNodeOneShot::start); + ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeOneShot::stop); + ClassDB::bind_method(D_METHOD("is_active"), &AnimationNodeOneShot::is_active); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeOneShot::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeOneShot::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadein_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadein_time", "get_fadein_time"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadeout_time", "get_fadeout_time"); + + ADD_GROUP("autorestart_", "Auto Restart"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart"); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_delay", "get_autorestart_delay"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_random_delay", "get_autorestart_random_delay"); + + ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); + + BIND_CONSTANT(MIX_MODE_BLEND) + BIND_CONSTANT(MIX_MODE_ADD) +} + +AnimationNodeOneShot::AnimationNodeOneShot() { + + add_input("in"); + add_input("shot"); + + time = 0; + fade_in = 0.1; + fade_out = 0.1; + autorestart = false; + autorestart_delay = 1; + autorestart_remaining = 0; + mix = MIX_MODE_BLEND; + active = false; + do_start = false; + sync = false; +} + +//////////////////////////////////////////////// + +void AnimationNodeAdd2::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeAdd2::get_amount() const { + return amount; +} + +String AnimationNodeAdd2::get_caption() const { + return "Add2"; +} +void AnimationNodeAdd2::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeAdd2::is_using_sync() const { + + return sync; +} + +bool AnimationNodeAdd2::has_filter() const { + + return true; +} + +float AnimationNodeAdd2::process(float p_time, bool p_seek) { + + float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync); + + return rem0; +} + +void AnimationNodeAdd2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd2::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd2::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd2::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd2::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} + +AnimationNodeAdd2::AnimationNodeAdd2() { + + add_input("in"); + add_input("add"); + amount = 0; + sync = false; +} + +//////////////////////////////////////////////// + +void AnimationNodeAdd3::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeAdd3::get_amount() const { + return amount; +} + +String AnimationNodeAdd3::get_caption() const { + return "Add3"; +} +void AnimationNodeAdd3::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeAdd3::is_using_sync() const { + + return sync; +} + +bool AnimationNodeAdd3::has_filter() const { + + return true; +} + +float AnimationNodeAdd3::process(float p_time, bool p_seek) { + + blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_PASS, !sync); + float rem0 = blend_input(1, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_PASS, !sync); + + return rem0; +} + +void AnimationNodeAdd3::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd3::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd3::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd3::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd3::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} + +AnimationNodeAdd3::AnimationNodeAdd3() { + + add_input("-add"); + add_input("in"); + add_input("+add"); + amount = 0; + sync = false; +} +///////////////////////////////////////////// + +void AnimationNodeBlend2::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeBlend2::get_amount() const { + return amount; +} +String AnimationNodeBlend2::get_caption() const { + return "Blend2"; +} + +float AnimationNodeBlend2::process(float p_time, bool p_seek) { + + float rem0 = blend_input(0, p_time, p_seek, 1.0 - amount, FILTER_BLEND, !sync); + float rem1 = blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync); + + return amount > 0.5 ? rem1 : rem0; //hacky but good enough +} + +void AnimationNodeBlend2::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeBlend2::is_using_sync() const { + + return sync; +} + +bool AnimationNodeBlend2::has_filter() const { + + return true; +} +void AnimationNodeBlend2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeBlend2::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend2::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend2::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend2::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} +AnimationNodeBlend2::AnimationNodeBlend2() { + add_input("in"); + add_input("blend"); + sync = false; + + amount = 0; +} + +////////////////////////////////////// + +void AnimationNodeBlend3::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeBlend3::get_amount() const { + return amount; +} + +String AnimationNodeBlend3::get_caption() const { + return "Blend3"; +} + +void AnimationNodeBlend3::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeBlend3::is_using_sync() const { + + return sync; +} + +float AnimationNodeBlend3::process(float p_time, bool p_seek) { + + float rem0 = blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_IGNORE, !sync); + float rem1 = blend_input(1, p_time, p_seek, 1.0 - ABS(amount), FILTER_IGNORE, !sync); + float rem2 = blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_IGNORE, !sync); + + return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough +} + +void AnimationNodeBlend3::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeBlend3::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend3::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend3::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend3::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} +AnimationNodeBlend3::AnimationNodeBlend3() { + add_input("-blend"); + add_input("in"); + add_input("+blend"); + sync = false; + amount = 0; +} + +///////////////////////////////// + +void AnimationNodeTimeScale::set_scale(float p_scale) { + scale = p_scale; +} + +float AnimationNodeTimeScale::get_scale() const { + return scale; +} + +String AnimationNodeTimeScale::get_caption() const { + return "TimeScale"; +} + +float AnimationNodeTimeScale::process(float p_time, bool p_seek) { + + if (p_seek) { + return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); + } else { + return blend_input(0, p_time * scale, false, 1.0, FILTER_IGNORE, false); + } +} + +void AnimationNodeTimeScale::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_scale", "scale"), &AnimationNodeTimeScale::set_scale); + ClassDB::bind_method(D_METHOD("get_scale"), &AnimationNodeTimeScale::get_scale); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,32,0.01,or_greater"), "set_scale", "get_scale"); +} +AnimationNodeTimeScale::AnimationNodeTimeScale() { + add_input("in"); + scale = 1.0; +} + +//////////////////////////////////// + +void AnimationNodeTimeSeek::set_seek_pos(float p_seek_pos) { + seek_pos = p_seek_pos; +} + +float AnimationNodeTimeSeek::get_seek_pos() const { + return seek_pos; +} + +String AnimationNodeTimeSeek::get_caption() const { + return "Seek"; +} + +float AnimationNodeTimeSeek::process(float p_time, bool p_seek) { + + if (p_seek) { + return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); + } else if (seek_pos >= 0) { + float ret = blend_input(0, seek_pos, true, 1.0, FILTER_IGNORE, false); + seek_pos = -1; + _change_notify("seek_pos"); + return ret; + } else { + return blend_input(0, p_time, false, 1.0, FILTER_IGNORE, false); + } +} + +void AnimationNodeTimeSeek::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_seek_pos", "seek_pos"), &AnimationNodeTimeSeek::set_seek_pos); + ClassDB::bind_method(D_METHOD("get_seek_pos"), &AnimationNodeTimeSeek::get_seek_pos); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "seek_pos", PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater"), "set_seek_pos", "get_seek_pos"); +} +AnimationNodeTimeSeek::AnimationNodeTimeSeek() { + add_input("in"); + seek_pos = -1; +} + +///////////////////////////////////////////////// + +String AnimationNodeTransition::get_caption() const { + return "Transition"; +} + +void AnimationNodeTransition::_update_inputs() { + while (get_input_count() < enabled_inputs) { + add_input(inputs[get_input_count()].name); + } + + while (get_input_count() > enabled_inputs) { + remove_input(get_input_count() - 1); + } +} + +void AnimationNodeTransition::set_enabled_inputs(int p_inputs) { + ERR_FAIL_INDEX(p_inputs, MAX_INPUTS); + enabled_inputs = p_inputs; + _update_inputs(); +} + +int AnimationNodeTransition::get_enabled_inputs() { + return enabled_inputs; +} + +void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) { + ERR_FAIL_INDEX(p_input, MAX_INPUTS); + inputs[p_input].auto_advance = p_enable; +} + +bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const { + ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, false); + return inputs[p_input].auto_advance; +} + +void AnimationNodeTransition::set_input_caption(int p_input, const String &p_name) { + ERR_FAIL_INDEX(p_input, MAX_INPUTS); + inputs[p_input].name = p_name; + set_input_name(p_input, p_name); +} + +String AnimationNodeTransition::get_input_caption(int p_input) const { + ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, String()); + return inputs[p_input].name; +} + +void AnimationNodeTransition::set_current(int p_current) { + + if (current == p_current) + return; + ERR_FAIL_INDEX(p_current, enabled_inputs); + + Ref<AnimationNodeBlendTree> tree = get_parent(); + + if (tree.is_valid() && current >= 0) { + prev = current; + prev_xfading = xfade; + prev_time = time; + time = 0; + current = p_current; + switched = true; + _change_notify("current"); + } else { + current = p_current; + } +} + +int AnimationNodeTransition::get_current() const { + return current; +} +void AnimationNodeTransition::set_cross_fade_time(float p_fade) { + xfade = p_fade; +} + +float AnimationNodeTransition::get_cross_fade_time() const { + return xfade; +} + +float AnimationNodeTransition::process(float p_time, bool p_seek) { + + if (prev < 0) { // process current animation, check for transition + + float rem = blend_input(current, p_time, p_seek, 1.0, FILTER_IGNORE, false); + + if (p_seek) + time = p_time; + else + time += p_time; + + if (inputs[current].auto_advance && rem <= xfade) { + + set_current((current + 1) % enabled_inputs); + } + + return rem; + } else { // cross-fading from prev to current + + float blend = xfade ? (prev_xfading / xfade) : 1; + + float rem; + + if (!p_seek && switched) { //just switched, seek to start of current + + rem = blend_input(current, 0, true, 1.0 - blend, FILTER_IGNORE, false); + } else { + + rem = blend_input(current, p_time, p_seek, 1.0 - blend, FILTER_IGNORE, false); + } + + switched = false; + + if (p_seek) { // don't seek prev animation + blend_input(prev, 0, false, blend, FILTER_IGNORE, false); + time = p_time; + } else { + blend_input(prev, p_time, false, blend, FILTER_IGNORE, false); + time += p_time; + prev_xfading -= p_time; + if (prev_xfading < 0) { + prev = -1; + } + } + + return rem; + } +} + +void AnimationNodeTransition::_validate_property(PropertyInfo &property) const { + + if (property.name == "current" && enabled_inputs > 0) { + property.hint = PROPERTY_HINT_ENUM; + String anims; + for (int i = 0; i < enabled_inputs; i++) { + if (i > 0) { + anims += ","; + } + anims += inputs[i].name; + } + property.hint_string = anims; + } + + if (property.name.begins_with("input_")) { + String n = property.name.get_slicec('/', 0).get_slicec('_', 1); + if (n != "count") { + int idx = n.to_int(); + if (idx >= enabled_inputs) { + property.usage = 0; + } + } + } + + AnimationNode::_validate_property(property); +} + +void AnimationNodeTransition::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_enabled_inputs", "amount"), &AnimationNodeTransition::set_enabled_inputs); + ClassDB::bind_method(D_METHOD("get_enabled_inputs"), &AnimationNodeTransition::get_enabled_inputs); + + ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance); + ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance); + + ClassDB::bind_method(D_METHOD("set_input_caption", "input", "caption"), &AnimationNodeTransition::set_input_caption); + ClassDB::bind_method(D_METHOD("get_input_caption", "input"), &AnimationNodeTransition::get_input_caption); + + ClassDB::bind_method(D_METHOD("set_current", "index"), &AnimationNodeTransition::set_current); + ClassDB::bind_method(D_METHOD("get_current"), &AnimationNodeTransition::get_current); + + ClassDB::bind_method(D_METHOD("set_cross_fade_time", "time"), &AnimationNodeTransition::set_cross_fade_time); + ClassDB::bind_method(D_METHOD("get_cross_fade_time"), &AnimationNodeTransition::get_cross_fade_time); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current", PROPERTY_HINT_RANGE, "0,64,1"), "set_current", "get_current"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01"), "set_cross_fade_time", "get_cross_fade_time"); + + for (int i = 0; i < MAX_INPUTS; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name"), "set_input_caption", "get_input_caption", i); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance"), "set_input_as_auto_advance", "is_input_set_as_auto_advance", i); + } +} + +AnimationNodeTransition::AnimationNodeTransition() { + enabled_inputs = 0; + xfade = 0; + current = -1; + prev = -1; + prev_time = 0; + prev_xfading = 0; + switched = false; + for (int i = 0; i < MAX_INPUTS; i++) { + inputs[i].auto_advance = false; + inputs[i].name = itos(i + 1); + } +} + +///////////////////// + +String AnimationNodeOutput::get_caption() const { + return "Output"; +} + +float AnimationNodeOutput::process(float p_time, bool p_seek) { + return blend_input(0, p_time, p_seek, 1.0); +} + +AnimationNodeOutput::AnimationNodeOutput() { + add_input("output"); +} + +/////////////////////////////////////////////////////// +void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNode> p_node) { + + ERR_FAIL_COND(nodes.has(p_name)); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_node->get_tree() != NULL); + ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(String(p_name).find("/") != -1); + nodes[p_name] = p_node; + + p_node->set_parent(this); + p_node->set_tree(get_tree()); + + emit_changed(); +} + +Ref<AnimationNode> AnimationNodeBlendTree::get_node(const StringName &p_name) const { + + ERR_FAIL_COND_V(!nodes.has(p_name), Ref<AnimationNode>()); + + return nodes[p_name]; +} + +StringName AnimationNodeBlendTree::get_node_name(const Ref<AnimationNode> &p_node) const { + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + if (E->get() == p_node) { + return E->key(); + } + } + + ERR_FAIL_V(StringName()); +} +bool AnimationNodeBlendTree::has_node(const StringName &p_name) const { + return nodes.has(p_name); +} +void AnimationNodeBlendTree::remove_node(const StringName &p_name) { + + ERR_FAIL_COND(!nodes.has(p_name)); + ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); //can't delete output + + { + //erase node connections + Ref<AnimationNode> node = nodes[p_name]; + for (int i = 0; i < node->get_input_count(); i++) { + node->set_input_connection(i, StringName()); + } + node->set_parent(NULL); + node->set_tree(NULL); + } + + nodes.erase(p_name); + + //erase connections to name + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + if (node->get_input_connection(i) == p_name) { + node->set_input_connection(i, StringName()); + } + } + } + + emit_changed(); +} + +void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringName &p_new_name) { + + ERR_FAIL_COND(!nodes.has(p_name)); + ERR_FAIL_COND(nodes.has(p_new_name)); + ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(p_new_name == SceneStringNames::get_singleton()->output); + + nodes[p_new_name] = nodes[p_name]; + nodes.erase(p_name); + + //rename connections + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + if (node->get_input_connection(i) == p_name) { + node->set_input_connection(i, p_new_name); + } + } + } +} + +void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) { + + ERR_FAIL_COND(!nodes.has(p_output_node)); + ERR_FAIL_COND(!nodes.has(p_input_node)); + ERR_FAIL_COND(p_output_node == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(p_input_node == p_output_node); + + Ref<AnimationNode> input = nodes[p_input_node]; + ERR_FAIL_INDEX(p_input_index, input->get_input_count()); + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + StringName output = node->get_input_connection(i); + ERR_FAIL_COND(output == p_output_node); + } + } + + input->set_input_connection(p_input_index, p_output_node); + emit_changed(); +} + +void AnimationNodeBlendTree::disconnect_node(const StringName &p_node, int p_input_index) { + + ERR_FAIL_COND(!nodes.has(p_node)); + + Ref<AnimationNode> input = nodes[p_node]; + ERR_FAIL_INDEX(p_input_index, input->get_input_count()); + + input->set_input_connection(p_input_index, StringName()); +} + +float AnimationNodeBlendTree::get_connection_activity(const StringName &p_input_node, int p_input_index) const { + + ERR_FAIL_COND_V(!nodes.has(p_input_node), 0); + + Ref<AnimationNode> input = nodes[p_input_node]; + ERR_FAIL_INDEX_V(p_input_index, input->get_input_count(), 0); + + return input->get_input_activity(p_input_index); +} + +AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const { + + if (!nodes.has(p_output_node) || p_output_node == SceneStringNames::get_singleton()->output) { + return CONNECTION_ERROR_NO_OUTPUT; + } + + if (!nodes.has(p_input_node)) { + return CONNECTION_ERROR_NO_INPUT; + } + + if (!nodes.has(p_input_node)) { + return CONNECTION_ERROR_SAME_NODE; + } + + Ref<AnimationNode> input = nodes[p_input_node]; + + if (p_input_index < 0 || p_input_index >= input->get_input_count()) { + return CONNECTION_ERROR_NO_INPUT_INDEX; + } + + if (input->get_input_connection(p_input_index) != StringName()) { + return CONNECTION_ERROR_CONNECTION_EXISTS; + } + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + StringName output = node->get_input_connection(i); + if (output == p_output_node) { + return CONNECTION_ERROR_CONNECTION_EXISTS; + } + } + } + return CONNECTION_OK; +} + +void AnimationNodeBlendTree::get_node_connections(List<NodeConnection> *r_connections) const { + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + StringName output = node->get_input_connection(i); + if (output != StringName()) { + NodeConnection nc; + nc.input_node = E->key(); + nc.input_index = i; + nc.output_node = output; + r_connections->push_back(nc); + } + } + } +} + +String AnimationNodeBlendTree::get_caption() const { + return "BlendTree"; +} + +float AnimationNodeBlendTree::process(float p_time, bool p_seek) { + + Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output]; + return blend_node(output, p_time, p_seek, 1.0); +} + +void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) { + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + r_list->push_back(E->key()); + } +} + +void AnimationNodeBlendTree::set_graph_offset(const Vector2 &p_graph_offset) { + + graph_offset = p_graph_offset; +} + +Vector2 AnimationNodeBlendTree::get_graph_offset() const { + + return graph_offset; +} + +void AnimationNodeBlendTree::set_tree(AnimationTree *p_player) { + + AnimationNode::set_tree(p_player); + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + node->set_tree(p_player); + } +} + +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); + + if (what == "node") { + Ref<AnimationNode> anode = p_value; + if (anode.is_valid()) { + add_node(node_name, p_value); + } + return true; + } + + if (what == "position") { + + if (nodes.has(node_name)) { + nodes[node_name]->set_position(p_value); + } + return true; + } + } else if (name == "node_connections") { + + Array conns = p_value; + ERR_FAIL_COND_V(conns.size() % 3 != 0, false); + + for (int i = 0; i < conns.size(); i += 3) { + connect_node(conns[i], conns[i + 1], conns[i + 2]); + } + return true; + } + + return false; +} + +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); + + if (what == "node") { + if (nodes.has(node_name)) { + r_ret = nodes[node_name]; + return true; + } + } + + if (what == "position") { + + if (nodes.has(node_name)) { + r_ret = nodes[node_name]->get_position(); + return true; + } + } + } else if (name == "node_connections") { + List<NodeConnection> nc; + get_node_connections(&nc); + Array conns; + conns.resize(nc.size() * 3); + + int idx = 0; + for (List<NodeConnection>::Element *E = nc.front(); E; E = E->next()) { + conns[idx * 3 + 0] = E->get().input_node; + conns[idx * 3 + 1] = E->get().input_index; + conns[idx * 3 + 2] = E->get().output_node; + idx++; + } + + r_ret = conns; + return true; + } + + return false; +} +void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) const { + + List<StringName> names; + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + names.push_back(E->key()); + } + names.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + String name = E->get(); + if (name != "output") { + p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } + + p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); +} + +void AnimationNodeBlendTree::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeBlendTree::add_node); + ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeBlendTree::get_node); + ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeBlendTree::remove_node); + ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeBlendTree::rename_node); + ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeBlendTree::has_node); + ClassDB::bind_method(D_METHOD("connect_node", "input_node", "input_index", "output_node"), &AnimationNodeBlendTree::connect_node); + ClassDB::bind_method(D_METHOD("disconnect_node", "input_node", "input_index"), &AnimationNodeBlendTree::disconnect_node); + + ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeBlendTree::set_graph_offset); + ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeBlendTree::get_graph_offset); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_graph_offset", "get_graph_offset"); + + BIND_CONSTANT(CONNECTION_OK); + BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT); + BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT_INDEX); + BIND_CONSTANT(CONNECTION_ERROR_NO_OUTPUT); + BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE); + BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS); +} + +AnimationNodeBlendTree::AnimationNodeBlendTree() { + + Ref<AnimationNodeOutput> output; + output.instance(); + output->set_position(Vector2(300, 150)); + output->set_parent(this); + nodes["output"] = output; +} + +AnimationNodeBlendTree::~AnimationNodeBlendTree() { + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + E->get()->set_parent(NULL); + E->get()->set_tree(NULL); + } +} diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h new file mode 100644 index 0000000000..e86cc2e823 --- /dev/null +++ b/scene/animation/animation_blend_tree.h @@ -0,0 +1,352 @@ +#ifndef ANIMATION_BLEND_TREE_H +#define ANIMATION_BLEND_TREE_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeAnimation : public AnimationRootNode { + + GDCLASS(AnimationNodeAnimation, AnimationRootNode); + + StringName animation; + + uint64_t last_version; + float time; + float step; + bool skip; + +protected: + void _validate_property(PropertyInfo &property) const; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + + void set_animation(const StringName &p_name); + StringName get_animation() const; + + float get_playback_time() const; + + AnimationNodeAnimation(); +}; + +class AnimationNodeOneShot : public AnimationNode { + GDCLASS(AnimationNodeOneShot, AnimationNode); + +public: + enum MixMode { + MIX_MODE_BLEND, + MIX_MODE_ADD + }; + +private: + bool active; + bool do_start; + float fade_in; + float fade_out; + + bool autorestart; + float autorestart_delay; + float autorestart_random_delay; + MixMode mix; + + float time; + float remaining; + float autorestart_remaining; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_fadein_time(float p_time); + void set_fadeout_time(float p_time); + + float get_fadein_time() const; + float 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); + + bool has_autorestart() const; + float get_autorestart_delay() const; + float get_autorestart_random_delay() const; + + void set_mix_mode(MixMode p_mix); + MixMode get_mix_mode() const; + + void start(); + void stop(); + bool is_active() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + virtual float process(float p_time, bool p_seek); + + AnimationNodeOneShot(); +}; + +VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode) + +class AnimationNodeAdd2 : public AnimationNode { + GDCLASS(AnimationNodeAdd2, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + virtual float process(float p_time, bool p_seek); + + AnimationNodeAdd2(); +}; + +class AnimationNodeAdd3 : public AnimationNode { + GDCLASS(AnimationNodeAdd3, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + virtual float process(float p_time, bool p_seek); + + AnimationNodeAdd3(); +}; + +class AnimationNodeBlend2 : public AnimationNode { + GDCLASS(AnimationNodeBlend2, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + AnimationNodeBlend2(); +}; + +class AnimationNodeBlend3 : public AnimationNode { + GDCLASS(AnimationNodeBlend3, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + float process(float p_time, bool p_seek); + AnimationNodeBlend3(); +}; + +class AnimationNodeTimeScale : public AnimationNode { + GDCLASS(AnimationNodeTimeScale, AnimationNode); + + float scale; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_scale(float p_scale); + float get_scale() const; + + float process(float p_time, bool p_seek); + + AnimationNodeTimeScale(); +}; + +class AnimationNodeTimeSeek : public AnimationNode { + GDCLASS(AnimationNodeTimeSeek, AnimationNode); + + float seek_pos; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_seek_pos(float p_sec); + float get_seek_pos() const; + + float process(float p_time, bool p_seek); + + AnimationNodeTimeSeek(); +}; + +class AnimationNodeTransition : public AnimationNode { + GDCLASS(AnimationNodeTransition, AnimationNode); + + enum { + MAX_INPUTS = 32 + }; + struct InputData { + + String name; + bool auto_advance; + InputData() { auto_advance = false; } + }; + + InputData inputs[MAX_INPUTS]; + int enabled_inputs; + + float prev_time; + float prev_xfading; + int prev; + bool switched; + + float time; + int current; + + float xfade; + + void _update_inputs(); + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; + +public: + virtual String get_caption() const; + + void set_enabled_inputs(int p_inputs); + int get_enabled_inputs(); + + void set_input_as_auto_advance(int p_input, bool p_enable); + bool is_input_set_as_auto_advance(int p_input) const; + + void set_input_caption(int p_input, const String &p_name); + String get_input_caption(int p_input) const; + + void set_current(int p_current); + int get_current() const; + + void set_cross_fade_time(float p_fade); + float get_cross_fade_time() const; + + float process(float p_time, bool p_seek); + + AnimationNodeTransition(); +}; + +class AnimationNodeOutput : public AnimationNode { + GDCLASS(AnimationNodeOutput, AnimationNode) +public: + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + AnimationNodeOutput(); +}; + +///// + +class AnimationNodeBlendTree : public AnimationRootNode { + GDCLASS(AnimationNodeBlendTree, AnimationRootNode) + + Map<StringName, Ref<AnimationNode> > nodes; + + Vector2 graph_offset; + +protected: + static void _bind_methods(); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + enum ConnectionError { + CONNECTION_OK, + CONNECTION_ERROR_NO_INPUT, + CONNECTION_ERROR_NO_INPUT_INDEX, + CONNECTION_ERROR_NO_OUTPUT, + CONNECTION_ERROR_SAME_NODE, + CONNECTION_ERROR_CONNECTION_EXISTS, + //no need to check for cycles due to tree topology + }; + + void add_node(const StringName &p_name, Ref<AnimationNode> p_node); + Ref<AnimationNode> get_node(const StringName &p_name) const; + void remove_node(const StringName &p_name); + void rename_node(const StringName &p_name, const StringName &p_new_name); + bool has_node(const StringName &p_name) const; + StringName get_node_name(const Ref<AnimationNode> &p_node) const; + + void connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node); + void disconnect_node(const StringName &p_node, int p_input_index); + float get_connection_activity(const StringName &p_input_node, int p_input_index) const; + + struct NodeConnection { + StringName input_node; + int input_index; + StringName output_node; + }; + + ConnectionError can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const; + void get_node_connections(List<NodeConnection> *r_connections) const; + + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + + void get_node_list(List<StringName> *r_list); + + void set_graph_offset(const Vector2 &p_graph_offset); + Vector2 get_graph_offset() const; + + virtual void set_tree(AnimationTree *p_player); + AnimationNodeBlendTree(); + ~AnimationNodeBlendTree(); +}; + +VARIANT_ENUM_CAST(AnimationNodeBlendTree::ConnectionError) + +#endif // ANIMATION_BLEND_TREE_H diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp new file mode 100644 index 0000000000..c5ad980806 --- /dev/null +++ b/scene/animation/animation_node_state_machine.cpp @@ -0,0 +1,790 @@ +#include "animation_node_state_machine.h" + +///////////////////////////////////////////////// + +void AnimationNodeStateMachineTransition::set_switch_mode(SwitchMode p_mode) { + + switch_mode = p_mode; +} + +AnimationNodeStateMachineTransition::SwitchMode AnimationNodeStateMachineTransition::get_switch_mode() const { + + return switch_mode; +} + +void AnimationNodeStateMachineTransition::set_auto_advance(bool p_enable) { + auto_advance = p_enable; +} + +bool AnimationNodeStateMachineTransition::has_auto_advance() const { + return auto_advance; +} + +void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) { + + ERR_FAIL_COND(p_xfade < 0); + xfade = p_xfade; + emit_changed(); +} + +float AnimationNodeStateMachineTransition::get_xfade_time() const { + return xfade; +} + +void AnimationNodeStateMachineTransition::set_disabled(bool p_disabled) { + disabled = p_disabled; + emit_changed(); +} + +bool AnimationNodeStateMachineTransition::is_disabled() const { + return disabled; +} + +void AnimationNodeStateMachineTransition::set_priority(int p_priority) { + priority = p_priority; + emit_changed(); +} + +int AnimationNodeStateMachineTransition::get_priority() const { + return priority; +} + +void AnimationNodeStateMachineTransition::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_switch_mode", "mode"), &AnimationNodeStateMachineTransition::set_switch_mode); + ClassDB::bind_method(D_METHOD("get_switch_mode"), &AnimationNodeStateMachineTransition::get_switch_mode); + + ClassDB::bind_method(D_METHOD("set_auto_advance", "auto_advance"), &AnimationNodeStateMachineTransition::set_auto_advance); + ClassDB::bind_method(D_METHOD("has_auto_advance"), &AnimationNodeStateMachineTransition::has_auto_advance); + + ClassDB::bind_method(D_METHOD("set_xfade_time", "secs"), &AnimationNodeStateMachineTransition::set_xfade_time); + ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeStateMachineTransition::get_xfade_time); + + ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &AnimationNodeStateMachineTransition::set_disabled); + ClassDB::bind_method(D_METHOD("is_disabled"), &AnimationNodeStateMachineTransition::is_disabled); + + ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority); + ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,AtEnd"), "set_switch_mode", "get_switch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); + + BIND_CONSTANT(SWITCH_MODE_IMMEDIATE); + BIND_CONSTANT(SWITCH_MODE_SYNC); + BIND_CONSTANT(SWITCH_MODE_AT_END); +} + +AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { + + switch_mode = SWITCH_MODE_IMMEDIATE; + auto_advance = false; + xfade = 0; + disabled = false; + priority = 1; +} + +/////////////////////////////////////////////////////// +void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node) { + + ERR_FAIL_COND(states.has(p_name)); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_node->get_tree() != NULL); + ERR_FAIL_COND(String(p_name).find("/") != -1); + states[p_name] = p_node; + + p_node->set_parent(this); + p_node->set_tree(get_tree()); + + emit_changed(); +} + +Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const { + + ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>()); + + return states[p_name]; +} + +StringName AnimationNodeStateMachine::get_node_name(const Ref<AnimationNode> &p_node) const { + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + if (E->get() == p_node) { + return E->key(); + } + } + + ERR_FAIL_V(StringName()); +} + +bool AnimationNodeStateMachine::has_node(const StringName &p_name) const { + return states.has(p_name); +} +void AnimationNodeStateMachine::remove_node(const StringName &p_name) { + + ERR_FAIL_COND(!states.has(p_name)); + + { + //erase node connections + Ref<AnimationNode> node = states[p_name]; + for (int i = 0; i < node->get_input_count(); i++) { + node->set_input_connection(i, StringName()); + } + node->set_parent(NULL); + node->set_tree(NULL); + } + + states.erase(p_name); + path.erase(p_name); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name || transitions[i].to == p_name) { + transitions.remove(i); + i--; + } + } + + if (start_node == p_name) { + start_node = StringName(); + } + + if (end_node == p_name) { + end_node = StringName(); + } + + if (playing && current == p_name) { + stop(); + } + emit_changed(); +} + +void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) { + + ERR_FAIL_COND(!states.has(p_name)); + ERR_FAIL_COND(states.has(p_new_name)); + + states[p_new_name] = states[p_name]; + states.erase(p_name); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name) { + transitions[i].from = p_new_name; + } + + if (transitions[i].to == p_name) { + transitions[i].to = p_new_name; + } + } + + if (start_node == p_name) { + start_node = p_new_name; + } + + if (end_node == p_name) { + end_node = p_new_name; + } + + if (playing && current == p_name) { + current = p_new_name; + } + + path.clear(); //clear path +} + +void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { + + List<StringName> nodes; + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + nodes.push_back(E->key()); + } + nodes.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) { + r_nodes->push_back(E->get()); + } +} + +bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) + return true; + } + return false; +} + +int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) + return i; + } + return -1; +} + +void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) { + + ERR_FAIL_COND(p_from == p_to); + ERR_FAIL_COND(!states.has(p_from)); + ERR_FAIL_COND(!states.has(p_to)); + ERR_FAIL_COND(p_transition.is_null()); + + for (int i = 0; i < transitions.size(); i++) { + ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to); + } + + Transition tr; + tr.from = p_from; + tr.to = p_to; + tr.transition = p_transition; + + transitions.push_back(tr); +} + +Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const { + ERR_FAIL_INDEX_V(p_transition, transitions.size(), Ref<AnimationNodeStateMachineTransition>()); + return transitions[p_transition].transition; +} +StringName AnimationNodeStateMachine::get_transition_from(int p_transition) const { + + ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName()); + return transitions[p_transition].from; +} +StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const { + + ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName()); + return transitions[p_transition].to; +} + +int AnimationNodeStateMachine::get_transition_count() const { + + return transitions.size(); +} +void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) { + transitions.remove(i); + return; + } + } + + if (playing) { + path.clear(); + } +} + +void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) { + + transitions.remove(p_transition); + if (playing) { + path.clear(); + } +} + +void AnimationNodeStateMachine::set_start_node(const StringName &p_node) { + + ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); + start_node = p_node; +} + +String AnimationNodeStateMachine::get_start_node() const { + + return start_node; +} + +void AnimationNodeStateMachine::set_end_node(const StringName &p_node) { + + ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); + end_node = p_node; +} + +String AnimationNodeStateMachine::get_end_node() const { + + return end_node; +} + +void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) { + graph_offset = p_offset; +} + +Vector2 AnimationNodeStateMachine::get_graph_offset() const { + return graph_offset; +} + +float AnimationNodeStateMachine::process(float p_time, bool p_seek) { + + //if not playing and it can restart, then restart + if (!playing) { + if (start_node) { + start(start_node); + } else { + return 0; + } + } + + bool do_start = (p_seek && p_time == 0) || play_start || current == StringName(); + + if (do_start) { + + if (start_node != StringName() && p_seek && p_time == 0) { + current = start_node; + } + + len_current = blend_node(states[current], 0, true, 1.0, FILTER_IGNORE, false); + pos_current = 0; + loops_current = 0; + play_start = false; + } + + float fade_blend = 1.0; + + if (fading_from != StringName()) { + + if (!p_seek) { + fading_pos += p_time; + } + fade_blend = MIN(1.0, fading_pos / fading_time); + if (fade_blend >= 1.0) { + fading_from = StringName(); + } + } + + float rem = blend_node(states[current], p_time, p_seek, fade_blend, FILTER_IGNORE, false); + + if (fading_from != StringName()) { + + blend_node(states[fading_from], p_time, p_seek, 1.0 - fade_blend, FILTER_IGNORE, false); + } + + //guess playback position + if (rem > len_current) { // weird but ok + len_current = rem; + } + + { //advance and loop check + + float next_pos = len_current - rem; + + if (next_pos < pos_current) { + loops_current++; + } + pos_current = next_pos; //looped + } + + //find next + StringName next; + float next_xfade = 0; + AnimationNodeStateMachineTransition::SwitchMode switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE; + + if (path.size()) { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current && transitions[i].to == path[0]) { + next_xfade = transitions[i].transition->get_xfade_time(); + switch_mode = transitions[i].transition->get_switch_mode(); + next = path[0]; + } + } + } else { + float priority_best = 1e20; + int auto_advance_to = -1; + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current && transitions[i].transition->has_auto_advance()) { + + if (transitions[i].transition->get_priority() < priority_best) { + auto_advance_to = i; + } + } + } + + if (auto_advance_to != -1) { + next = transitions[auto_advance_to].to; + next_xfade = transitions[auto_advance_to].transition->get_xfade_time(); + switch_mode = transitions[auto_advance_to].transition->get_switch_mode(); + } + } + + //if next, see when to transition + if (next != StringName()) { + + bool goto_next = false; + + if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE) { + goto_next = fading_from == StringName(); + } else { + goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0; + if (loops_current > 0) { + next_xfade = 0; + } + } + + if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped + + if (next_xfade) { + //time to fade, baby + fading_from = current; + fading_time = next_xfade; + fading_pos = 0; + } else { + fading_from = StringName(); + fading_pos = 0; + } + + if (path.size()) { //if it came from path, remove path + path.remove(0); + } + current = next; + if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { + len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false); + pos_current = MIN(pos_current, len_current); + blend_node(states[current], pos_current, true, 0, FILTER_IGNORE, false); + + } else { + len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false); + pos_current = 0; + } + + rem = len_current; //so it does not show 0 on transition + loops_current = 0; + } + } + + //compute time left for transitions by using the end node + + if (end_node != StringName() && end_node != current) { + + rem = blend_node(states[end_node], 0, true, 0, FILTER_IGNORE, false); + } + + return rem; +} + +bool AnimationNodeStateMachine::travel(const StringName &p_state) { + ERR_FAIL_COND_V(!playing, false); + ERR_FAIL_COND_V(!states.has(p_state), false); + ERR_FAIL_COND_V(!states.has(current), false); + + path.clear(); //a new one will be needed + + if (current == p_state) + return true; //nothing to do + + loops_current = 0; // reset loops, so fade does not happen immediately + + Vector2 current_pos = states[current]->get_position(); + Vector2 target_pos = states[p_state]->get_position(); + + Map<StringName, AStarCost> cost_map; + + List<int> open_list; + + //build open list + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current) { + open_list.push_back(i); + float cost = states[transitions[i].to]->get_position().distance_to(current_pos); + cost *= transitions[i].transition->get_priority(); + AStarCost ap; + ap.prev = current; + ap.distance = cost; + cost_map[transitions[i].to] = ap; + + if (transitions[i].to == p_state) { //prematurely found it! :D + path.push_back(p_state); + return true; + } + } + } + + //begin astar + bool found_route = false; + while (!found_route) { + + if (open_list.size() == 0) { + return false; //no path found + } + + //find the last cost transition + List<int>::Element *least_cost_transition = NULL; + float least_cost = 1e20; + + for (List<int>::Element *E = open_list.front(); E; E = E->next()) { + + float cost = cost_map[transitions[E->get()].to].distance; + cost += states[transitions[E->get()].to]->get_position().distance_to(target_pos); + + if (cost < least_cost) { + least_cost_transition = E; + } + } + + StringName transition_prev = transitions[least_cost_transition->get()].from; + StringName transition = transitions[least_cost_transition->get()].to; + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from != transition || transitions[i].to == transition_prev) { + continue; //not interested on those + } + + float distance = states[transitions[i].from]->get_position().distance_to(states[transitions[i].to]->get_position()); + distance *= transitions[i].transition->get_priority(); + distance += cost_map[transitions[i].from].distance; + + if (cost_map.has(transitions[i].to)) { + //oh this was visited already, can we win the cost? + if (distance < cost_map[transitions[i].to].distance) { + cost_map[transitions[i].to].distance = distance; + cost_map[transitions[i].to].prev = transitions[i].from; + } + } else { + //add to open list + AStarCost ac; + ac.prev = transitions[i].from; + ac.distance = distance; + cost_map[transitions[i].to] = ac; + + open_list.push_back(i); + + if (transitions[i].to == p_state) { + found_route = true; + break; + } + } + } + + if (found_route) { + break; + } + + open_list.erase(least_cost_transition); + } + + //make path + StringName at = p_state; + while (at != current) { + path.push_back(at); + at = cost_map[at].prev; + } + + path.invert(); + + return true; +} + +void AnimationNodeStateMachine::start(const StringName &p_state) { + + ERR_FAIL_COND(!states.has(p_state)); + path.clear(); + current = p_state; + playing = true; + play_start = true; +} +void AnimationNodeStateMachine::stop() { + playing = false; + play_start = false; + current = StringName(); +} +bool AnimationNodeStateMachine::is_playing() const { + + return playing; +} +StringName AnimationNodeStateMachine::get_current_node() const { + if (!playing) { + return StringName(); + } + + return current; +} + +StringName AnimationNodeStateMachine::get_blend_from_node() const { + if (!playing) { + return StringName(); + } + + return fading_from; +} + +float AnimationNodeStateMachine::get_current_play_pos() const { + return pos_current; +} +float AnimationNodeStateMachine::get_current_length() const { + return len_current; +} + +Vector<StringName> AnimationNodeStateMachine::get_travel_path() const { + return path; +} +String AnimationNodeStateMachine::get_caption() const { + return "StateMachine"; +} + +void AnimationNodeStateMachine::_notification(int p_what) { +} + +void AnimationNodeStateMachine::set_tree(AnimationTree *p_player) { + + AnimationNode::set_tree(p_player); + + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + Ref<AnimationRootNode> node = E->get(); + node->set_tree(p_player); + } +} + +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); + + if (what == "node") { + Ref<AnimationNode> anode = p_value; + if (anode.is_valid()) { + add_node(node_name, p_value); + } + return true; + } + + if (what == "position") { + + if (states.has(node_name)) { + states[node_name]->set_position(p_value); + } + return true; + } + } else if (name == "transitions") { + + Array trans = p_value; + ERR_FAIL_COND_V(trans.size() % 3 != 0, false); + + for (int i = 0; i < trans.size(); i += 3) { + add_transition(trans[i], trans[i + 1], trans[i + 2]); + } + return true; + } else if (name == "start_node") { + set_start_node(p_value); + return true; + } else if (name == "end_node") { + set_end_node(p_value); + return true; + } else if (name == "graph_offset") { + set_graph_offset(p_value); + return true; + } + + return false; +} + +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); + + if (what == "node") { + if (states.has(node_name)) { + r_ret = states[node_name]; + return true; + } + } + + if (what == "position") { + + if (states.has(node_name)) { + r_ret = states[node_name]->get_position(); + return true; + } + } + } else if (name == "transitions") { + Array trans; + trans.resize(transitions.size() * 3); + + for (int i = 0; i < transitions.size(); i++) { + trans[i * 3 + 0] = transitions[i].from; + trans[i * 3 + 1] = transitions[i].to; + trans[i * 3 + 2] = transitions[i].transition; + } + + r_ret = trans; + return true; + } else if (name == "start_node") { + r_ret = get_start_node(); + return true; + } else if (name == "end_node") { + r_ret = get_end_node(); + return true; + } else if (name == "graph_offset") { + r_ret = get_graph_offset(); + return true; + } + + return false; +} +void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) const { + + List<StringName> names; + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + names.push_back(E->key()); + } + names.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + String name = E->get(); + p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } + + p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); +} + +void AnimationNodeStateMachine::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeStateMachine::add_node); + ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeStateMachine::get_node); + ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeStateMachine::remove_node); + ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeStateMachine::rename_node); + ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeStateMachine::has_node); + ClassDB::bind_method(D_METHOD("get_node_name", "node"), &AnimationNodeStateMachine::get_node_name); + + ClassDB::bind_method(D_METHOD("has_transition", "from", "to"), &AnimationNodeStateMachine::add_transition); + ClassDB::bind_method(D_METHOD("add_transition", "from", "to", "transition"), &AnimationNodeStateMachine::add_transition); + ClassDB::bind_method(D_METHOD("get_transition", "idx"), &AnimationNodeStateMachine::get_transition); + ClassDB::bind_method(D_METHOD("get_transition_from", "idx"), &AnimationNodeStateMachine::get_transition_from); + ClassDB::bind_method(D_METHOD("get_transition_to", "idx"), &AnimationNodeStateMachine::get_transition_to); + ClassDB::bind_method(D_METHOD("get_transition_count"), &AnimationNodeStateMachine::get_transition_count); + ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index); + ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition); + + ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node); + ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node); + + ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node); + ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node); + + ClassDB::bind_method(D_METHOD("set_graph_offset", "name"), &AnimationNodeStateMachine::set_graph_offset); + ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); + + ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachine::travel); + ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachine::start); + ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachine::stop); + ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachine::is_playing); + ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachine::get_current_node); + ClassDB::bind_method(D_METHOD("get_travel_path"), &AnimationNodeStateMachine::get_travel_path); +} + +AnimationNodeStateMachine::AnimationNodeStateMachine() { + + play_start = false; + + playing = false; + len_current = 0; + + fading_time = 0; +} diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h new file mode 100644 index 0000000000..e7357e09ea --- /dev/null +++ b/scene/animation/animation_node_state_machine.h @@ -0,0 +1,142 @@ +#ifndef ANIMATION_NODE_STATE_MACHINE_H +#define ANIMATION_NODE_STATE_MACHINE_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeStateMachineTransition : public Resource { + GDCLASS(AnimationNodeStateMachineTransition, Resource) +public: + enum SwitchMode { + SWITCH_MODE_IMMEDIATE, + SWITCH_MODE_SYNC, + SWITCH_MODE_AT_END, + }; + +private: + SwitchMode switch_mode; + bool auto_advance; + float xfade; + bool disabled; + int priority; + +protected: + static void _bind_methods(); + +public: + void set_switch_mode(SwitchMode p_mode); + SwitchMode get_switch_mode() const; + + void set_auto_advance(bool p_enable); + bool has_auto_advance() const; + + void set_xfade_time(float p_xfade); + float get_xfade_time() const; + + void set_disabled(bool p_disabled); + bool is_disabled() const; + + void set_priority(int p_priority); + int get_priority() const; + + AnimationNodeStateMachineTransition(); +}; + +VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode) + +class AnimationNodeStateMachine : public AnimationRootNode { + + GDCLASS(AnimationNodeStateMachine, AnimationRootNode); + +private: + Map<StringName, Ref<AnimationRootNode> > states; + + struct Transition { + + StringName from; + StringName to; + Ref<AnimationNodeStateMachineTransition> transition; + }; + + struct AStarCost { + float distance; + StringName prev; + }; + + Vector<Transition> transitions; + + float len_total; + + float len_current; + float pos_current; + int loops_current; + + bool play_start; + StringName start_node; + StringName end_node; + + Vector2 graph_offset; + + StringName current; + + StringName fading_from; + float fading_time; + float fading_pos; + + Vector<StringName> path; + bool playing; + +protected: + void _notification(int p_what); + static void _bind_methods(); + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void add_node(const StringName &p_name, Ref<AnimationNode> p_node); + Ref<AnimationNode> get_node(const StringName &p_name) const; + void remove_node(const StringName &p_name); + void rename_node(const StringName &p_name, const StringName &p_new_name); + bool has_node(const StringName &p_name) const; + StringName get_node_name(const Ref<AnimationNode> &p_node) const; + void get_node_list(List<StringName> *r_nodes) const; + + bool has_transition(const StringName &p_from, const StringName &p_to) const; + int find_transition(const StringName &p_from, const StringName &p_to) const; + void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition); + Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const; + StringName get_transition_from(int p_transition) const; + StringName get_transition_to(int p_transition) const; + int get_transition_count() const; + void remove_transition_by_index(int p_transition); + void remove_transition(const StringName &p_from, const StringName &p_to); + + void set_start_node(const StringName &p_node); + String get_start_node() const; + + void set_end_node(const StringName &p_node); + String get_end_node() const; + + void set_graph_offset(const Vector2 &p_offset); + Vector2 get_graph_offset() const; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + bool travel(const StringName &p_state); + void start(const StringName &p_state); + void stop(); + bool is_playing() const; + StringName get_current_node() const; + StringName get_blend_from_node() const; + Vector<StringName> get_travel_path() const; + float get_current_play_pos() const; + float get_current_length() const; + + virtual void set_tree(AnimationTree *p_player); + + AnimationNodeStateMachine(); +}; + +#endif // ANIMATION_NODE_STATE_MACHINE_H diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 9db4a5fb04..eac2c8d0c1 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -33,7 +33,7 @@ #include "engine.h" #include "message_queue.h" #include "scene/scene_string_names.h" - +#include "servers/audio/audio_stream.h" #ifdef TOOLS_ENABLED void AnimatedValuesBackup::update_skeletons() { @@ -182,8 +182,8 @@ void AnimationPlayer::_notification(int p_what) { if (!processing) { //make sure that a previous process state was not saved //only process if "processing" is set - set_physics_process(false); - set_process(false); + set_physics_process_internal(false); + set_process_internal(false); } //_set_process(false); clear_caches(); @@ -246,8 +246,9 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) { if (a->track_get_path(i).get_subname_count() == 1 && Object::cast_to<Skeleton>(child)) { - bone_idx = Object::cast_to<Skeleton>(child)->find_bone(a->track_get_path(i).get_subname(0)); - if (bone_idx == -1) { + Skeleton *sk = Object::cast_to<Skeleton>(child); + bone_idx = sk->find_bone(a->track_get_path(i).get_subname(0)); + if (bone_idx == -1 || sk->is_bone_ignore_animation(bone_idx)) { continue; } @@ -324,10 +325,27 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) { p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa; } } + + if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) { + + if (!p_anim->node_cache[i]->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) { + + TrackNodeCache::BezierAnim ba; + String path = leftover_path[leftover_path.size() - 1]; + Vector<String> index = path.split("."); + for (int j = 0; j < index.size(); j++) { + ba.bezier_property.push_back(index[j]); + } + ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child; + ba.owner = p_anim->node_cache[i]; + + p_anim->node_cache[i]->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba; + } + } } } -void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete) { +void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) { _ensure_node_caches(p_anim); ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count()); @@ -393,7 +411,51 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float TrackNodeCache::PropertyAnim *pa = &E->get(); - if (a->value_track_get_update_mode(i) == Animation::UPDATE_CONTINUOUS || (p_delta == 0 && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek + Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); + + if (update_mode == Animation::UPDATE_CAPTURE) { + + if (p_started) { + pa->capture = pa->object->get_indexed(pa->subpath); + } + + int key_count = a->track_get_key_count(i); + if (key_count == 0) + continue; //eeh not worth it + + float first_key_time = a->track_get_key_time(i, 0); + float transition = 1.0; + int first_key = 0; + + if (first_key_time == 0.0) { + //ignore, use for transition + if (key_count == 1) + continue; //with one key we cant do anything + transition = a->track_get_key_transition(i, 0); + first_key_time = a->track_get_key_time(i, 1); + first_key = 1; + } + + if (p_time < first_key_time) { + float c = Math::ease(p_time / first_key_time, transition); + Variant first_value = a->track_get_key_value(i, first_key); + Variant interp_value; + Variant::interpolate(pa->capture, first_value, c, interp_value); + + 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); + } + + continue; //handled + } + } + + if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE || (p_delta == 0 && update_mode == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek Variant value = a->value_track_interpolate(i, p_time); @@ -414,7 +476,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float Variant::interpolate(pa->value_accum, value, p_interp, pa->value_accum); } - } else if (p_allow_discrete && p_delta != 0) { + } else if (p_is_current && p_delta != 0) { List<int> indices; a->value_track_get_key_indices(i, p_time, p_delta, &indices); @@ -469,9 +531,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float if (!nc->node) continue; - if (p_delta == 0) + if (p_delta == 0) { continue; - if (!p_allow_discrete) + } + if (!p_is_current) break; List<int> indices; @@ -499,14 +562,197 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float } } break; + case Animation::TYPE_BEZIER: { + + if (!nc->node) + continue; + + Map<StringName, TrackNodeCache::BezierAnim>::Element *E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames()); + ERR_CONTINUE(!E); //should it continue, or create a new one? + + TrackNodeCache::BezierAnim *ba = &E->get(); + + float bezier = a->bezier_track_interpolate(i, p_time); + if (ba->accum_pass != accum_pass) { + ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX); + cache_update_bezier[cache_update_bezier_size++] = ba; + ba->bezier_accum = bezier; + ba->accum_pass = accum_pass; + } else { + ba->bezier_accum = Math::lerp(ba->bezier_accum, bezier, p_interp); + } + + } break; + case Animation::TYPE_AUDIO: { + + if (!nc->node) + continue; + if (p_delta == 0) { + continue; + } + + if (p_seeked) { + //find whathever should be playing + int idx = a->track_find_key(i, p_time); + if (idx < 0) + continue; + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + start_ofs += p_time - a->track_get_key_time(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + if (start_ofs > len - end_ofs) { + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + continue; + } + + nc->node->call("set_stream", stream); + nc->node->call("play", start_ofs); + + nc->audio_playing = true; + playing_caches.insert(nc); + if (len && end_ofs > 0) { //force a end at a time + nc->audio_len = len - start_ofs - end_ofs; + } else { + nc->audio_len = 0; + } + + nc->audio_start = p_time; + } + + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + nc->node->call("set_stream", stream); + nc->node->call("play", start_ofs); + + nc->audio_playing = true; + playing_caches.insert(nc); + if (len && end_ofs > 0) { //force a end at a time + nc->audio_len = len - start_ofs - end_ofs; + } else { + nc->audio_len = 0; + } + + nc->audio_start = p_time; + } + } else if (nc->audio_playing) { + + bool loop = a->has_loop(); + + bool stop = false; + + if (!loop && p_time < nc->audio_start) { + stop = true; + } else if (nc->audio_len > 0) { + float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start; + + if (len > nc->audio_len) { + stop = true; + } + } + + if (stop) { + //time to stop + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + } + } + } + + } break; + case Animation::TYPE_ANIMATION: { + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(nc->node); + if (!player) + continue; + + if (p_delta == 0 || p_seeked) { + //seek + int idx = a->track_find_key(i, p_time); + if (idx < 0) + continue; + + float pos = a->track_get_key_time(i, idx); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) + continue; + + Ref<Animation> anim = player->get_animation(anim_name); + + float at_anim_pos; + + if (anim->has_loop()) { + at_anim_pos = Math::fposmod(p_time - pos, anim->get_length()); //seek to loop + } else { + at_anim_pos = MAX(anim->get_length(), p_time - pos); //seek to end + } + + if (player->is_playing() || p_seeked) { + player->play(anim_name); + player->seek(at_anim_pos); + nc->animation_playing = true; + playing_caches.insert(nc); + } else { + player->set_assigned_animation(anim_name); + player->seek(at_anim_pos, true); + } + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) { + + if (playing_caches.has(nc)) { + playing_caches.erase(nc); + player->stop(); + nc->animation_playing = false; + } + } else { + player->play(anim_name); + nc->animation_playing = true; + playing_caches.insert(nc); + } + } + } + + } break; } } } -void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend) { +void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started) { float delta = p_delta * speed_scale * cd.speed_scale; - bool backwards = delta < 0; float next_pos = cd.pos + delta; float len = cd.from->animation->get_length(); @@ -524,6 +770,8 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f if (&cd == &playback.current) { + bool backwards = delta < 0; + if (!backwards && cd.pos <= len && next_pos == len /*&& playback.blend.empty()*/) { //playback finished end_reached = true; @@ -551,22 +799,25 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f cd.pos = next_pos; - _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current); + _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started); } -void AnimationPlayer::_animation_process2(float p_delta) { +void AnimationPlayer::_animation_process2(float p_delta, bool p_started) { Playback &c = playback; accum_pass++; - _animation_process_data(c.current, p_delta, 1.0f); + _animation_process_data(c.current, p_delta, 1.0f, c.seeked && p_delta != 0, p_started); + if (p_delta != 0) { + c.seeked = false; + } List<Blend>::Element *prev = NULL; for (List<Blend>::Element *E = c.blend.back(); E; E = prev) { Blend &b = E->get(); float blend = b.blend_left / b.blend_time; - _animation_process_data(b.data, p_delta, blend); + _animation_process_data(b.data, p_delta, blend, false, false); b.blend_left -= Math::absf(speed_scale * p_delta); @@ -579,20 +830,16 @@ void AnimationPlayer::_animation_process2(float p_delta) { } void AnimationPlayer::_animation_update_transforms() { + { + Transform t; + for (int i = 0; i < cache_update_size; i++) { - for (int i = 0; i < cache_update_size; i++) { - - TrackNodeCache *nc = cache_update[i]; + TrackNodeCache *nc = cache_update[i]; - ERR_CONTINUE(nc->accum_pass != accum_pass); + ERR_CONTINUE(nc->accum_pass != accum_pass); - if (nc->spatial) { - - Transform t; t.origin = nc->loc_accum; - t.basis = nc->rot_accum; - t.basis.scale(nc->scale_accum); - + t.basis.set_quat_scale(nc->rot_accum, nc->scale_accum); if (nc->skeleton && nc->bone_idx >= 0) { nc->skeleton->set_bone_pose(nc->bone_idx, t); @@ -654,6 +901,16 @@ void AnimationPlayer::_animation_update_transforms() { } cache_update_prop_size = 0; + + for (int i = 0; i < cache_update_bezier_size; i++) { + + TrackNodeCache::BezierAnim *ba = cache_update_bezier[i]; + + ERR_CONTINUE(ba->accum_pass != accum_pass); + ba->object->set_indexed(ba->bezier_property, ba->bezier_accum); + } + + cache_update_bezier_size = 0; } void AnimationPlayer::_animation_process(float p_delta) { @@ -662,7 +919,12 @@ void AnimationPlayer::_animation_process(float p_delta) { end_reached = false; end_notify = false; - _animation_process2(p_delta); + _animation_process2(p_delta, playback.started); + + if (playback.started) { + playback.started = false; + } + _animation_update_transforms(); if (end_reached) { if (queued.size()) { @@ -867,7 +1129,7 @@ void AnimationPlayer::queue(const StringName &p_name) { void AnimationPlayer::clear_queue() { queued.clear(); -}; +} void AnimationPlayer::play_backwards(const StringName &p_name, float p_custom_blend) { @@ -932,10 +1194,14 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float } } + _stop_playing_caches(); + c.current.from = &animation_set[name]; c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0; c.current.speed_scale = p_custom_scale; c.assigned = p_name; + c.seeked = false; + c.started = true; if (!end_reached) queued.clear(); @@ -1006,10 +1272,12 @@ String AnimationPlayer::get_assigned_animation() const { void AnimationPlayer::stop(bool p_reset) { + _stop_playing_caches(); Playback &c = playback; c.blend.clear(); if (p_reset) { c.current.from = NULL; + c.current.speed_scale = 1; } _set_process(false); queued.clear(); @@ -1024,6 +1292,13 @@ float AnimationPlayer::get_speed_scale() const { return speed_scale; } +float AnimationPlayer::get_playing_speed() const { + + if (!playing) { + return 0; + } + return speed_scale * playback.current.speed_scale; +} void AnimationPlayer::seek(float p_time, bool p_update) { @@ -1036,6 +1311,7 @@ void AnimationPlayer::seek(float p_time, bool p_update) { } playback.current.pos = p_time; + playback.seeked = true; if (p_update) { _animation_process(0); } @@ -1078,6 +1354,25 @@ float AnimationPlayer::get_current_animation_length() const { void AnimationPlayer::_animation_changed() { clear_caches(); + emit_signal("caches_cleared"); +} + +void AnimationPlayer::_stop_playing_caches() { + + for (Set<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) { + + if (E->get()->node && E->get()->audio_playing) { + E->get()->node->call("stop"); + } + if (E->get()->node && E->get()->animation_playing) { + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(E->get()->node); + if (!player) + continue; + player->stop(); + } + } + + playing_caches.clear(); } void AnimationPlayer::_node_removed(Node *p_node) { @@ -1087,6 +1382,8 @@ void AnimationPlayer::_node_removed(Node *p_node) { void AnimationPlayer::clear_caches() { + _stop_playing_caches(); + node_cache_map.clear(); for (Map<StringName, AnimationData>::Element *E = animation_set.front(); E; E = E->next()) { @@ -1096,6 +1393,7 @@ void AnimationPlayer::clear_caches() { cache_update_size = 0; cache_update_prop_size = 0; + cache_update_bezier_size = 0; } void AnimationPlayer::set_active(bool p_active) { @@ -1202,7 +1500,7 @@ NodePath AnimationPlayer::get_root() const { void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { String pf = p_function; - if (p_function == "play" || p_function == "remove_animation" || p_function == "has_animation" || p_function == "queue") { + if (p_function == "play" || p_function == "play_backwards" || p_function == "remove_animation" || p_function == "has_animation" || p_function == "queue") { List<StringName> al; get_animation_list(&al); for (List<StringName>::Element *E = al.front(); E; E = E->next()) { @@ -1315,6 +1613,7 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_speed_scale", "speed"), &AnimationPlayer::set_speed_scale); ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimationPlayer::get_speed_scale); + ClassDB::bind_method(D_METHOD("get_playing_speed"), &AnimationPlayer::get_playing_speed); ClassDB::bind_method(D_METHOD("set_autoplay", "name"), &AnimationPlayer::set_autoplay); ClassDB::bind_method(D_METHOD("get_autoplay"), &AnimationPlayer::get_autoplay); @@ -1351,6 +1650,7 @@ void AnimationPlayer::_bind_methods() { ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING, "anim_name"))); ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name"))); ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING, "anim_name"))); + ADD_SIGNAL(MethodInfo("caches_cleared")); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); @@ -1361,6 +1661,7 @@ AnimationPlayer::AnimationPlayer() { accum_pass = 1; cache_update_size = 0; cache_update_prop_size = 0; + cache_update_bezier_size = 0; speed_scale = 1; end_reached = false; end_notify = false; @@ -1370,6 +1671,8 @@ AnimationPlayer::AnimationPlayer() { root = SceneStringNames::get_singleton()->path_pp; playing = false; active = true; + playback.seeked = false; + playback.started = false; } AnimationPlayer::~AnimationPlayer() { diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index ef758bac44..49c73e54ad 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -98,6 +98,12 @@ private: Vector3 scale_accum; uint64_t accum_pass; + bool audio_playing; + float audio_start; + float audio_len; + + bool animation_playing; + struct PropertyAnim { TrackNodeCache *owner; @@ -106,6 +112,7 @@ private: Object *object; Variant value_accum; uint64_t accum_pass; + Variant capture; PropertyAnim() { accum_pass = 0; object = NULL; @@ -114,6 +121,22 @@ private: Map<StringName, PropertyAnim> property_anim; + struct BezierAnim { + + Vector<StringName> bezier_property; + TrackNodeCache *owner; + float bezier_accum; + Object *object; + uint64_t accum_pass; + BezierAnim() { + accum_pass = 0; + bezier_accum = 0; + object = NULL; + } + }; + + Map<StringName, BezierAnim> bezier_anim; + TrackNodeCache() { skeleton = NULL; spatial = NULL; @@ -121,6 +144,8 @@ private: accum_pass = 0; bone_idx = -1; node_2d = NULL; + audio_playing = false; + animation_playing = false; } }; @@ -146,6 +171,10 @@ private: int cache_update_size; TrackNodeCache::PropertyAnim *cache_update_prop[NODE_CACHE_UPDATE_MAX]; int cache_update_prop_size; + TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX]; + int cache_update_bezier_size; + Set<TrackNodeCache *> playing_caches; + Map<Ref<Animation>, int> used_anims; uint64_t accum_pass; @@ -202,6 +231,8 @@ private: List<Blend> blend; PlaybackData current; StringName assigned; + bool seeked; + bool started; } playback; List<StringName> queued; @@ -216,15 +247,16 @@ private: NodePath root; - void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete = true); + void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false); void _ensure_node_caches(AnimationData *p_anim); - void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend); - void _animation_process2(float p_delta); + void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started); + void _animation_process2(float p_delta, bool p_started); void _animation_update_transforms(); void _animation_process(float p_delta); void _node_removed(Node *p_node); + void _stop_playing_caches(); // bind helpers PoolVector<String> _get_animation_list() const { @@ -293,6 +325,7 @@ public: void set_speed_scale(float p_speed); float get_speed_scale() const; + float get_playing_speed() const; void set_autoplay(const String &p_name); String get_autoplay() const; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp new file mode 100644 index 0000000000..83ec9f819b --- /dev/null +++ b/scene/animation/animation_tree.cpp @@ -0,0 +1,1338 @@ +#include "animation_tree.h" +#include "animation_blend_tree.h" +#include "core/method_bind_ext.gen.inc" +#include "engine.h" +#include "scene/scene_string_names.h" +#include "servers/audio/audio_stream.h" + +void AnimationNode::blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend) { + + ERR_FAIL_COND(!state); + ERR_FAIL_COND(!state->player->has_animation(p_animation)); + + Ref<Animation> animation = state->player->get_animation(p_animation); + + if (animation.is_null()) { + + Ref<AnimationNodeBlendTree> btree = get_parent(); + if (btree.is_valid()) { + String name = btree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), name, p_animation)); + } else { + make_invalid(vformat(RTR("Invalid animation: '%s'."), p_animation)); + } + return; + } + + ERR_FAIL_COND(!animation.is_valid()); + + AnimationState anim_state; + anim_state.blend = p_blend; + anim_state.track_blends = &blends; + anim_state.delta = p_delta; + anim_state.time = p_time; + anim_state.animation = animation; + anim_state.seeked = p_seeked; + + state->animation_states.push_back(anim_state); +} + +float AnimationNode::_pre_process(State *p_state, float p_time, bool p_seek) { + state = p_state; + float t = process(p_time, p_seek); + state = NULL; + return t; +} + +void AnimationNode::make_invalid(const String &p_reason) { + ERR_FAIL_COND(!state); + state->valid = false; + if (state->invalid_reasons != String()) { + state->invalid_reasons += "\n"; + } + state->invalid_reasons += "- " + p_reason; +} + +float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); + ERR_FAIL_COND_V(!state, 0); + ERR_FAIL_COND_V(!get_tree(), 0); //should not happen, but used to catch bugs + + Ref<AnimationNodeBlendTree> tree = get_parent(); + + if (!tree.is_valid() && get_tree()->get_tree_root().ptr() != this) { + make_invalid(RTR("Can't blend input because node is not in a tree")); + return 0; + } + + ERR_FAIL_COND_V(!tree.is_valid(), 0); //should not happen + + StringName anim_name = inputs[p_input].connected_to; + + Ref<AnimationNode> node = tree->get_node(anim_name); + + if (node.is_null()) { + + String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), name)); + return 0; + } + + inputs[p_input].last_pass = state->last_pass; + + return _blend_node(node, p_time, p_seek, p_blend, p_filter, p_optimize, &inputs[p_input].activity); +} + +float AnimationNode::blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) { + + return _blend_node(p_node, p_time, p_seek, p_blend, p_filter, p_optimize); +} + +float AnimationNode::_blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, float *r_max) { + + ERR_FAIL_COND_V(!p_node.is_valid(), 0); + ERR_FAIL_COND_V(!state, 0); + + int blend_count = blends.size(); + + if (p_node->blends.size() != blend_count) { + p_node->blends.resize(blend_count); + } + + float *blendw = p_node->blends.ptrw(); + const float *blendr = blends.ptr(); + + bool any_valid = false; + + if (has_filter() && is_filter_enabled() && p_filter != FILTER_IGNORE) { + + for (int i = 0; i < blend_count; i++) { + blendw[i] = 0.0; //all to zero by default + } + + const NodePath *K = NULL; + while ((K = filter.next(K))) { + if (!state->track_map.has(*K)) { + continue; + } + int idx = state->track_map[*K]; + blendw[idx] = 1.0; //filtered goes to one + } + + switch (p_filter) { + case FILTER_IGNORE: + break; //will not happen anyway + case FILTER_PASS: { + //values filtered pass, the rest dont + for (int i = 0; i < blend_count; i++) { + if (blendw[i] == 0) //not filtered, does not pass + continue; + + blendw[i] = blendr[i] * p_blend; + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + + } break; + case FILTER_STOP: { + + //values filtered dont pass, the rest are blended + + for (int i = 0; i < blend_count; i++) { + if (blendw[i] > 0) //filtered, does not pass + continue; + + blendw[i] = blendr[i] * p_blend; + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + + } break; + case FILTER_BLEND: { + + //filtered values are blended, the rest are passed without blending + + for (int i = 0; i < blend_count; i++) { + if (blendw[i] == 1.0) { + blendw[i] = blendr[i] * p_blend; //filtered, blend + } else { + blendw[i] = blendr[i]; //not filtered, do not blend + } + + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + + } break; + } + } else { + for (int i = 0; i < blend_count; i++) { + + //regular blend + blendw[i] = blendr[i] * p_blend; + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + } + + if (r_max) { + *r_max = 0; + for (int i = 0; i < blend_count; i++) { + *r_max = MAX(*r_max, blendw[i]); + } + } + + if (!p_seek && p_optimize && !any_valid) //pointless to go on, all are zero + return 0; + + return p_node->_pre_process(state, p_time, p_seek); +} + +int AnimationNode::get_input_count() const { + + return inputs.size(); +} +String AnimationNode::get_input_name(int p_input) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), String()); + return inputs[p_input].name; +} + +float AnimationNode::get_input_activity(int p_input) const { + + ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); + if (!get_tree()) + return 0; + + if (get_tree()->get_last_process_pass() != inputs[p_input].last_pass) { + return 0; + } + return inputs[p_input].activity; +} +StringName AnimationNode::get_input_connection(int p_input) { + + ERR_FAIL_INDEX_V(p_input, inputs.size(), StringName()); + return inputs[p_input].connected_to; +} + +void AnimationNode::set_input_connection(int p_input, const StringName &p_connection) { + + ERR_FAIL_INDEX(p_input, inputs.size()); + inputs[p_input].connected_to = p_connection; +} + +String AnimationNode::get_caption() const { + + if (get_script_instance()) { + return get_script_instance()->call("get_caption"); + } + + return "Node"; +} + +void AnimationNode::add_input(const String &p_name) { + //root nodes cant add inputs + ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != NULL) + Input input; + ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1); + input.name = p_name; + input.activity = 0; + input.last_pass = 0; + inputs.push_back(input); + emit_changed(); +} + +void AnimationNode::set_input_name(int p_input, const String &p_name) { + ERR_FAIL_INDEX(p_input, inputs.size()); + ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1); + inputs[p_input].name = p_name; + emit_changed(); +} + +void AnimationNode::remove_input(int p_index) { + ERR_FAIL_INDEX(p_index, inputs.size()); + inputs.remove(p_index); + emit_changed(); +} + +void AnimationNode::_set_parent(Object *p_parent) { + set_parent(Object::cast_to<AnimationNode>(p_parent)); +} + +void AnimationNode::set_parent(AnimationNode *p_parent) { + parent = p_parent; //do not use ref because parent contains children + if (get_script_instance()) { + get_script_instance()->call("_parent_set", p_parent); + } +} + +Ref<AnimationNode> AnimationNode::get_parent() const { + if (parent) { + return Ref<AnimationNode>(parent); + } + + return Ref<AnimationNode>(); +} + +AnimationTree *AnimationNode::get_tree() const { + + return player; +} + +AnimationPlayer *AnimationNode::get_player() const { + ERR_FAIL_COND_V(!state, NULL); + return state->player; +} + +float AnimationNode::process(float p_time, bool p_seek) { + + if (get_script_instance()) { + return get_script_instance()->call("process", p_time, p_seek); + } + + return 0; +} + +void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { + if (p_enable) { + filter[p_path] = true; + } else { + filter.erase(p_path); + } +} + +void AnimationNode::set_filter_enabled(bool p_enable) { + filter_enabled = p_enable; +} + +bool AnimationNode::is_filter_enabled() const { + return filter_enabled; +} + +bool AnimationNode::is_path_filtered(const NodePath &p_path) const { + return filter.has(p_path); +} + +bool AnimationNode::has_filter() const { + return false; +} + +void AnimationNode::set_position(const Vector2 &p_position) { + position = p_position; +} + +Vector2 AnimationNode::get_position() const { + return position; +} + +void AnimationNode::set_tree(AnimationTree *p_player) { + + if (player != NULL && p_player == NULL) { + emit_signal("removed_from_graph"); + } + player = p_player; +} + +Array AnimationNode::_get_filters() const { + + Array paths; + + const NodePath *K = NULL; + while ((K = filter.next(K))) { + paths.push_back(String(*K)); //use strings, so sorting is possible + } + paths.sort(); //done so every time the scene is saved, it does not change + + return paths; +} +void AnimationNode::_set_filters(const Array &p_filters) { + filter.clear(); + for (int i = 0; i < p_filters.size(); i++) { + set_filter_path(p_filters[i], true); + } +} + +void AnimationNode::_validate_property(PropertyInfo &property) const { + if (!has_filter() && (property.name == "filter_enabled" || property.name == "filters")) { + property.usage = 0; + } +} + +void AnimationNode::_bind_methods() { + + ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count); + ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name); + ClassDB::bind_method(D_METHOD("get_input_connection", "input"), &AnimationNode::get_input_connection); + ClassDB::bind_method(D_METHOD("get_input_activity", "input"), &AnimationNode::get_input_activity); + + ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input); + ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input); + + ClassDB::bind_method(D_METHOD("set_filter_path", "path", "enable"), &AnimationNode::set_filter_path); + ClassDB::bind_method(D_METHOD("is_path_filtered", "path"), &AnimationNode::is_path_filtered); + + ClassDB::bind_method(D_METHOD("set_filter_enabled", "enable"), &AnimationNode::set_filter_enabled); + ClassDB::bind_method(D_METHOD("is_filter_enabled"), &AnimationNode::is_filter_enabled); + + ClassDB::bind_method(D_METHOD("set_position", "position"), &AnimationNode::set_position); + ClassDB::bind_method(D_METHOD("get_position"), &AnimationNode::get_position); + + ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters); + ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters); + + ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend"), &AnimationNode::blend_animation); + ClassDB::bind_method(D_METHOD("blend_node", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + + ClassDB::bind_method(D_METHOD("set_parent", "parent"), &AnimationNode::_set_parent); + ClassDB::bind_method(D_METHOD("get_parent"), &AnimationNode::get_parent); + ClassDB::bind_method(D_METHOD("get_tree"), &AnimationNode::get_tree); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters"); + + BIND_VMETHOD(MethodInfo("process", PropertyInfo(Variant::REAL, "time"), PropertyInfo(Variant::BOOL, "seek"))); + BIND_VMETHOD(MethodInfo(Variant::STRING, "get_caption")); + BIND_VMETHOD(MethodInfo(Variant::STRING, "has_filter")); + BIND_VMETHOD(MethodInfo("_parent_set", PropertyInfo(Variant::OBJECT, "parent"))); + + ADD_SIGNAL(MethodInfo("removed_from_graph")); + BIND_ENUM_CONSTANT(FILTER_IGNORE); + BIND_ENUM_CONSTANT(FILTER_PASS); + BIND_ENUM_CONSTANT(FILTER_STOP); + BIND_ENUM_CONSTANT(FILTER_BLEND); +} + +AnimationNode::AnimationNode() { + + state = NULL; + parent = NULL; + player = NULL; + set_local_to_scene(true); + filter_enabled = false; +} + +//////////////////// + +void AnimationTree::set_tree_root(const Ref<AnimationNode> &p_root) { + + if (root.is_valid()) { + root->set_tree(NULL); + } + if (p_root.is_valid()) { + ERR_EXPLAIN("root node already set to another player"); + ERR_FAIL_COND(p_root->player); + } + root = p_root; + + if (root.is_valid()) { + root->set_tree(this); + } + + update_configuration_warning(); +} + +Ref<AnimationNode> AnimationTree::get_tree_root() const { + return root; +} + +void AnimationTree::set_active(bool p_active) { + + if (active == p_active) + return; + + active = p_active; + started = active; + + if (process_mode == ANIMATION_PROCESS_IDLE) { + set_process_internal(active); + } else { + + set_physics_process_internal(active); + } + + if (!active && is_inside_tree()) { + for (Set<TrackCache *>::Element *E = playing_caches.front(); E; E = E->next()) { + + if (ObjectDB::get_instance(E->get()->object_id)) { + E->get()->object->call("stop"); + } + } + + playing_caches.clear(); + } +} + +bool AnimationTree::is_active() const { + + return active; +} + +void AnimationTree::set_process_mode(AnimationProcessMode p_mode) { + + if (process_mode == p_mode) + return; + + bool was_active = is_active(); + if (was_active) { + set_active(false); + } + + process_mode = p_mode; + + if (was_active) { + set_active(true); + } +} + +AnimationTree::AnimationProcessMode AnimationTree::get_process_mode() const { + return process_mode; +} + +void AnimationTree::_node_removed(Node *p_node) { + cache_valid = false; +} + +bool AnimationTree::_update_caches(AnimationPlayer *player) { + + setup_pass++; + + if (!player->has_node(player->get_root())) { + ERR_PRINT("AnimationTree: AnimationPlayer root is invalid."); + set_active(false); + return false; + } + Node *parent = player->get_node(player->get_root()); + + List<StringName> sname; + player->get_animation_list(&sname); + + for (List<StringName>::Element *E = sname.front(); E; E = E->next()) { + Ref<Animation> anim = player->get_animation(E->get()); + for (int i = 0; i < anim->get_track_count(); i++) { + NodePath path = anim->track_get_path(i); + Animation::TrackType track_type = anim->track_get_type(i); + + TrackCache *track = NULL; + if (track_cache.has(path)) { + track = track_cache.get(path); + } + + //if not valid, delete track + if (track && (track->type != track_type || ObjectDB::get_instance(track->object_id) == NULL)) { + playing_caches.erase(track); + memdelete(track); + track_cache.erase(path); + track = NULL; + } + + if (!track) { + + RES resource; + Vector<StringName> leftover_path; + Node *child = parent->get_node_and_resource(path, resource, leftover_path); + + if (!child) { + ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', couldn't resolve track: '" + String(path) + "'"); + continue; + } + + if (!child->is_connected("tree_exited", this, "_node_removed")) { + child->connect("tree_exited", this, "_node_removed", varray(child)); + } + + switch (track_type) { + case Animation::TYPE_VALUE: { + + TrackCacheValue *track_value = memnew(TrackCacheValue); + + if (resource.is_valid()) { + track_value->object = resource.ptr(); + } else { + track_value->object = child; + } + + track_value->subpath = leftover_path; + track_value->object_id = track_value->object->get_instance_id(); + + track = track_value; + + } break; + case Animation::TYPE_TRANSFORM: { + + Spatial *spatial = Object::cast_to<Spatial>(child); + + if (!spatial) { + ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', transform track does not point to spatial: '" + String(path) + "'"); + continue; + } + + TrackCacheTransform *track_xform = memnew(TrackCacheTransform); + + track_xform->spatial = spatial; + track_xform->skeleton = NULL; + track_xform->bone_idx = -1; + + if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) { + + Skeleton *sk = Object::cast_to<Skeleton>(spatial); + int bone_idx = sk->find_bone(path.get_subname(0)); + if (bone_idx != -1 && !sk->is_bone_ignore_animation(bone_idx)) { + + track_xform->skeleton = sk; + track_xform->bone_idx = bone_idx; + } + } + + track_xform->object = spatial; + track_xform->object_id = track_xform->object->get_instance_id(); + + track = track_xform; + + } break; + case Animation::TYPE_METHOD: { + + TrackCacheMethod *track_method = memnew(TrackCacheMethod); + + if (resource.is_valid()) { + track_method->object = resource.ptr(); + } else { + track_method->object = child; + } + + track_method->object_id = track_method->object->get_instance_id(); + + track = track_method; + + } break; + case Animation::TYPE_BEZIER: { + + TrackCacheBezier *track_bezier = memnew(TrackCacheBezier); + + if (resource.is_valid()) { + track_bezier->object = resource.ptr(); + } else { + track_bezier->object = child; + } + + track_bezier->subpath = leftover_path; + track_bezier->object_id = track_bezier->object->get_instance_id(); + + track = track_bezier; + } break; + case Animation::TYPE_AUDIO: { + + TrackCacheAudio *track_audio = memnew(TrackCacheAudio); + + track_audio->object = child; + track_audio->object_id = track_audio->object->get_instance_id(); + + track = track_audio; + + } break; + case Animation::TYPE_ANIMATION: { + + TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation); + + track_animation->object = child; + track_animation->object_id = track_animation->object->get_instance_id(); + + track = track_animation; + + } break; + } + + track_cache[path] = track; + } + + track->setup_pass = setup_pass; + } + } + + List<NodePath> to_delete; + + const NodePath *K = NULL; + while ((K = track_cache.next(K))) { + TrackCache *tc = track_cache[*K]; + if (tc->setup_pass != setup_pass) { + to_delete.push_back(*K); + } + } + + while (to_delete.front()) { + NodePath np = to_delete.front()->get(); + memdelete(track_cache[np]); + track_cache.erase(np); + to_delete.pop_front(); + } + + state.track_map.clear(); + + K = NULL; + int idx = 0; + while ((K = track_cache.next(K))) { + state.track_map[*K] = idx; + idx++; + } + + state.track_count = idx; + + cache_valid = true; + + return true; +} + +void AnimationTree::_clear_caches() { + + const NodePath *K = NULL; + while ((K = track_cache.next(K))) { + memdelete(track_cache[*K]); + } + playing_caches.clear(); + + track_cache.clear(); + cache_valid = false; +} + +void AnimationTree::_process_graph(float p_delta) { + + //check all tracks, see if they need modification + root_motion_transform = Transform(); + + if (!root.is_valid()) { + ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback."); + set_active(false); + cache_valid = false; + return; + } + + if (!has_node(animation_player)) { + ERR_PRINT("AnimationTree: no valid AnimationPlayer path set, disabling playback"); + set_active(false); + cache_valid = false; + return; + } + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); + + if (!player) { + ERR_PRINT("AnimationTree: path points to a node not an AnimationPlayer, disabling playback"); + set_active(false); + cache_valid = false; + return; + } + + if (!cache_valid) { + if (!_update_caches(player)) { + return; + } + } + + { //setup + + process_pass++; + + state.valid = true; + state.invalid_reasons = ""; + state.animation_states.clear(); //will need to be re-created + state.valid = true; + state.player = player; + state.last_pass = process_pass; + + // root source blends + + root->blends.resize(state.track_count); + float *src_blendsw = root->blends.ptrw(); + for (int i = 0; i < state.track_count; i++) { + src_blendsw[i] = 1.0; //by default all go to 1 for the root input + } + } + + //process + + { + + if (started) { + //if started, seek + root->_pre_process(&state, 0, true); + started = false; + } + + root->_pre_process(&state, p_delta, false); + } + + if (!state.valid) { + return; //state is not valid. do nothing. + } + //apply value/transform/bezier blends to track caches and execute method/audio/animation tracks + + { + + bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); + + for (List<AnimationNode::AnimationState>::Element *E = state.animation_states.front(); E; E = E->next()) { + + const AnimationNode::AnimationState &as = E->get(); + + Ref<Animation> a = as.animation; + float time = as.time; + float delta = as.delta; + bool seeked = as.seeked; + + for (int i = 0; i < a->get_track_count(); i++) { + + NodePath path = a->track_get_path(i); + TrackCache *track = track_cache[path]; + if (track->type != a->track_get_type(i)) { + continue; //may happen should not + } + + track->root_motion = root_motion_track == path; + + ERR_CONTINUE(!state.track_map.has(path)); + int blend_idx = state.track_map[path]; + + ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count); + + float blend = (*as.track_blends)[blend_idx]; + + if (blend < CMP_EPSILON) + continue; //nothing to blend + + switch (track->type) { + + case Animation::TYPE_TRANSFORM: { + + TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + + if (t->process_pass != process_pass) { + + t->process_pass = process_pass; + t->loc = Vector3(); + t->rot = Quat(); + t->rot_blend_accum = 0; + t->scale = Vector3(); + } + + if (track->root_motion) { + + float prev_time = time - delta; + if (prev_time < 0) { + if (!a->has_loop()) { + prev_time = 0; + } else { + prev_time = a->get_length() + prev_time; + } + } + + Vector3 loc[2]; + Quat rot[2]; + Vector3 scale[2]; + + if (prev_time > time) { + + Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]); + if (err != OK) { + continue; + } + + a->transform_track_interpolate(i, a->get_length(), &loc[1], &rot[1], &scale[1]); + + t->loc += (loc[1] - loc[0]) * blend; + t->scale += (scale[1] - scale[0]) * blend; + Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + + prev_time = 0; + } + + Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]); + if (err != OK) { + continue; + } + + a->transform_track_interpolate(i, time, &loc[1], &rot[1], &scale[1]); + + t->loc += (loc[1] - loc[0]) * blend; + t->scale += (scale[1] - scale[0]) * blend; + Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + + prev_time = 0; + + } else { + Vector3 loc; + Quat rot; + Vector3 scale; + + Error err = a->transform_track_interpolate(i, time, &loc, &rot, &scale); + //ERR_CONTINUE(err!=OK); //used for testing, should be removed + + scale -= Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes + + if (err != OK) + continue; + + t->loc = t->loc.linear_interpolate(loc, blend); + if (t->rot_blend_accum==0) { + t->rot = rot; + t->rot_blend_accum = blend; + } else { + float rot_total = t->rot_blend_accum + blend; + t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized(); + t->rot_blend_accum = rot_total; + } + t->scale = t->scale.linear_interpolate(scale, blend); + } + + } break; + case Animation::TYPE_VALUE: { + + TrackCacheValue *t = static_cast<TrackCacheValue *>(track); + + Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); + + if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { //delta == 0 means seek + + Variant value = a->value_track_interpolate(i, time); + + if (value == Variant()) + continue; + + if (t->process_pass != process_pass) { + Variant::CallError ce; + t->value = Variant::construct(value.get_type(), NULL, 0, ce); //reset + t->process_pass = process_pass; + } + + Variant::interpolate(t->value, value, blend, t->value); + + } else if (delta != 0) { + + List<int> indices; + a->value_track_get_key_indices(i, time, delta, &indices); + + for (List<int>::Element *F = indices.front(); F; F = F->next()) { + + Variant value = a->track_get_key_value(i, F->get()); + t->object->set_indexed(t->subpath, value); + } + } + + } break; + case Animation::TYPE_METHOD: { + + if (delta == 0) { + continue; + } + TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track); + + List<int> indices; + + a->method_track_get_key_indices(i, time, delta, &indices); + + for (List<int>::Element *E = indices.front(); E; E = E->next()) { + + StringName method = a->method_track_get_name(i, E->get()); + Vector<Variant> params = a->method_track_get_params(i, E->get()); + + int s = params.size(); + + ERR_CONTINUE(s > VARIANT_ARG_MAX); + if (can_call) { + t->object->call_deferred( + method, + s >= 1 ? params[0] : Variant(), + s >= 2 ? params[1] : Variant(), + s >= 3 ? params[2] : Variant(), + s >= 4 ? params[3] : Variant(), + s >= 5 ? params[4] : Variant()); + } + } + + } break; + case Animation::TYPE_BEZIER: { + + TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); + + float bezier = a->bezier_track_interpolate(i, time); + + if (t->process_pass != process_pass) { + t->value = 0; + t->process_pass = process_pass; + } + + t->value = Math::lerp(t->value, bezier, blend); + + } break; + case Animation::TYPE_AUDIO: { + + TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); + + if (seeked) { + //find whathever should be playing + int idx = a->track_find_key(i, time); + if (idx < 0) + continue; + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + start_ofs += time - a->track_get_key_time(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + if (start_ofs > len - end_ofs) { + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + continue; + } + + t->object->call("set_stream", stream); + t->object->call("play", start_ofs); + + t->playing = true; + playing_caches.insert(t); + if (len && end_ofs > 0) { //force a end at a time + t->len = len - start_ofs - end_ofs; + } else { + t->len = 0; + } + + t->start = time; + } + + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, time, delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + t->object->call("set_stream", stream); + t->object->call("play", start_ofs); + + t->playing = true; + playing_caches.insert(t); + if (len && end_ofs > 0) { //force a end at a time + t->len = len - start_ofs - end_ofs; + } else { + t->len = 0; + } + + t->start = time; + } + } else if (t->playing) { + + bool loop = a->has_loop(); + + bool stop = false; + + if (!loop && time < t->start) { + stop = true; + } else if (t->len > 0) { + float len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; + + if (len > t->len) { + stop=true; + } + } + + if (stop) { + //time to stop + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + } + } + } + + float db = Math::linear2db(MAX(blend,0.00001)); + if (t->object->has_method("set_unit_db")) { + t->object->call("set_unit_db", db); + } else { + t->object->call("set_volume_db", db); + } + } break; + case Animation::TYPE_ANIMATION: { + + TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track); + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(t->object); + + if (!player) + continue; + + if (delta == 0 || seeked) { + //seek + int idx = a->track_find_key(i, time); + if (idx < 0) + continue; + + float pos = a->track_get_key_time(i, idx); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) + continue; + + Ref<Animation> anim = player->get_animation(anim_name); + + float at_anim_pos; + + if (anim->has_loop()) { + at_anim_pos = Math::fposmod(time - pos, anim->get_length()); //seek to loop + } else { + at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end + } + + if (player->is_playing() || seeked) { + player->play(anim_name); + player->seek(at_anim_pos); + t->playing = true; + playing_caches.insert(t); + } else { + player->set_assigned_animation(anim_name); + player->seek(at_anim_pos, true); + } + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, time, delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) { + + if (playing_caches.has(t)) { + playing_caches.erase(t); + player->stop(); + t->playing = false; + } + } else { + player->play(anim_name); + t->playing = true; + playing_caches.insert(t); + } + } + } + + } break; + } + } + } + } + + { + // finally, set the tracks + const NodePath *K = NULL; + while ((K = track_cache.next(K))) { + TrackCache *track = track_cache[*K]; + if (track->process_pass != process_pass) + continue; //not processed, ignore + + switch (track->type) { + + case Animation::TYPE_TRANSFORM: { + + TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + + Transform xform; + xform.origin = t->loc; + + t->scale += Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes and root motion + + xform.basis.set_quat_scale(t->rot, t->scale); + + if (t->root_motion) { + + root_motion_transform = xform; + + if (t->skeleton && t->bone_idx >= 0) { + root_motion_transform = (t->skeleton->get_bone_rest(t->bone_idx) * root_motion_transform) * t->skeleton->get_bone_rest(t->bone_idx).affine_inverse(); + } + } else if (t->skeleton && t->bone_idx >= 0) { + + t->skeleton->set_bone_pose(t->bone_idx, xform); + + } else { + + t->spatial->set_transform(xform); + } + + } break; + case Animation::TYPE_VALUE: { + + TrackCacheValue *t = static_cast<TrackCacheValue *>(track); + + t->object->set_indexed(t->subpath, t->value); + + } break; + case Animation::TYPE_BEZIER: { + + TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); + + t->object->set_indexed(t->subpath, t->value); + + } break; + default: {} //the rest dont matter + } + } + } +} + +void AnimationTree::_notification(int p_what) { + + if (active && p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS && process_mode == ANIMATION_PROCESS_PHYSICS) { + _process_graph(get_physics_process_delta_time()); + } + + if (active && p_what == NOTIFICATION_INTERNAL_PROCESS && process_mode == ANIMATION_PROCESS_IDLE) { + _process_graph(get_process_delta_time()); + } + + if (p_what == NOTIFICATION_EXIT_TREE) { + _clear_caches(); + } +} + +void AnimationTree::set_animation_player(const NodePath &p_player) { + animation_player = p_player; + update_configuration_warning(); +} + +NodePath AnimationTree::get_animation_player() const { + return animation_player; +} + +bool AnimationTree::is_state_invalid() const { + + return !state.valid; +} +String AnimationTree::get_invalid_state_reason() const { + + return state.invalid_reasons; +} + +uint64_t AnimationTree::get_last_process_pass() const { + return process_pass; +} + +String AnimationTree::get_configuration_warning() const { + + String warning = Node::get_configuration_warning(); + + if (!root.is_valid()) { + if (warning != String()) { + warning += "\n"; + } + warning += TTR("A root AnimationNode for the graph is not set."); + } + + if (!has_node(animation_player)) { + + if (warning != String()) { + warning += "\n"; + } + + warning += TTR("Path to an AnimationPlayer node containing animations is not set."); + return warning; + } + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); + + if (!player) { + if (warning != String()) { + warning += "\n"; + } + + warning += TTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node."); + return warning; + } + + if (!player->has_node(player->get_root())) { + if (warning != String()) { + warning += "\n"; + } + + warning += TTR("AnimationPlayer root is not a valid node."); + return warning; + } + + return warning; +} + +void AnimationTree::set_root_motion_track(const NodePath &p_track) { + root_motion_track = p_track; +} + +NodePath AnimationTree::get_root_motion_track() const { + return root_motion_track; +} + +Transform AnimationTree::get_root_motion_transform() const { + return root_motion_transform; +} + +void AnimationTree::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationTree::set_active); + ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active); + + ClassDB::bind_method(D_METHOD("set_tree_root", "root"), &AnimationTree::set_tree_root); + ClassDB::bind_method(D_METHOD("get_tree_root"), &AnimationTree::get_tree_root); + + ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &AnimationTree::set_process_mode); + ClassDB::bind_method(D_METHOD("get_process_mode"), &AnimationTree::get_process_mode); + + ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player); + + ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); + ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); + + ClassDB::bind_method(D_METHOD("get_root_motion_transform"), &AnimationTree::get_root_motion_transform); + + + + ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_tree_root", "get_tree_root"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player",PROPERTY_HINT_NODE_PATH_VALID_TYPES,"AnimationPlayer"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode"); + ADD_GROUP("Root Motion", "root_motion_"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); +} + +AnimationTree::AnimationTree() { + + process_mode = ANIMATION_PROCESS_IDLE; + active = false; + cache_valid = false; + setup_pass = 1; + started = true; +} + +AnimationTree::~AnimationTree() { + if (root.is_valid()) { + root->player = NULL; + } +} diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h new file mode 100644 index 0000000000..540c36437a --- /dev/null +++ b/scene/animation/animation_tree.h @@ -0,0 +1,281 @@ +#ifndef ANIMATION_GRAPH_PLAYER_H +#define ANIMATION_GRAPH_PLAYER_H + +#include "animation_player.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/resources/animation.h" + +class AnimationNodeBlendTree; +class AnimationPlayer; +class AnimationTree; + +class AnimationNode : public Resource { + GDCLASS(AnimationNode, Resource) +public: + enum FilterAction { + FILTER_IGNORE, + FILTER_PASS, + FILTER_STOP, + FILTER_BLEND + }; + + struct Input { + + String name; + StringName connected_to; + float activity; + uint64_t last_pass; + }; + + Vector<Input> inputs; + + float process_input(int p_input, float p_time, bool p_seek, float p_blend); + + friend class AnimationTree; + + struct AnimationState { + + Ref<Animation> animation; + float time; + float delta; + const Vector<float> *track_blends; + float blend; + bool seeked; + }; + + struct State { + + int track_count; + HashMap<NodePath, int> track_map; + List<AnimationState> animation_states; + bool valid; + AnimationPlayer *player; + String invalid_reasons; + uint64_t last_pass; + }; + + Vector<float> blends; + State *state; + float _pre_process(State *p_state, float p_time, bool p_seek); + void _pre_update_animations(HashMap<NodePath, int> *track_map); + Vector2 position; + + AnimationNode *parent; + AnimationTree *player; + + float _blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, float *r_max = NULL); + + HashMap<NodePath, bool> filter; + bool filter_enabled; + + Array _get_filters() const; + void _set_filters(const Array &p_filters); + +protected: + void blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend); + float blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + float blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + void make_invalid(const String &p_reason); + + static void _bind_methods(); + + void _validate_property(PropertyInfo &property) const; + + void _set_parent(Object *p_parent); + +public: + void set_parent(AnimationNode *p_parent); + Ref<AnimationNode> get_parent() const; + virtual void set_tree(AnimationTree *p_player); + AnimationTree *get_tree() const; + AnimationPlayer *get_player() const; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + int get_input_count() const; + String get_input_name(int p_input); + StringName get_input_connection(int p_input); + void set_input_connection(int p_input, const StringName &p_connection); + float get_input_activity(int p_input) const; + + void add_input(const String &p_name); + void set_input_name(int p_input, const String &p_name); + void remove_input(int p_index); + + void set_filter_path(const NodePath &p_path, bool p_enable); + bool is_path_filtered(const NodePath &p_path) const; + + void set_filter_enabled(bool p_enable); + bool is_filter_enabled() const; + + virtual bool has_filter() const; + + void set_position(const Vector2 &p_position); + Vector2 get_position() const; + + AnimationNode(); +}; + +VARIANT_ENUM_CAST(AnimationNode::FilterAction) + +//root node does not allow inputs +class AnimationRootNode : public AnimationNode { + GDCLASS(AnimationRootNode, AnimationNode) +public: + AnimationRootNode() {} +}; + +class AnimationTree : public Node { + GDCLASS(AnimationTree, Node) +public: + enum AnimationProcessMode { + ANIMATION_PROCESS_PHYSICS, + ANIMATION_PROCESS_IDLE, + }; + +private: + struct TrackCache { + + bool root_motion; + uint64_t setup_pass; + uint64_t process_pass; + Animation::TrackType type; + Object *object; + ObjectID object_id; + + TrackCache() { + root_motion = false; + setup_pass = 0; + process_pass = 0; + object = NULL; + object_id = 0; + } + virtual ~TrackCache() {} + }; + + struct TrackCacheTransform : public TrackCache { + Spatial *spatial; + Skeleton *skeleton; + int bone_idx; + Vector3 loc; + Quat rot; + float rot_blend_accum; + Vector3 scale; + + TrackCacheTransform() { + type = Animation::TYPE_TRANSFORM; + spatial = NULL; + bone_idx = -1; + skeleton = NULL; + } + }; + + struct TrackCacheValue : public TrackCache { + + Variant value; + Vector<StringName> subpath; + TrackCacheValue() { type = Animation::TYPE_VALUE; } + }; + + struct TrackCacheMethod : public TrackCache { + + TrackCacheMethod() { type = Animation::TYPE_METHOD; } + }; + + struct TrackCacheBezier : public TrackCache { + + float value; + Vector<StringName> subpath; + TrackCacheBezier() { + type = Animation::TYPE_BEZIER; + value = 0; + } + }; + + struct TrackCacheAudio : public TrackCache { + + bool playing; + float start; + float len; + + TrackCacheAudio() { + type = Animation::TYPE_AUDIO; + playing = false; + start = 0; + len = 0; + } + }; + + struct TrackCacheAnimation : public TrackCache { + + bool playing; + + TrackCacheAnimation() { + type = Animation::TYPE_ANIMATION; + playing = false; + } + }; + + HashMap<NodePath, TrackCache *> track_cache; + Set<TrackCache *> playing_caches; + + Ref<AnimationNode> root; + + AnimationProcessMode process_mode; + bool active; + NodePath animation_player; + + AnimationNode::State state; + bool cache_valid; + void _node_removed(Node *p_node); + void _caches_cleared(); + + void _clear_caches(); + bool _update_caches(AnimationPlayer *player); + void _process_graph(float p_delta); + + uint64_t setup_pass; + uint64_t process_pass; + + bool started; + + NodePath root_motion_track; + Transform root_motion_transform; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_tree_root(const Ref<AnimationNode> &p_root); + Ref<AnimationNode> get_tree_root() const; + + void set_active(bool p_active); + bool is_active() const; + + void set_process_mode(AnimationProcessMode p_mode); + AnimationProcessMode get_process_mode() const; + + void set_animation_player(const NodePath &p_player); + NodePath get_animation_player() const; + + virtual String get_configuration_warning() const; + + bool is_state_invalid() const; + String get_invalid_state_reason() const; + + void set_root_motion_track(const NodePath &p_track); + NodePath get_root_motion_track() const; + + Transform get_root_motion_transform() const; + + uint64_t get_last_process_pass() const; + AnimationTree(); + ~AnimationTree(); +}; + +VARIANT_ENUM_CAST(AnimationTree::AnimationProcessMode) + +#endif // ANIMATION_GRAPH_PLAYER_H diff --git a/scene/animation/animation_tree_player.cpp b/scene/animation/animation_tree_player.cpp index 89f0e43a86..026215508b 100644 --- a/scene/animation/animation_tree_player.cpp +++ b/scene/animation/animation_tree_player.cpp @@ -813,7 +813,11 @@ void AnimationTreePlayer::_process_animation(float p_delta) { t.value = t.object->get_indexed(t.subpath); t.value.zero(); - t.skip = false; + if (t.skeleton) { + t.skip = t.skeleton->is_bone_ignore_animation(t.bone_idx); + } else { + t.skip = false; + } } /* STEP 2 PROCESS ANIMATIONS */ @@ -895,13 +899,12 @@ void AnimationTreePlayer::_process_animation(float p_delta) { } Transform xform; - xform.basis = t.rot; xform.origin = t.loc; t.scale.x += 1.0; t.scale.y += 1.0; t.scale.z += 1.0; - xform.basis.scale(t.scale); + xform.basis.set_quat_scale(t.rot, t.scale); if (t.bone_idx >= 0) { if (t.skeleton) @@ -1213,6 +1216,12 @@ String AnimationTreePlayer::animation_node_get_master_animation(const StringName return n->from; } +float AnimationTreePlayer::animation_node_get_position(const StringName &p_node) const { + + GET_NODE_V(NODE_ANIMATION, AnimationNode, 0); + return n->time; +} + bool AnimationTreePlayer::animation_node_is_path_filtered(const StringName &p_node, const NodePath &p_path) const { GET_NODE_V(NODE_ANIMATION, AnimationNode, 0); @@ -1721,6 +1730,7 @@ void AnimationTreePlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("animation_node_set_master_animation", "id", "source"), &AnimationTreePlayer::animation_node_set_master_animation); ClassDB::bind_method(D_METHOD("animation_node_get_master_animation", "id"), &AnimationTreePlayer::animation_node_get_master_animation); + ClassDB::bind_method(D_METHOD("animation_node_get_position", "id"), &AnimationTreePlayer::animation_node_get_position); ClassDB::bind_method(D_METHOD("animation_node_set_filter_path", "id", "path", "enable"), &AnimationTreePlayer::animation_node_set_filter_path); ClassDB::bind_method(D_METHOD("oneshot_node_set_fadein_time", "id", "time_sec"), &AnimationTreePlayer::oneshot_node_set_fadein_time); @@ -1804,7 +1814,7 @@ void AnimationTreePlayer::_bind_methods() { ADD_GROUP("Playback", "playback_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_animation_process_mode", "get_animation_process_mode"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player"), "set_master_player", "get_master_player"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_master_player", "get_master_player"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "base_path"), "set_base_path", "get_base_path"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); diff --git a/scene/animation/animation_tree_player.h b/scene/animation/animation_tree_player.h index 09d6f6fcb4..d2d7b1c9ec 100644 --- a/scene/animation/animation_tree_player.h +++ b/scene/animation/animation_tree_player.h @@ -348,6 +348,7 @@ public: Ref<Animation> animation_node_get_animation(const StringName &p_node) const; void animation_node_set_master_animation(const StringName &p_node, const String &p_master_animation); String animation_node_get_master_animation(const StringName &p_node) const; + float animation_node_get_position(const StringName &p_node) const; void animation_node_set_filter_path(const StringName &p_node, const NodePath &p_track_path, bool p_filter); void animation_node_set_get_filtered_paths(const StringName &p_node, List<NodePath> *r_paths) const; diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp new file mode 100644 index 0000000000..a28c63064a --- /dev/null +++ b/scene/animation/root_motion_view.cpp @@ -0,0 +1,178 @@ +#include "root_motion_view.h" +#include "scene/animation/animation_tree.h" +#include "scene/resources/material.h" +void RootMotionView::set_animation_path(const NodePath &p_path) { + path = p_path; + first = true; +} + +NodePath RootMotionView::get_animation_path() const { + return path; +} + +void RootMotionView::set_color(const Color &p_color) { + color = p_color; + first = true; +} + +Color RootMotionView::get_color() const { + return color; +} + +void RootMotionView::set_cell_size(float p_size) { + cell_size = p_size; + first = true; +} + +float RootMotionView::get_cell_size() const { + return cell_size; +} + +void RootMotionView::set_radius(float p_radius) { + radius = p_radius; + first = true; +} + +float RootMotionView::get_radius() const { + return radius; +} + +void RootMotionView::set_zero_y(bool p_zero_y) { + zero_y = p_zero_y; +} + +bool RootMotionView::get_zero_y() const { + return zero_y; +} + +void RootMotionView::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + VS::get_singleton()->immediate_set_material(immediate, SpatialMaterial::get_material_rid_for_2d(false, true, false, false, false)); + first = true; + } + + if (p_what == NOTIFICATION_INTERNAL_PROCESS || p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { + Transform transform; + + if (has_node(path)) { + + Node *node = get_node(path); + + AnimationTree *tree = Object::cast_to<AnimationTree>(node); + if (tree && tree->is_active() && tree->get_root_motion_track() != NodePath()) { + if (is_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_PHYSICS) { + set_process_internal(false); + set_physics_process_internal(true); + } + + if (is_physics_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_IDLE) { + set_process_internal(true); + set_physics_process_internal(false); + } + + transform = tree->get_root_motion_transform(); + } + } + + if (!first && transform == Transform()) { + return; + } + + first = false; + + transform.orthonormalize(); //dont want scale, too imprecise + transform.affine_invert(); + + accumulated = transform * accumulated; + accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size); + if (zero_y) { + accumulated.origin.y = 0; + } + accumulated.origin.z = Math::fposmod(accumulated.origin.z, cell_size); + + VS::get_singleton()->immediate_clear(immediate); + + int cells_in_radius = int((radius / cell_size) + 1.0); + + VS::get_singleton()->immediate_begin(immediate, VS::PRIMITIVE_LINES); + for (int i = -cells_in_radius; i < cells_in_radius; i++) { + for (int j = -cells_in_radius; j < cells_in_radius; j++) { + + Vector3 from(i * cell_size, 0, j * cell_size); + Vector3 from_i((i + 1) * cell_size, 0, j * cell_size); + Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size); + from = accumulated.xform(from); + from_i = accumulated.xform(from_i); + from_j = accumulated.xform(from_j); + + Color c = color, c_i = color, c_j = color; + c.a *= MAX(0, 1.0 - from.length() / radius); + c_i.a *= MAX(0, 1.0 - from_i.length() / radius); + c_j.a *= MAX(0, 1.0 - from_j.length() / radius); + + VS::get_singleton()->immediate_color(immediate, c); + VS::get_singleton()->immediate_vertex(immediate, from); + + VS::get_singleton()->immediate_color(immediate, c_i); + VS::get_singleton()->immediate_vertex(immediate, from_i); + + VS::get_singleton()->immediate_color(immediate, c); + VS::get_singleton()->immediate_vertex(immediate, from); + + VS::get_singleton()->immediate_color(immediate, c_j); + VS::get_singleton()->immediate_vertex(immediate, from_j); + } + } + + VS::get_singleton()->immediate_end(immediate); + } +} + +AABB RootMotionView::get_aabb() const { + + return AABB(Vector3(-radius, 0, -radius), Vector3(radius * 2, 0.001, radius * 2)); +} +PoolVector<Face3> RootMotionView::get_faces(uint32_t p_usage_flags) const { + return PoolVector<Face3>(); +} + +void RootMotionView::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_path); + ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_path); + + ClassDB::bind_method(D_METHOD("set_color", "color"), &RootMotionView::set_color); + ClassDB::bind_method(D_METHOD("get_color"), &RootMotionView::get_color); + + ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &RootMotionView::set_cell_size); + ClassDB::bind_method(D_METHOD("get_cell_size"), &RootMotionView::get_cell_size); + + ClassDB::bind_method(D_METHOD("set_radius", "size"), &RootMotionView::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &RootMotionView::get_radius); + + ClassDB::bind_method(D_METHOD("set_zero_y", "enable"), &RootMotionView::set_zero_y); + ClassDB::bind_method(D_METHOD("get_zero_y"), &RootMotionView::get_zero_y); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationTree"), "set_animation_path", "get_animation_path"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_y"), "set_zero_y", "get_zero_y"); +} + +RootMotionView::RootMotionView() { + zero_y = true; + radius = 10; + cell_size = 1; + set_process_internal(true); + immediate = VisualServer::get_singleton()->immediate_create(); + set_base(immediate); + color = Color(0.5, 0.5, 1.0); +} + +RootMotionView::~RootMotionView() { + set_base(RID()); + VisualServer::get_singleton()->free(immediate); +} diff --git a/scene/animation/root_motion_view.h b/scene/animation/root_motion_view.h new file mode 100644 index 0000000000..611183d364 --- /dev/null +++ b/scene/animation/root_motion_view.h @@ -0,0 +1,47 @@ +#ifndef ROOT_MOTION_VIEW_H +#define ROOT_MOTION_VIEW_H + +#include "scene/3d/visual_instance.h" + +class RootMotionView : public VisualInstance { + GDCLASS(RootMotionView, VisualInstance) +public: + RID immediate; + NodePath path; + float cell_size; + float radius; + bool use_in_game; + Color color; + bool first; + bool zero_y; + + Transform accumulated; + +private: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_animation_path(const NodePath &p_path); + NodePath get_animation_path() const; + + void set_color(const Color &p_path); + Color get_color() const; + + void set_cell_size(float p_size); + float get_cell_size() const; + + void set_radius(float p_radius); + float get_radius() const; + + void set_zero_y(bool p_zero_y); + bool get_zero_y() const; + + virtual AABB get_aabb() const; + virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + + RootMotionView(); + ~RootMotionView(); +}; + +#endif // ROOT_MOTION_VIEW_H diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 49013b160a..9f7503577b 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -201,6 +201,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all); ClassDB::bind_method(D_METHOD("stop", "object", "key"), &Tween::stop, DEFVAL("")); ClassDB::bind_method(D_METHOD("stop_all"), &Tween::stop_all); + ClassDB::bind_method(D_METHOD("is_stopped"), &Tween::is_stopped); ClassDB::bind_method(D_METHOD("resume", "object", "key"), &Tween::resume, DEFVAL("")); ClassDB::bind_method(D_METHOD("resume_all"), &Tween::resume_all); ClassDB::bind_method(D_METHOD("remove", "object", "key"), &Tween::remove, DEFVAL("")); @@ -743,6 +744,10 @@ bool Tween::stop(Object *p_object, StringName p_key) { return true; } +bool Tween::is_stopped() const { + return tell() >= get_runtime(); +} + bool Tween::stop_all() { set_active(false); @@ -886,6 +891,10 @@ real_t Tween::tell() const { real_t Tween::get_runtime() const { + if (speed_scale == 0) { + return INFINITY; + } + pending_update++; real_t runtime = 0; for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { @@ -896,7 +905,8 @@ real_t Tween::get_runtime() const { runtime = t; } pending_update--; - return runtime; + + return runtime / speed_scale; } bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) { diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 757d80e90a..36094bf294 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -162,6 +162,7 @@ public: bool reset_all(); bool stop(Object *p_object, StringName p_key); bool stop_all(); + bool is_stopped() const; bool resume(Object *p_object, StringName p_key); bool resume_all(); bool remove(Object *p_object, StringName p_key); diff --git a/scene/audio/audio_player.cpp b/scene/audio/audio_player.cpp index 408c00334a..b5c232c33c 100644 --- a/scene/audio/audio_player.cpp +++ b/scene/audio/audio_player.cpp @@ -44,14 +44,23 @@ void AudioStreamPlayer::_mix_internal(bool p_fadeout) { buffer_size = MIN(buffer_size, 16); //short fadeout ramp } - //mix - stream_playback->mix(buffer, pitch_scale, buffer_size); + // Mix if we're not paused or we're fading out + if (!stream_paused || stream_paused_fade > 0.f) { + stream_playback->mix(buffer, pitch_scale, buffer_size); + } //multiply volume interpolating to avoid clicks if this changes float target_volume = p_fadeout ? -80.0 : volume_db; float vol = Math::db2linear(mix_volume_db); float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size); + if (stream_paused) { + vol = vol * stream_paused_fade; + if (stream_paused_fade > 0.f) { + stream_paused_fade -= 0.1f; + } + } + for (int i = 0; i < buffer_size; i++) { buffer[i] *= vol; vol += vol_inc; @@ -90,11 +99,8 @@ void AudioStreamPlayer::_mix_internal(bool p_fadeout) { void AudioStreamPlayer::_mix_audio() { - if (!stream_playback.is_valid()) { - return; - } - - if (!active) { + if (!stream_playback.is_valid() || !active || + (stream_paused && stream_paused_fade <= 0.f)) { return; } @@ -135,6 +141,17 @@ void AudioStreamPlayer::_notification(int p_what) { AudioServer::get_singleton()->remove_callback(_mix_audios, this); } + + if (p_what == NOTIFICATION_PAUSED) { + if (!can_process()) { + // Node can't process so we start fading out to silence + set_stream_paused(true); + } + } + + if (p_what == NOTIFICATION_UNPAUSED) { + set_stream_paused(false); + } } void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { @@ -275,6 +292,19 @@ bool AudioStreamPlayer::_is_active() const { return active; } +void AudioStreamPlayer::set_stream_paused(bool p_pause) { + + if (p_pause != stream_paused) { + stream_paused = p_pause; + stream_paused_fade = stream_paused ? 1.f : 0.f; + } +} + +bool AudioStreamPlayer::get_stream_paused() const { + + return stream_paused; +} + void AudioStreamPlayer::_validate_property(PropertyInfo &property) const { if (property.name == "bus") { @@ -328,11 +358,15 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("_bus_layout_changed"), &AudioStreamPlayer::_bus_layout_changed); + ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); + ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "volume_db", PROPERTY_HINT_RANGE, "-80,24"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,32,0.01"), "set_pitch_scale", "get_pitch_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); @@ -351,6 +385,8 @@ AudioStreamPlayer::AudioStreamPlayer() { autoplay = false; setseek = -1; active = false; + stream_paused = false; + stream_paused_fade = 0.f; mix_target = MIX_TARGET_STEREO; AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); diff --git a/scene/audio/audio_player.h b/scene/audio/audio_player.h index 21189aea6d..c42f191589 100644 --- a/scene/audio/audio_player.h +++ b/scene/audio/audio_player.h @@ -56,7 +56,9 @@ private: float mix_volume_db; float pitch_scale; float volume_db; + float stream_paused_fade; bool autoplay; + bool stream_paused; StringName bus; MixTarget mix_target; @@ -100,6 +102,9 @@ public: void set_mix_target(MixTarget p_target); MixTarget get_mix_target() const; + void set_stream_paused(bool p_pause); + bool get_stream_paused() const; + AudioStreamPlayer(); ~AudioStreamPlayer(); }; diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 5f541ea16a..acdbd9de08 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -60,7 +60,7 @@ void BaseButton::_gui_input(Ref<InputEvent> p_event) { Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { - if (status.disabled || b->get_button_index() != 1) + if (status.disabled || ((1 << (b->get_button_index() - 1)) & button_mask) == 0) return; if (status.pressing_button) @@ -211,6 +211,11 @@ void BaseButton::_gui_input(Ref<InputEvent> p_event) { if (!toggle_mode) { //mouse press attempt pressed(); + if (get_script_instance()) { + Variant::CallError ce; + get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce); + } + emit_signal("pressed"); } else { @@ -247,7 +252,7 @@ void BaseButton::_notification(int p_what) { status.hovering = false; update(); } - if (p_what == NOTIFICATION_DRAG_BEGIN) { + if (p_what == NOTIFICATION_DRAG_BEGIN || p_what == NOTIFICATION_SCROLL_BEGIN) { if (status.press_attempt) { status.press_attempt = false; @@ -403,6 +408,16 @@ BaseButton::ActionMode BaseButton::get_action_mode() const { return action_mode; } +void BaseButton::set_button_mask(int p_mask) { + + button_mask = p_mask; +} + +int BaseButton::get_button_mask() const { + + return button_mask; +} + void BaseButton::set_enabled_focus_mode(FocusMode p_mode) { enabled_focus_mode = p_mode; @@ -491,6 +506,8 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("is_disabled"), &BaseButton::is_disabled); ClassDB::bind_method(D_METHOD("set_action_mode", "mode"), &BaseButton::set_action_mode); ClassDB::bind_method(D_METHOD("get_action_mode"), &BaseButton::get_action_mode); + ClassDB::bind_method(D_METHOD("set_button_mask", "mask"), &BaseButton::set_button_mask); + ClassDB::bind_method(D_METHOD("get_button_mask"), &BaseButton::get_button_mask); ClassDB::bind_method(D_METHOD("get_draw_mode"), &BaseButton::get_draw_mode); ClassDB::bind_method(D_METHOD("set_enabled_focus_mode", "mode"), &BaseButton::set_enabled_focus_mode); ClassDB::bind_method(D_METHOD("get_enabled_focus_mode"), &BaseButton::get_enabled_focus_mode); @@ -512,6 +529,7 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode"); ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTYNO(PropertyInfo(Variant::INT, "action_mode", PROPERTY_HINT_ENUM, "Button Press,Button Release"), "set_action_mode", "get_action_mode"); + ADD_PROPERTYNO(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_enabled_focus_mode", "get_enabled_focus_mode"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "ShortCut"), "set_shortcut", "get_shortcut"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); @@ -537,6 +555,7 @@ BaseButton::BaseButton() { set_focus_mode(FOCUS_ALL); enabled_focus_mode = FOCUS_ALL; action_mode = ACTION_MODE_BUTTON_RELEASE; + button_mask = BUTTON_MASK_LEFT; } BaseButton::~BaseButton() { diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index 6917e112ab..79638bbcce 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -49,6 +49,7 @@ public: }; private: + int button_mask; bool toggle_mode; FocusMode enabled_focus_mode; Ref<ShortCut> shortcut; @@ -104,6 +105,9 @@ public: void set_action_mode(ActionMode p_mode); ActionMode get_action_mode() const; + void set_button_mask(int p_mask); + int get_button_mask() const; + void set_enabled_focus_mode(FocusMode p_mode); FocusMode get_enabled_focus_mode() const; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 30bcc48149..f8c188d33d 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -77,8 +77,7 @@ void ColorPicker::_notification(int p_what) { void ColorPicker::set_focus_on_line_edit() { - c_text->grab_focus(); - c_text->select(); + c_text->call_deferred("grab_focus"); } void ColorPicker::_update_controls() { @@ -159,14 +158,16 @@ void ColorPicker::_update_color() { updating = true; for (int i = 0; i < 4; i++) { - scroll[i]->set_max(255); scroll[i]->set_step(0.01); if (raw_mode_enabled) { + scroll[i]->set_max(100); if (i == 3) scroll[i]->set_max(1); scroll[i]->set_value(color.components[i]); } else { - scroll[i]->set_value(color.components[i] * 255); + const int byte_value = color.components[i] * 255; + scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); + scroll[i]->set_value(byte_value); } } @@ -241,7 +242,16 @@ bool ColorPicker::is_raw_mode() const { return raw_mode_enabled; } +void ColorPicker::set_deferred_mode(bool p_enabled) { + deferred_mode_enabled = p_enabled; +} + +bool ColorPicker::is_deferred_mode() const { + return deferred_mode_enabled; +} + void ColorPicker::_update_text_value() { + bool 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) @@ -250,8 +260,13 @@ void ColorPicker::_update_text_value() { t += ")"; c_text->set_text(t); } else { - c_text->set_text(color.to_html(edit_alpha && color.a < 1)); + if (color.r > 1 || color.g > 1 || color.b > 1 || color.r < 0 || color.g < 0 || color.b < 0) { + visible = false; + } else { + c_text->set_text(color.to_html(edit_alpha && color.a < 1)); + } } + c_text->set_visible(visible); } void ColorPicker::_sample_draw() { @@ -321,7 +336,11 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); + } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { emit_signal("color_changed", color); + changing_color = false; } else { changing_color = false; } @@ -340,7 +359,8 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); } } @@ -361,7 +381,10 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); + else if (!bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) + emit_signal("color_changed", color); } Ref<InputEventMouseMotion> mev = p_event; @@ -376,7 +399,8 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); } } @@ -462,12 +486,39 @@ void ColorPicker::_screen_pick_pressed() { screen->show_modal(); } +void ColorPicker::_focus_enter() { + if (c_text->has_focus()) { + c_text->select_all(); + return; + } + for (int i = 0; i < 4; i++) { + if (values[i]->get_line_edit()->has_focus()) { + values[i]->get_line_edit()->select_all(); + break; + } + } +} + +void ColorPicker::_focus_exit() { + for (int i = 0; i < 4; i++) { + values[i]->get_line_edit()->select(0, 0); + } + c_text->select(0, 0); +} + +void ColorPicker::_html_focus_exit() { + _html_entered(c_text->get_text()); + _focus_exit(); +} + void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPicker::set_pick_color); ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); ClassDB::bind_method(D_METHOD("set_raw_mode", "mode"), &ColorPicker::set_raw_mode); ClassDB::bind_method(D_METHOD("is_raw_mode"), &ColorPicker::is_raw_mode); + ClassDB::bind_method(D_METHOD("set_deferred_mode", "mode"), &ColorPicker::set_deferred_mode); + ClassDB::bind_method(D_METHOD("is_deferred_mode"), &ColorPicker::is_deferred_mode); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPicker::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPicker::is_editing_alpha); ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); @@ -483,10 +534,14 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("_w_input"), &ColorPicker::_w_input); ClassDB::bind_method(D_METHOD("_preset_input"), &ColorPicker::_preset_input); ClassDB::bind_method(D_METHOD("_screen_input"), &ColorPicker::_screen_input); + ClassDB::bind_method(D_METHOD("_focus_enter"), &ColorPicker::_focus_enter); + ClassDB::bind_method(D_METHOD("_focus_exit"), &ColorPicker::_focus_exit); + ClassDB::bind_method(D_METHOD("_html_focus_exit"), &ColorPicker::_html_focus_exit); 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::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); } @@ -498,6 +553,7 @@ ColorPicker::ColorPicker() : edit_alpha = true; text_is_constructor = false; raw_mode_enabled = false; + deferred_mode_enabled = false; changing_color = false; screen = NULL; @@ -559,11 +615,14 @@ ColorPicker::ColorPicker() : scroll[i] = memnew(HSlider); scroll[i]->set_v_size_flags(SIZE_SHRINK_CENTER); + scroll[i]->set_focus_mode(FOCUS_NONE); hbc->add_child(scroll[i]); values[i] = memnew(SpinBox); scroll[i]->share(values[i]); hbc->add_child(values[i]); + values[i]->get_line_edit()->connect("focus_entered", this, "_focus_enter"); + values[i]->get_line_edit()->connect("focus_exited", this, "_focus_exit"); scroll[i]->set_min(0); scroll[i]->set_page(0); @@ -589,6 +648,9 @@ ColorPicker::ColorPicker() : c_text = memnew(LineEdit); hhb->add_child(c_text); c_text->connect("text_entered", this, "_html_entered"); + c_text->connect("focus_entered", this, "_focus_enter"); + c_text->connect("focus_exited", this, "_html_focus_exit"); + text_type->set_text("#"); c_text->set_h_size_flags(SIZE_EXPAND_FILL); @@ -615,12 +677,19 @@ ColorPicker::ColorPicker() : void ColorPickerButton::_color_changed(const Color &p_color) { + color = p_color; update(); - emit_signal("color_changed", p_color); + emit_signal("color_changed", color); +} + +void ColorPickerButton::_modal_closed() { + + emit_signal("popup_closed"); } void ColorPickerButton::pressed() { + _update_picker(); popup->set_position(get_global_position() - picker->get_combined_minimum_size()); popup->popup(); picker->set_focus_on_line_edit(); @@ -633,37 +702,44 @@ void ColorPickerButton::_notification(int p_what) { Ref<StyleBox> normal = get_stylebox("normal"); Rect2 r = Rect2(normal->get_offset(), get_size() - normal->get_minimum_size()); draw_texture_rect(Control::get_icon("bg", "ColorPickerButton"), r, true); - draw_rect(r, picker->get_pick_color()); + draw_rect(r, color); } - if (p_what == MainLoop::NOTIFICATION_WM_QUIT_REQUEST) { + if (p_what == MainLoop::NOTIFICATION_WM_QUIT_REQUEST && popup) { popup->hide(); } } void ColorPickerButton::set_pick_color(const Color &p_color) { - picker->set_pick_color(p_color); + color = p_color; + if (picker) { + picker->set_pick_color(p_color); + } + update(); - emit_signal("color_changed", p_color); } Color ColorPickerButton::get_pick_color() const { - return picker->get_pick_color(); + return color; } void ColorPickerButton::set_edit_alpha(bool p_show) { - picker->set_edit_alpha(p_show); + edit_alpha = p_show; + if (picker) { + picker->set_edit_alpha(p_show); + } } bool ColorPickerButton::is_editing_alpha() const { - return picker->is_editing_alpha(); + return edit_alpha; } -ColorPicker *ColorPickerButton::get_picker() const { +ColorPicker *ColorPickerButton::get_picker() { + _update_picker(); return picker; } @@ -672,6 +748,19 @@ PopupPanel *ColorPickerButton::get_popup() const { return popup; } +void ColorPickerButton::_update_picker() { + if (!picker) { + popup = memnew(PopupPanel); + picker = memnew(ColorPicker); + popup->add_child(picker); + add_child(popup); + picker->connect("color_changed", this, "_color_changed"); + popup->connect("modal_closed", this, "_modal_closed"); + picker->set_pick_color(color); + picker->set_edit_alpha(edit_alpha); + } +} + void ColorPickerButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPickerButton::set_pick_color); @@ -681,18 +770,20 @@ void ColorPickerButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPickerButton::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPickerButton::is_editing_alpha); ClassDB::bind_method(D_METHOD("_color_changed"), &ColorPickerButton::_color_changed); + ClassDB::bind_method(D_METHOD("_modal_closed"), &ColorPickerButton::_modal_closed); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); + ADD_SIGNAL(MethodInfo("popup_closed")); 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"); } ColorPickerButton::ColorPickerButton() { - popup = memnew(PopupPanel); - picker = memnew(ColorPicker); - popup->add_child(picker); - - picker->connect("color_changed", this, "_color_changed"); - add_child(popup); + //Initialization is now done deferred + //this improves performance in the inspector as the color picker + //can be expensive to initialize + picker = NULL; + popup = NULL; + edit_alpha = true; } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 01ae1cc464..c8d8e1aa8a 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -67,6 +67,7 @@ private: Color color; bool raw_mode_enabled; + bool deferred_mode_enabled; bool updating; bool changing_color; float h, s, v; @@ -88,6 +89,9 @@ private: void _screen_input(const Ref<InputEvent> &p_event); void _add_preset_pressed(); void _screen_pick_pressed(); + void _focus_enter(); + void _focus_exit(); + void _html_focus_exit(); protected: void _notification(int); @@ -104,6 +108,9 @@ public: void set_raw_mode(bool p_enabled); bool is_raw_mode() const; + void set_deferred_mode(bool p_enabled); + bool is_deferred_mode() const; + void set_focus_on_line_edit(); ColorPicker(); @@ -115,10 +122,16 @@ class ColorPickerButton : public Button { PopupPanel *popup; ColorPicker *picker; + Color color; + bool edit_alpha; void _color_changed(const Color &p_color); + void _modal_closed(); + virtual void pressed(); + void _update_picker(); + protected: void _notification(int); static void _bind_methods(); @@ -130,7 +143,7 @@ public: void set_edit_alpha(bool p_show); bool is_editing_alpha() const; - ColorPicker *get_picker() const; + ColorPicker *get_picker(); PopupPanel *get_popup() const; ColorPickerButton(); diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 7cb0ad5707..7df03bf7c6 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -34,9 +34,9 @@ void Container::_child_minsize_changed() { - Size2 ms = get_combined_minimum_size(); - if (ms.width > get_size().width || ms.height > get_size().height) - minimum_size_changed(); + //Size2 ms = get_combined_minimum_size(); + //if (ms.width > get_size().width || ms.height > get_size().height) { + minimum_size_changed(); queue_sort(); } @@ -51,6 +51,8 @@ void Container::add_child_notify(Node *p_child) { control->connect("size_flags_changed", this, "queue_sort"); control->connect("minimum_size_changed", this, "_child_minsize_changed"); control->connect("visibility_changed", this, "_child_minsize_changed"); + + minimum_size_changed(); queue_sort(); } @@ -61,6 +63,7 @@ void Container::move_child_notify(Node *p_child) { if (!Object::cast_to<Control>(p_child)) return; + minimum_size_changed(); queue_sort(); } @@ -75,6 +78,8 @@ void Container::remove_child_notify(Node *p_child) { control->disconnect("size_flags_changed", this, "queue_sort"); control->disconnect("minimum_size_changed", this, "_child_minsize_changed"); control->disconnect("visibility_changed", this, "_child_minsize_changed"); + + minimum_size_changed(); queue_sort(); } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index a5883863cd..289924976d 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -94,6 +94,14 @@ Point2 Control::_edit_get_position() const { return get_position(); }; +void Control::_edit_set_scale(const Size2 &p_scale) { + set_scale(p_scale); +} + +Size2 Control::_edit_get_scale() const { + return data.scale; +} + void Control::_edit_set_rect(const Rect2 &p_edit_rect) { set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1))); set_size(p_edit_rect.size.snapped(Vector2(1, 1))); @@ -147,12 +155,29 @@ Size2 Control::get_custom_minimum_size() const { return data.custom_minimum_size; } -Size2 Control::get_combined_minimum_size() const { +void Control::_update_minimum_size_cache() { Size2 minsize = get_minimum_size(); minsize.x = MAX(minsize.x, data.custom_minimum_size.x); minsize.y = MAX(minsize.y, data.custom_minimum_size.y); - return minsize; + + bool size_changed = false; + if (data.minimum_size_cache != minsize) + size_changed = true; + + data.minimum_size_cache = minsize; + data.minimum_size_valid = true; + + if (size_changed) + minimum_size_changed(); +} + +Size2 Control::get_combined_minimum_size() const { + + if (!data.minimum_size_valid) { + const_cast<Control *>(this)->_update_minimum_size_cache(); + } + return data.minimum_size_cache; } Size2 Control::_edit_get_minimum_size() const { @@ -251,14 +276,18 @@ void Control::_update_minimum_size() { if (!is_inside_tree()) return; - data.pending_min_size_update = false; Size2 minsize = get_combined_minimum_size(); if (minsize.x > data.size_cache.x || minsize.y > data.size_cache.y) { _size_changed(); } - emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + data.updating_last_minimum_size = false; + + if (minsize != data.last_minimum_size) { + data.last_minimum_size = minsize; + emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + } } bool Control::_get(const StringName &p_name, Variant &r_ret) const { @@ -429,8 +458,10 @@ void Control::_notification(int p_notification) { case NOTIFICATION_ENTER_TREE: { + } break; + case NOTIFICATION_POST_ENTER_TREE: { + data.minimum_size_valid = false; _size_changed(); - } break; case NOTIFICATION_EXIT_TREE: { @@ -612,13 +643,12 @@ void Control::_notification(int p_notification) { if (is_inside_tree()) { _modal_stack_remove(); - minimum_size_changed(); } //remove key focus //remove modalness } else { - + data.minimum_size_valid = false; _size_changed(); } @@ -1244,21 +1274,23 @@ bool Control::has_constant(const StringName &p_name, const StringName &p_type) c return Theme::get_default()->has_constant(p_name, type); } -Size2 Control::get_parent_area_size() const { - - ERR_FAIL_COND_V(!is_inside_tree(), Size2()); - - Size2 parent_size; +Rect2 Control::get_parent_anchorable_rect() const { + if (!is_inside_tree()) + return Rect2(); + Rect2 parent_rect; if (data.parent_canvas_item) { - - parent_size = data.parent_canvas_item->_edit_get_rect().size; + parent_rect = data.parent_canvas_item->get_anchorable_rect(); } else { - - parent_size = get_viewport()->get_visible_rect().size; + parent_rect = get_viewport()->get_visible_rect(); } - return parent_size; + return parent_rect; +} + +Size2 Control::get_parent_area_size() const { + + return get_parent_anchorable_rect().size; } void Control::_size_changed() { @@ -1266,13 +1298,13 @@ void Control::_size_changed() { if (!is_inside_tree()) return; - Size2 parent_size = get_parent_area_size(); + Rect2 parent_rect = get_parent_anchorable_rect(); float margin_pos[4]; for (int i = 0; i < 4; i++) { - float area = parent_size[i & 1]; + float area = parent_rect.size[i & 1]; margin_pos[i] = data.margin[i] + (data.anchor[i] * area); } @@ -1281,22 +1313,24 @@ void Control::_size_changed() { Size2 minimum_size = get_combined_minimum_size(); - if (data.h_grow == GROW_DIRECTION_BEGIN) { - if (minimum_size.width > new_size_cache.width) { - new_pos_cache.x = new_pos_cache.x + new_size_cache.width - minimum_size.width; - new_size_cache.width = minimum_size.width; + if (minimum_size.width > new_size_cache.width) { + if (data.h_grow == GROW_DIRECTION_BEGIN) { + new_pos_cache.x += new_size_cache.width - minimum_size.width; + } else if (data.h_grow == GROW_DIRECTION_BOTH) { + new_pos_cache.x += 0.5 * (new_size_cache.width - minimum_size.width); } - } else { - new_size_cache.width = MAX(minimum_size.width, new_size_cache.width); + + new_size_cache.width = minimum_size.width; } - if (data.v_grow == GROW_DIRECTION_BEGIN) { - if (minimum_size.height > new_size_cache.height) { - new_pos_cache.y = new_pos_cache.y + new_size_cache.height - minimum_size.height; - new_size_cache.height = minimum_size.height; + if (minimum_size.height > new_size_cache.height) { + if (data.v_grow == GROW_DIRECTION_BEGIN) { + new_pos_cache.y += new_size_cache.height - minimum_size.height; + } else if (data.v_grow == GROW_DIRECTION_BOTH) { + new_pos_cache.y += 0.5 * (new_size_cache.height - minimum_size.height); } - } else { - new_size_cache.height = MAX(minimum_size.height, new_size_cache.height); + + new_size_cache.height = minimum_size.height; } // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() @@ -1324,43 +1358,9 @@ void Control::_size_changed() { } } -float Control::_get_parent_range(int p_idx) const { - - if (!is_inside_tree()) { - - return 0; - } - if (data.parent_canvas_item) { - - return data.parent_canvas_item->_edit_get_rect().size[p_idx & 1]; - } else { - return get_viewport()->get_visible_rect().size[p_idx & 1]; - } - - return 0; -} - -float Control::_get_range(int p_idx) const { - - p_idx &= 1; - - float parent_range = _get_parent_range(p_idx); - float from = _a2s(data.margin[p_idx], data.anchor[p_idx], parent_range); - float to = _a2s(data.margin[p_idx + 2], data.anchor[p_idx + 2], parent_range); - - return to - from; -} - -float Control::_s2a(float p_val, float p_anchor, float p_range) const { - return p_val - (p_anchor * p_range); -} - -float Control::_a2s(float p_val, float p_anchor, float p_range) const { - return Math::floor(p_val + (p_anchor * p_range)); -} - void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bool p_push_opposite_anchor) { - float parent_range = _get_parent_range((p_margin == MARGIN_LEFT || p_margin == MARGIN_RIGHT) ? 0 : 1); + Rect2 parent_rect = get_parent_anchorable_rect(); + float parent_range = (p_margin == MARGIN_LEFT || p_margin == MARGIN_RIGHT) ? parent_rect.size.x : parent_rect.size.y; float previous_margin_pos = data.margin[p_margin] + data.anchor[p_margin] * parent_range; float previous_opposite_margin_pos = data.margin[(p_margin + 2) % 4] + data.anchor[(p_margin + 2) % 4] * parent_range; @@ -1376,9 +1376,9 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo } if (!p_keep_margin) { - data.margin[p_margin] = _s2a(previous_margin_pos, data.anchor[p_margin], parent_range); + data.margin[p_margin] = previous_margin_pos - data.anchor[p_margin] * parent_range; if (p_push_opposite_anchor) { - data.margin[(p_margin + 2) % 4] = _s2a(previous_opposite_margin_pos, data.anchor[(p_margin + 2) % 4], parent_range); + data.margin[(p_margin + 2) % 4] = previous_opposite_margin_pos - data.anchor[(p_margin + 2) % 4] * parent_range; } } if (is_inside_tree()) { @@ -1532,8 +1532,7 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz new_size.y = min_size.y; } - float pw = _get_parent_range(0); - float ph = _get_parent_range(1); + Rect2 parent_rect = get_parent_anchorable_rect(); //Left switch (p_preset) { @@ -1545,21 +1544,21 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - data.margin[0] = pw * (0.0 - data.anchor[0]) + p_margin; + data.margin[0] = parent_rect.size.x * (0.0 - data.anchor[0]) + p_margin + parent_rect.position.x; break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - data.margin[0] = pw * (0.5 - data.anchor[0]) - new_size.x / 2; + data.margin[0] = parent_rect.size.x * (0.5 - data.anchor[0]) - new_size.x / 2 + parent_rect.position.x; break; case PRESET_TOP_RIGHT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_RIGHT: case PRESET_RIGHT_WIDE: - data.margin[0] = pw * (1.0 - data.anchor[0]) - new_size.x - p_margin; + data.margin[0] = parent_rect.size.x * (1.0 - data.anchor[0]) - new_size.x - p_margin + parent_rect.position.x; break; } @@ -1573,21 +1572,21 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - data.margin[1] = ph * (0.0 - data.anchor[1]) + p_margin; + data.margin[1] = parent_rect.size.y * (0.0 - data.anchor[1]) + p_margin + parent_rect.position.y; break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - data.margin[1] = ph * (0.5 - data.anchor[1]) - new_size.y / 2; + data.margin[1] = parent_rect.size.y * (0.5 - data.anchor[1]) - new_size.y / 2 + parent_rect.position.y; break; case PRESET_BOTTOM_LEFT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_BOTTOM: case PRESET_BOTTOM_WIDE: - data.margin[1] = ph * (1.0 - data.anchor[1]) - new_size.y - p_margin; + data.margin[1] = parent_rect.size.y * (1.0 - data.anchor[1]) - new_size.y - p_margin + parent_rect.position.y; break; } @@ -1597,14 +1596,14 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_LEFT: case PRESET_CENTER_LEFT: case PRESET_LEFT_WIDE: - data.margin[2] = pw * (0.0 - data.anchor[2]) + new_size.x + p_margin; + data.margin[2] = parent_rect.size.x * (0.0 - data.anchor[2]) + new_size.x + p_margin + parent_rect.position.x; break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - data.margin[2] = pw * (0.5 - data.anchor[2]) + new_size.x / 2; + data.margin[2] = parent_rect.size.x * (0.5 - data.anchor[2]) + new_size.x / 2 + parent_rect.position.x; break; case PRESET_TOP_RIGHT: @@ -1615,7 +1614,7 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - data.margin[2] = pw * (1.0 - data.anchor[2]) - p_margin; + data.margin[2] = parent_rect.size.x * (1.0 - data.anchor[2]) - p_margin + parent_rect.position.x; break; } @@ -1625,14 +1624,14 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_TOP_RIGHT: case PRESET_CENTER_TOP: case PRESET_TOP_WIDE: - data.margin[3] = ph * (0.0 - data.anchor[3]) + new_size.y + p_margin; + data.margin[3] = parent_rect.size.y * (0.0 - data.anchor[3]) + new_size.y + p_margin + parent_rect.position.y; break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - data.margin[3] = ph * (0.5 - data.anchor[3]) + new_size.y / 2; + data.margin[3] = parent_rect.size.y * (0.5 - data.anchor[3]) + new_size.y / 2 + parent_rect.position.y; break; case PRESET_BOTTOM_LEFT: @@ -1643,7 +1642,7 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - data.margin[3] = ph * (1.0 - data.anchor[3]) - p_margin; + data.margin[3] = parent_rect.size.y * (1.0 - data.anchor[3]) - p_margin + parent_rect.position.y; break; } @@ -1722,31 +1721,29 @@ void Control::set_global_position(const Point2 &p_point) { set_position(inv.xform(p_point)); } -void Control::set_position(const Size2 &p_point) { - - float pw = _get_parent_range(0); - float ph = _get_parent_range(1); +Rect2 Control::_compute_child_rect(const float p_anchors[4], const float p_margins[4]) const { - float x = _a2s(data.margin[0], data.anchor[0], pw); - float y = _a2s(data.margin[1], data.anchor[1], ph); - float x2 = _a2s(data.margin[2], data.anchor[2], pw); - float y2 = _a2s(data.margin[3], data.anchor[3], ph); + Rect2 anchorable = get_parent_anchorable_rect(); + Rect2 result = anchorable; + for (int i = 0; i < 4; i++) { + result.grow_margin((Margin)i, p_anchors[i] * anchorable.get_size()[i % 2] + p_margins[i]); + } - Size2 ret = Size2(x2 - x, y2 - y); - Size2 min = get_combined_minimum_size(); + return result; +} - Size2 size = Size2(MAX(min.width, ret.width), MAX(min.height, ret.height)); - float w = size.x; - float h = size.y; +void Control::_compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]) { - x = p_point.x; - y = p_point.y; + Size2 parent_rect_size = get_parent_anchorable_rect().size; + r_margins[0] = Math::floor(p_rect.position.x - (p_anchors[0] * parent_rect_size.x)); + r_margins[1] = Math::floor(p_rect.position.y - (p_anchors[1] * parent_rect_size.y)); + r_margins[2] = Math::floor(p_rect.position.x + p_rect.size.x - (p_anchors[2] * parent_rect_size.x)); + r_margins[3] = Math::floor(p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y)); +} - data.margin[0] = _s2a(x, data.anchor[0], pw); - data.margin[1] = _s2a(y, data.anchor[1], ph); - data.margin[2] = _s2a(x + w, data.anchor[2], pw); - data.margin[3] = _s2a(y + h, data.anchor[3], ph); +void Control::set_position(const Size2 &p_point) { + _compute_margins(Rect2(p_point, data.size_cache), data.anchor, data.margin); _size_changed(); } @@ -1759,18 +1756,7 @@ void Control::set_size(const Size2 &p_size) { if (new_size.y < min.y) new_size.y = min.y; - float pw = _get_parent_range(0); - float ph = _get_parent_range(1); - - float x = _a2s(data.margin[0], data.anchor[0], pw); - float y = _a2s(data.margin[1], data.anchor[1], ph); - - float w = new_size.width; - float h = new_size.height; - - data.margin[2] = _s2a(x + w, data.anchor[2], pw); - data.margin[3] = _s2a(y + h, data.anchor[3], ph); - + _compute_margins(Rect2(data.pos_cache, new_size), data.anchor, data.margin); _size_changed(); } @@ -1801,6 +1787,11 @@ Rect2 Control::get_rect() const { return Rect2(get_position(), get_size()); } +Rect2 Control::get_anchorable_rect() const { + + return Rect2(Point2(), get_size()); +} + void Control::add_icon_override(const StringName &p_name, const Ref<Texture> &p_icon) { ERR_FAIL_COND(p_icon.is_null()); @@ -2300,12 +2291,11 @@ Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { Point2 points[4]; Transform2D xform = get_global_transform(); - Rect2 rect = _edit_get_rect(); - points[0] = xform.xform(rect.position); - points[1] = xform.xform(rect.position + Point2(rect.size.x, 0)); - points[2] = xform.xform(rect.position + rect.size); - points[3] = xform.xform(rect.position + Point2(0, rect.size.y)); + points[0] = xform.xform(Point2()); + points[1] = xform.xform(Point2(get_size().x, 0)); + points[2] = xform.xform(get_size()); + points[3] = xform.xform(Point2(0, get_size().y)); const Vector2 dir[4] = { Vector2(-1, 0), @@ -2359,12 +2349,11 @@ void Control::_window_find_focus_neighbour(const Vector2 &p_dir, Node *p_at, con Point2 points[4]; Transform2D xform = c->get_global_transform(); - Rect2 rect = c->_edit_get_rect(); - points[0] = xform.xform(rect.position); - points[1] = xform.xform(rect.position + Point2(rect.size.x, 0)); - points[2] = xform.xform(rect.position + rect.size); - points[3] = xform.xform(rect.position + Point2(0, rect.size.y)); + points[0] = xform.xform(Point2()); + points[1] = xform.xform(Point2(get_size().x, 0)); + points[2] = xform.xform(get_size()); + points[3] = xform.xform(Point2(0, get_size().y)); float min = 1e7; @@ -2454,17 +2443,25 @@ void Control::minimum_size_changed() { if (!is_inside_tree() || data.block_minimum_size_adjust) return; - if (data.pending_min_size_update) + Control *invalidate = this; + + //invalidate cache upwards + while (invalidate && invalidate->data.minimum_size_valid) { + invalidate->data.minimum_size_valid = false; + if (invalidate->is_set_as_toplevel()) + break; // do not go further up + invalidate = invalidate->data.parent; + } + + if (!is_visible_in_tree()) return; - data.pending_min_size_update = true; - MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); + if (data.updating_last_minimum_size) + return; - if (!is_toplevel_control()) { - Control *pc = get_parent_control(); - if (pc) - pc->minimum_size_changed(); - } + data.updating_last_minimum_size = true; + + MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); } int Control::get_v_size_flags() const { @@ -2838,8 +2835,8 @@ void Control::_bind_methods() { ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "margin_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_margin", "get_margin", MARGIN_BOTTOM); ADD_GROUP("Grow Direction", "grow_"); - ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End"), "set_h_grow_direction", "get_h_grow_direction"); - ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End"), "set_v_grow_direction", "get_v_grow_direction"); + ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_h_grow_direction", "get_h_grow_direction"); + ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction"); ADD_GROUP("Rect", "rect_"); ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); @@ -2855,12 +2852,12 @@ void Control::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "hint_tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "_get_tooltip"); ADD_GROUP("Focus", "focus_"); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_left"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_LEFT); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_top"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_TOP); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_right"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_RIGHT); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_bottom"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_BOTTOM); - ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_next"), "set_focus_next", "get_focus_next"); - ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_previous"), "set_focus_previous", "get_focus_previous"); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_left", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_LEFT); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_top", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_TOP); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_right", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_RIGHT); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_bottom", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_BOTTOM); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next"); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_GROUP("Mouse", "mouse_"); @@ -2886,6 +2883,8 @@ void Control::_bind_methods() { BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT); BIND_CONSTANT(NOTIFICATION_THEME_CHANGED); BIND_CONSTANT(NOTIFICATION_MODAL_CLOSE); + BIND_CONSTANT(NOTIFICATION_SCROLL_BEGIN); + BIND_CONSTANT(NOTIFICATION_SCROLL_END); BIND_ENUM_CONSTANT(CURSOR_ARROW); BIND_ENUM_CONSTANT(CURSOR_IBEAM); @@ -2939,6 +2938,7 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(GROW_DIRECTION_BEGIN); BIND_ENUM_CONSTANT(GROW_DIRECTION_END); + BIND_ENUM_CONSTANT(GROW_DIRECTION_BOTH); BIND_ENUM_CONSTANT(ANCHOR_BEGIN); BIND_ENUM_CONSTANT(ANCHOR_END); @@ -2972,7 +2972,6 @@ Control::Control() { data.h_size_flags = SIZE_FILL; data.v_size_flags = SIZE_FILL; data.expand = 1; - data.pending_min_size_update = false; data.rotation = 0; data.parent_canvas_item = NULL; data.scale = Vector2(1, 1); @@ -2982,6 +2981,8 @@ Control::Control() { data.disable_visibility_clip = false; data.h_grow = GROW_DIRECTION_END; data.v_grow = GROW_DIRECTION_END; + data.minimum_size_valid = false; + data.updating_last_minimum_size = false; data.clip_contents = false; for (int i = 0; i < 4; i++) { diff --git a/scene/gui/control.h b/scene/gui/control.h index 51325f27b5..fa5274d854 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -60,7 +60,8 @@ public: enum GrowDirection { GROW_DIRECTION_BEGIN, - GROW_DIRECTION_END + GROW_DIRECTION_END, + GROW_DIRECTION_BOTH }; enum FocusMode { @@ -147,6 +148,11 @@ private: Point2 pos_cache; Size2 size_cache; + Size2 minimum_size_cache; + bool minimum_size_valid; + + Size2 last_minimum_size; + bool updating_last_minimum_size; float margin[4]; float anchor[4]; @@ -163,7 +169,6 @@ private: int h_size_flags; int v_size_flags; float expand; - bool pending_min_size_update; Point2 custom_minimum_size; bool pass_on_modal_close_click; @@ -197,12 +202,12 @@ private: NodePath focus_next; NodePath focus_prev; - HashMap<StringName, Ref<Texture>, StringNameHasher> icon_override; - HashMap<StringName, Ref<Shader>, StringNameHasher> shader_override; - HashMap<StringName, Ref<StyleBox>, StringNameHasher> style_override; - HashMap<StringName, Ref<Font>, StringNameHasher> font_override; - HashMap<StringName, Color, StringNameHasher> color_override; - HashMap<StringName, int, StringNameHasher> constant_override; + HashMap<StringName, Ref<Texture> > icon_override; + HashMap<StringName, Ref<Shader> > shader_override; + HashMap<StringName, Ref<StyleBox> > style_override; + HashMap<StringName, Ref<Font> > font_override; + HashMap<StringName, Color> color_override; + HashMap<StringName, int> constant_override; Map<Ref<Font>, int> font_refcount; } data; @@ -215,10 +220,6 @@ private: void _set_anchor(Margin p_margin, float p_anchor); - float _get_parent_range(int p_idx) const; - float _get_range(int p_idx) const; - float _s2a(float p_val, float p_anchor, float p_range) const; - float _a2s(float p_val, float p_anchor, float p_range) const; void _propagate_theme_changed(CanvasItem *p_at, Control *p_owner, bool p_assign = true); void _theme_changed(); @@ -228,6 +229,9 @@ private: void _update_scroll(); void _resize(const Size2 &p_size); + Rect2 _compute_child_rect(const float p_anchors[4], const float p_margins[4]) const; + void _compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]); + void _size_changed(); String _get_tooltip() const; @@ -243,6 +247,8 @@ private: void _modal_stack_remove(); void _modal_set_prev_focus_owner(ObjectID p_prev); + void _update_minimum_size_cache(); + protected: virtual void add_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child); @@ -271,15 +277,21 @@ public: NOTIFICATION_FOCUS_EXIT = 44, NOTIFICATION_THEME_CHANGED = 45, NOTIFICATION_MODAL_CLOSE = 46, + NOTIFICATION_SCROLL_BEGIN = 47, + NOTIFICATION_SCROLL_END = 48, }; + /* EDITOR */ virtual Dictionary _edit_get_state() const; virtual void _edit_set_state(const Dictionary &p_state); virtual void _edit_set_position(const Point2 &p_position); virtual Point2 _edit_get_position() const; + virtual void _edit_set_scale(const Size2 &p_scale); + virtual Size2 _edit_get_scale() const; + virtual void _edit_set_rect(const Rect2 &p_edit_rect); virtual Rect2 _edit_get_rect() const; virtual bool _edit_use_rect() const; @@ -346,6 +358,7 @@ public: Rect2 get_rect() const; Rect2 get_global_rect() const; Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the visual server + Rect2 get_anchorable_rect() const; void set_rotation(float p_radians); void set_rotation_degrees(float p_degrees); @@ -453,6 +466,7 @@ public: bool is_toplevel_control() const; Size2 get_parent_area_size() const; + Rect2 get_parent_anchorable_rect() const; void grab_click_focus(); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 4bd92d888d..25cb74a494 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -777,6 +777,7 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mode", "mode"), &FileDialog::set_mode); ClassDB::bind_method(D_METHOD("get_mode"), &FileDialog::get_mode); ClassDB::bind_method(D_METHOD("get_vbox"), &FileDialog::get_vbox); + ClassDB::bind_method(D_METHOD("get_line_edit"), &FileDialog::get_line_edit); ClassDB::bind_method(D_METHOD("set_access", "access"), &FileDialog::set_access); ClassDB::bind_method(D_METHOD("get_access"), &FileDialog::get_access); ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files); diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 3985039716..b5622604e2 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -147,6 +147,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { grabbed = _get_point_from_pos(x); //grab or select if (grabbed != -1) { + grabbed = false; return; } @@ -367,6 +368,13 @@ void GradientEdit::_notification(int p_what) { draw_line(Vector2(-1, -1), Vector2(-1, h + 1), Color(1, 1, 1, 0.6)); } } + + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + + if (!is_visible()) { + grabbing = false; + } + } } void GradientEdit::_draw_checker(int x, int y, int w, int h) { diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 38ce91a4df..e2c730a56e 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -58,6 +58,7 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S c.from_port = p_from_port; c.to = p_to; c.to_port = p_to_port; + c.activity = 0; connections.push_back(c); top_layer->update(); update(); @@ -624,6 +625,7 @@ void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const void GraphEdit::_connections_layer_draw() { + Color activity_color = get_color("activity"); //draw connections List<List<Connection>::Element *> to_erase; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { @@ -661,6 +663,11 @@ void GraphEdit::_connections_layer_draw() { Color color = gfrom->get_connection_output_color(E->get().from_port); Vector2 topos = gto->get_connection_input_position(E->get().to_port) + gto->get_offset() * zoom; Color tocolor = gto->get_connection_input_color(E->get().to_port); + + if (E->get().activity > 0) { + color = color.linear_interpolate(activity_color, E->get().activity); + tocolor = tocolor.linear_interpolate(activity_color, E->get().activity); + } _draw_cos_line(connections_layer, frompos, topos, color, tocolor); } @@ -980,6 +987,23 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } } +void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) { + + for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + + if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { + + if (ABS(E->get().activity != p_activity)) { + //update only if changed + top_layer->update(); + connections_layer->update(); + } + E->get().activity = p_activity; + return; + } + } +} + void GraphEdit::clear_connections() { connections.clear(); @@ -1141,11 +1165,16 @@ void GraphEdit::_snap_value_changed(double) { update(); } +HBoxContainer *GraphEdit::get_zoom_hbox() { + return zoom_hb; +} + void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node); ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected); ClassDB::bind_method(D_METHOD("disconnect_node", "from", "from_port", "to", "to_port"), &GraphEdit::disconnect_node); + ClassDB::bind_method(D_METHOD("set_connection_activity", "from", "from_port", "to", "to_port", "amount"), &GraphEdit::set_connection_activity); ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list); ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections); ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs); @@ -1187,6 +1216,8 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset); ClassDB::bind_method(D_METHOD("_connections_layer_draw"), &GraphEdit::_connections_layer_draw); + ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox); + ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); @@ -1253,7 +1284,7 @@ GraphEdit::GraphEdit() { zoom = 1; - HBoxContainer *zoom_hb = memnew(HBoxContainer); + zoom_hb = memnew(HBoxContainer); top_layer->add_child(zoom_hb); zoom_hb->set_position(Vector2(10, 10)); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 3bfde44854..14789001e4 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -31,6 +31,7 @@ #ifndef GRAPH_EDIT_H #define GRAPH_EDIT_H +#include "scene/gui/box_container.h" #include "scene/gui/graph_node.h" #include "scene/gui/scroll_bar.h" #include "scene/gui/slider.h" @@ -62,6 +63,7 @@ public: StringName to; int from_port; int to_port; + float activity; }; private: @@ -157,6 +159,8 @@ private: Set<int> valid_left_disconnect_types; Set<int> valid_right_disconnect_types; + HBoxContainer *zoom_hb; + friend class GraphEditFilter; bool _filter_input(const Point2 &p_point); void _snap_toggled(); @@ -175,6 +179,8 @@ public: void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void clear_connections(); + void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity); + void add_valid_connection_type(int p_type, int p_with_type); void remove_valid_connection_type(int p_type, int p_with_type); bool is_valid_connection_type(int p_type, int p_with_type) const; @@ -206,6 +212,8 @@ public: int get_snap() const; void set_snap(int p_snap); + HBoxContainer *get_zoom_hbox(); + GraphEdit(); }; diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index c2b8a7dfab..278e4123d7 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -36,10 +36,12 @@ void GridContainer::_notification(int p_what) { case NOTIFICATION_SORT_CHILDREN: { - Map<int, int> col_minw; - Map<int, int> row_minh; - Set<int> col_expanded; - Set<int> row_expanded; + int valid_controls_index; + + Map<int, int> col_minw; // max of min_width of all controls in each col (indexed by col) + Map<int, int> row_minh; // max of min_height of all controls in each row (indexed by row) + Set<int> col_expanded; // columns which have the SIZE_EXPAND flag set + Set<int> row_expanded; // rows which have the SIZE_EXPAND flag set int hsep = get_constant("hseparation"); int vsep = get_constant("vseparation"); @@ -47,13 +49,15 @@ void GridContainer::_notification(int p_what) { int max_row = get_child_count() / columns; // Compute the per-column/per-row data + valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); if (!c || !c->is_visible_in_tree()) continue; - int row = i / columns; - int col = i % columns; + int row = valid_controls_index / columns; + int col = valid_controls_index % columns; + valid_controls_index++; Size2i ms = c->get_combined_minimum_size(); if (col_minw.has(col)) @@ -84,17 +88,17 @@ void GridContainer::_notification(int p_what) { if (!row_expanded.has(E->key())) remaining_space.height -= E->get(); } - remaining_space.height -= vsep * (max_row - 1); - remaining_space.width -= hsep * (max_col - 1); + remaining_space.height -= vsep * MAX(max_row - 1, 0); + remaining_space.width -= hsep * MAX(max_col - 1, 0); bool can_fit = false; - while (!can_fit) { + while (!can_fit && col_expanded.size() > 0) { // Check if all minwidth constraints are ok if we use the remaining space can_fit = true; - int max_index = 0; + int max_index = col_expanded.front()->get(); for (Set<int>::Element *E = col_expanded.front(); E; E = E->next()) { if (col_minw[E->get()] > col_minw[max_index]) { - max_index = col_minw[E->get()]; + max_index = E->get(); } if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E->get()]) { can_fit = false; @@ -109,13 +113,13 @@ void GridContainer::_notification(int p_what) { } can_fit = false; - while (!can_fit) { + while (!can_fit && row_expanded.size() > 0) { // Check if all minwidth constraints are ok if we use the remaining space can_fit = true; - int max_index = 0; + int max_index = row_expanded.front()->get(); for (Set<int>::Element *E = row_expanded.front(); E; E = E->next()) { if (row_minh[E->get()] > row_minh[max_index]) { - max_index = row_minh[E->get()]; + max_index = E->get(); } if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E->get()]) { can_fit = false; @@ -130,18 +134,20 @@ void GridContainer::_notification(int p_what) { } // Finally, fit the nodes - int col_expand = remaining_space.width / col_expanded.size(); - int row_expand = remaining_space.height / row_expanded.size(); + int col_expand = col_expanded.size() > 0 ? remaining_space.width / col_expanded.size() : 0; + int row_expand = row_expanded.size() > 0 ? remaining_space.height / row_expanded.size() : 0; int col_ofs = 0; int row_ofs = 0; + valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); if (!c || !c->is_visible_in_tree()) continue; - int row = i / columns; - int col = i % columns; + int row = valid_controls_index / columns; + int col = valid_controls_index % columns; + valid_controls_index++; if (col == 0) { col_ofs = 0; @@ -178,6 +184,8 @@ void GridContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_columns", "columns"), &GridContainer::set_columns); ClassDB::bind_method(D_METHOD("get_columns"), &GridContainer::get_columns); + ClassDB::bind_method(D_METHOD("get_child_control_at_cell", "row", "column"), + &GridContainer::get_child_control_at_cell); ADD_PROPERTY(PropertyInfo(Variant::INT, "columns", PROPERTY_HINT_RANGE, "1,1024,1"), "set_columns", "get_columns"); } @@ -190,17 +198,19 @@ Size2 GridContainer::get_minimum_size() const { int hsep = get_constant("hseparation"); int vsep = get_constant("vseparation"); - int idx = 0; int max_row = 0; int max_col = 0; + int valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); if (!c || !c->is_visible_in_tree()) continue; - int row = idx / columns; - int col = idx % columns; + int row = valid_controls_index / columns; + int col = valid_controls_index % columns; + valid_controls_index++; + Size2i ms = c->get_combined_minimum_size(); if (col_minw.has(col)) col_minw[col] = MAX(col_minw[col], ms.width); @@ -213,7 +223,6 @@ Size2 GridContainer::get_minimum_size() const { row_minh[row] = ms.height; max_col = MAX(col, max_col); max_row = MAX(row, max_row); - idx++; } Size2 ms; @@ -232,6 +241,21 @@ Size2 GridContainer::get_minimum_size() const { return ms; } +Control *GridContainer::get_child_control_at_cell(int row, int column) { + Control *c; + int grid_index = row * columns + column; + for (int i = 0; i < get_child_count(); i++) { + c = Object::cast_to<Control>(get_child(i)); + if (!c || !c->is_visible_in_tree()) + continue; + + if (grid_index == i) { + break; + } + } + return c; +} + GridContainer::GridContainer() { set_mouse_filter(MOUSE_FILTER_PASS); diff --git a/scene/gui/grid_container.h b/scene/gui/grid_container.h index 243d06f034..7e3470dc89 100644 --- a/scene/gui/grid_container.h +++ b/scene/gui/grid_container.h @@ -47,6 +47,7 @@ public: void set_columns(int p_columns); int get_columns() const; virtual Size2 get_minimum_size() const; + Control *get_child_control_at_cell(int row, int column); GridContainer(); }; diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index f7574ca2cd..72ed0e9b81 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -37,6 +37,7 @@ void ItemList::add_item(const String &p_item, const Ref<Texture> &p_texture, boo Item item; item.icon = p_texture; item.icon_region = Rect2i(); + item.icon_modulate = Color(1, 1, 1, 1); item.text = p_item; item.selectable = p_selectable; item.selected = false; @@ -54,6 +55,7 @@ void ItemList::add_icon_item(const Ref<Texture> &p_item, bool p_selectable) { Item item; item.icon = p_item; item.icon_region = Rect2i(); + item.icon_modulate = Color(1, 1, 1, 1); //item.text=p_item; item.selectable = p_selectable; item.selected = false; @@ -138,6 +140,21 @@ Rect2 ItemList::get_item_icon_region(int p_idx) const { return items[p_idx].icon_region; } +void ItemList::set_item_icon_modulate(int p_idx, const Color &p_modulate) { + + ERR_FAIL_INDEX(p_idx, items.size()); + + items[p_idx].icon_modulate = p_modulate; + update(); +} + +Color ItemList::get_item_icon_modulate(int p_idx) const { + + ERR_FAIL_INDEX_V(p_idx, items.size(), Color()); + + return items[p_idx].icon_modulate; +} + void ItemList::set_item_custom_bg_color(int p_idx, const Color &p_custom_bg_color) { ERR_FAIL_INDEX(p_idx, items.size()); @@ -295,35 +312,21 @@ int ItemList::get_current() const { return current; } -void ItemList::move_item(int p_item, int p_to_pos) { +void ItemList::move_item(int p_from_idx, int p_to_idx) { - ERR_FAIL_INDEX(p_item, items.size()); - ERR_FAIL_INDEX(p_to_pos, items.size() + 1); + ERR_FAIL_INDEX(p_from_idx, items.size()); + ERR_FAIL_INDEX(p_to_idx, items.size()); - Item it = items[p_item]; - items.remove(p_item); - - if (p_to_pos > p_item) { - p_to_pos--; + if (is_anything_selected() && get_selected_items()[0] == p_from_idx) { + current = p_to_idx; } - if (p_to_pos >= items.size()) { - items.push_back(it); - } else { - items.insert(p_to_pos, it); - } - - if (current < 0) { - //do none - } else if (p_item == current) { - current = p_to_pos; - } else if (p_to_pos > p_item && current > p_item && current < p_to_pos) { - current--; - } else if (p_to_pos < p_item && current < p_item && current > p_to_pos) { - current++; - } + Item item = items[p_from_idx]; + items.remove(p_from_idx); + items.insert(p_to_idx, item); update(); + shape_changed = true; } int ItemList::get_item_count() const { @@ -675,7 +678,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels - if (current % current_columns != (current_columns - 1)) { + if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) { set_current(current + 1); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -939,6 +942,7 @@ void ItemList::_notification(int p_what) { } } + minimum_size_changed(); shape_changed = false; } @@ -1059,7 +1063,7 @@ void ItemList::_notification(int p_what) { draw_rect.size = adj.size; } - Color modulate = Color(1, 1, 1, 1); + Color modulate = items[i].icon_modulate; if (items[i].disabled) modulate.a *= 0.5; @@ -1118,6 +1122,7 @@ void ItemList::_notification(int p_what) { text_ofs += base_ofs; text_ofs += items[i].rect_cache.position; + FontDrawer drawer(font, Color(1, 1, 1)); for (int j = 0; j < ss; j++) { if (j == line_limit_cache[line]) { @@ -1126,7 +1131,7 @@ void ItemList::_notification(int p_what) { if (line >= max_text_lines) break; } - ofs += font->draw_char(get_canvas_item(), text_ofs + Vector2(ofs + (max_len - line_size_cache[line]) / 2, line * (font_height + line_separation)).floor(), items[i].text[j], items[i].text[j + 1], modulate); + ofs += drawer.draw_char(get_canvas_item(), text_ofs + Vector2(ofs + (max_len - line_size_cache[line]) / 2, line * (font_height + line_separation)).floor(), items[i].text[j], items[i].text[j + 1], modulate); } //special multiline mode @@ -1240,7 +1245,7 @@ bool ItemList::is_pos_at_end_of_items(const Point2 &p_pos) const { String ItemList::get_tooltip(const Point2 &p_pos) const { - int closest = get_item_at_position(p_pos); + int closest = get_item_at_position(p_pos, true); if (closest != -1) { if (!items[closest].tooltip_enabled) { @@ -1403,6 +1408,9 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_icon_region", "idx", "rect"), &ItemList::set_item_icon_region); ClassDB::bind_method(D_METHOD("get_item_icon_region", "idx"), &ItemList::get_item_icon_region); + ClassDB::bind_method(D_METHOD("set_item_icon_modulate", "idx", "modulate"), &ItemList::set_item_icon_modulate); + ClassDB::bind_method(D_METHOD("get_item_icon_modulate", "idx"), &ItemList::get_item_icon_modulate); + ClassDB::bind_method(D_METHOD("set_item_selectable", "idx", "selectable"), &ItemList::set_item_selectable); ClassDB::bind_method(D_METHOD("is_item_selectable", "idx"), &ItemList::is_item_selectable); @@ -1423,9 +1431,13 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("select", "idx", "single"), &ItemList::select, DEFVAL(true)); ClassDB::bind_method(D_METHOD("unselect", "idx"), &ItemList::unselect); + ClassDB::bind_method(D_METHOD("unselect_all"), &ItemList::unselect_all); + ClassDB::bind_method(D_METHOD("is_selected", "idx"), &ItemList::is_selected); ClassDB::bind_method(D_METHOD("get_selected_items"), &ItemList::get_selected_items); + ClassDB::bind_method(D_METHOD("move_item", "from_idx", "to_idx"), &ItemList::move_item); + ClassDB::bind_method(D_METHOD("get_item_count"), &ItemList::get_item_count); ClassDB::bind_method(D_METHOD("remove_item", "idx"), &ItemList::remove_item); @@ -1465,6 +1477,8 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_height", "enable"), &ItemList::set_auto_height); ClassDB::bind_method(D_METHOD("has_auto_height"), &ItemList::has_auto_height); + ClassDB::bind_method(D_METHOD("is_anything_selected"), &ItemList::is_anything_selected); + ClassDB::bind_method(D_METHOD("get_item_at_position", "position", "exact"), &ItemList::get_item_at_position, DEFVAL(false)); ClassDB::bind_method(D_METHOD("ensure_current_is_visible"), &ItemList::ensure_current_is_visible); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index 7f34a250bd..58771c1777 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -54,6 +54,7 @@ private: Ref<Texture> icon; Rect2i icon_region; + Color icon_modulate; Ref<Texture> tag_icon; String text; bool selectable; @@ -135,6 +136,9 @@ public: void set_item_icon_region(int p_idx, const Rect2 &p_region); Rect2 get_item_icon_region(int p_idx) const; + void set_item_icon_modulate(int p_idx, const Color &p_modulate); + Color get_item_icon_modulate(int p_idx) const; + void set_item_selectable(int p_idx, bool p_selectable); bool is_item_selectable(int p_idx) const; @@ -169,7 +173,7 @@ public: void set_current(int p_current); int get_current() const; - void move_item(int p_item, int p_to_pos); + void move_item(int p_from_idx, int p_to_idx); int get_item_count() const; void remove_item(int p_idx); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 830f724b3c..9af479c1cc 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -93,6 +93,7 @@ void Label::_notification(int p_what) { bool use_outline = get_constant("shadow_as_outline"); Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); int line_spacing = get_constant("line_spacing"); + Color font_outline_modulate = get_color("font_outline_modulate"); style->draw(ci, Rect2(Point2(0, 0), get_size())); @@ -102,7 +103,8 @@ void Label::_notification(int p_what) { int lines_visible = (size.y + line_spacing) / font_h; - int space_w = font->get_char_size(' ').width; + // ceiling to ensure autowrapping does not cut text + int space_w = Math::ceil(font->get_char_size(' ').width); int chars_total = 0; int vbegin = 0, vsep = 0; @@ -150,6 +152,7 @@ void Label::_notification(int p_what) { int line = 0; int line_to = lines_skipped + (lines_visible > 0 ? lines_visible : 1); + FontDrawer drawer(font, font_outline_modulate); while (wc) { /* handle lines not meant to be drawn quickly */ if (line >= line_to) @@ -244,11 +247,11 @@ void Label::_notification(int p_what) { n = String::char_uppercase(c); } - float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow); + float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow, false); if (use_outline) { - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow); - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow); - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow); + font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow, false); + font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow, false); + font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow, false); } x_ofs_shadow += move; chars_total_shadow++; @@ -265,7 +268,7 @@ void Label::_notification(int p_what) { n = String::char_uppercase(c); } - x_ofs += font->draw_char(ci, Point2(x_ofs, y_ofs), c, n, font_color); + x_ofs += drawer.draw_char(ci, Point2(x_ofs, y_ofs), c, n, font_color); chars_total++; } } @@ -329,7 +332,8 @@ int Label::get_longest_line_width() const { } } else { - int char_width = font->get_char_size(current, xl_text[i + 1]).width; + // ceiling to ensure autowrapping does not cut text + int char_width = Math::ceil(font->get_char_size(current, xl_text[i + 1]).width); line_width += char_width; } } @@ -382,7 +386,8 @@ void Label::regenerate_word_cache() { int word_pos = 0; int line_width = 0; int space_count = 0; - int space_width = font->get_char_size(' ').width; + // ceiling to ensure autowrapping does not cut text + int space_width = Math::ceil(font->get_char_size(' ').width); int line_spacing = get_constant("line_spacing"); line_count = 1; total_char_cache = 0; @@ -444,8 +449,8 @@ void Label::regenerate_word_cache() { if (current_word_size == 0) { word_pos = i; } - - char_width = font->get_char_size(current, xl_text[i + 1]).width; + // ceiling to ensure autowrapping does not cut text + char_width = Math::ceil(font->get_char_size(current, xl_text[i + 1]).width); current_word_size += char_width; line_width += char_width; total_char_cache++; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 03dc6686b8..0cd5219f8f 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -215,6 +215,14 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { case (KEY_A): { //Select All select(); } break; +#ifdef APPLE_STYLE_KEYS + case (KEY_LEFT): { // Go to start of text - like HOME key + set_cursor_position(0); + } break; + case (KEY_RIGHT): { // Go to end of text - like END key + set_cursor_position(text.length()); + } break; +#endif default: { handled = false; } } @@ -373,12 +381,14 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { case KEY_UP: { shift_selection_check_pre(k->get_shift()); + if (get_cursor_position() == 0) handled = false; set_cursor_position(0); shift_selection_check_post(k->get_shift()); } break; case KEY_DOWN: { shift_selection_check_pre(k->get_shift()); + if (get_cursor_position() == text.length()) handled = false; set_cursor_position(text.length()); shift_selection_check_post(k->get_shift()); } break; @@ -555,6 +565,9 @@ void LineEdit::_notification(int p_what) { #endif case NOTIFICATION_RESIZED: { + if (expand_to_text_length) { + window_pos = 0; //force scroll back since it's expanding to text length + } set_cursor_position(get_cursor_position()); } break; @@ -600,6 +613,8 @@ void LineEdit::_notification(int p_what) { } int x_ofs = 0; + bool using_placeholder = text.empty(); + int cached_text_width = using_placeholder ? cached_placeholder_width : cached_width; switch (align) { @@ -613,15 +628,15 @@ void LineEdit::_notification(int p_what) { if (window_pos != 0) x_ofs = style->get_offset().x; else - x_ofs = int(size.width - (cached_width)) / 2; + x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - (cached_text_width)) / 2); } break; case ALIGN_RIGHT: { - x_ofs = int(size.width - style->get_offset().x - (cached_width)); + x_ofs = MAX(style->get_margin(MARGIN_LEFT), int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_text_width))); } break; } - int ofs_max = width - style->get_minimum_size().width; + int ofs_max = width - style->get_margin(MARGIN_RIGHT); int char_ofs = window_pos; int y_area = height - style->get_minimum_size().height; @@ -634,9 +649,9 @@ void LineEdit::_notification(int p_what) { Color font_color_selected = get_color("font_color_selected"); Color cursor_color = get_color("cursor_color"); - const String &t = text.empty() ? placeholder : text; + const String &t = using_placeholder ? placeholder : text; // draw placeholder color - if (text.empty()) + if (using_placeholder) font_color.a *= placeholder_alpha; font_color.a *= disabled_alpha; @@ -647,6 +662,7 @@ void LineEdit::_notification(int p_what) { } int caret_height = font->get_height() > y_area ? y_area : font->get_height(); + FontDrawer drawer(font, Color(1, 1, 1)); while (true) { //end of string, break! @@ -660,8 +676,8 @@ void LineEdit::_notification(int p_what) { if (ofs >= ime_text.length()) break; - CharType cchar = (pass && !text.empty()) ? '*' : ime_text[ofs]; - CharType next = (pass && !text.empty()) ? '*' : ime_text[ofs + 1]; + CharType cchar = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs]; + CharType next = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs + 1]; int im_char_width = font->get_char_size(cchar, next).width; if ((x_ofs + im_char_width) > ofs_max) @@ -674,7 +690,7 @@ void LineEdit::_notification(int p_what) { VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color); } - font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color); + drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color); x_ofs += im_char_width; ofs++; @@ -682,8 +698,8 @@ void LineEdit::_notification(int p_what) { } } - CharType cchar = (pass && !text.empty()) ? '*' : t[char_ofs]; - CharType next = (pass && !text.empty()) ? '*' : t[char_ofs + 1]; + CharType cchar = (pass && !text.empty()) ? secret_character[0] : t[char_ofs]; + CharType next = (pass && !text.empty()) ? secret_character[0] : t[char_ofs + 1]; int char_width = font->get_char_size(cchar, next).width; // end of widget, break! @@ -695,7 +711,7 @@ void LineEdit::_notification(int p_what) { if (selected) VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs), Size2(char_width, caret_height)), selection_color); - font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, selected ? font_color_selected : font_color); + drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, selected ? font_color_selected : font_color); if (char_ofs == cursor_pos && draw_caret) { if (ime_text.length() == 0) { @@ -714,8 +730,8 @@ void LineEdit::_notification(int p_what) { if (ofs >= ime_text.length()) break; - CharType cchar = (pass && !text.empty()) ? '*' : ime_text[ofs]; - CharType next = (pass && !text.empty()) ? '*' : ime_text[ofs + 1]; + CharType cchar = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs]; + CharType next = (pass && !text.empty()) ? secret_character[0] : ime_text[ofs + 1]; int im_char_width = font->get_char_size(cchar, next).width; if ((x_ofs + im_char_width) > ofs_max) @@ -728,7 +744,7 @@ void LineEdit::_notification(int p_what) { VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color); } - font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color); + drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color); x_ofs += im_char_width; ofs++; @@ -744,7 +760,8 @@ void LineEdit::_notification(int p_what) { if (has_focus()) { - OS::get_singleton()->set_ime_position(get_global_position() + Point2(x_ofs, y_ofs + caret_height)); + OS::get_singleton()->set_ime_active(true); + OS::get_singleton()->set_ime_position(get_global_position() + Point2(using_placeholder ? 0 : x_ofs, y_ofs + caret_height)); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); } } break; @@ -754,6 +771,7 @@ void LineEdit::_notification(int p_what) { draw_caret = true; } + OS::get_singleton()->set_ime_active(true); Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height; OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); @@ -766,6 +784,7 @@ void LineEdit::_notification(int p_what) { OS::get_singleton()->set_ime_position(Point2()); OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL); + OS::get_singleton()->set_ime_active(false); ime_text = ""; ime_selection = Point2(); @@ -879,7 +898,7 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) { } break; case ALIGN_RIGHT: { - pixel_ofs = int(size.width - style->get_offset().x - (cached_width)); + pixel_ofs = int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_width)); } break; } @@ -995,7 +1014,6 @@ void LineEdit::set_text(String p_text) { update(); cursor_pos = 0; window_pos = 0; - _text_changed(); } void LineEdit::clear() { @@ -1012,6 +1030,15 @@ String LineEdit::get_text() const { void LineEdit::set_placeholder(String p_text) { placeholder = tr(p_text); + if ((max_length <= 0) || (placeholder.length() <= max_length)) { + Ref<Font> font = get_font("font"); + cached_placeholder_width = 0; + if (font != NULL) { + for (int i = 0; i < placeholder.length(); i++) { + cached_placeholder_width += font->get_char_size(placeholder[i]).width; + } + } + } update(); } @@ -1074,11 +1101,12 @@ void LineEdit::set_cursor_position(int p_pos) { for (int i = cursor_pos; i >= window_pos; i--) { if (i >= text.length()) { - accum_width = font->get_char_size(' ').width; //anything should do + //do not do this, because if the cursor is at the end, its just fine that it takes no space + //accum_width = font->get_char_size(' ').width; //anything should do } else { accum_width += font->get_char_size(text[i], i + 1 < text.length() ? text[i + 1] : 0).width; //anything should do } - if (accum_width >= window_width) + if (accum_width > window_width) break; wp = i; @@ -1145,7 +1173,7 @@ Size2 LineEdit::get_minimum_size() const { int mstext = get_constant("minimum_spaces") * space_size; if (expand_to_text_length) { - mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact + mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact, and because cursor needs a bit more when at the end } min.width += mstext; @@ -1212,6 +1240,7 @@ void LineEdit::select_all() { selection.enabled = true; update(); } + void LineEdit::set_editable(bool p_editable) { editable = p_editable; @@ -1228,11 +1257,27 @@ void LineEdit::set_secret(bool p_secret) { pass = p_secret; update(); } + bool LineEdit::is_secret() const { return pass; } +void LineEdit::set_secret_character(const String &p_string) { + + // An empty string as the secret character would crash the engine + // It also wouldn't make sense to use multiple characters as the secret character + ERR_EXPLAIN("Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given)"); + ERR_FAIL_COND(p_string.length() != 1); + + secret_character = p_string; + update(); +} + +String LineEdit::get_secret_character() const { + return secret_character; +} + void LineEdit::select(int p_from, int p_to) { if (p_from == 0 && p_to == 0) { @@ -1314,12 +1359,12 @@ PopupMenu *LineEdit::get_menu() const { return menu; } -#ifdef TOOLS_ENABLED void LineEdit::_editor_settings_changed() { +#ifdef TOOLS_ENABLED cursor_set_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); cursor_set_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); -} #endif +} void LineEdit::set_expand_to_text_length(bool p_enabled) { @@ -1388,9 +1433,7 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_text_changed"), &LineEdit::_text_changed); ClassDB::bind_method(D_METHOD("_toggle_draw_caret"), &LineEdit::_toggle_draw_caret); -#ifdef TOOLS_ENABLED ClassDB::bind_method("_editor_settings_changed", &LineEdit::_editor_settings_changed); -#endif ClassDB::bind_method(D_METHOD("set_align", "align"), &LineEdit::set_align); ClassDB::bind_method(D_METHOD("get_align"), &LineEdit::get_align); @@ -1421,6 +1464,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_editable"), &LineEdit::is_editable); ClassDB::bind_method(D_METHOD("set_secret", "enabled"), &LineEdit::set_secret); ClassDB::bind_method(D_METHOD("is_secret"), &LineEdit::is_secret); + ClassDB::bind_method(D_METHOD("set_secret_character", "character"), &LineEdit::set_secret_character); + ClassDB::bind_method(D_METHOD("get_secret_character"), &LineEdit::get_secret_character); ClassDB::bind_method(D_METHOD("menu_option", "option"), &LineEdit::menu_option); ClassDB::bind_method(D_METHOD("get_menu"), &LineEdit::get_menu); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &LineEdit::set_context_menu_enabled); @@ -1448,6 +1493,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "max_length"), "set_max_length", "get_max_length"); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret"); + ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character"); ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length", "get_expand_to_text_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); @@ -1456,7 +1502,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "placeholder_alpha", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_placeholder_alpha", "get_placeholder_alpha"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled"); - ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.1"), "cursor_set_blink_speed", "cursor_get_blink_speed"); + ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_position"), "set_cursor_position", "get_cursor_position"); } @@ -1466,11 +1512,13 @@ LineEdit::LineEdit() { _create_undo_state(); align = ALIGN_LEFT; cached_width = 0; + cached_placeholder_width = 0; cursor_pos = 0; window_pos = 0; window_has_focus = true; max_length = 0; pass = false; + secret_character = "*"; text_changed_dirty = false; placeholder_alpha = 0.6; @@ -1491,15 +1539,15 @@ LineEdit::LineEdit() { context_menu_enabled = true; menu = memnew(PopupMenu); add_child(menu); - menu->add_item(TTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); - menu->add_item(TTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); - menu->add_item(TTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); + menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); menu->add_separator(); - menu->add_item(TTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); - menu->add_item(TTR("Clear"), MENU_CLEAR); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + menu->add_item(RTR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(TTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); - menu->add_item(TTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); + menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); + menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); menu->connect("id_pressed", this, "menu_option"); expand_to_text_length = false; } diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index e3ad3b17f1..e9314ba8dd 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -72,6 +72,7 @@ private: String undo_text; String text; String placeholder; + String secret_character; float placeholder_alpha; String ime_text; Point2 ime_selection; @@ -84,6 +85,7 @@ private: int max_length; // 0 for no maximum int cached_width; + int cached_placeholder_width; struct Selection { @@ -122,7 +124,6 @@ private: void shift_selection_check_post(bool); void selection_fill_at_cursor(); - void selection_delete(); void set_window_pos(int p_pos); void set_cursor_at_pixel_pos(int p_x); @@ -133,9 +134,7 @@ private: void clear_internal(); void changed_internal(); -#ifdef TOOLS_ENABLED void _editor_settings_changed(); -#endif void _gui_input(Ref<InputEvent> p_event); void _notification(int p_what); @@ -158,6 +157,7 @@ public: void select(int p_from = 0, int p_to = -1); void select_all(); + void selection_delete(); void deselect(); void delete_char(); @@ -193,6 +193,9 @@ public: void set_secret(bool p_secret); bool is_secret() const; + void set_secret_character(const String &p_string); + String get_secret_character() const; + virtual Size2 get_minimum_size() const; void set_expand_to_text_length(bool p_enabled); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 2e74faa61d..87cf4dc334 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -59,7 +59,6 @@ void MenuButton::pressed() { popup->set_size(Size2(size.width, 0)); popup->set_parent_rect(Rect2(Point2(gp - popup->get_global_position()), get_size())); popup->popup(); - popup->set_invalidate_click_until_motion(); } void MenuButton::_gui_input(Ref<InputEvent> p_event) { @@ -109,7 +108,6 @@ MenuButton::MenuButton() { add_child(popup); popup->set_as_toplevel(true); popup->set_pass_on_modal_close_click(false); - connect("button_up", popup, "call_deferred", make_binds("grab_click_focus")); set_process_unhandled_key_input(true); set_action_mode(ACTION_MODE_BUTTON_PRESS); } diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 71c14810f6..c5e4149782 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -75,6 +75,10 @@ void OptionButton::_notification(int p_what) { } } +void OptionButton::_focused(int p_which) { + emit_signal("item_focused", p_which); +} + void OptionButton::_selected(int p_which) { int selid = -1; @@ -108,13 +112,13 @@ void OptionButton::pressed() { void OptionButton::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID) { - popup->add_icon_check_item(p_icon, p_label, p_ID); + popup->add_icon_radio_check_item(p_icon, p_label, p_ID); if (popup->get_item_count() == 1) select(0); } void OptionButton::add_item(const String &p_label, int p_ID) { - popup->add_check_item(p_label, p_ID); + popup->add_radio_check_item(p_label, p_ID); if (popup->get_item_count() == 1) select(0); } @@ -290,9 +294,10 @@ void OptionButton::get_translatable_strings(List<String> *p_strings) const { void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("_selected"), &OptionButton::_selected); + ClassDB::bind_method(D_METHOD("_focused"), &OptionButton::_focused); ClassDB::bind_method(D_METHOD("add_item", "label", "id"), &OptionButton::add_item, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id"), &OptionButton::add_icon_item); + ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id"), &OptionButton::add_icon_item, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &OptionButton::set_item_text); ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "texture"), &OptionButton::set_item_icon); ClassDB::bind_method(D_METHOD("set_item_disabled", "idx", "disabled"), &OptionButton::set_item_disabled); @@ -322,6 +327,7 @@ void OptionButton::_bind_methods() { // "selected" property must come after "items", otherwise GH-10213 occurs ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected"); ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "ID"))); + ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "ID"))); } OptionButton::OptionButton() { @@ -336,6 +342,7 @@ OptionButton::OptionButton() { popup->set_as_toplevel(true); popup->set_pass_on_modal_close_click(false); popup->connect("id_pressed", this, "_selected"); + popup->connect("id_focused", this, "_focused"); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index f65fa1b631..d5f866d806 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -43,6 +43,7 @@ class OptionButton : public Button { PopupMenu *popup; int current; + void _focused(int p_which); void _selected(int p_which); void _select(int p_which, bool p_emit = false); void _select_int(int p_which); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index d18a3a3f2f..26d01ecc09 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -113,37 +113,9 @@ void Popup::set_as_minsize() { void Popup::popup_centered_minsize(const Size2 &p_minsize) { - Size2 total_minsize = p_minsize; - - for (int i = 0; i < get_child_count(); i++) { - - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) - continue; - if (!c->is_visible()) - continue; - - Size2 minsize = c->get_combined_minimum_size(); - - for (int j = 0; j < 2; j++) { - - Margin m_beg = Margin(0 + j); - Margin m_end = Margin(2 + j); - - float margin_begin = c->get_margin(m_beg); - float margin_end = c->get_margin(m_end); - float anchor_begin = c->get_anchor(m_beg); - float anchor_end = c->get_anchor(m_end); - - minsize[j] += margin_begin * (ANCHOR_END - anchor_begin) + margin_end * anchor_end; - } - - total_minsize.width = MAX(total_minsize.width, minsize.width); - total_minsize.height = MAX(total_minsize.height, minsize.height); - } - - popup_centered(total_minsize); - popped_up = true; + set_custom_minimum_size(p_minsize); + _fix_size(); + popup_centered(); } void Popup::popup_centered(const Size2 &p_size) { diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 89000fcde1..18e609c798 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -55,7 +55,7 @@ Size2 PopupMenu::get_minimum_size() const { float max_w = 0; int font_h = font->get_height(); - int check_w = get_icon("checked")->get_width(); + int check_w = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()); int accel_max_w = 0; for (int i = 0; i < items.size(); i++) { @@ -74,7 +74,7 @@ Size2 PopupMenu::get_minimum_size() const { size.width += items[i].h_ofs; - if (items[i].checkable) { + if (items[i].checkable_type) { size.width += check_w + hseparation; } @@ -211,86 +211,69 @@ void PopupMenu::_scroll(float p_factor, const Point2 &p_over) { void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { - Ref<InputEventKey> k = p_event; - - if (k.is_valid()) { - - if (!k->is_pressed()) - return; - - switch (k->get_scancode()) { + if (p_event->is_action("ui_down") && p_event->is_pressed()) { - case KEY_DOWN: { + int search_from = mouse_over + 1; + if (search_from >= items.size()) + search_from = 0; - int search_from = mouse_over + 1; - if (search_from >= items.size()) - search_from = 0; + for (int i = search_from; i < items.size(); i++) { - for (int i = search_from; i < items.size(); i++) { - - if (i < 0 || i >= items.size()) - continue; - - if (!items[i].separator && !items[i].disabled) { - - mouse_over = i; - update(); - break; - } - } - } break; - case KEY_UP: { - - int search_from = mouse_over - 1; - if (search_from < 0) - search_from = items.size() - 1; - - for (int i = search_from; i >= 0; i--) { - - if (i < 0 || i >= items.size()) - continue; + if (i < 0 || i >= items.size()) + continue; - if (!items[i].separator && !items[i].disabled) { + if (!items[i].separator && !items[i].disabled) { - mouse_over = i; - update(); - break; - } - } - } break; - - case KEY_LEFT: { + mouse_over = i; + emit_signal("id_focused", i); + update(); + accept_event(); + break; + } + } + } else if (p_event->is_action("ui_up") && p_event->is_pressed()) { - Node *n = get_parent(); - if (!n) - break; + int search_from = mouse_over - 1; + if (search_from < 0) + search_from = items.size() - 1; - PopupMenu *pm = Object::cast_to<PopupMenu>(n); - if (!pm) - break; + for (int i = search_from; i >= 0; i--) { - hide(); - } break; + if (i < 0 || i >= items.size()) + continue; - case KEY_RIGHT: { + if (!items[i].separator && !items[i].disabled) { - if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && items[mouse_over].submenu != "" && submenu_over != mouse_over) - _activate_submenu(mouse_over); - } break; + mouse_over = i; + emit_signal("id_focused", i); + update(); + accept_event(); + break; + } + } + } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { - case KEY_ENTER: - case KEY_KP_ENTER: { + Node *n = get_parent(); + if (n && Object::cast_to<PopupMenu>(n)) { + hide(); + accept_event(); + } + } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { - if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) { + if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && items[mouse_over].submenu != "" && submenu_over != mouse_over) { + _activate_submenu(mouse_over); + accept_event(); + } + } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { - if (items[mouse_over].submenu != "" && submenu_over != mouse_over) { - _activate_submenu(mouse_over); - break; - } + if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) { - activate_item(mouse_over); - } - } break; + if (items[mouse_over].submenu != "" && submenu_over != mouse_over) { + _activate_submenu(mouse_over); + } else { + activate_item(mouse_over); + } + accept_event(); } } @@ -301,7 +284,8 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { if (b->is_pressed()) return; - switch (b->get_button_index()) { + int button_idx = b->get_button_index(); + switch (button_idx) { case BUTTON_WHEEL_DOWN: { @@ -315,30 +299,37 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { _scroll(b->get_factor(), b->get_position()); } } break; - case BUTTON_LEFT: { + default: { + // Allow activating item by releasing the LMB or any that was down when the popup appeared + if (button_idx == BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) { - int over = _get_mouse_over(b->get_position()); + bool was_during_grabbed_click = during_grabbed_click; + during_grabbed_click = false; - if (invalidated_click) { - invalidated_click = false; - break; - } - if (over < 0) { - hide(); - break; //non-activable - } + int over = _get_mouse_over(b->get_position()); + + if (invalidated_click) { + invalidated_click = false; + break; + } + if (over < 0) { + if (!was_during_grabbed_click) { + hide(); + } + break; //non-activable + } - if (items[over].separator || items[over].disabled) - break; + if (items[over].separator || items[over].disabled) + break; - if (items[over].submenu != "") { + if (items[over].submenu != "") { - _activate_submenu(over); - return; + _activate_submenu(over); + return; + } + activate_item(over); } - activate_item(over); - - } break; + } } //update(); @@ -425,8 +416,9 @@ void PopupMenu::_notification(int p_what) { Ref<StyleBox> style = get_stylebox("panel"); Ref<StyleBox> hover = get_stylebox("hover"); Ref<Font> font = get_font("font"); - Ref<Texture> check = get_icon("checked"); - Ref<Texture> uncheck = get_icon("unchecked"); + // In Item::checkable_type enum order (less the non-checkable member) + Ref<Texture> check[] = { get_icon("checked"), get_icon("radio_checked") }; + Ref<Texture> uncheck[] = { get_icon("unchecked"), get_icon("radio_unchecked") }; Ref<Texture> submenu = get_icon("submenu"); Ref<StyleBox> separator = get_stylebox("separator"); @@ -448,6 +440,8 @@ void PopupMenu::_notification(int p_what) { float h; Size2 icon_size; + Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + item_ofs.x += items[i].h_ofs; if (!items[i].icon.is_null()) { @@ -469,24 +463,20 @@ void PopupMenu::_notification(int p_what) { separator->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h))); } - if (items[i].checkable) { - - if (items[i].checked) - check->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0))); - else - uncheck->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0))); - - item_ofs.x += check->get_width() + hseparation; + if (items[i].checkable_type) { + Texture *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr(); + icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color); + item_ofs.x += icon->get_width() + hseparation; } if (!items[i].icon.is_null()) { - items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0))); + items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); item_ofs.x += items[i].icon->get_width(); item_ofs.x += hseparation; } if (items[i].submenu != "") { - submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2)); + submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); } item_ofs.y += font->get_ascent(); @@ -520,6 +510,11 @@ void PopupMenu::_notification(int p_what) { update(); } } break; + case NOTIFICATION_POST_POPUP: { + + initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); + during_grabbed_click = (bool)initial_button_mask; + } break; case NOTIFICATION_POPUP_HIDE: { if (mouse_over >= 0) { @@ -540,6 +535,7 @@ void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, item.ID = p_ID; items.push_back(item); update(); + minimum_size_changed(); } void PopupMenu::add_item(const String &p_label, int p_ID, uint32_t p_accel) { @@ -550,6 +546,7 @@ void PopupMenu::add_item(const String &p_label, int p_ID, uint32_t p_accel) { item.ID = p_ID; items.push_back(item); update(); + minimum_size_changed(); } void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_ID) { @@ -561,6 +558,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, item.submenu = p_submenu; items.push_back(item); update(); + minimum_size_changed(); } void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) { @@ -571,10 +569,12 @@ void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_ item.xl_text = tr(p_label); item.accel = p_accel; item.ID = p_ID; - item.checkable = true; + item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); + minimum_size_changed(); } + void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel) { Item item; @@ -582,9 +582,26 @@ void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel item.xl_text = tr(p_label); item.accel = p_accel; item.ID = p_ID; - item.checkable = true; + item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); + minimum_size_changed(); +} + +void PopupMenu::add_radio_check_item(const String &p_label, int p_ID, uint32_t p_accel) { + + add_check_item(p_label, p_ID, p_accel); + items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + update(); + minimum_size_changed(); +} + +void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) { + + add_icon_check_item(p_icon, p_label, p_ID, p_accel); + items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + update(); + minimum_size_changed(); } void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { @@ -600,6 +617,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut item.shortcut_is_global = p_global; items.push_back(item); update(); + minimum_size_changed(); } void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { @@ -614,7 +632,9 @@ void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_g item.shortcut_is_global = p_global; items.push_back(item); update(); + minimum_size_changed(); } + void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); @@ -624,11 +644,12 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<Sh Item item; item.ID = p_ID; item.shortcut = p_shortcut; - item.checkable = true; + item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; item.icon = p_icon; item.shortcut_is_global = p_global; items.push_back(item); update(); + minimum_size_changed(); } void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { @@ -641,9 +662,18 @@ void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bo item.ID = p_ID; item.shortcut = p_shortcut; item.shortcut_is_global = p_global; - item.checkable = true; + item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); + minimum_size_changed(); +} + +void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { + + add_check_shortcut(p_shortcut, p_ID, p_global); + items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + update(); + minimum_size_changed(); } void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_ID, uint32_t p_accel) { @@ -653,11 +683,11 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int item.xl_text = tr(p_label); item.accel = p_accel; item.ID = p_ID; - item.checkable = false; item.max_states = p_max_states; item.state = p_default_state; items.push_back(item); update(); + minimum_size_changed(); } void PopupMenu::set_item_text(int p_idx, const String &p_text) { @@ -667,6 +697,7 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) { items[p_idx].xl_text = tr(p_text); update(); + minimum_size_changed(); } void PopupMenu::set_item_icon(int p_idx, const Ref<Texture> &p_icon) { @@ -674,6 +705,7 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture> &p_icon) { items[p_idx].icon = p_icon; update(); + minimum_size_changed(); } void PopupMenu::set_item_checked(int p_idx, bool p_checked) { @@ -682,6 +714,7 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) { items[p_idx].checked = p_checked; update(); + minimum_size_changed(); } void PopupMenu::set_item_id(int p_idx, int p_ID) { @@ -689,6 +722,7 @@ void PopupMenu::set_item_id(int p_idx, int p_ID) { items[p_idx].ID = p_ID; update(); + minimum_size_changed(); } void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) { @@ -697,6 +731,7 @@ void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) { items[p_idx].accel = p_accel; update(); + minimum_size_changed(); } void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { @@ -704,6 +739,7 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { ERR_FAIL_INDEX(p_idx, items.size()); items[p_idx].metadata = p_meta; update(); + minimum_size_changed(); } void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { @@ -711,6 +747,7 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { ERR_FAIL_INDEX(p_idx, items.size()); items[p_idx].disabled = p_disabled; update(); + minimum_size_changed(); } void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { @@ -718,6 +755,7 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { ERR_FAIL_INDEX(p_idx, items.size()); items[p_idx].submenu = p_submenu; update(); + minimum_size_changed(); } void PopupMenu::toggle_item_checked(int p_idx) { @@ -725,6 +763,7 @@ void PopupMenu::toggle_item_checked(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); items[p_idx].checked = !items[p_idx].checked; update(); + minimum_size_changed(); } String PopupMenu::get_item_text(int p_idx) const { @@ -828,7 +867,14 @@ bool PopupMenu::is_item_separator(int p_idx) const { void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) { ERR_FAIL_INDEX(p_idx, items.size()); - items[p_idx].checkable = p_checkable; + items[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE; + update(); +} + +void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) { + + ERR_FAIL_INDEX(p_idx, items.size()); + items[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE; update(); } @@ -859,6 +905,7 @@ void PopupMenu::set_item_h_offset(int p_idx, int p_offset) { ERR_FAIL_INDEX(p_idx, items.size()); items[p_idx].h_ofs = p_offset; update(); + minimum_size_changed(); } void PopupMenu::set_item_multistate(int p_idx, int p_state) { @@ -868,6 +915,13 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) { update(); } +void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { + + ERR_FAIL_INDEX(p_idx, items.size()); + items[p_idx].shortcut_is_disabled = p_disabled; + update(); +} + void PopupMenu::toggle_item_multistate(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); @@ -884,7 +938,18 @@ void PopupMenu::toggle_item_multistate(int p_idx) { bool PopupMenu::is_item_checkable(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), false); - return items[p_idx].checkable; + return items[p_idx].checkable_type; +} + +bool PopupMenu::is_item_radio_checkable(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), false); + return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON; +} + +bool PopupMenu::is_item_shortcut_disabled(int p_idx) const { + + ERR_FAIL_INDEX_V(p_idx, items.size(), false); + return items[p_idx].shortcut_is_disabled; } int PopupMenu::get_item_count() const { @@ -913,7 +978,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo int il = items.size(); for (int i = 0; i < il; i++) { - if (is_item_disabled(i)) + if (is_item_disabled(i) || items[i].shortcut_is_disabled) continue; if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) { @@ -958,7 +1023,7 @@ void PopupMenu::activate_item(int p_item) { // We close all parents that are chained together, // with hide_on_item_selection enabled - if (items[p_item].checkable) { + if (items[p_item].checkable_type) { if (!hide_on_checkable_item_selection || !pop->is_hide_on_checkable_item_selection()) break; } else if (0 < items[p_item].max_states) { @@ -975,7 +1040,7 @@ void PopupMenu::activate_item(int p_item) { // Hides popup by default; unless otherwise specified // by using set_hide_on_item_selection and set_hide_on_checkable_item_selection - if (items[p_item].checkable) { + if (items[p_item].checkable_type) { if (!hide_on_checkable_item_selection) return; } else if (0 < items[p_item].max_states) { @@ -1018,6 +1083,7 @@ void PopupMenu::clear() { items.clear(); mouse_over = -1; update(); + minimum_size_changed(); } Array PopupMenu::_get_items() const { @@ -1027,7 +1093,9 @@ Array PopupMenu::_get_items() const { items.push_back(get_item_text(i)); items.push_back(get_item_icon(i)); - items.push_back(is_item_checkable(i)); + // For compatibility, use false/true for no/checkbox and integers for other values + int ct = this->items[i].checkable_type; + items.push_back(Variant(ct <= Item::CHECKABLE_TYPE_CHECK_BOX ? is_item_checkable(i) : ct)); items.push_back(is_item_checked(i)); items.push_back(is_item_disabled(i)); @@ -1070,7 +1138,9 @@ void PopupMenu::_set_items(const Array &p_items) { String text = p_items[i + 0]; Ref<Texture> icon = p_items[i + 1]; + // For compatibility, use false/true for no/checkbox and integers for other values bool checkable = p_items[i + 2]; + bool radio_checkable = (int)p_items[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON; bool checked = p_items[i + 3]; bool disabled = p_items[i + 4]; @@ -1083,7 +1153,13 @@ void PopupMenu::_set_items(const Array &p_items) { int idx = get_item_count(); add_item(text, id); set_item_icon(idx, icon); - set_item_as_checkable(idx, checkable); + if (checkable) { + if (radio_checkable) { + set_item_as_radio_checkable(idx, true); + } else { + set_item_as_checkable(idx, true); + } + } set_item_checked(idx, checked); set_item_disabled(idx, disabled); set_item_id(idx, id); @@ -1164,12 +1240,14 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("add_radio_check_item", "label", "id", "accel"), &PopupMenu::add_radio_check_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_check_shortcut, DEFVAL(-1), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_radio_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_radio_check_shortcut, DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &PopupMenu::set_item_text); ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon); @@ -1181,9 +1259,11 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_submenu", "idx", "submenu"), &PopupMenu::set_item_submenu); ClassDB::bind_method(D_METHOD("set_item_as_separator", "idx", "enable"), &PopupMenu::set_item_as_separator); ClassDB::bind_method(D_METHOD("set_item_as_checkable", "idx", "enable"), &PopupMenu::set_item_as_checkable); + ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "idx", "enable"), &PopupMenu::set_item_as_radio_checkable); ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip); ClassDB::bind_method(D_METHOD("set_item_shortcut", "idx", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_item_multistate", "idx", "state"), &PopupMenu::set_item_multistate); + ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "idx", "disabled"), &PopupMenu::set_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked); ClassDB::bind_method(D_METHOD("toggle_item_multistate", "idx"), &PopupMenu::toggle_item_multistate); @@ -1199,6 +1279,8 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_submenu", "idx"), &PopupMenu::get_item_submenu); ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &PopupMenu::is_item_separator); ClassDB::bind_method(D_METHOD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable); + ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "idx"), &PopupMenu::is_item_radio_checkable); + ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "idx"), &PopupMenu::is_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip); ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut); @@ -1229,18 +1311,24 @@ void PopupMenu::_bind_methods() { ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection"); ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "ID"))); + ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "ID"))); ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index"))); } -void PopupMenu::set_invalidate_click_until_motion() { +void PopupMenu::popup(const Rect2 &p_bounds) { + + grab_click_focus(); moved = Vector2(); invalidated_click = true; + Popup::popup(p_bounds); } PopupMenu::PopupMenu() { mouse_over = -1; submenu_over = -1; + initial_button_mask = 0; + during_grabbed_click = false; set_focus_mode(FOCUS_ALL); set_as_toplevel(true); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 60f36e95ec..d3ee9be1c0 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -46,7 +46,11 @@ class PopupMenu : public Popup { String text; String xl_text; bool checked; - bool checkable; + enum { + CHECKABLE_TYPE_NONE, + CHECKABLE_TYPE_CHECK_BOX, + CHECKABLE_TYPE_RADIO_BUTTON, + } checkable_type; int max_states; int state; bool separator; @@ -60,10 +64,11 @@ class PopupMenu : public Popup { int h_ofs; Ref<ShortCut> shortcut; bool shortcut_is_global; + bool shortcut_is_disabled; Item() { checked = false; - checkable = false; + checkable_type = CHECKABLE_TYPE_NONE; separator = false; max_states = 0; state = 0; @@ -72,12 +77,15 @@ class PopupMenu : public Popup { _ofs_cache = 0; h_ofs = 0; shortcut_is_global = false; + shortcut_is_disabled = false; } }; Timer *submenu_timer; List<Rect2> autohide_areas; Vector<Item> items; + int initial_button_mask; + bool during_grabbed_click; int mouse_over; int submenu_over; Rect2 parent_rect; @@ -115,12 +123,15 @@ public: void add_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); void add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); void add_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); + void add_radio_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); + void add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); void add_submenu_item(const String &p_label, const String &p_submenu, int p_ID = -1); void add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); void add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); void add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); void add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); + void add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); void add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_ID = -1, uint32_t p_accel = 0); @@ -134,11 +145,13 @@ public: void set_item_submenu(int p_idx, const String &p_submenu); void set_item_as_separator(int p_idx, bool p_separator); void set_item_as_checkable(int p_idx, bool p_checkable); + void set_item_as_radio_checkable(int p_idx, bool p_radio_checkable); void set_item_tooltip(int p_idx, const String &p_tooltip); void set_item_shortcut(int p_idx, const Ref<ShortCut> &p_shortcut, bool p_global = false); void set_item_h_offset(int p_idx, int p_offset); void set_item_multistate(int p_idx, int p_state); void toggle_item_multistate(int p_idx); + void set_item_shortcut_disabled(int p_idx, bool p_disabled); void toggle_item_checked(int p_idx); @@ -154,6 +167,8 @@ public: String get_item_submenu(int p_idx) const; bool is_item_separator(int p_idx) const; bool is_item_checkable(int p_idx) const; + bool is_item_radio_checkable(int p_idx) const; + bool is_item_shortcut_disabled(int p_idx) const; String get_item_tooltip(int p_idx) const; Ref<ShortCut> get_item_shortcut(int p_idx) const; int get_item_state(int p_idx) const; @@ -178,7 +193,6 @@ public: void add_autohide_area(const Rect2 &p_area); void clear_autohide_areas(); - void set_invalidate_click_until_motion(); void set_hide_on_item_selection(bool p_enabled); bool is_hide_on_item_selection() const; @@ -188,6 +202,8 @@ public: void set_hide_on_multistate_item_selection(bool p_enabled); bool is_hide_on_multistate_item_selection() const; + virtual void popup(const Rect2 &p_bounds = Rect2()); + PopupMenu(); ~PopupMenu(); }; diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index c85bed0451..fc5d56237a 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -33,13 +33,16 @@ Size2 ProgressBar::get_minimum_size() const { Ref<StyleBox> bg = get_stylebox("bg"); + Ref<StyleBox> fg = get_stylebox("fg"); Ref<Font> font = get_font("font"); - Size2 ms = bg->get_minimum_size() + bg->get_center_size(); - if (percent_visible) { - ms.height = MAX(ms.height, bg->get_minimum_size().height + font->get_height()); - } - return ms; + Size2 minimum_size = bg->get_minimum_size(); + minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height); + minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width); + //if (percent_visible) { this is needed, else the progressbar will collapse + minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height()); + //} + return minimum_size; } void ProgressBar::_notification(int p_what) { diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index cd6c6bb65c..4062e48640 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -71,10 +71,10 @@ void Range::set_value(double p_val) { p_val = Math::round(p_val); } - if (p_val > shared->max - shared->page) + if (!shared->allow_greater && p_val > shared->max - shared->page) p_val = shared->max - shared->page; - if (p_val < shared->min) + if (!shared->allow_lesser && p_val < shared->min) p_val = shared->min; if (shared->val == p_val) @@ -136,9 +136,9 @@ void Range::set_as_ratio(double p_value) { double v; - if (shared->exp_ratio && get_min() > 0) { + if (shared->exp_ratio && get_min() >= 0) { - double exp_min = Math::log(get_min()) / Math::log((double)2); + double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2); double exp_max = Math::log(get_max()) / Math::log((double)2); v = Math::pow(2, exp_min + (exp_max - exp_min) * p_value); } else { @@ -151,21 +151,24 @@ void Range::set_as_ratio(double p_value) { v = percent + get_min(); } } + v = CLAMP(v, get_min(), get_max()); set_value(v); } double Range::get_as_ratio() const { - if (shared->exp_ratio && get_min() > 0) { + if (shared->exp_ratio && get_min() >= 0) { - double exp_min = Math::log(get_min()) / Math::log((double)2); + double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2); double exp_max = Math::log(get_max()) / Math::log((double)2); - double v = Math::log(get_value()) / Math::log((double)2); + float value = CLAMP(get_value(), shared->min, shared->max); + double v = Math::log(value) / Math::log((double)2); return (v - exp_min) / (exp_max - exp_min); } else { - return (get_value() - get_min()) / (get_max() - get_min()); + float value = CLAMP(get_value(), shared->min, shared->max); + return (value - get_min()) / (get_max() - get_min()); } } @@ -193,6 +196,8 @@ void Range::unshare() { nshared->val = shared->val; nshared->step = shared->step; nshared->page = shared->page; + nshared->allow_greater = shared->allow_greater; + nshared->allow_lesser = shared->allow_lesser; _unref_shared(); _ref_shared(nshared); } @@ -236,6 +241,10 @@ void Range::_bind_methods() { ClassDB::bind_method(D_METHOD("is_using_rounded_values"), &Range::is_using_rounded_values); ClassDB::bind_method(D_METHOD("set_exp_ratio", "enabled"), &Range::set_exp_ratio); ClassDB::bind_method(D_METHOD("is_ratio_exp"), &Range::is_ratio_exp); + ClassDB::bind_method(D_METHOD("set_allow_greater", "allow"), &Range::set_allow_greater); + ClassDB::bind_method(D_METHOD("is_greater_allowed"), &Range::is_greater_allowed); + ClassDB::bind_method(D_METHOD("set_allow_lesser", "allow"), &Range::set_allow_lesser); + ClassDB::bind_method(D_METHOD("is_lesser_allowed"), &Range::is_lesser_allowed); ClassDB::bind_method(D_METHOD("share", "with"), &Range::_share); ClassDB::bind_method(D_METHOD("unshare"), &Range::unshare); @@ -251,6 +260,8 @@ void Range::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "ratio", PROPERTY_HINT_RANGE, "0,1,0.01", 0), "set_as_ratio", "get_as_ratio"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exp_edit"), "set_exp_ratio", "is_ratio_exp"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rounded"), "set_use_rounded_values", "is_using_rounded_values"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "allow_greater"), "set_allow_greater", "is_greater_allowed"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "allow_lesser"), "set_allow_lesser", "is_lesser_allowed"); } void Range::set_use_rounded_values(bool p_enable) { @@ -273,6 +284,22 @@ bool Range::is_ratio_exp() const { return shared->exp_ratio; } +void Range::set_allow_greater(bool p_allow) { + shared->allow_greater = p_allow; +} + +bool Range::is_greater_allowed() const { + return shared->allow_greater; +} + +void Range::set_allow_lesser(bool p_allow) { + shared->allow_lesser = p_allow; +} + +bool Range::is_lesser_allowed() const { + return shared->allow_lesser; +} + Range::Range() { shared = memnew(Shared); shared->min = 0; @@ -282,6 +309,8 @@ Range::Range() { shared->page = 0; shared->owners.insert(this); shared->exp_ratio = false; + shared->allow_greater = false; + shared->allow_lesser = false; _rounded_values = false; } diff --git a/scene/gui/range.h b/scene/gui/range.h index de9383afd8..125f559248 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -43,6 +43,8 @@ class Range : public Control { double val, min, max; double step, page; bool exp_ratio; + bool allow_greater; + bool allow_lesser; Set<Range *> owners; void emit_value_changed(); void emit_changed(const char *p_what = ""); @@ -86,6 +88,12 @@ public: void set_exp_ratio(bool p_enable); bool is_ratio_exp() const; + void set_allow_greater(bool p_allow); + bool is_greater_allowed() const; + + void set_allow_lesser(bool p_allow); + bool is_lesser_allowed() const; + void share(Range *p_range); void unshare(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 381c6c75a5..ce2e3538da 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -86,11 +86,59 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) { return NULL; } +RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) { + if (p_free) { + + if (p_item->subitems.size()) { + + return p_item->subitems.back()->get(); + } else if (!p_item->parent) { + return NULL; + } else if (p_item->E->prev()) { + + return p_item->E->prev()->get(); + } else { + //go back until something with a prev is found + while (p_item->parent && !p_item->E->prev()) { + p_item = p_item->parent; + } + + if (p_item->parent) + return p_item->E->prev()->get(); + else + return NULL; + } + + } else { + if (p_item->subitems.size() && p_item->type != ITEM_TABLE) { + + return p_item->subitems.back()->get(); + } else if (p_item->type == ITEM_FRAME) { + return NULL; + } else if (p_item->E->prev()) { + + return p_item->E->prev()->get(); + } else { + //go back until something with a prev is found + while (p_item->type != ITEM_FRAME && !p_item->E->prev()) { + p_item = p_item->parent; + } + + if (p_item->type != ITEM_FRAME) + return p_item->E->prev()->get(); + else + return NULL; + } + } + + return NULL; +} + Rect2 RichTextLabel::_get_text_rect() { Ref<StyleBox> style = get_stylebox("normal"); return Rect2(style->get_offset(), get_size() - style->get_minimum_size()); } -int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &y, int p_width, int p_line, ProcessMode p_mode, const Ref<Font> &p_base_font, const Color &p_base_color, const Point2i &p_click_pos, Item **r_click_item, int *r_click_char, bool *r_outside, int p_char_count) { +int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &y, int p_width, int p_line, ProcessMode p_mode, const Ref<Font> &p_base_font, const Color &p_base_color, const Color &p_font_color_shadow, bool p_shadow_as_outline, const Point2 &shadow_ofs, const Point2i &p_click_pos, Item **r_click_item, int *r_click_char, bool *r_outside, int p_char_count) { RID ci; if (r_outside) @@ -125,6 +173,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & l.descent_caches.clear(); l.char_count = 0; l.minimum_width = 0; + l.maximum_width = 0; } int wofs = margin; @@ -200,7 +249,8 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & #define ENSURE_WIDTH(m_width) \ if (p_mode == PROCESS_CACHE) { \ - l.minimum_width = MAX(l.minimum_width, wofs + m_width); \ + l.maximum_width = MAX(l.maximum_width, MIN(p_width, wofs + m_width)); \ + l.minimum_width = MAX(l.minimum_width, m_width); \ } \ if (wofs + m_width > p_width) { \ if (p_mode == PROCESS_CACHE) { \ @@ -247,6 +297,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & int rchar = 0; int lh = 0; bool line_is_blank = true; + int fh = 0; while (it) { @@ -262,21 +313,18 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & const CharType *c = text->text.c_str(); const CharType *cf = c; - int fh = font->get_height(); int ascent = font->get_ascent(); int descent = font->get_descent(); - line_ascent = MAX(line_ascent, ascent); - line_descent = MAX(line_descent, descent); - fh = MAX(fh, line_ascent + line_descent); // various fonts! - Color color; + Color font_color_shadow; bool underline = false; if (p_mode == PROCESS_DRAW) { color = _find_color(text, p_base_color); + font_color_shadow = _find_color(text, p_font_color_shadow); underline = _find_underline(text); - if (_find_meta(text, &meta)) { + if (_find_meta(text, &meta) && underline_meta) { underline = true; } @@ -286,7 +334,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & } rchar = 0; - + FontDrawer drawer(font, Color(1, 1, 1)); while (*c) { int end = 0; @@ -299,7 +347,6 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & line_ascent = line < l.ascent_caches.size() ? l.ascent_caches[line] : 1; line_descent = line < l.descent_caches.size() ? l.descent_caches[line] : 1; } - while (c[end] != 0 && !(end && c[end - 1] == ' ' && c[end] != ' ')) { int cw = font->get_char_size(c[end], c[end + 1]).width; @@ -316,9 +363,13 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & end++; } - + CHECK_HEIGHT(fh); ENSURE_WIDTH(w); + line_ascent = MAX(line_ascent, ascent); + line_descent = MAX(line_descent, descent); + fh = line_ascent + line_descent; + if (end && c[end - 1] == ' ') { if (p_mode == PROCESS_CACHE) { spaces_size += font->get_char_size(' ').width; @@ -373,16 +424,30 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & if (c[i] == '\t') visible = false; - if (selected) { + if (visible) { + if (selected) { + cw = font->get_char_size(c[i], c[i + 1]).x; + draw_rect(Rect2(p_ofs.x + pofs, p_ofs.y + y, cw, lh), selection_bg); + } - cw = font->get_char_size(c[i], c[i + 1]).x; - draw_rect(Rect2(p_ofs.x + pofs, p_ofs.y + y, cw, lh), selection_bg); - if (visible) - font->draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], override_selected_font_color ? selection_fg : color); + if (p_font_color_shadow.a > 0) { + float x_ofs_shadow = align_ofs + pofs; + float y_ofs_shadow = y + lh - line_descent; + float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs, c[i], c[i + 1], p_font_color_shadow); + + if (p_shadow_as_outline) { + font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow); + font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow); + font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow); + } + x_ofs_shadow += move; + } - } else { - if (visible) - cw = font->draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], color); + if (selected) { + drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], override_selected_font_color ? selection_fg : color); + } else { + cw = drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], color); + } } p_char_count++; @@ -461,6 +526,9 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & int vseparation = get_constant("table_vseparation"); Color ccolor = _find_color(table, p_base_color); Vector2 draw_ofs = Point2(wofs, y); + Color font_color_shadow = get_color("font_color_shadow"); + bool use_outline = get_constant("shadow_as_outline"); + Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); if (p_mode == PROCESS_CACHE) { @@ -468,6 +536,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & //set minimums to zero for (int i = 0; i < table->columns.size(); i++) { table->columns[i].min_width = 0; + table->columns[i].max_width = 0; table->columns[i].width = 0; } //compute minimum width for each cell @@ -483,8 +552,9 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & for (int i = 0; i < frame->lines.size(); i++) { - _process_line(frame, Point2(), ly, available_width, i, PROCESS_CACHE, cfont, Color()); + _process_line(frame, Point2(), ly, available_width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs); table->columns[column].min_width = MAX(table->columns[column].min_width, frame->lines[i].minimum_width); + table->columns[column].max_width = MAX(table->columns[column].max_width, frame->lines[i].maximum_width); } idx++; } @@ -497,12 +567,13 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & for (int i = 0; i < table->columns.size(); i++) { remaining_width -= table->columns[i].min_width; + if (table->columns[i].max_width > table->columns[i].min_width) + table->columns[i].expand = true; if (table->columns[i].expand) total_ratio += table->columns[i].expand_ratio; } //assign actual widths - for (int i = 0; i < table->columns.size(); i++) { table->columns[i].width = table->columns[i].min_width; if (table->columns[i].expand) @@ -510,6 +581,39 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & table->total_width += table->columns[i].width + hseparation; } + //resize to max_width if needed and distribute the remaining space + bool table_need_fit = true; + while (table_need_fit) { + table_need_fit = false; + //fit slim + for (int i = 0; i < table->columns.size(); i++) { + if (!table->columns[i].expand) + continue; + int dif = table->columns[i].width - table->columns[i].max_width; + if (dif > 0) { + table_need_fit = true; + table->columns[i].width = table->columns[i].max_width; + table->total_width -= dif; + total_ratio -= table->columns[i].expand_ratio; + } + } + //grow + remaining_width = available_width - table->total_width; + if (remaining_width > 0 && total_ratio > 0) { + for (int i = 0; i < table->columns.size(); i++) { + if (table->columns[i].expand) { + int dif = table->columns[i].max_width - table->columns[i].width; + if (dif > 0) { + int slice = table->columns[i].expand_ratio * remaining_width / total_ratio; + int incr = MIN(dif, slice); + table->columns[i].width += incr; + table->total_width += incr; + } + } + } + } + } + //compute caches properly again with the right width idx = 0; for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { @@ -521,7 +625,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & for (int i = 0; i < frame->lines.size(); i++) { int ly = 0; - _process_line(frame, Point2(), ly, table->columns[column].width, i, PROCESS_CACHE, cfont, Color()); + _process_line(frame, Point2(), ly, table->columns[column].width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs); frame->lines[i].height_cache = ly; //actual height frame->lines[i].height_accum_cache = ly; //actual height } @@ -554,9 +658,9 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & if (visible) { if (p_mode == PROCESS_DRAW) { - nonblank_line_count += _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_DRAW, cfont, ccolor); + nonblank_line_count += _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_DRAW, cfont, ccolor, font_color_shadow, use_outline, shadow_ofs); } else if (p_mode == PROCESS_POINTER) { - _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_POINTER, cfont, ccolor, p_click_pos, r_click_item, r_click_char, r_outside); + _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_POINTER, cfont, ccolor, font_color_shadow, use_outline, shadow_ofs, p_click_pos, r_click_item, r_click_char, r_outside); if (r_click_item && *r_click_item) { RETURN; // exit early } @@ -637,9 +741,7 @@ void RichTextLabel::_scroll_changed(double) { void RichTextLabel::_update_scroll() { - int total_height = 0; - if (main->lines.size()) - total_height = main->lines[main->lines.size() - 1].height_accum_cache + get_stylebox("normal")->get_minimum_size().height; + int total_height = get_content_height(); bool exceeds = total_height > get_size().height && scroll_active; @@ -728,12 +830,18 @@ void RichTextLabel::_notification(int p_what) { int y = (main->lines[from_line].height_accum_cache - main->lines[from_line].height_cache) - ofs; Ref<Font> base_font = get_font("normal_font"); Color base_color = get_color("default_color"); + Color font_color_shadow = get_color("font_color_shadow"); + bool use_outline = get_constant("shadow_as_outline"); + Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); + + float x_ofs = 0; visible_line_count = 0; while (y < size.height && from_line < main->lines.size()) { - visible_line_count += _process_line(main, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_DRAW, base_font, base_color, Point2i(), NULL, NULL, NULL, total_chars); + visible_line_count += _process_line(main, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_DRAW, base_font, base_color, font_color_shadow, use_outline, shadow_ofs, Point2i(), NULL, NULL, NULL, total_chars); total_chars += main->lines[from_line].char_count; + from_line++; } } @@ -748,6 +856,9 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item Size2 size = get_size(); Rect2 text_rect = _get_text_rect(); int ofs = vscroll->get_value(); + Color font_color_shadow = get_color("font_color_shadow"); + bool use_outline = get_constant("shadow_as_outline"); + Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); //todo, change to binary search int from_line = 0; @@ -768,7 +879,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item while (y < text_rect.get_size().height && from_line < p_frame->lines.size()) { - _process_line(p_frame, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_POINTER, base_font, base_color, p_click, r_click_item, r_click_char, r_outside); + _process_line(p_frame, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_POINTER, base_font, base_color, font_color_shadow, use_outline, shadow_ofs, p_click, r_click_item, r_click_char, r_outside); if (r_click_item && *r_click_item) return; from_line++; @@ -828,9 +939,9 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { // Erase previous selection. if (selection.active) { selection.from = NULL; - selection.from_char = NULL; + selection.from_char = '\0'; selection.to = NULL; - selection.to_char = NULL; + selection.to_char = '\0'; selection.active = false; update(); @@ -1143,13 +1254,16 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { //validate invalid lines Size2 size = get_size(); Rect2 text_rect = _get_text_rect(); + Color font_color_shadow = get_color("font_color_shadow"); + bool use_outline = get_constant("shadow_as_outline"); + Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); Ref<Font> base_font = get_font("normal_font"); for (int i = p_frame->first_invalid_line; i < p_frame->lines.size(); i++) { int y = 0; - _process_line(p_frame, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, i, PROCESS_CACHE, base_font, Color()); + _process_line(p_frame, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, i, PROCESS_CACHE, base_font, Color(), font_color_shadow, use_outline, shadow_ofs); p_frame->lines[i].height_cache = y; p_frame->lines[i].height_accum_cache = y; @@ -1632,7 +1746,7 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { tag_stack.push_front(tag); } else if (tag.begins_with("cell=")) { - int ratio = tag.substr(6, tag.length()).to_int(); + int ratio = tag.substr(5, tag.length()).to_int(); if (ratio < 1) ratio = 1; //use monospace font @@ -1823,7 +1937,7 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) { } } -bool RichTextLabel::search(const String &p_string, bool p_from_selection) { +bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p_search_previous) { ERR_FAIL_COND_V(!selection.enabled, false); Item *it = main; @@ -1872,7 +1986,10 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection) { } } - it = _get_next_item(it, true); + if (p_search_previous) + it = _get_prev_item(it, true); + else + it = _get_next_item(it, true); charidx = 0; } @@ -1991,6 +2108,13 @@ float RichTextLabel::get_percent_visible() const { return percent_visible; } +int RichTextLabel::get_content_height() { + int total_height = 0; + if (main->lines.size()) + total_height = main->lines[main->lines.size() - 1].height_accum_cache + get_stylebox("normal")->get_minimum_size().height; + return total_height; +} + void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &RichTextLabel::_gui_input); @@ -2057,6 +2181,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_count"), &RichTextLabel::get_line_count); ClassDB::bind_method(D_METHOD("get_visible_line_count"), &RichTextLabel::get_visible_line_count); + ClassDB::bind_method(D_METHOD("get_content_height"), &RichTextLabel::get_content_height); + ADD_GROUP("BBCode", "bbcode_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bbcode_text", PROPERTY_HINT_MULTILINE_TEXT), "set_bbcode", "get_bbcode"); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index e7d5e6bb1b..af368af46a 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -87,6 +87,7 @@ private: int height_accum_cache; int char_count; int minimum_width; + int maximum_width; Line() { from = NULL; @@ -189,7 +190,6 @@ private: struct ItemNewline : public Item { - int line; // FIXME: Overriding base's line ? ItemNewline() { type = ITEM_NEWLINE; } }; @@ -199,6 +199,7 @@ private: bool expand; int expand_ratio; int min_width; + int max_width; int width; }; @@ -268,7 +269,7 @@ private: int visible_characters; float percent_visible; - int _process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &y, int p_width, int p_line, ProcessMode p_mode, const Ref<Font> &p_base_font, const Color &p_base_color, const Point2i &p_click_pos = Point2i(), Item **r_click_item = NULL, int *r_click_char = NULL, bool *r_outside = NULL, int p_char_count = 0); + int _process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &y, int p_width, int p_line, ProcessMode p_mode, const Ref<Font> &p_base_font, const Color &p_base_color, const Color &p_font_color_shadow, bool p_shadow_as_outline, const Point2 &shadow_ofs, const Point2i &p_click_pos = Point2i(), Item **r_click_item = NULL, int *r_click_char = NULL, bool *r_outside = NULL, int p_char_count = 0); void _find_click(ItemFrame *p_frame, const Point2i &p_click, Item **r_click_item = NULL, int *r_click_char = NULL, bool *r_outside = NULL); Ref<Font> _find_font(Item *p_item); @@ -283,6 +284,7 @@ private: void _gui_input(Ref<InputEvent> p_event); Item *_get_next_item(Item *p_item, bool p_free = false); + Item *_get_prev_item(Item *p_item, bool p_free = false); Rect2 _get_text_rect(); @@ -332,12 +334,14 @@ public: void set_tab_size(int p_spaces); int get_tab_size() const; - bool search(const String &p_string, bool p_from_selection = false); + bool search(const String &p_string, bool p_from_selection = false, bool p_search_previous = false); void scroll_to_line(int p_line); int get_line_count() const; int get_visible_line_count() const; + int get_content_height(); + VScrollBar *get_v_scroll() { return vscroll; } virtual CursorShape get_cursor_shape(const Point2 &p_pos) const; diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 95fcda2db3..6ec67aca6b 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -114,7 +114,7 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { if (smooth_scroll_enabled) { scrolling = true; - set_physics_process(true); + set_physics_process_internal(true); } else { set_value(target_scroll); } @@ -138,7 +138,7 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { if (smooth_scroll_enabled) { scrolling = true; - set_physics_process(true); + set_physics_process_internal(true); } else { set_value(target_scroll); } @@ -199,54 +199,40 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { } } - Ref<InputEventKey> k = p_event; + if (p_event->is_pressed()) { - if (k.is_valid()) { + if (p_event->is_action("ui_left")) { - if (!k->is_pressed()) - return; - - switch (k->get_scancode()) { - - case KEY_LEFT: { - - if (orientation != HORIZONTAL) - return; - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + if (orientation != HORIZONTAL) + return; + set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); - } break; - case KEY_RIGHT: { + } else if (p_event->is_action("ui_right")) { - if (orientation != HORIZONTAL) - return; - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - - } break; - case KEY_UP: { + if (orientation != HORIZONTAL) + return; + set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - if (orientation != VERTICAL) - return; + } else if (p_event->is_action("ui_up")) { - set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + if (orientation != VERTICAL) + return; - } break; - case KEY_DOWN: { + set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); - if (orientation != VERTICAL) - return; - set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + } else if (p_event->is_action("ui_down")) { - } break; - case KEY_HOME: { + if (orientation != VERTICAL) + return; + set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - set_value(get_min()); + } else if (p_event->is_action("ui_home")) { - } break; - case KEY_END: { + set_value(get_min()); - set_value(get_max()); + } else if (p_event->is_action("ui_end")) { - } break; + set_value(get_max()); } } } @@ -336,7 +322,7 @@ void ScrollBar::_notification(int p_what) { drag_slave = NULL; } - if (p_what == NOTIFICATION_PHYSICS_PROCESS) { + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { if (scrolling) { if (get_value() != target_scroll) { @@ -351,7 +337,7 @@ void ScrollBar::_notification(int p_what) { } } else { scrolling = false; - set_physics_process(false); + set_physics_process_internal(false); } } else if (drag_slave_touching) { @@ -411,7 +397,7 @@ void ScrollBar::_notification(int p_what) { } if (turnoff) { - set_physics_process(false); + set_physics_process_internal(false); drag_slave_touching = false; drag_slave_touching_deaccel = false; } @@ -580,7 +566,7 @@ void ScrollBar::_drag_slave_input(const Ref<InputEvent> &p_input) { if (mb->is_pressed()) { if (drag_slave_touching) { - set_physics_process(false); + set_physics_process_internal(false); drag_slave_touching_deaccel = false; drag_slave_touching = false; drag_slave_speed = Vector2(); @@ -600,7 +586,7 @@ void ScrollBar::_drag_slave_input(const Ref<InputEvent> &p_input) { drag_slave_touching_deaccel = false; time_since_motion = 0; if (drag_slave_touching) { - set_physics_process(true); + set_physics_process_internal(true); time_since_motion = 0; } } @@ -612,7 +598,7 @@ void ScrollBar::_drag_slave_input(const Ref<InputEvent> &p_input) { if (drag_slave_speed == Vector2()) { drag_slave_touching_deaccel = false; drag_slave_touching = false; - set_physics_process(false); + set_physics_process_internal(false); } else { drag_slave_touching_deaccel = true; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 33b3d46486..495d618930 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -37,6 +37,7 @@ bool ScrollContainer::clips_input() const { Size2 ScrollContainer::get_minimum_size() const { + Ref<StyleBox> sb = get_stylebox("bg"); Size2 min_size; for (int i = 0; i < get_child_count(); i++) { @@ -64,17 +65,24 @@ Size2 ScrollContainer::get_minimum_size() const { if (v_scroll->is_visible_in_tree()) { min_size.x += v_scroll->get_minimum_size().x; } + min_size += sb->get_minimum_size(); return min_size; -}; +} void ScrollContainer::_cancel_drag() { - set_physics_process(false); + set_physics_process_internal(false); drag_touching_deaccel = false; drag_touching = false; drag_speed = Vector2(); drag_accum = Vector2(); last_drag_accum = Vector2(); drag_from = Vector2(); + + if (beyond_deadzone) { + emit_signal("scroll_ended"); + propagate_notification(NOTIFICATION_SCROLL_END); + beyond_deadzone = false; + } } void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { @@ -122,13 +130,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->is_pressed()) { if (drag_touching) { - set_physics_process(false); - drag_touching_deaccel = false; - drag_touching = false; - drag_speed = Vector2(); - drag_accum = Vector2(); - last_drag_accum = Vector2(); - drag_from = Vector2(); + _cancel_drag(); } if (true) { @@ -138,9 +140,10 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value()); drag_touching = OS::get_singleton()->has_touchscreen_ui_hint(); drag_touching_deaccel = false; + beyond_deadzone = false; time_since_motion = 0; if (drag_touching) { - set_physics_process(true); + set_physics_process_internal(true); time_since_motion = 0; } } @@ -149,9 +152,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { if (drag_touching) { if (drag_speed == Vector2()) { - drag_touching_deaccel = false; - drag_touching = false; - set_physics_process(false); + _cancel_drag(); } else { drag_touching_deaccel = true; @@ -168,17 +169,27 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { Vector2 motion = Vector2(mm->get_relative().x, mm->get_relative().y); drag_accum -= motion; - Vector2 diff = drag_from + drag_accum; - - if (scroll_h) - h_scroll->set_value(diff.x); - else - drag_accum.x = 0; - if (scroll_v) - v_scroll->set_value(diff.y); - else - drag_accum.y = 0; - time_since_motion = 0; + + if (beyond_deadzone || scroll_h && Math::abs(drag_accum.x) > deadzone || scroll_v && Math::abs(drag_accum.y) > deadzone) { + if (!beyond_deadzone) { + propagate_notification(NOTIFICATION_SCROLL_BEGIN); + emit_signal("scroll_started"); + + beyond_deadzone = true; + // resetting drag_accum here ensures smooth scrolling after reaching deadzone + drag_accum = -motion; + } + Vector2 diff = drag_from + drag_accum; + if (scroll_h) + h_scroll->set_value(diff.x); + else + drag_accum.x = 0; + if (scroll_v) + v_scroll->set_value(diff.y); + else + drag_accum.y = 0; + time_since_motion = 0; + } } } @@ -224,10 +235,16 @@ void ScrollContainer::_notification(int p_what) { child_max_size = Size2(0, 0); Size2 size = get_size(); - if (h_scroll->is_visible_in_tree()) + Point2 ofs; + + Ref<StyleBox> sb = get_stylebox("bg"); + size -= sb->get_minimum_size(); + ofs += sb->get_offset(); + + if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) //scrolls may have been moved out for reasons size.y -= h_scroll->get_minimum_size().y; - if (v_scroll->is_visible_in_tree()) + if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) //scrolls may have been moved out for reasons size.x -= h_scroll->get_minimum_size().x; for (int i = 0; i < get_child_count(); i++) { @@ -259,6 +276,7 @@ void ScrollContainer::_notification(int p_what) { else r.size.height = minsize.height; } + r.position += ofs; fit_child_in_rect(c, r); } update(); @@ -266,10 +284,13 @@ void ScrollContainer::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { + Ref<StyleBox> sb = get_stylebox("bg"); + draw_style_box(sb, Rect2(Vector2(), get_size())); + update_scrollbars(); } - if (p_what == NOTIFICATION_PHYSICS_PROCESS) { + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { if (drag_touching) { @@ -323,9 +344,7 @@ void ScrollContainer::_notification(int p_what) { drag_speed = Vector2(sgn_x * val_x, sgn_y * val_y); if (turnoff_h && turnoff_v) { - set_physics_process(false); - drag_touching = false; - drag_touching_deaccel = false; + _cancel_drag(); } } else { @@ -346,6 +365,8 @@ void ScrollContainer::_notification(int p_what) { void ScrollContainer::update_scrollbars() { Size2 size = get_size(); + Ref<StyleBox> sb = get_stylebox("bg"); + size -= sb->get_minimum_size(); Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); @@ -430,6 +451,14 @@ void ScrollContainer::set_h_scroll(int p_pos) { _cancel_drag(); } +int ScrollContainer::get_deadzone() const { + return deadzone; +} + +void ScrollContainer::set_deadzone(int p_deadzone) { + deadzone = p_deadzone; +} + String ScrollContainer::get_configuration_warning() const { int found = 0; @@ -453,6 +482,16 @@ String ScrollContainer::get_configuration_warning() const { return ""; } +HScrollBar *ScrollContainer::get_h_scrollbar() { + + return h_scroll; +} + +VScrollBar *ScrollContainer::get_v_scrollbar() { + + return v_scroll; +} + void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("_scroll_moved"), &ScrollContainer::_scroll_moved); @@ -466,12 +505,23 @@ void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_h_scroll"), &ScrollContainer::get_h_scroll); ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &ScrollContainer::set_v_scroll); ClassDB::bind_method(D_METHOD("get_v_scroll"), &ScrollContainer::get_v_scroll); + ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone); + ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone); + + ClassDB::bind_method(D_METHOD("get_h_scrollbar"), &ScrollContainer::get_h_scrollbar); + ClassDB::bind_method(D_METHOD("get_v_scrollbar"), &ScrollContainer::get_v_scrollbar); + + ADD_SIGNAL(MethodInfo("scroll_started")); + ADD_SIGNAL(MethodInfo("scroll_ended")); ADD_GROUP("Scroll", "scroll_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_enable_h_scroll", "is_h_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_enable_v_scroll", "is_v_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone"); + + GLOBAL_DEF("gui/common/default_scroll_deadzone", 0); }; ScrollContainer::ScrollContainer() { @@ -490,8 +540,11 @@ ScrollContainer::ScrollContainer() { drag_speed = Vector2(); drag_touching = false; drag_touching_deaccel = false; + beyond_deadzone = false; scroll_h = true; scroll_v = true; + deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone"); + set_clip_contents(true); }; diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 6e3387918b..abef80294a 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -56,10 +56,13 @@ class ScrollContainer : public Container { bool drag_touching; bool drag_touching_deaccel; bool click_handled; + bool beyond_deadzone; bool scroll_h; bool scroll_v; + int deadzone; + void _cancel_drag(); protected: @@ -86,6 +89,12 @@ public: void set_enable_v_scroll(bool p_enable); bool is_v_scroll_enabled() const; + int get_deadzone() const; + void set_deadzone(int p_deadzone); + + HScrollBar *get_h_scrollbar(); + VScrollBar *get_v_scrollbar(); + virtual bool clips_input() const; virtual String get_configuration_warning() const; diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index a7a1b499c3..46215c9277 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -118,28 +118,14 @@ void Slider::_gui_input(Ref<InputEvent> p_event) { return; 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 { - - Ref<InputEventKey> k = p_event; - - if (!k.is_valid() || !k->is_pressed()) - return; - - switch (k->get_scancode()) { - - case KEY_HOME: { - - set_value(get_min()); - accept_event(); - } break; - case KEY_END: { - - set_value(get_max()); - accept_event(); + set_value(get_min()); + accept_event(); + } else if (p_event->is_action("ui_end") && p_event->is_pressed()) { - } break; - } + set_value(get_max()); + accept_event(); } } } diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index bf7033e8ba..c38c411333 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -33,13 +33,6 @@ #include "label.h" #include "margin_container.h" -struct _MinSizeCache { - - int min_size; - bool will_stretch; - int final_size; -}; - Control *SplitContainer::_getch(int p_idx) const { int idx = 0; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 6e85ce5eb4..4f72b5c6ed 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -31,6 +31,9 @@ #include "tab_container.h" #include "message_queue.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" int TabContainer::_get_top_margin() const { @@ -140,6 +143,42 @@ void TabContainer::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_RESIZED: { + + Vector<Control *> tabs = _get_tabs(); + int side_margin = get_constant("side_margin"); + Ref<Texture> menu = get_icon("menu"); + Ref<Texture> increment = get_icon("increment"); + Ref<Texture> decrement = get_icon("decrement"); + int header_width = get_size().width - side_margin * 2; + + // Find the width of the header area. + if (popup) + header_width -= menu->get_width(); + if (buttons_visible_cache) + header_width -= increment->get_width() + decrement->get_width(); + if (popup || buttons_visible_cache) + header_width += side_margin; + + // Find the width of all tabs after first_tab_cache. + int all_tabs_width = 0; + for (int i = first_tab_cache; i < tabs.size(); i++) { + int tab_width = _get_tab_width(i); + all_tabs_width += tab_width; + } + + // Check if tabs before first_tab_cache would fit into the header area. + for (int i = first_tab_cache - 1; i >= 0; i--) { + int tab_width = _get_tab_width(i); + + if (all_tabs_width + tab_width > header_width) + break; + + all_tabs_width += tab_width; + first_tab_cache--; + } + } break; + case NOTIFICATION_DRAW: { RID canvas = get_canvas_item(); @@ -194,6 +233,10 @@ void TabContainer::_notification(int p_what) { header_width += side_margin; } + if (!buttons_visible_cache) { + first_tab_cache = 0; + } + // Go through the visible tabs to find the width they occupy. all_tabs_width = 0; Vector<int> tab_widths; @@ -492,6 +535,141 @@ void TabContainer::_update_current_tab() { set_current_tab(current); } +Variant TabContainer::get_drag_data(const Point2 &p_point) { + + if (!drag_to_rearrange_enabled) + return Variant(); + + int tab_over = get_tab_idx_at_point(p_point); + + if (tab_over < 0) + return Variant(); + + HBoxContainer *drag_preview = memnew(HBoxContainer); + + Ref<Texture> icon = get_tab_icon(tab_over); + if (!icon.is_null()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(icon); + drag_preview->add_child(tf); + } + Label *label = memnew(Label(get_tab_title(tab_over))); + drag_preview->add_child(label); + set_drag_preview(drag_preview); + + Dictionary drag_data; + drag_data["type"] = "tabc_element"; + drag_data["tabc_element"] = tab_over; + drag_data["from_path"] = get_path(); + return drag_data; +} + +bool TabContainer::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + + if (!drag_to_rearrange_enabled) + return false; + + Dictionary d = p_data; + if (!d.has("type")) + return false; + + if (String(d["type"]) == "tabc_element") { + + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + return true; + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between other TabContainers + Node *from_node = get_node(from_path); + TabContainer *from_tabc = Object::cast_to<TabContainer>(from_node); + if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + return true; + } + } + } + return false; +} + +void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) { + + if (!drag_to_rearrange_enabled) + return; + + int hover_now = get_tab_idx_at_point(p_point); + + Dictionary d = p_data; + if (!d.has("type")) + return; + + if (String(d["type"]) == "tabc_element") { + + int tab_from_id = d["tabc_element"]; + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + if (hover_now < 0) + hover_now = get_tab_count() - 1; + move_child(get_tab_control(tab_from_id), hover_now); + set_current_tab(hover_now); + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between TabContainers + Node *from_node = get_node(from_path); + TabContainer *from_tabc = Object::cast_to<TabContainer>(from_node); + if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + Control *moving_tabc = from_tabc->get_tab_control(tab_from_id); + from_tabc->remove_child(moving_tabc); + add_child(moving_tabc); + if (hover_now < 0) + hover_now = get_tab_count() - 1; + move_child(moving_tabc, hover_now); + set_current_tab(hover_now); + emit_signal("tab_changed", hover_now); + } + } + } + update(); +} + +int TabContainer::get_tab_idx_at_point(const Point2 &p_point) const { + + if (get_tab_count() == 0) + return -1; + + // must be on tabs in the tab header area. + if (p_point.x < tabs_ofs_cache || p_point.y > _get_top_margin()) + return -1; + + Size2 size = get_size(); + int right_ofs = 0; + + if (popup) { + Ref<Texture> menu = get_icon("menu"); + right_ofs += menu->get_width(); + } + if (buttons_visible_cache) { + Ref<Texture> increment = get_icon("increment"); + Ref<Texture> decrement = get_icon("decrement"); + right_ofs += increment->get_width() + decrement->get_width(); + } + if (p_point.x > size.width - right_ofs) { + return -1; + } + + // get the tab at the point + Vector<Control *> tabs = _get_tabs(); + int px = p_point.x; + px -= tabs_ofs_cache; + for (int i = first_tab_cache; i <= last_tab_cache; i++) { + int tab_width = _get_tab_width(i); + if (px < tab_width) { + return i; + } + px -= tab_width; + } + return -1; +} + void TabContainer::set_tab_align(TabAlign p_align) { ERR_FAIL_INDEX(p_align, 3); @@ -500,6 +678,7 @@ void TabContainer::set_tab_align(TabAlign p_align) { _change_notify("tab_align"); } + TabContainer::TabAlign TabContainer::get_tab_align() const { return align; @@ -643,6 +822,21 @@ Popup *TabContainer::get_popup() const { return popup; } +void TabContainer::set_drag_to_rearrange_enabled(bool p_enabled) { + drag_to_rearrange_enabled = p_enabled; +} + +bool TabContainer::get_drag_to_rearrange_enabled() const { + return drag_to_rearrange_enabled; +} +void TabContainer::set_tabs_rearrange_group(int p_group_id) { + tabs_rearrange_group = p_group_id; +} + +int TabContainer::get_tabs_rearrange_group() const { + return tabs_rearrange_group; +} + void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &TabContainer::_gui_input); @@ -664,6 +858,10 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); + ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabContainer::get_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabContainer::set_tabs_rearrange_group); + ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabContainer::get_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("_child_renamed_callback"), &TabContainer::_child_renamed_callback); ClassDB::bind_method(D_METHOD("_on_theme_changed"), &TabContainer::_on_theme_changed); @@ -676,6 +874,7 @@ void TabContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align"); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); BIND_ENUM_CONSTANT(ALIGN_LEFT); BIND_ENUM_CONSTANT(ALIGN_CENTER); @@ -694,4 +893,6 @@ TabContainer::TabContainer() { align = ALIGN_CENTER; tabs_visible = true; popup = NULL; + drag_to_rearrange_enabled = false; + tabs_rearrange_group = -1; } diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 4bc6e00145..1afe5f7541 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -58,6 +58,8 @@ private: Control *_get_tab(int p_idx) const; int _get_top_margin() const; Popup *popup; + bool drag_to_rearrange_enabled; + int tabs_rearrange_group; Vector<Control *> _get_tabs() const; int _get_tab_width(int p_index) const; @@ -71,6 +73,11 @@ protected: virtual void add_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child); + Variant get_drag_data(const Point2 &p_point); + bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; + void drop_data(const Point2 &p_point, const Variant &p_data); + int get_tab_idx_at_point(const Point2 &p_point) const; + static void _bind_methods(); public: @@ -104,6 +111,11 @@ public: void set_popup(Node *p_popup); Popup *get_popup() const; + void set_drag_to_rearrange_enabled(bool p_enabled); + bool get_drag_to_rearrange_enabled() const; + void set_tabs_rearrange_group(int p_group_id); + int get_tabs_rearrange_group() const; + TabContainer(); }; diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index f0e89877cd..b114264de1 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -31,6 +31,9 @@ #include "tabs.h" #include "message_queue.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/texture_rect.h" Size2 Tabs::get_minimum_size() const { @@ -624,20 +627,105 @@ void Tabs::remove_tab(int p_idx) { Variant Tabs::get_drag_data(const Point2 &p_point) { - return get_tab_idx_at_point(p_point); + if (!drag_to_rearrange_enabled) + return Variant(); + + int tab_over = get_tab_idx_at_point(p_point); + + if (tab_over < 0) + return Variant(); + + HBoxContainer *drag_preview = memnew(HBoxContainer); + + if (!tabs[tab_over].icon.is_null()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(tabs[tab_over].icon); + drag_preview->add_child(tf); + } + Label *label = memnew(Label(tabs[tab_over].text)); + drag_preview->add_child(label); + if (!tabs[tab_over].right_button.is_null()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(tabs[tab_over].right_button); + drag_preview->add_child(tf); + } + set_drag_preview(drag_preview); + + Dictionary drag_data; + drag_data["type"] = "tab_element"; + drag_data["tab_element"] = tab_over; + drag_data["from_path"] = get_path(); + return drag_data; } bool Tabs::can_drop_data(const Point2 &p_point, const Variant &p_data) const { - return get_tab_idx_at_point(p_point) > -1; + if (!drag_to_rearrange_enabled) + return false; + + Dictionary d = p_data; + if (!d.has("type")) + return false; + + if (String(d["type"]) == "tab_element") { + + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + return true; + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between other Tabs + Node *from_node = get_node(from_path); + Tabs *from_tabs = Object::cast_to<Tabs>(from_node); + if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + return true; + } + } + } + return false; } void Tabs::drop_data(const Point2 &p_point, const Variant &p_data) { + if (!drag_to_rearrange_enabled) + return; + int hover_now = get_tab_idx_at_point(p_point); - ERR_FAIL_INDEX(hover_now, tabs.size()); - emit_signal("reposition_active_tab_request", hover_now); + Dictionary d = p_data; + if (!d.has("type")) + return; + + if (String(d["type"]) == "tab_element") { + + int tab_from_id = d["tab_element"]; + NodePath from_path = d["from_path"]; + NodePath to_path = get_path(); + if (from_path == to_path) { + if (hover_now < 0) + hover_now = get_tab_count() - 1; + move_tab(tab_from_id, hover_now); + emit_signal("reposition_active_tab_request", hover_now); + set_current_tab(hover_now); + } else if (get_tabs_rearrange_group() != -1) { + // drag and drop between Tabs + Node *from_node = get_node(from_path); + Tabs *from_tabs = Object::cast_to<Tabs>(from_node); + if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { + if (tab_from_id >= from_tabs->get_tab_count()) + return; + Tab moving_tab = from_tabs->tabs[tab_from_id]; + if (hover_now < 0) + hover_now = get_tab_count(); + tabs.insert(hover_now, moving_tab); + from_tabs->remove_tab(tab_from_id); + set_current_tab(hover_now); + emit_signal("tab_changed", hover_now); + _update_cache(); + } + } + } + update(); } int Tabs::get_tab_idx_at_point(const Point2 &p_point) const { @@ -817,6 +905,21 @@ bool Tabs::get_scrolling_enabled() const { return scrolling_enabled; } +void Tabs::set_drag_to_rearrange_enabled(bool p_enabled) { + drag_to_rearrange_enabled = p_enabled; +} + +bool Tabs::get_drag_to_rearrange_enabled() const { + return drag_to_rearrange_enabled; +} +void Tabs::set_tabs_rearrange_group(int p_group_id) { + tabs_rearrange_group = p_group_id; +} + +int Tabs::get_tabs_rearrange_group() const { + return tabs_rearrange_group; +} + void Tabs::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &Tabs::_gui_input); @@ -842,6 +945,10 @@ void Tabs::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_close_display_policy"), &Tabs::get_tab_close_display_policy); ClassDB::bind_method(D_METHOD("set_scrolling_enabled", "enabled"), &Tabs::set_scrolling_enabled); ClassDB::bind_method(D_METHOD("get_scrolling_enabled"), &Tabs::get_scrolling_enabled); + ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &Tabs::set_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &Tabs::get_drag_to_rearrange_enabled); + ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &Tabs::set_tabs_rearrange_group); + ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &Tabs::get_tabs_rearrange_group); ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("right_button_pressed", PropertyInfo(Variant::INT, "tab"))); @@ -854,6 +961,7 @@ void Tabs::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); BIND_ENUM_CONSTANT(ALIGN_LEFT); BIND_ENUM_CONSTANT(ALIGN_CENTER); @@ -882,4 +990,8 @@ Tabs::Tabs() { min_width = 0; scrolling_enabled = true; + buttons_visible = false; + hover = -1; + drag_to_rearrange_enabled = false; + tabs_rearrange_group = -1; } diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h index 246b3cba67..3b38e7f2cb 100644 --- a/scene/gui/tabs.h +++ b/scene/gui/tabs.h @@ -90,6 +90,8 @@ private: int hover; // hovered tab int min_width; bool scrolling_enabled; + bool drag_to_rearrange_enabled; + int tabs_rearrange_group; int get_tab_width(int p_idx) const; void _ensure_no_over_offset(); @@ -143,6 +145,11 @@ public: void set_scrolling_enabled(bool p_enabled); bool get_scrolling_enabled() const; + void set_drag_to_rearrange_enabled(bool p_enabled); + bool get_drag_to_rearrange_enabled() const; + void set_tabs_rearrange_group(int p_group_id); + int get_tabs_rearrange_group() const; + void ensure_tab_visible(int p_idx); void set_min_width(int p_width); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 95d9173d39..327c236f8c 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -117,7 +117,6 @@ void TextEdit::Text::set_indent_size(int p_indent_size) { void TextEdit::Text::_update_line_cache(int p_line) const { int w = 0; - int tab_w = font->get_char_size(' ').width * indent_size; int len = text[p_line].data.length(); const CharType *str = text[p_line].data.c_str(); @@ -125,22 +124,13 @@ void TextEdit::Text::_update_line_cache(int p_line) const { //update width for (int i = 0; i < len; i++) { - if (str[i] == '\t') { - - int left = w % tab_w; - if (left == 0) - w += tab_w; - else - w += tab_w - w % tab_w; // is right... - - } else { - - w += font->get_char_size(str[i], str[i + 1]).width; - } + w += get_char_width(str[i], str[i + 1], w); } text[p_line].width_cache = w; + text[p_line].wrap_amount_cache = -1; + //update regions text[p_line].region_info.clear(); @@ -184,6 +174,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { cri.region = j; text[p_line].region_info[i] = cri; i += lr - 1; + break; } @@ -211,6 +202,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { cri.region = j; text[p_line].region_info[i] = cri; i += lr - 1; + break; } } @@ -240,10 +232,32 @@ int TextEdit::Text::get_line_width(int p_line) const { return text[p_line].width_cache; } -void TextEdit::Text::clear_caches() { +void TextEdit::Text::set_line_wrap_amount(int p_line, int p_wrap_amount) const { + + ERR_FAIL_INDEX(p_line, text.size()); + + text[p_line].wrap_amount_cache = p_wrap_amount; +} + +int TextEdit::Text::get_line_wrap_amount(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), -1); - for (int i = 0; i < text.size(); i++) + return text[p_line].wrap_amount_cache; +} + +void TextEdit::Text::clear_width_cache() { + + for (int i = 0; i < text.size(); i++) { text[i].width_cache = -1; + } +} + +void TextEdit::Text::clear_wrap_cache() { + + for (int i = 0; i < text.size(); i++) { + text[i].wrap_amount_cache = -1; + } } void TextEdit::Text::clear() { @@ -268,6 +282,7 @@ void TextEdit::Text::set(int p_line, const String &p_text) { ERR_FAIL_INDEX(p_line, text.size()); text[p_line].width_cache = -1; + text[p_line].wrap_amount_cache = -1; text[p_line].data = p_text; } @@ -278,6 +293,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) { line.breakpoint = false; line.hidden = false; line.width_cache = -1; + line.wrap_amount_cache = -1; line.data = p_text; text.insert(p_at, line); } @@ -286,6 +302,25 @@ void TextEdit::Text::remove(int p_at) { text.remove(p_at); } +int TextEdit::Text::get_char_width(CharType c, CharType next_c, int px) const { + + int tab_w = font->get_char_size(' ').width * indent_size; + int w = 0; + + if (c == '\t') { + + int left = px % tab_w; + if (left == 0) + w = tab_w; + else + w = tab_w - px % tab_w; // is right... + } else { + + w = font->get_char_size(c, next_c).width; + } + return w; +} + void TextEdit::_update_scrollbars() { Size2 size = get_size(); @@ -300,9 +335,12 @@ void TextEdit::_update_scrollbars() { int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1; int visible_rows = get_visible_rows(); - int num_rows = MAX(visible_rows, num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs))); - int total_rows = (is_hiding_enabled() ? get_total_unhidden_rows() : text.size()); + int first_vis_line = get_first_visible_line(); + int wi; + int num_rows = MAX(visible_rows, num_lines_from_rows(first_vis_line, cursor.wrap_ofs, visible_rows, wi)); + + int total_rows = get_total_visible_rows(); if (scroll_past_end_of_file_enabled) { total_rows += visible_rows - 1; } @@ -348,28 +386,24 @@ void TextEdit::_update_scrollbars() { if (use_vscroll) { v_scroll->show(); - v_scroll->set_max(total_rows); - v_scroll->set_page(visible_rows); + v_scroll->set_max(total_rows + get_visible_rows_offset()); + v_scroll->set_page(visible_rows + get_visible_rows_offset()); if (smooth_scroll_enabled) { v_scroll->set_step(0.25); } else { v_scroll->set_step(1); } - - update_line_scroll_pos(); - if (fabs(v_scroll->get_value() - get_line_scroll_pos()) >= 1) { - cursor.line_ofs += v_scroll->get_value() - get_line_scroll_pos(); - } + set_v_scroll(get_v_scroll()); } else { cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; v_scroll->set_value(0); v_scroll->hide(); } - if (use_hscroll) { + if (use_hscroll && !is_wrap_enabled()) { h_scroll->show(); h_scroll->set_max(total_width); @@ -392,6 +426,9 @@ void TextEdit::_update_scrollbars() { void TextEdit::_click_selection_held() { + // Warning: is_mouse_button_pressed(BUTTON_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(BUTTON_LEFT) && selection.selecting_mode != Selection::MODE_NONE) { switch (selection.selecting_mode) { case Selection::MODE_POINTER: { @@ -413,14 +450,14 @@ void TextEdit::_click_selection_held() { } void TextEdit::_update_selection_mode_pointer() { - Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position(); + Point2 mp = get_local_mouse_position(); int row, col; _get_mouse_pos(Point2i(mp.x, mp.y), row, col); select(selection.selecting_line, selection.selecting_column, row, col); - cursor_set_line(row); + cursor_set_line(row, false); cursor_set_column(col); update(); @@ -428,7 +465,7 @@ void TextEdit::_update_selection_mode_pointer() { } void TextEdit::_update_selection_mode_word() { - Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position(); + Point2 mp = get_local_mouse_position(); int row, col; _get_mouse_pos(Point2i(mp.x, mp.y), row, col); @@ -462,26 +499,29 @@ void TextEdit::_update_selection_mode_word() { selection.selected_word_beg = beg; selection.selected_word_end = end; selection.selected_word_origin = beg; + cursor_set_line(selection.to_line, false); cursor_set_column(selection.to_column); } else { if ((col <= selection.selected_word_origin && row == selection.selecting_line) || row < selection.selecting_line) { selection.selecting_column = selection.selected_word_end; select(row, beg, selection.selecting_line, selection.selected_word_end); + cursor_set_line(selection.from_line, false); cursor_set_column(selection.from_column); } else { selection.selecting_column = selection.selected_word_beg; select(selection.selecting_line, selection.selected_word_beg, row, end); + cursor_set_line(selection.to_line, false); cursor_set_column(selection.to_column); } } - cursor_set_line(row); update(); + click_select_held->start(); } void TextEdit::_update_selection_mode_line() { - Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position(); + Point2 mp = get_local_mouse_position(); int row, col; _get_mouse_pos(Point2i(mp.x, mp.y), row, col); @@ -489,11 +529,11 @@ void TextEdit::_update_selection_mode_line() { col = 0; if (row < selection.selecting_line) { // cursor is above us - cursor_set_line(row - 1); + cursor_set_line(row - 1, false); selection.selecting_column = text[selection.selecting_line].length(); } else { // cursor is below us - cursor_set_line(row + 1); + cursor_set_line(row + 1, false); selection.selecting_column = 0; col = text[row].length(); } @@ -515,13 +555,13 @@ void TextEdit::_notification(int p_what) { MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit"); if (text_changed_dirty) MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); - + update_wrap_at(); } break; case NOTIFICATION_RESIZED: { cache.size = get_size(); - adjust_viewport_to_cursor(); - + _update_scrollbars(); + update_wrap_at(); } break; case NOTIFICATION_THEME_CHANGED: { @@ -537,26 +577,25 @@ void TextEdit::_notification(int p_what) { draw_caret = false; update(); } break; - case NOTIFICATION_PHYSICS_PROCESS: { - if (scrolling && v_scroll->get_value() != target_v_scroll) { - double target_y = target_v_scroll - v_scroll->get_value(); + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (scrolling && get_v_scroll() != target_v_scroll) { + double target_y = target_v_scroll - get_v_scroll(); double dist = sqrt(target_y * target_y); double vel = ((target_y / dist) * v_scroll_speed) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); scrolling = false; - set_physics_process(false); + set_physics_process_internal(false); } else { - v_scroll->set_value(v_scroll->get_value() + vel); + set_v_scroll(get_v_scroll() + vel); } } else { scrolling = false; - set_physics_process(false); + set_physics_process_internal(false); } } break; case NOTIFICATION_DRAW: { - if ((!has_focus() && !menu->has_focus()) || !window_has_focus) { draw_caret = false; } @@ -619,44 +658,10 @@ void TextEdit::_notification(int p_what) { Color color = cache.font_color; color.a *= readonly_alpha; - int in_region = -1; - if (syntax_coloring) { - if (cache.background_color.a > 0.01) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), cache.background_color); } - //compute actual region to start (may be inside say, a comment). - //slow in very large documents :( but ok for source! - - for (int i = 0; i < cursor.line_ofs; i++) { - - const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(i); - - if (in_region >= 0 && color_regions[in_region].line_only) { - in_region = -1; //reset regions that end at end of line - } - - for (const Map<int, Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { - - const Text::ColorRegionInfo &cri = E->get(); - - if (in_region == -1) { - - if (!cri.end) { - - in_region = cri.region; - } - } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise - - if (cri.end || color_regions[cri.region].eq) { - - in_region = -1; - } - } - } - } } int brace_open_match_line = -1; @@ -804,18 +809,20 @@ void TextEdit::_notification(int p_what) { } } - int deregion = 0; //force it to clear inrgion Point2 cursor_pos; // get the highlighted words String highlighted_text = get_selection_text(); String line_num_padding = line_numbers_zero_padded ? "0" : " "; - update_line_scroll_pos(); - int line = cursor.line_ofs - 1; - // another row may be visible during smooth scrolling + int cursor_wrap_index = get_cursor_wrap_index(); + + FontDrawer drawer(cache.font, Color(1, 1, 1)); + + int line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + draw_amount += times_line_wraps(line + 1); for (int i = 0; i < draw_amount; i++) { line++; @@ -833,407 +840,360 @@ void TextEdit::_notification(int p_what) { if (line < 0 || line >= (int)text.size()) continue; - const String &str = text[line]; - - int char_margin = xmargin_beg - cursor.x_ofs; - int char_ofs = 0; + const String &fullstr = text[line]; - int ofs_readonly = 0; - int ofs_x = 0; + Map<int, HighlighterInfo> color_map; + if (syntax_coloring) { + color_map = _get_line_syntax_highlighting(line); + } + // ensure we at least use the font color + Color current_color = cache.font_color; if (readonly) { - ofs_readonly = cache.style_readonly->get_offset().y / 2; - ofs_x = cache.style_readonly->get_offset().x / 2; + current_color.a *= readonly_alpha; } - int ofs_y = (i * get_row_height() + cache.line_spacing / 2) + ofs_readonly; - if (smooth_scroll_enabled) - ofs_y -= ((v_scroll->get_value() - get_line_scroll_pos()) * get_row_height()); - bool prev_is_char = false; - bool prev_is_number = false; - bool in_keyword = false; bool underlined = false; - bool in_word = false; - bool in_function_name = false; - bool in_member_variable = false; - bool is_hex_notation = false; - Color keyword_color; - - // check if line contains highlighted word - int highlighted_text_col = -1; - int search_text_col = -1; - if (!search_text.empty()) - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); + int line_wrap_amount = times_line_wraps(line); + int last_wrap_column = 0; + Vector<String> wrap_rows = get_wrap_rows_text(line); - if (highlighted_text.length() != 0 && highlighted_text != search_text) - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) { + if (line_wrap_index != 0) { + i++; + if (i >= draw_amount) + break; + } - const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(line); + const String &str = wrap_rows[line_wrap_index]; + int indent_px = line_wrap_index != 0 ? get_indent_level(line) * cache.font->get_char_size(' ').width : 0; - if (text.is_marked(line)) { + if (line_wrap_index > 0) + last_wrap_column += wrap_rows[line_wrap_index - 1].length(); - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.mark_color); - } + int char_margin = xmargin_beg - cursor.x_ofs; + char_margin += indent_px; + int char_ofs = 0; - if (str.length() == 0) { - // draw line background if empty as we won't loop at at all - if (line == cursor.line && highlight_current_line) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color); + int ofs_readonly = 0; + int ofs_x = 0; + if (readonly) { + ofs_readonly = cache.style_readonly->get_offset().y / 2; + ofs_x = cache.style_readonly->get_offset().x / 2; } - // give visual indication of empty selected line - if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - int char_w = cache.font->get_char_size(' ').width; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, get_row_height()), cache.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 == cursor.line && highlight_current_line) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg + ofs_x, get_row_height()), cache.current_line_color); - } - } + int ofs_y = (i * get_row_height() + cache.line_spacing / 2) + ofs_readonly; + ofs_y -= cursor.wrap_ofs * get_row_height(); + if (smooth_scroll_enabled) + ofs_y += (-get_v_scroll_offset()) * get_row_height(); - if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { -#ifdef TOOLS_ENABLED - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); -#else - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.breakpoint_color); -#endif - } + // check if line contains highlighted word + int highlighted_text_col = -1; + int search_text_col = -1; + int highlighted_word_col = -1; - // draw breakpoint marker - if (text.is_breakpoint(line)) { - if (draw_breakpoint_gutter) { - int vertical_gap = (get_row_height() * 40) / 100; - int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; - int marker_height = get_row_height() - (vertical_gap * 2); - int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); - // no transparency on marker - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); - } - } + if (!search_text.empty()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); - // draw fold markers - if (draw_fold_gutter) { - int horizontal_gap = (cache.fold_gutter_width * 30) / 100; - int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w; - if (is_folded(line)) { - int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; - int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; - cache.folded_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } else if (can_fold(line)) { - int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; - int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; - cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } - } + if (highlighted_text.length() != 0 && highlighted_text != search_text) + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); - if (cache.line_number_w) { - String fc = String::num(line + 1); - while (fc.length() < line_number_char_count) { - fc = line_num_padding + fc; + if (select_identifiers_enabled && highlighted_word.length() != 0) { + if (_is_char(highlighted_word[0])) { + highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + } } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color); - } - //loop through characters in one line - for (int j = 0; j < str.length(); j++) { - - //look for keyword - - if (deregion > 0) { - deregion--; - if (deregion == 0) - in_region = -1; + if (text.is_marked(line)) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.mark_color); } - if (syntax_coloring && deregion == 0) { - - color = cache.font_color; //reset - color.a *= readonly_alpha; - //find keyword - bool is_char = _is_text_char(str[j]); - bool is_symbol = _is_symbol(str[j]); - bool is_number = _is_number(str[j]); - if (j == 0 && in_region >= 0 && color_regions[in_region].line_only) { - in_region = -1; //reset regions that end at end of line + if (str.length() == 0) { + // draw line background if empty as we won't loop at at all + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color); } - // allow ABCDEF in hex notation - if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { - is_number = true; - } else { - is_hex_notation = false; + // give visual indication of empty selected line + if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { + int char_w = cache.font->get_char_size(' ').width; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, get_row_height()), cache.selection_color); } - - // check for dot or underscore or 'x' for hex notation in floating point number - if ((str[j] == '.' || str[j] == 'x' || str[j] == '_') && !in_word && prev_is_number && !is_number) { - is_number = true; - is_symbol = false; - is_char = false; - - if (str[j] == 'x' && str[j - 1] == '0') { - is_hex_notation = true; - } + } 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 == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg, get_row_height()), cache.current_line_color); } + } - if (!in_word && _is_char(str[j]) && !is_number) { - in_word = true; - } + if (line_wrap_index == 0) { + // only do these if we are on the first wrapped part of a line - if ((in_keyword || in_word) && !is_hex_notation) { - is_number = false; + if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { +#ifdef TOOLS_ENABLED + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); +#else + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.breakpoint_color); +#endif } - if (is_symbol && str[j] != '.' && in_word) { - in_word = false; + // draw breakpoint marker + if (text.is_breakpoint(line)) { + if (draw_breakpoint_gutter) { + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; + int marker_height = get_row_height() - (vertical_gap * 2); + int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); + // no transparency on marker + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); + } } - if (is_symbol && cri_map.has(j)) { - - const Text::ColorRegionInfo &cri = cri_map[j]; - - if (in_region == -1) { + // draw fold markers + if (draw_fold_gutter) { + int horizontal_gap = (cache.fold_gutter_width * 30) / 100; + int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w; + if (is_folded(line)) { + int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; + int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; + cache.folded_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); + } else if (can_fold(line)) { + int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; + int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; + cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); + } + } - if (!cri.end) { + // draw line numbers + if (cache.line_number_w) { + String fc = String::num(line + 1); + while (fc.length() < line_number_char_count) { + fc = line_num_padding + fc; + } - in_region = cri.region; - } - } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise + cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color); + } + } - if (cri.end || color_regions[cri.region].eq) { + //loop through characters in one line + for (int j = 0; j < str.length(); j++) { - deregion = color_regions[cri.region].eq ? color_regions[cri.region].begin_key.length() : color_regions[cri.region].end_key.length(); + if (syntax_coloring) { + if (color_map.has(last_wrap_column + j)) { + current_color = color_map[last_wrap_column + j].color; + if (readonly) { + current_color.a *= readonly_alpha; } } + color = current_color; } - if (!is_char) { - in_keyword = false; - underlined = false; - } - - if (in_region == -1 && !in_keyword && is_char && !prev_is_char) { - - int to = j; - while (to < str.length() && _is_text_char(str[to])) - to++; + int char_w; - uint32_t hash = String::hash(&str[j], to - j); - StrRange range(&str[j], to - j); + //handle tabulator + char_w = text.get_char_width(str[j], str[j + 1], char_ofs); - const Color *col = keywords.custom_getptr(range, hash); + if ((char_ofs + char_margin) < xmargin_beg) { + char_ofs += char_w; - if (!col) { - col = member_keywords.custom_getptr(range, hash); + // line highlighting handle horizontal clipping + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { - if (col) { - for (int k = j - 1; k >= 0; k--) { - if (str[k] == '.') { - col = NULL; //member indexing not allowed - break; - } else if (str[k] > 32) { - break; - } - } + if (j == str.length() - 1) { + // end of line when last char is skipped + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + } else if ((char_ofs + char_margin) > xmargin_beg) { + // char next to margin is skipped + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color); } } + continue; + } - if (col) { - - in_keyword = true; - keyword_color = *col; - } - - if (select_identifiers_enabled && highlighted_word != String()) { - if (highlighted_word == range) { - underlined = true; - } - } + if ((char_ofs + char_margin + char_w) >= xmargin_end) { + if (syntax_coloring) + continue; + else + break; } - if (!in_function_name && in_word && !in_keyword) { + bool in_search_result = false; - int k = j; - while (k < str.length() && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { - k++; - } + if (search_text_col != -1) { + // if we are at the end check for new search result on same line + if (j >= search_text_col + search_text.length()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); - // check for space between name and bracket - while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { - k++; - } + in_search_result = j >= search_text_col && j < search_text_col + search_text.length(); - if (str[k] == '(') { - in_function_name = true; + if (in_search_result) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin, ofs_y), Size2i(char_w, get_row_height())), cache.search_result_color); } } - if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) { - int k = j; - while (k > 0 && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { - k--; - } + //current line highlighting + bool in_selection = (selection.active && line >= selection.from_line && line <= selection.to_line && (line > selection.from_line || last_wrap_column + j >= selection.from_column) && (line < selection.to_line || last_wrap_column + j < selection.to_column)); - if (str[k] == '.') { - in_member_variable = true; + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + // draw the wrap indent offset highlight + if (line_wrap_index != 0 && j == 0) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin - indent_px, ofs_y, (char_ofs + char_margin), get_row_height()), cache.current_line_color); + } + // if its the last char draw to end of the line + if (j == str.length() - 1) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + } + // actual text + if (!in_selection) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color); } } - if (is_symbol) { - in_function_name = false; - in_member_variable = false; + if (in_selection) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.selection_color); } - if (in_region >= 0) - color = color_regions[in_region].color; - else if (in_keyword) - color = keyword_color; - else if (in_member_variable) - color = cache.member_variable_color; - else if (in_function_name) - color = cache.function_color; - else if (is_symbol) - color = cache.symbol_color; - else if (is_number) - color = cache.number_color; - - prev_is_char = is_char; - prev_is_number = is_number; - } - int char_w; + if (in_search_result) { + Color border_color = (line == search_result_line && j >= search_result_col && j < search_result_col + search_text.length()) ? cache.font_color : cache.search_result_border_color; - //handle tabulator + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, 1)), border_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y + get_row_height() - 1), Size2i(char_w, 1)), border_color); - if (str[j] == '\t') { - int left = char_ofs % tab_w; - if (left == 0) - char_w = tab_w; - else - char_w = tab_w - char_ofs % tab_w; // is right... + if (j == search_text_col) + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(1, get_row_height())), border_color); + if (j == search_text_col + search_text.length() - 1) + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color); + } - } else { - char_w = cache.font->get_char_size(str[j], str[j + 1]).width; - } + if (highlight_all_occurrences) { + if (highlighted_text_col != -1) { - if ((char_ofs + char_margin) < xmargin_beg) { - char_ofs += char_w; + // if we are at the end check for new word on same line + if (j > highlighted_text_col + highlighted_text.length()) { + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j); + } - // line highlighting handle horizontal clipping - if (line == cursor.line && highlight_current_line) { + bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length()); - if (j == str.length() - 1) { - // end of line when last char is skipped - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color); + // if this is the original highlighted text we don't want to highlight it again + if (cursor.line == line && cursor_wrap_index == line_wrap_index && (cursor.column >= highlighted_text_col && cursor.column <= highlighted_text_col + highlighted_text.length())) { + in_highlighted_word = false; + } - } else if ((char_ofs + char_margin) > xmargin_beg) { - // char next to margin is skipped - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - xmargin_beg, get_row_height()), cache.current_line_color); + if (in_highlighted_word) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.word_highlighted_color); + } } } - continue; - } - if ((char_ofs + char_margin + char_w) >= xmargin_end) { - if (syntax_coloring) - continue; - else - break; - } + if (highlighted_word_col != -1) { + if (j + last_wrap_column > highlighted_word_col + highlighted_word.length()) { + highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j + last_wrap_column); + } + underlined = (j + last_wrap_column >= highlighted_word_col && j + last_wrap_column < highlighted_word_col + highlighted_word.length()); + } - bool in_search_result = false; + if (brace_matching_enabled) { + if ((brace_open_match_line == line && brace_open_match_column == last_wrap_column + j) || + (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { - if (search_text_col != -1) { - // if we are at the end check for new search result on same line - if (j >= search_text_col + search_text.length()) - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); + if (brace_open_mismatch) + color = cache.brace_mismatch_color; + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + } - in_search_result = j >= search_text_col && j < search_text_col + search_text.length(); + if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) || + (cursor.column == last_wrap_column + j + 1 && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) { - if (in_search_result) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin, ofs_y), Size2i(char_w, get_row_height())), cache.search_result_color); + if (brace_close_mismatch) + color = cache.brace_mismatch_color; + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + } } - } - //current line highlighting - bool in_selection = (selection.active && line >= selection.from_line && line <= selection.to_line && (line > selection.from_line || j >= selection.from_column) && (line < selection.to_line || j < selection.to_column)); + if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index) { - if (line == cursor.line && highlight_current_line) { - // if its the last char draw to end of the line - if (j == str.length() - 1) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); - } - // actual text - if (!in_selection) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color); - } - } + cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); - if (in_selection) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.selection_color); - } + if (insert_mode) { + cursor_pos.y += (get_row_height() - 3); + } - if (in_search_result) { - Color border_color = (line == search_result_line && j >= search_result_col && j < search_result_col + search_text.length()) ? cache.font_color : cache.search_result_border_color; + int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w; + if (ime_text.length() > 0) { + int ofs = 0; + while (true) { + if (ofs >= ime_text.length()) + break; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, 1)), border_color); - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y + get_row_height() - 1), Size2i(char_w, 1)), border_color); + CharType cchar = ime_text[ofs]; + CharType next = ime_text[ofs + 1]; + int im_char_width = cache.font->get_char_size(cchar, next).width; - if (j == search_text_col) - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(1, get_row_height())), border_color); - if (j == search_text_col + search_text.length() - 1) - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color); - } - - if (highlight_all_occurrences) { - if (highlighted_text_col != -1) { + if ((char_ofs + char_margin + im_char_width) >= xmargin_end) + break; - // if we are at the end check for new word on same line - if (j > highlighted_text_col + highlighted_text.length()) { - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j); - } + bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; + if (selected) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); + } else { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); + } - bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length()); + drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); - /* if this is the original highlighted text we don't want to highlight it again */ - if (cursor.line == line && (cursor.column >= highlighted_text_col && cursor.column <= highlighted_text_col + highlighted_text.length())) { - in_highlighted_word = false; + char_ofs += im_char_width; + ofs++; + } } - - if (in_highlighted_word) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.word_highlighted_color); + if (ime_text.length() == 0) { + if (draw_caret) { + if (insert_mode) { + int caret_h = (block_caret) ? 4 : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); + } else { + caret_w = (block_caret) ? caret_w : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); + } + } } } - } - if (brace_matching_enabled) { - if ((brace_open_match_line == line && brace_open_match_column == j) || - (cursor.column == j && cursor.line == line && (brace_open_matching || brace_open_mismatch))) { + if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && block_caret && draw_caret && !insert_mode) { + color = cache.caret_background_color; + } else if (!syntax_coloring && block_caret) { + color = cache.font_color; + color.a *= readonly_alpha; + } - if (brace_open_mismatch) - color = cache.brace_mismatch_color; - cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (str[j] >= 32) { + int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (underlined) { + draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); + } + } else if (draw_tabs && str[j] == '\t') { + int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; + cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_selected_color : color); } - if ( - (brace_close_match_line == line && brace_close_match_column == j) || - (cursor.column == j + 1 && cursor.line == line && (brace_close_matching || brace_close_mismatch))) { + char_ofs += char_w; - if (brace_close_mismatch) - color = cache.brace_mismatch_color; - cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (line_wrap_index == line_wrap_amount && j == str.length() - 1 && is_folded(line)) { + int yofs = (get_row_height() - cache.folded_eol_icon->get_height()) / 2; + int xofs = cache.folded_eol_icon->get_width() / 2; + Color eol_color = cache.code_folding_color; + eol_color.a = 1; + cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color); } } - if (cursor.column == j && cursor.line == line) { + if (cursor.column == last_wrap_column + str.length() && cursor.line == line && cursor_wrap_index == line_wrap_index && (char_ofs + char_margin) >= xmargin_beg) { cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); if (insert_mode) { cursor_pos.y += (get_row_height() - 3); } - - int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w; if (ime_text.length() > 0) { int ofs = 0; while (true) { @@ -1254,7 +1214,7 @@ void TextEdit::_notification(int p_what) { VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); } - cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); + drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); char_ofs += im_char_width; ofs++; @@ -1263,92 +1223,17 @@ void TextEdit::_notification(int p_what) { if (ime_text.length() == 0) { if (draw_caret) { if (insert_mode) { + int char_w = cache.font->get_char_size(' ').width; int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); } else { - caret_w = (block_caret) ? caret_w : 1; + int char_w = cache.font->get_char_size(' ').width; + int caret_w = (block_caret) ? char_w : 1; VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); } } } } - - if (cursor.column == j && cursor.line == line && block_caret && draw_caret && !insert_mode) { - color = cache.caret_background_color; - } else if (!syntax_coloring && block_caret) { - color = cache.font_color; - color.a *= readonly_alpha; - } - - if (str[j] >= 32) { - int w = cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); - if (underlined) { - draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); - } - } - - else if (draw_tabs && str[j] == '\t') { - int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; - cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_selected_color : color); - } - - char_ofs += char_w; - - if (j == str.length() - 1 && is_folded(line)) { - int yofs = (get_row_height() - cache.folded_eol_icon->get_height()) / 2; - int xofs = cache.folded_eol_icon->get_width() / 2; - Color eol_color = cache.code_folding_color; - eol_color.a = 1; - cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color); - } - } - - if (cursor.column == str.length() && cursor.line == line && (char_ofs + char_margin) >= xmargin_beg) { - - cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); - - if (insert_mode) { - cursor_pos.y += (get_row_height() - 3); - } - if (ime_text.length() > 0) { - int ofs = 0; - while (true) { - if (ofs >= ime_text.length()) - break; - - CharType cchar = ime_text[ofs]; - CharType next = ime_text[ofs + 1]; - int im_char_width = cache.font->get_char_size(cchar, next).width; - - if ((char_ofs + char_margin + im_char_width) >= xmargin_end) - break; - - bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; - if (selected) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); - } else { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); - } - - cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); - - char_ofs += im_char_width; - ofs++; - } - } - if (ime_text.length() == 0) { - if (draw_caret) { - if (insert_mode) { - int char_w = cache.font->get_char_size(' ').width; - int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); - } else { - int char_w = cache.font->get_char_size(' ').width; - int caret_w = (block_caret) ? char_w : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); - } - } - } } } @@ -1509,9 +1394,11 @@ void TextEdit::_notification(int p_what) { } if (has_focus()) { + OS::get_singleton()->set_ime_active(true); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height())); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); } + } break; case NOTIFICATION_FOCUS_ENTER: { @@ -1519,6 +1406,7 @@ void TextEdit::_notification(int p_what) { draw_caret = true; } + OS::get_singleton()->set_ime_active(true); Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height(); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); @@ -1528,12 +1416,12 @@ void TextEdit::_notification(int p_what) { if (raised_from_completion) { VisualServer::get_singleton()->canvas_item_set_z_index(get_canvas_item(), 1); } - } break; case NOTIFICATION_FOCUS_EXIT: { OS::get_singleton()->set_ime_position(Point2()); OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL); + OS::get_singleton()->set_ime_active(false); ime_text = ""; ime_selection = Point2(); @@ -1775,16 +1663,20 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co float rows = p_mouse.y; rows -= cache.style_normal->get_margin(MARGIN_TOP); - rows += (CLAMP(v_scroll->get_value() - get_line_scroll_pos(true), 0, 1) * get_row_height()); rows /= get_row_height(); - int first_vis_line = CLAMP(cursor.line_ofs, 0, text.size() - 1); + rows += get_v_scroll_offset(); + int first_vis_line = get_first_visible_line(); + int last_vis_line = get_last_visible_line(); int row = first_vis_line + Math::floor(rows); + int wrap_index = 0; - if (is_hiding_enabled()) { - // row will be offset by the hidden rows - int f_ofs = num_lines_from(first_vis_line, rows + 1) - 1; - row = first_vis_line + f_ofs; - row = CLAMP(row, 0, text.size() - num_lines_from(text.size() - 1, -1)); + if (is_wrap_enabled() || is_hiding_enabled()) { + + int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; + if (rows < 0) + row = first_vis_line - f_ofs; + else + row = first_vis_line + f_ofs; } if (row < 0) @@ -1798,9 +1690,19 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co col = text[row].size(); } else { - col = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); - col += cursor.x_ofs; - col = get_char_pos_for(col, get_line(row)); + int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); + colx += cursor.x_ofs; + col = get_char_pos_for_line(colx, row, wrap_index); + if (is_wrap_enabled() && wrap_index < times_line_wraps(row)) { + // move back one if we are at the end of the row + Vector<String> rows = get_wrap_rows_text(row); + int row_end_col = 0; + for (int i = 0; i < wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (col >= row_end_col) + col -= 1; + } } r_row = row; @@ -1875,7 +1777,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _reset_caret_blink_timer(); int row, col; - update_line_scroll_pos(); _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); if (mb->get_command() && highlighted_word != String()) { @@ -2007,7 +1908,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _reset_caret_blink_timer(); int row, col; - update_line_scroll_pos(); _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); if (is_right_click_moving_caret()) { @@ -2141,9 +2041,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (completion_index > 0) { completion_index--; - completion_current = completion_options[completion_index]; - update(); + } else { + completion_index = completion_options.size() - 1; } + completion_current = completion_options[completion_index]; + update(); + accept_event(); return; } @@ -2152,9 +2055,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (completion_index < completion_options.size() - 1) { completion_index++; - completion_current = completion_options[completion_index]; - update(); + } else { + completion_index = 0; } + completion_current = completion_options[completion_index]; + update(); + accept_event(); return; } @@ -2576,6 +2482,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_line(line); cursor_set_column(column); +#ifdef APPLE_STYLE_KEYS + } else if (k->get_command()) { + int cursor_current_column = cursor.column; + cursor.column = 0; + _remove_text(cursor.line, 0, cursor.line, cursor_current_column); +#endif } else { if (cursor.line > 0 && is_line_hidden(cursor.line - 1)) unfold_line(cursor.line - 1); @@ -2612,13 +2524,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } else if (k->get_command()) { #endif - bool prev_char = false; int cc = cursor.column; if (cc == 0 && cursor.line > 0) { cursor_set_line(cursor.line - 1); cursor_set_column(text[cursor.line].length()); } else { + bool prev_char = false; + while (cc > 0) { bool ischar = _is_text_char(text[cursor.line][cc - 1]); @@ -2673,13 +2586,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } else if (k->get_command()) { #endif - bool prev_char = false; int cc = cursor.column; if (cc == text[cursor.line].length() && cursor.line < text.size() - 1) { cursor_set_line(cursor.line + 1); cursor_set_column(0); } else { + bool prev_char = false; + while (cc < text[cursor.line].length()) { bool ischar = _is_text_char(text[cursor.line][cc]); @@ -2731,11 +2645,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } - if (k->get_command()) + if (k->get_command()) { cursor_set_line(0); - else + } else #endif - cursor_set_line(cursor_get_line() - num_lines_from(CLAMP(cursor.line - 1, 0, text.size() - 1), -1)); + { + int cur_wrap_index = get_cursor_wrap_index(); + if (cur_wrap_index > 0) { + cursor_set_line(cursor.line, true, false, cur_wrap_index - 1); + } else if (cursor.line == 0) { + cursor_set_column(0); + } else { + int new_line = cursor.line - num_lines_from(cursor.line - 1, -1); + if (line_wraps(new_line)) { + cursor_set_line(new_line, true, false, times_line_wraps(new_line)); + } else { + cursor_set_line(new_line, true, false); + } + } + } if (k->get_shift()) _post_shift_selection(); @@ -2763,22 +2691,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } - { #else if (k->get_command() && k->get_alt()) { _scroll_lines_down(); break; } - if (k->get_command()) - cursor_set_line(text.size() - 1, true, false); - else { + if (k->get_command()) { + cursor_set_line(get_last_unhidden_line(), true, false, 9999); + } else #endif - if (!is_last_visible_line(cursor.line)) { - cursor_set_line(cursor_get_line() + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1), true, false); + { + int cur_wrap_index = get_cursor_wrap_index(); + if (cur_wrap_index < times_line_wraps(cursor.line)) { + cursor_set_line(cursor.line, true, false, cur_wrap_index + 1); + } else if (cursor.line == get_last_unhidden_line()) { + cursor_set_column(text[cursor.line].length()); } else { - cursor_set_line(text.size() - 1); - cursor_set_column(get_line(cursor.line).length(), true); + int new_line = cursor.line + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1); + cursor_set_line(new_line, true, false, 0); } } @@ -2787,7 +2718,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_code_hint(); } break; - case KEY_DELETE: { if (readonly) @@ -2850,7 +2780,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { next_line = line; next_column = column; - +#ifdef APPLE_STYLE_KEYS + } else if (k->get_command()) { + next_column = curline_len; + next_line = cursor.line; +#endif } else { next_column = cursor.column < curline_len ? (cursor.column + 1) : 0; } @@ -2890,19 +2824,31 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_line(0); cursor_set_column(0); } else { - // compute whitespace symbols seq length - int current_line_whitespace_len = 0; - while (current_line_whitespace_len < text[cursor.line].length()) { - CharType c = text[cursor.line][current_line_whitespace_len]; - if (c != '\t' && c != ' ') - break; - current_line_whitespace_len++; + + // move cursor column to start of wrapped row and then to start of text + Vector<String> rows = get_wrap_rows_text(cursor.line); + int wi = get_cursor_wrap_index(); + int row_start_col = 0; + for (int i = 0; i < wi; i++) { + row_start_col += rows[i].length(); } + if (cursor.column == row_start_col || wi == 0) { + // compute whitespace symbols seq length + int current_line_whitespace_len = 0; + while (current_line_whitespace_len < text[cursor.line].length()) { + CharType c = text[cursor.line][current_line_whitespace_len]; + if (c != '\t' && c != ' ') + break; + current_line_whitespace_len++; + } - if (cursor_get_column() == current_line_whitespace_len) - cursor_set_column(0); - else - cursor_set_column(current_line_whitespace_len); + if (cursor_get_column() == current_line_whitespace_len) + cursor_set_column(0); + else + cursor_set_column(current_line_whitespace_len); + } else { + cursor_set_column(row_start_col); + } } if (k->get_shift()) @@ -2927,7 +2873,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(text.size() - 1, true, false); + cursor_set_line(get_last_unhidden_line(), true, false, 9999); if (k->get_shift()) _post_shift_selection(); @@ -2942,8 +2888,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _pre_shift_selection(); if (k->get_command()) - cursor_set_line(text.size() - 1, true, false); - cursor_set_column(text[cursor.line].length()); + cursor_set_line(get_last_unhidden_line(), true, false, 9999); + + // move cursor column to end of wrapped row and then to end of text + Vector<String> rows = get_wrap_rows_text(cursor.line); + int wi = get_cursor_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 || cursor.column == row_end_col) { + cursor_set_column(text[cursor.line].length()); + } else { + cursor_set_column(row_end_col); + } if (k->get_shift()) _post_shift_selection(); @@ -2967,7 +2925,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() - num_lines_from(cursor.line, -get_visible_rows()), true, false); + int wi; + int n_line = cursor.line - num_lines_from_rows(cursor.line, get_cursor_wrap_index(), -get_visible_rows(), wi) + 1; + cursor_set_line(n_line, true, false, wi); if (k->get_shift()) _post_shift_selection(); @@ -2981,14 +2941,16 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_pageup + // numlock disabled. fallthrough to key_pagedown } case KEY_PAGEDOWN: { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() + num_lines_from(cursor.line, get_visible_rows()), true, false); + int wi; + int n_line = cursor.line + num_lines_from_rows(cursor.line, get_cursor_wrap_index(), get_visible_rows(), wi) - 1; + cursor_set_line(n_line, true, false, wi); if (k->get_shift()) _post_shift_selection(); @@ -2999,13 +2961,64 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_A: { - if (!k->get_command() || k->get_shift() || k->get_alt()) { +#ifndef APPLE_STYLE_KEYS + if (!k->get_control() || k->get_shift() || k->get_alt()) { scancode_handled = false; break; } - select_all(); +#else + if ((!k->get_command() && !k->get_control())) { + scancode_handled = false; + break; + } + if (!k->get_shift() && k->get_command()) + select_all(); + else if (k->get_control()) { + if (k->get_shift()) + _pre_shift_selection(); + int current_line_whitespace_len = 0; + while (current_line_whitespace_len < text[cursor.line].length()) { + CharType c = text[cursor.line][current_line_whitespace_len]; + if (c != '\t' && c != ' ') + break; + current_line_whitespace_len++; + } + + if (cursor_get_column() == current_line_whitespace_len) + cursor_set_column(0); + else + cursor_set_column(current_line_whitespace_len); + + if (k->get_shift()) + _post_shift_selection(); + else if (k->get_command() || k->get_control()) + deselect(); + } + } break; + case KEY_E: { + + if (!k->get_control() || k->get_command() || k->get_alt()) { + scancode_handled = false; + break; + } + + if (k->get_shift()) + _pre_shift_selection(); + + if (k->get_command()) + cursor_set_line(text.size() - 1, true, false); + cursor_set_column(text[cursor.line].length()); + + if (k->get_shift()) + _post_shift_selection(); + else if (k->get_command() || k->get_control()) + deselect(); + + _cancel_completion(); + completion_hint = ""; +#endif } break; case KEY_X: { if (readonly) { @@ -3182,7 +3195,7 @@ void TextEdit::_scroll_up(real_t p_delta) { if (scrolling) { target_v_scroll = (target_v_scroll - p_delta); } else { - target_v_scroll = (v_scroll->get_value() - p_delta); + target_v_scroll = (get_v_scroll() - p_delta); } if (smooth_scroll_enabled) { @@ -3193,10 +3206,10 @@ void TextEdit::_scroll_up(real_t p_delta) { v_scroll->set_value(target_v_scroll); } else { scrolling = true; - set_physics_process(true); + set_physics_process_internal(true); } } else { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); } } @@ -3208,28 +3221,23 @@ void TextEdit::_scroll_down(real_t p_delta) { if (scrolling) { target_v_scroll = (target_v_scroll + p_delta); } else { - target_v_scroll = (v_scroll->get_value() + p_delta); + target_v_scroll = (get_v_scroll() + p_delta); } if (smooth_scroll_enabled) { - int max_v_scroll = get_total_unhidden_rows(); - if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows(); - max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); - } - + int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); if (target_v_scroll > max_v_scroll) { target_v_scroll = max_v_scroll; + v_scroll->set_value(target_v_scroll); } - if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) { v_scroll->set_value(target_v_scroll); } else { scrolling = true; - set_physics_process(true); + set_physics_process_internal(true); } } else { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); } } @@ -3260,35 +3268,37 @@ void TextEdit::_scroll_lines_up() { scrolling = false; // adjust the vertical scroll - if (get_v_scroll() >= 0) { - set_v_scroll(get_v_scroll() - 1); - } + set_v_scroll(get_v_scroll() - 1); - // adjust the cursor - int num_lines = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), get_visible_rows()); - if (cursor.line >= cursor.line_ofs + num_lines && !selection.active) { - cursor_set_line(cursor.line_ofs + num_lines, false, false); + // adjust the cursor to viewport + if (!selection.active) { + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); + int last_vis_line = get_last_visible_line(); + int last_vis_wrap = get_last_visible_line_wrap_index(); + + if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + cursor_set_line(last_vis_line, false, false, last_vis_wrap); + } } } void TextEdit::_scroll_lines_down() { scrolling = false; - // calculate the maximum vertical scroll position - int max_v_scroll = get_total_unhidden_rows(); - if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows(); - max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); - } - // adjust the vertical scroll - if (get_v_scroll() < max_v_scroll) { - set_v_scroll(get_v_scroll() + 1); - } + set_v_scroll(get_v_scroll() + 1); - // adjust the cursor - if (cursor.line <= cursor.line_ofs - 1 && !selection.active) { - cursor_set_line(cursor.line_ofs, false, false); + // adjust the cursor to viewport + if (!selection.active) { + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = cursor.wrap_ofs; + + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + cursor_set_line(first_vis_line, false, false, first_vis_wrap); + } } } @@ -3342,6 +3352,8 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i text.set_hidden(p_line, false); } + text.set_line_wrap_amount(p_line, -1); + r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -3350,6 +3362,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); text_changed_dirty = true; } + _line_edited_from(p_line); } String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const { @@ -3395,11 +3408,14 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li text.set(p_from_line, pre_text + post_text); + text.set_line_wrap_amount(p_from_line, -1); + if (!text_changed_dirty && !setting_text) { if (is_inside_tree()) MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); text_changed_dirty = true; } + _line_edited_from(p_from_line); } void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r_end_line, int *r_end_char) { @@ -3522,6 +3538,13 @@ void TextEdit::_insert_text_at_cursor(const String &p_text) { update(); } +void TextEdit::_line_edited_from(int p_line) { + int cache_size = color_region_cache.size(); + for (int i = p_line; i < cache_size; i++) { + color_region_cache.erase(i); + } +} + int TextEdit::get_char_count() { int totalsize = 0; @@ -3545,61 +3568,63 @@ int TextEdit::get_visible_rows() const { int total = cache.size.height; total -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) + total -= h_scroll->get_size().height; total /= get_row_height(); return total; } -int TextEdit::get_total_unhidden_rows() const { - if (!is_hiding_enabled()) +int TextEdit::get_total_visible_rows() const { + + // returns the total amount of rows we need in the editor. + // This skips hidden lines and counts each wrapping of a line. + if (!is_hiding_enabled() && !is_wrap_enabled()) return text.size(); - int total_unhidden = 0; + int total_rows = 0; for (int i = 0; i < text.size(); i++) { - if (!text.is_hidden(i)) - total_unhidden++; + if (!text.is_hidden(i)) { + total_rows++; + total_rows += times_line_wraps(i); + } } - return total_unhidden; + return total_rows; } -double TextEdit::get_line_scroll_pos(bool p_recalculate) const { +void TextEdit::update_wrap_at() { - if (!is_hiding_enabled()) - return cursor.line_ofs; - if (!p_recalculate) - return line_scroll_pos; + wrap_at = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - wrap_right_offset; + update_cursor_wrap_offset(); + text.clear_wrap_cache(); - // count num unhidden lines to the cursor line ofs - double new_line_scroll_pos = 0; - int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) - new_line_scroll_pos++; + for (int i = 0; i < text.size(); i++) { + // update all values that wrap + if (!line_wraps(i)) + continue; + Vector<String> rows = get_wrap_rows_text(i); + text.set_line_wrap_amount(i, rows.size() - 1); } - return new_line_scroll_pos; } -void TextEdit::update_line_scroll_pos() { +void TextEdit::adjust_viewport_to_cursor() { - if (!is_hiding_enabled()) { - line_scroll_pos = cursor.line_ofs; - return; - } + // make sure cursor is visible on the screen + scrolling = false; - // count num unhidden lines to the cursor line ofs - double new_line_scroll_pos = 0; - int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) - new_line_scroll_pos++; - } - line_scroll_pos = new_line_scroll_pos; -} + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); -void TextEdit::adjust_viewport_to_cursor() { - scrolling = false; + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = cursor.wrap_ofs; + int last_vis_line = get_last_visible_line(); + int last_vis_wrap = get_last_visible_line_wrap_index(); - if (cursor.line_ofs > cursor.line) { - cursor.line_ofs = cursor.line; + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + // cursor is above screen + set_line_as_first_visible(cur_line, cur_wrap); + } else if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + // cursor is below screen + set_line_as_last_visible(cur_line, cur_wrap); } int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; @@ -3607,91 +3632,174 @@ void TextEdit::adjust_viewport_to_cursor() { visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // give it a little more space - int visible_rows = get_visible_rows(); - if (h_scroll->is_visible_in_tree() && !scroll_past_end_of_file_enabled) - visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); - int num_rows = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs)); - - // make sure the cursor is on the screen - // above the caret - if (cursor.line > (cursor.line_ofs + MAX(num_rows, visible_rows))) { - cursor.line_ofs = cursor.line - num_lines_from(cursor.line, -visible_rows) + 1; - } - // below the caret - if (cursor.line_ofs == cursor.line) { - cursor.line_ofs = cursor.line - 2; - } - int line_ofs_max = text.size() - 1; - if (!scroll_past_end_of_file_enabled) { - line_ofs_max -= num_lines_from(text.size() - 1, -visible_rows) - 1; - line_ofs_max += (h_scroll->is_visible_in_tree() ? 1 : 0); - line_ofs_max += (cursor.line == text.size() - 1 ? 1 : 0); - } - line_ofs_max = MAX(line_ofs_max, 0); - cursor.line_ofs = CLAMP(cursor.line_ofs, 0, line_ofs_max); - - // adjust x offset - int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); - - if (cursor_x > (cursor.x_ofs + visible_width)) - cursor.x_ofs = cursor_x - visible_width + 1; + if (!is_wrap_enabled()) { + // adjust x offset + int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); - if (cursor_x < cursor.x_ofs) - cursor.x_ofs = cursor_x; + if (cursor_x > (cursor.x_ofs + visible_width)) + cursor.x_ofs = cursor_x - visible_width + 1; - updating_scrolls = true; - h_scroll->set_value(cursor.x_ofs); - update_line_scroll_pos(); - double new_v_scroll = get_line_scroll_pos(); - // keep offset if smooth scroll is enabled - if (smooth_scroll_enabled) { - new_v_scroll += fmod(v_scroll->get_value(), 1.0); + if (cursor_x < cursor.x_ofs) + cursor.x_ofs = cursor_x; + } else { + cursor.x_ofs = 0; } - v_scroll->set_value(new_v_scroll); - updating_scrolls = false; + h_scroll->set_value(cursor.x_ofs); + update(); } void TextEdit::center_viewport_to_cursor() { - scrolling = false; - if (cursor.line_ofs > cursor.line) - cursor.line_ofs = cursor.line; + // move viewport so the cursor is in the center of the screen + scrolling = false; if (is_line_hidden(cursor.line)) unfold_line(cursor.line); + set_line_as_center_visible(cursor.line, get_cursor_wrap_index()); int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; if (v_scroll->is_visible_in_tree()) visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // give it a little more space - int visible_rows = get_visible_rows(); - if (h_scroll->is_visible_in_tree()) - visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); - if (text.size() >= visible_rows) { - int max_ofs = text.size() - (scroll_past_end_of_file_enabled ? 1 : MAX(num_lines_from(text.size() - 1, -visible_rows), 0)); - cursor.line_ofs = CLAMP(cursor.line - num_lines_from(MAX(cursor.line - visible_rows / 2, 0), -visible_rows / 2), 0, max_ofs); + if (is_wrap_enabled()) { + // center x offset + int cursor_x = get_column_x_offset_for_line(cursor.column, cursor.line); + + if (cursor_x > (cursor.x_ofs + visible_width)) + cursor.x_ofs = cursor_x - visible_width + 1; + + if (cursor_x < cursor.x_ofs) + cursor.x_ofs = cursor_x; + } else { + cursor.x_ofs = 0; + } + h_scroll->set_value(cursor.x_ofs); + + update(); +} + +void TextEdit::update_cursor_wrap_offset() { + int first_vis_line = get_first_visible_line(); + if (line_wraps(first_vis_line)) { + cursor.wrap_ofs = MIN(cursor.wrap_ofs, times_line_wraps(first_vis_line)); + } else { + cursor.wrap_ofs = 0; } - int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); + set_line_as_first_visible(cursor.line_ofs, cursor.wrap_ofs); +} - if (cursor_x > (cursor.x_ofs + visible_width)) - cursor.x_ofs = cursor_x - visible_width + 1; +bool TextEdit::line_wraps(int line) const { - if (cursor_x < cursor.x_ofs) - cursor.x_ofs = cursor_x; + ERR_FAIL_INDEX_V(line, text.size(), 0); + if (!is_wrap_enabled()) + return false; + return text.get_line_width(line) > wrap_at; +} - updating_scrolls = true; - h_scroll->set_value(cursor.x_ofs); - update_line_scroll_pos(); - double new_v_scroll = get_line_scroll_pos(); - // keep offset if smooth scroll is enabled - if (smooth_scroll_enabled) { - new_v_scroll += fmod(v_scroll->get_value(), 1.0); +int TextEdit::times_line_wraps(int line) const { + + ERR_FAIL_INDEX_V(line, text.size(), 0); + if (!line_wraps(line)) + return 0; + + int wrap_amount = text.get_line_wrap_amount(line); + if (wrap_amount == -1) { + // update the value + Vector<String> rows = get_wrap_rows_text(line); + wrap_amount = rows.size() - 1; + text.set_line_wrap_amount(line, wrap_amount); } - v_scroll->set_value(new_v_scroll); - updating_scrolls = false; - update(); + + return wrap_amount; +} + +Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), Vector<String>()); + + Vector<String> lines; + if (!line_wraps(p_line)) { + lines.push_back(text[p_line]); + return lines; + } + + int px = 0; + int col = 0; + String line_text = text[p_line]; + String wrap_substring = ""; + + int word_px = 0; + String word_str = ""; + int cur_wrap_index = 0; + + int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + + while (col < line_text.length()) { + char c = line_text[col]; + int w = text.get_char_width(c, line_text[col + 1], px + word_px); + + int indent_ofs = (cur_wrap_index != 0 ? tab_offset_px : 0); + + word_str += c; + word_px += w; + if (c == ' ') { + // end of a word; add this word to the substring + wrap_substring += word_str; + px += word_px; + word_str = ""; + word_px = 0; + } + + if ((indent_ofs + px + word_px) > wrap_at) { + // do not want to add this word + if (indent_ofs + word_px > wrap_at) { + // not enough space; add it anyway + wrap_substring += word_str; + px += word_px; + word_str = ""; + word_px = 0; + } + lines.push_back(wrap_substring); + // reset for next wrap + cur_wrap_index++; + wrap_substring = ""; + px = 0; + } + col++; + } + // line ends before hit wrap_at; add this word to the substring + wrap_substring += word_str; + px += word_px; + lines.push_back(wrap_substring); + return lines; +} + +int TextEdit::get_cursor_wrap_index() const { + + return get_line_wrap_index_at_col(cursor.line, cursor.column); +} + +int TextEdit::get_line_wrap_index_at_col(int p_line, int p_column) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + if (!line_wraps(p_line)) + return 0; + + // loop through wraps in the line text until we get to the column + int wrap_index = 0; + int col = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + for (int i = 0; i < rows.size(); i++) { + wrap_index = i; + String s = rows[wrap_index]; + col += s.length(); + if (col > p_column) + break; + } + return wrap_index; } void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { @@ -3703,7 +3811,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { if (cursor.column > get_line(cursor.line).length()) cursor.column = get_line(cursor.line).length(); - cursor.last_fit_x = get_column_x_offset(cursor.column, get_line(cursor.line)); + cursor.last_fit_x = get_column_x_offset_for_line(cursor.column, cursor.line); if (p_adjust_viewport) adjust_viewport_to_cursor(); @@ -3715,7 +3823,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { } } -void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden) { +void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { if (setting_row) return; @@ -3724,8 +3832,8 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ if (p_row < 0) p_row = 0; - if (p_row >= (int)text.size()) - p_row = (int)text.size() - 1; + if (p_row >= text.size()) + p_row = text.size() - 1; if (!p_can_be_hidden) { if (is_line_hidden(CLAMP(p_row, 0, text.size() - 1))) { @@ -3743,7 +3851,18 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ } } cursor.line = p_row; - cursor.column = get_char_pos_for(cursor.last_fit_x, get_line(cursor.line)); + + int n_col = get_char_pos_for_line(cursor.last_fit_x, p_row, p_wrap_index); + if (is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) { + Vector<String> rows = get_wrap_rows_text(p_row); + int row_end_col = 0; + for (int i = 0; i < p_wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (n_col >= row_end_col) + n_col -= 1; + } + cursor.column = n_col; if (p_adjust_viewport) adjust_viewport_to_cursor(); @@ -3820,9 +3939,25 @@ void TextEdit::_scroll_moved(double p_to_val) { if (h_scroll->is_visible_in_tree()) cursor.x_ofs = h_scroll->get_value(); if (v_scroll->is_visible_in_tree()) { - double val = v_scroll->get_value(); - cursor.line_ofs = num_lines_from(0, (int)floor(val)); - line_scroll_pos = (int)floor(val); + + // set line ofs and wrap ofs + int v_scroll_i = floor(get_v_scroll()); + int sc = 0; + int n_line; + for (n_line = 0; n_line < text.size(); n_line++) { + if (!is_line_hidden(n_line)) { + sc++; + sc += times_line_wraps(n_line); + if (sc > v_scroll_i) + break; + } + } + int line_wrap_amount = times_line_wraps(n_line); + int wi = line_wrap_amount - (sc - v_scroll_i - 1); + wi = CLAMP(wi, 0, line_wrap_amount); + + cursor.line_ofs = n_line; + cursor.wrap_ofs = wi; } update(); } @@ -3832,29 +3967,73 @@ int TextEdit::get_row_height() const { return cache.font->get_height() + cache.line_spacing; } -int TextEdit::get_char_pos_for(int p_px, String p_str) const { +int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const { - int px = 0; - int c = 0; + ERR_FAIL_INDEX_V(p_line, text.size(), 0); - int tab_w = cache.font->get_char_size(' ').width * indent_size; + if (line_wraps(p_line)) { - while (c < p_str.length()) { + int line_wrap_amount = times_line_wraps(p_line); + int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (p_wrap_index > line_wrap_amount) + p_wrap_index = line_wrap_amount; + if (p_wrap_index > 0) + p_px -= wrap_offset_px; + else + p_wrap_index = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + int c_pos = get_char_pos_for(p_px, rows[p_wrap_index]); + for (int i = 0; i < p_wrap_index; i++) { + String s = rows[i]; + c_pos += s.length(); + } - int w = 0; + return c_pos; + } else { - if (p_str[c] == '\t') { + return get_char_pos_for(p_px, text[p_line]); + } +} - int left = px % tab_w; - if (left == 0) - w = tab_w; - else - w = tab_w - px % tab_w; // is right... +int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { - } else { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); - w = cache.font->get_char_size(p_str[c], p_str[c + 1]).width; + if (line_wraps(p_line)) { + + int n_char = p_char; + int col = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + int wrap_index = 0; + for (int i = 0; i < rows.size(); i++) { + wrap_index = i; + String s = rows[wrap_index]; + col += s.length(); + if (col > p_char) + break; + n_char -= s.length(); } + int px = get_column_x_offset(n_char, rows[wrap_index]); + + int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (wrap_index != 0) + px += wrap_offset_px; + + return px; + } else { + + return get_column_x_offset(p_char, text[p_line]); + } +} + +int TextEdit::get_char_pos_for(int p_px, String p_str) const { + + int px = 0; + int c = 0; + + while (c < p_str.length()) { + + int w = text.get_char_width(p_str[c], p_str[c + 1], px); if (p_px < (px + w / 2)) break; @@ -3865,28 +4044,16 @@ int TextEdit::get_char_pos_for(int p_px, String p_str) const { return c; } -int TextEdit::get_column_x_offset(int p_char, String p_str) { +int TextEdit::get_column_x_offset(int p_char, String p_str) const { int px = 0; - int tab_w = cache.font->get_char_size(' ').width * indent_size; - for (int i = 0; i < p_char; i++) { if (i >= p_str.length()) break; - if (p_str[i] == '\t') { - - int left = px % tab_w; - if (left == 0) - px += tab_w; - else - px += tab_w - px % tab_w; // is right... - - } else { - px += cache.font->get_char_size(p_str[i], p_str[i + 1]).width; - } + px += text.get_char_width(p_str[i], p_str[i + 1], px); } return px; @@ -3955,20 +4122,20 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { void TextEdit::set_text(String p_text) { setting_text = true; - clear(); + _clear(); _insert_text_at_cursor(p_text); clear_undo_history(); cursor.column = 0; cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; cursor.last_fit_x = 0; cursor_set_line(0); cursor_set_column(0); update(); setting_text = false; - _text_changed_emit(); + //get_range()->set(0); }; @@ -4048,7 +4215,7 @@ void TextEdit::_clear() { cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; cursor.last_fit_x = 0; } @@ -4070,14 +4237,14 @@ bool TextEdit::is_readonly() const { return readonly; } -void TextEdit::set_wrap(bool p_wrap) { +void TextEdit::set_wrap_enabled(bool p_wrap_enabled) { - wrap = p_wrap; + wrap_enabled = p_wrap_enabled; } -bool TextEdit::is_wrapping() const { +bool TextEdit::is_wrap_enabled() const { - return wrap; + return wrap_enabled; } void TextEdit::set_max_chars(int p_max_chars) { @@ -4144,13 +4311,89 @@ void TextEdit::_update_caches() { cache.can_fold_icon = get_icon("GuiTreeArrowDown", "EditorIcons"); cache.folded_eol_icon = get_icon("GuiEllipsis", "EditorIcons"); text.set_font(cache.font); + + if (syntax_highlighter) { + syntax_highlighter->_update_cache(); + } +} + +SyntaxHighlighter *TextEdit::_get_syntax_highlighting() { + return syntax_highlighter; +} + +void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter) { + syntax_highlighter = p_syntax_highlighter; + if (syntax_highlighter) { + syntax_highlighter->set_text_editor(this); + syntax_highlighter->_update_cache(); + } + update(); +} + +int TextEdit::_is_line_in_region(int p_line) { + + // do we have in cache? + if (color_region_cache.has(p_line)) { + return color_region_cache[p_line]; + } + + // if not find the closest line we have + int previous_line = p_line - 1; + for (previous_line; previous_line > -1; previous_line--) { + if (color_region_cache.has(p_line)) { + break; + } + } + + // calculate up to line we need and update the cache along the way. + int in_region = color_region_cache[previous_line]; + if (previous_line == -1) { + in_region = -1; + } + for (int i = previous_line; i < p_line; i++) { + const Map<int, Text::ColorRegionInfo> &cri_map = _get_line_color_region_info(i); + for (const Map<int, Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { + const Text::ColorRegionInfo &cri = E->get(); + if (in_region == -1) { + if (!cri.end) { + in_region = cri.region; + } + } else if (in_region == cri.region && !_get_color_region(cri.region).line_only) { + if (cri.end || _get_color_region(cri.region).eq) { + in_region = -1; + } + } + } + + if (in_region >= 0 && _get_color_region(in_region).line_only) { + in_region = -1; + } + + color_region_cache[i + 1] = in_region; + } + return in_region; +} + +TextEdit::ColorRegion TextEdit::_get_color_region(int p_region) const { + if (p_region < 0 || p_region >= color_regions.size()) { + return ColorRegion(); + } + return color_regions[p_region]; +} + +Map<int, TextEdit::Text::ColorRegionInfo> TextEdit::_get_line_color_region_info(int p_line) const { + if (p_line < 0 || p_line > text.size() - 1) { + return Map<int, Text::ColorRegionInfo>(); + } + return text.get_color_region_info(p_line); } void TextEdit::clear_colors() { keywords.clear(); color_regions.clear(); - text.clear_caches(); + color_region_cache.clear(); + text.clear_width_cache(); } void TextEdit::add_keyword_color(const String &p_keyword, const Color &p_color) { @@ -4159,10 +4402,18 @@ void TextEdit::add_keyword_color(const String &p_keyword, const Color &p_color) update(); } +bool TextEdit::has_keyword_color(String p_keyword) const { + return keywords.has(p_keyword); +} + +Color TextEdit::get_keyword_color(String p_keyword) const { + return keywords[p_keyword]; +} + void TextEdit::add_color_region(const String &p_begin_key, const String &p_end_key, const Color &p_color, bool p_line_only) { color_regions.push_back(ColorRegion(p_begin_key, p_end_key, p_color, p_line_only)); - text.clear_caches(); + text.clear_width_cache(); update(); } @@ -4171,6 +4422,14 @@ void TextEdit::add_member_keyword(const String &p_keyword, const Color &p_color) update(); } +bool TextEdit::has_member_color(String p_member) const { + return member_keywords.has(p_member); +} + +Color TextEdit::get_member_color(String p_member) const { + return member_keywords[p_member]; +} + void TextEdit::clear_member_keywords() { member_keywords.clear(); update(); @@ -4239,6 +4498,7 @@ void TextEdit::paste() { String clipboard = OS::get_singleton()->get_clipboard(); + begin_complex_operation(); if (selection.active) { selection.active = false; @@ -4255,6 +4515,8 @@ void TextEdit::paste() { } _insert_text_at_cursor(clipboard); + end_complex_operation(); + update(); } @@ -4648,52 +4910,99 @@ void TextEdit::unhide_all_lines() { update(); } -int TextEdit::num_lines_from(int p_line_from, int unhidden_amount) const { +int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { - // returns the number of hidden and unhidden lines from p_line_from to p_line_from + amount of visible lines - ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(unhidden_amount)); + // returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines) + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); if (!is_hiding_enabled()) - return ABS(unhidden_amount); + return ABS(visible_amount); + int num_visible = 0; int num_total = 0; - if (unhidden_amount >= 0) { + if (visible_amount >= 0) { for (int i = p_line_from; i < text.size(); i++) { num_total++; - if (!is_line_hidden(i)) + if (!is_line_hidden(i)) { num_visible++; - if (num_visible >= unhidden_amount) + } + if (num_visible >= visible_amount) break; } } else { - unhidden_amount = ABS(unhidden_amount); + visible_amount = ABS(visible_amount); for (int i = p_line_from; i >= 0; i--) { num_total++; - if (!is_line_hidden(i)) + if (!is_line_hidden(i)) { num_visible++; - if (num_visible >= unhidden_amount) + } + if (num_visible >= visible_amount) break; } } return num_total; } -bool TextEdit::is_last_visible_line(int p_line) const { +int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), false); + // returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows) + // wrap index is set to the wrap index of the last line + wrap_index = 0; + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); - if (p_line == text.size() - 1) - return true; + if (!is_hiding_enabled() && !is_wrap_enabled()) + return ABS(visible_amount); + int num_visible = 0; + int num_total = 0; + if (visible_amount == 0) { + num_total = 0; + wrap_index = 0; + } else if (visible_amount > 0) { + int i; + num_visible -= p_wrap_index_from; + for (i = p_line_from; i < text.size(); i++) { + num_total++; + if (!is_line_hidden(i)) { + num_visible++; + num_visible += times_line_wraps(i); + } + if (num_visible >= visible_amount) + break; + } + wrap_index = times_line_wraps(MIN(i, text.size() - 1)) - (num_visible - visible_amount); + } else { + visible_amount = ABS(visible_amount); + int i; + num_visible -= times_line_wraps(p_line_from) - p_wrap_index_from; + for (i = p_line_from; i >= 0; i--) { + num_total++; + if (!is_line_hidden(i)) { + num_visible++; + num_visible += times_line_wraps(i); + } + if (num_visible >= visible_amount) + break; + } + wrap_index = (num_visible - visible_amount); + } + wrap_index = MAX(wrap_index, 0); + return num_total; +} + +int TextEdit::get_last_unhidden_line() const { + + // returns the last line in the text that is not hidden if (!is_hiding_enabled()) - return false; + return text.size() - 1; - for (int i = p_line + 1; i < text.size(); i++) { - if (!is_line_hidden(i)) - return false; + int last_line; + for (last_line = text.size() - 1; last_line > 0; last_line--) { + if (!is_line_hidden(last_line)) { + break; + } } - - return true; + return last_line; } int TextEdit::get_indent_level(int p_line) const { @@ -4713,7 +5022,7 @@ int TextEdit::get_indent_level(int p_line) const { break; } } - return tab_count + whitespace_count / indent_size; + return tab_count * indent_size + whitespace_count; } bool TextEdit::is_line_comment(int p_line) const { @@ -5043,6 +5352,11 @@ void TextEdit::set_indent_size(const int p_size) { update(); } +int TextEdit::get_indent_size() { + + return indent_size; +} + void TextEdit::set_draw_tabs(bool p_draw) { draw_tabs = p_draw; @@ -5056,6 +5370,7 @@ bool TextEdit::is_drawing_tabs() const { void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { override_selected_font_color = p_override_selected_font_color; } + bool TextEdit::is_overriding_selected_font_color() const { return override_selected_font_color; } @@ -5076,58 +5391,143 @@ bool TextEdit::is_insert_text_operation() { uint32_t TextEdit::get_version() const { return current_op.version; } + uint32_t TextEdit::get_saved_version() const { return saved_version; } + void TextEdit::tag_saved_version() { saved_version = get_version(); } -int TextEdit::get_v_scroll() const { +double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { - return v_scroll->get_value(); -} -void TextEdit::set_v_scroll(int p_scroll) { + if (!is_wrap_enabled() && !is_hiding_enabled()) + return p_line; - if (p_scroll < 0) { - p_scroll = 0; - } - if (!scroll_past_end_of_file_enabled) { - if (p_scroll + get_visible_rows() > get_total_unhidden_rows()) { - int num_rows = num_lines_from(CLAMP(p_scroll, 0, text.size() - 1), MIN(get_visible_rows(), text.size() - 1 - p_scroll)); - p_scroll = text.size() - num_rows; + // count the number of visible lines up to this line + double new_line_scroll_pos = 0; + int to = CLAMP(p_line, 0, text.size() - 1); + for (int i = 0; i < to; i++) { + if (!text.is_hidden(i)) { + new_line_scroll_pos++; + new_line_scroll_pos += times_line_wraps(i); } } + new_line_scroll_pos += p_wrap_index; + return new_line_scroll_pos; +} + +void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) { + + set_v_scroll(get_scroll_pos_for_line(p_line, p_wrap_index)); +} + +void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { + + int visible_rows = get_visible_rows(); + int wi; + int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -visible_rows / 2, wi) + 1; + + set_v_scroll(get_scroll_pos_for_line(first_line, wi)); +} + +void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { + + int wi; + int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -get_visible_rows() - 1, wi) + 1; + + set_v_scroll(get_scroll_pos_for_line(first_line, wi) + get_visible_rows_offset()); +} + +int TextEdit::get_first_visible_line() const { + + return CLAMP(cursor.line_ofs, 0, text.size() - 1); +} + +int TextEdit::get_last_visible_line() const { + + int first_vis_line = get_first_visible_line(); + int last_vis_line = 0; + int wi; + last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1; + last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1); + return last_vis_line; +} + +int TextEdit::get_last_visible_line_wrap_index() const { + + int first_vis_line = get_first_visible_line(); + int last_vis_line = 0; + int wi; + last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1; + return wi; +} + +double TextEdit::get_visible_rows_offset() const { + + double total = cache.size.height; + total -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) + total -= h_scroll->get_size().height; + total /= (double)get_row_height(); + total = total - floor(total); + total = -CLAMP(total, 0.001, 1) + 1; + return total; +} + +double TextEdit::get_v_scroll_offset() const { + + double val = get_v_scroll() - floor(get_v_scroll()); + return CLAMP(val, 0, 1); +} + +double TextEdit::get_v_scroll() const { + + return v_scroll->get_value(); +} + +void TextEdit::set_v_scroll(double p_scroll) { + v_scroll->set_value(p_scroll); - cursor.line_ofs = num_lines_from(0, p_scroll); - line_scroll_pos = p_scroll; + int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); + if (p_scroll >= max_v_scroll - 1.0) + _scroll_moved(v_scroll->get_value()); } int TextEdit::get_h_scroll() const { return h_scroll->get_value(); } + void TextEdit::set_h_scroll(int p_scroll) { + if (p_scroll < 0) { + p_scroll = 0; + } h_scroll->set_value(p_scroll); } void TextEdit::set_smooth_scroll_enabled(bool p_enable) { + v_scroll->set_smooth_scroll_enabled(p_enable); smooth_scroll_enabled = p_enable; } bool TextEdit::is_smooth_scroll_enabled() const { + return smooth_scroll_enabled; } void TextEdit::set_v_scroll_speed(float p_speed) { + v_scroll_speed = p_speed; } float TextEdit::get_v_scroll_speed() const { + return v_scroll_speed; } @@ -5232,7 +5632,7 @@ void TextEdit::_update_completion_candidates() { } else { - while (cofs > 0 && l[cofs - 1] > 32 && _is_completable(l[cofs - 1])) { + while (cofs > 0 && l[cofs - 1] > 32 && (l[cofs - 1] == '/' || _is_completable(l[cofs - 1]))) { s = String::chr(l[cofs - 1]) + s; if (l[cofs - 1] == '\'' || l[cofs - 1] == '"' || l[cofs - 1] == '$') break; @@ -5374,18 +5774,23 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { if (select_word(s, col, beg, end)) { bool inside_quotes = false; + char selected_quote = '\0'; int qbegin = 0, qend = 0; for (int i = 0; i < s.length(); i++) { - if (s[i] == '"') { - if (inside_quotes) { - qend = i; - inside_quotes = false; - if (col >= qbegin && col <= qend) { - return s.substr(qbegin, qend - qbegin); + if (s[i] == '"' || s[i] == '\'') { + if (i == 0 || s[i - 1] != '\\') { + if (inside_quotes && selected_quote == s[i]) { + qend = i; + inside_quotes = false; + selected_quote = '\0'; + if (col >= qbegin && col <= qend) { + return s.substr(qbegin, qend - qbegin); + } + } else if (!inside_quotes) { + qbegin = i + 1; + inside_quotes = true; + selected_quote = s[i]; } - } else { - qbegin = i + 1; - inside_quotes = true; } } } @@ -5607,7 +6012,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column); ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line); @@ -5624,8 +6029,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly); ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly); - ClassDB::bind_method(D_METHOD("set_wrap", "enable"), &TextEdit::set_wrap); - ClassDB::bind_method(D_METHOD("is_wrapping"), &TextEdit::is_wrapping); + ClassDB::bind_method(D_METHOD("set_wrap_enabled", "enable"), &TextEdit::set_wrap_enabled); + ClassDB::bind_method(D_METHOD("is_wrap_enabled"), &TextEdit::is_wrap_enabled); // ClassDB::bind_method(D_METHOD("set_max_chars", "amount"), &TextEdit::set_max_chars); // ClassDB::bind_method(D_METHOD("get_max_char"), &TextEdit::get_max_chars); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &TextEdit::set_context_menu_enabled); @@ -5685,6 +6090,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_v_scroll_speed"), &TextEdit::get_v_scroll_speed); ClassDB::bind_method(D_METHOD("add_keyword_color", "keyword", "color"), &TextEdit::add_keyword_color); + ClassDB::bind_method(D_METHOD("has_keyword_color", "keyword"), &TextEdit::has_keyword_color); + ClassDB::bind_method(D_METHOD("get_keyword_color", "keyword"), &TextEdit::get_keyword_color); ClassDB::bind_method(D_METHOD("add_color_region", "begin_key", "end_key", "color", "line_only"), &TextEdit::add_color_region, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_colors"), &TextEdit::clear_colors); ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option); @@ -5701,13 +6108,13 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_lines"), "set_wrap", "is_wrapping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled"); // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "max_chars"), "set_max_chars", "get_max_chars"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled"); - ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.1"), "cursor_set_blink_speed", "cursor_get_blink_speed"); + ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_moving_by_right_click"), "set_right_click_moves_caret", "is_right_click_moving_caret"); ADD_SIGNAL(MethodInfo("cursor_changed")); @@ -5736,8 +6143,10 @@ TextEdit::TextEdit() { draw_caret = true; max_chars = 0; clear(); - wrap = false; + wrap_enabled = false; + wrap_right_offset = 10; set_focus_mode(FOCUS_ALL); + syntax_highlighter = NULL; _update_caches(); cache.size = Size2(1, 1); cache.row_height = 1; @@ -5751,7 +6160,7 @@ TextEdit::TextEdit() { indent_size = 4; text.set_indent_size(indent_size); text.clear(); - //text.insert(1,"Mongolia.."); + //text.insert(1,"Mongolia..."); //text.insert(2,"PAIS GENEROSO!!"); text.set_color_regions(&color_regions); @@ -5841,16 +6250,213 @@ TextEdit::TextEdit() { context_menu_enabled = true; menu = memnew(PopupMenu); add_child(menu); - menu->add_item(TTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); - menu->add_item(TTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); - menu->add_item(TTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); + menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); menu->add_separator(); - menu->add_item(TTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); - menu->add_item(TTR("Clear"), MENU_CLEAR); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + menu->add_item(RTR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(TTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); + menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); menu->connect("id_pressed", this, "menu_option"); } TextEdit::~TextEdit() { } + +/////////////////////////////////////////////////////////////////////////////// + +Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighter != NULL) { + return syntax_highlighter->_get_line_syntax_highlighting(p_line); + } + + Map<int, HighlighterInfo> color_map; + + bool prev_is_char = false; + bool prev_is_number = false; + bool in_keyword = false; + bool in_word = false; + bool in_function_name = false; + bool in_member_variable = false; + bool is_hex_notation = false; + Color keyword_color; + Color color; + + int in_region = _is_line_in_region(p_line); + int deregion = 0; + + const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text.get_color_region_info(p_line); + const String &str = text[p_line]; + Color prev_color; + for (int j = 0; j < str.length(); j++) { + HighlighterInfo highlighter_info; + + if (deregion > 0) { + deregion--; + if (deregion == 0) { + in_region = -1; + } + } + + if (deregion != 0) { + if (color != prev_color) { + prev_color = color; + highlighter_info.color = color; + color_map[j] = highlighter_info; + } + continue; + } + + color = cache.font_color; + + bool is_char = _is_text_char(str[j]); + bool is_symbol = _is_symbol(str[j]); + bool is_number = _is_number(str[j]); + + // allow ABCDEF in hex notation + if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { + is_number = true; + } else { + is_hex_notation = false; + } + + // check for dot or underscore or 'x' for hex notation in floating point number + if ((str[j] == '.' || str[j] == 'x' || str[j] == '_') && !in_word && prev_is_number && !is_number) { + is_number = true; + is_symbol = false; + is_char = false; + + if (str[j] == 'x' && str[j - 1] == '0') { + is_hex_notation = true; + } + } + + if (!in_word && _is_char(str[j]) && !is_number) { + in_word = true; + } + + if ((in_keyword || in_word) && !is_hex_notation) { + is_number = false; + } + + if (is_symbol && str[j] != '.' && in_word) { + in_word = false; + } + + if (is_symbol && cri_map.has(j)) { + const TextEdit::Text::ColorRegionInfo &cri = cri_map[j]; + + if (in_region == -1) { + if (!cri.end) { + in_region = cri.region; + } + } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise + if (cri.end || color_regions[cri.region].eq) { + deregion = color_regions[cri.region].eq ? color_regions[cri.region].begin_key.length() : color_regions[cri.region].end_key.length(); + } + } + } + + if (!is_char) { + in_keyword = false; + } + + if (in_region == -1 && !in_keyword && is_char && !prev_is_char) { + + int to = j; + while (to < str.length() && _is_text_char(str[to])) + to++; + + uint32_t hash = String::hash(&str[j], to - j); + StrRange range(&str[j], to - j); + + const Color *col = keywords.custom_getptr(range, hash); + + if (!col) { + col = member_keywords.custom_getptr(range, hash); + + if (col) { + for (int k = j - 1; k >= 0; k--) { + if (str[k] == '.') { + col = NULL; //member indexing not allowed + break; + } else if (str[k] > 32) { + break; + } + } + } + } + + if (col) { + in_keyword = true; + keyword_color = *col; + } + } + + if (!in_function_name && in_word && !in_keyword) { + + int k = j; + while (k < str.length() && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + k++; + } + + // check for space between name and bracket + while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { + k++; + } + + if (str[k] == '(') { + in_function_name = true; + } + } + + if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) { + int k = j; + while (k > 0 && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + k--; + } + + if (str[k] == '.') { + in_member_variable = true; + } + } + + if (is_symbol) { + in_function_name = false; + in_member_variable = false; + } + + if (in_region >= 0) + color = color_regions[in_region].color; + else if (in_keyword) + color = keyword_color; + else if (in_member_variable) + color = cache.member_variable_color; + else if (in_function_name) + color = cache.function_color; + else if (is_symbol) + color = cache.symbol_color; + else if (is_number) + color = cache.number_color; + + prev_is_char = is_char; + prev_is_number = is_number; + + if (color != prev_color) { + prev_color = color; + highlighter_info.color = color; + color_map[j] = highlighter_info; + } + } + + return color_map; +} + +void SyntaxHighlighter::set_text_editor(TextEdit *p_text_editor) { + text_editor = p_text_editor; +} + +TextEdit *SyntaxHighlighter::get_text_editor() { + return text_editor; +} diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 8ac3b9fce6..5c82d1ac20 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -36,14 +36,91 @@ #include "scene/gui/scroll_bar.h" #include "scene/main/timer.h" +class SyntaxHighlighter; + class TextEdit : public Control { - GDCLASS(TextEdit, Control); + GDCLASS(TextEdit, Control) + +public: + struct HighlighterInfo { + Color color; + }; + + struct ColorRegion { + Color color; + String begin_key; + String end_key; + bool line_only; + bool eq; + ColorRegion(const String &p_begin_key = "", const String &p_end_key = "", const Color &p_color = Color(), bool p_line_only = false) { + begin_key = p_begin_key; + end_key = p_end_key; + color = p_color; + line_only = p_line_only || p_end_key == ""; + eq = begin_key == end_key; + } + }; + + class Text { + public: + struct ColorRegionInfo { + + int region; + bool end; + }; + + struct Line { + int width_cache : 24; + bool marked : 1; + bool breakpoint : 1; + bool hidden : 1; + int wrap_amount_cache : 24; + Map<int, ColorRegionInfo> region_info; + String data; + }; + + private: + const Vector<ColorRegion> *color_regions; + mutable Vector<Line> text; + Ref<Font> font; + int indent_size; + + void _update_line_cache(int p_line) const; + + public: + void set_indent_size(int p_indent_size); + void set_font(const Ref<Font> &p_font); + void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; } + int get_line_width(int p_line) const; + int get_max_width(bool p_exclude_hidden = false) const; + int get_char_width(CharType c, CharType next_c, int px) const; + void set_line_wrap_amount(int p_line, int p_wrap_amount) const; + int get_line_wrap_amount(int p_line) const; + const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const; + void set(int p_line, const String &p_text); + void set_marked(int p_line, bool p_marked) { text[p_line].marked = p_marked; } + bool is_marked(int p_line) const { return text[p_line].marked; } + void set_breakpoint(int p_line, bool p_breakpoint) { text[p_line].breakpoint = p_breakpoint; } + bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; } + void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; } + bool is_hidden(int p_line) const { return text[p_line].hidden; } + void insert(int p_at, const String &p_text); + void remove(int p_at); + int size() const { return text.size(); } + void clear(); + void clear_width_cache(); + void clear_wrap_cache(); + _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; } + Text() { indent_size = 4; } + }; + +private: struct Cursor { int last_fit_x; int line, column; ///< cursor - int x_ofs, line_ofs; + int x_ofs, line_ofs, wrap_ofs; } cursor; struct Selection { @@ -115,69 +192,7 @@ class TextEdit : public Control { Size2 size; } cache; - struct ColorRegion { - - Color color; - String begin_key; - String end_key; - bool line_only; - bool eq; - ColorRegion(const String &p_begin_key = "", const String &p_end_key = "", const Color &p_color = Color(), bool p_line_only = false) { - begin_key = p_begin_key; - end_key = p_end_key; - color = p_color; - line_only = p_line_only || p_end_key == ""; - eq = begin_key == end_key; - } - }; - - class Text { - public: - struct ColorRegionInfo { - - int region; - bool end; - }; - - struct Line { - int width_cache : 24; - bool marked : 1; - bool breakpoint : 1; - bool hidden : 1; - Map<int, ColorRegionInfo> region_info; - String data; - }; - - private: - const Vector<ColorRegion> *color_regions; - mutable Vector<Line> text; - Ref<Font> font; - int indent_size; - - void _update_line_cache(int p_line) const; - - public: - void set_indent_size(int p_indent_size); - void set_font(const Ref<Font> &p_font); - void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; } - int get_line_width(int p_line) const; - int get_max_width(bool p_exclude_hidden = false) const; - const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const; - void set(int p_line, const String &p_text); - void set_marked(int p_line, bool p_marked) { text[p_line].marked = p_marked; } - bool is_marked(int p_line) const { return text[p_line].marked; } - void set_breakpoint(int p_line, bool p_breakpoint) { text[p_line].breakpoint = p_breakpoint; } - bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; } - void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; } - bool is_hidden(int p_line) const { return text[p_line].hidden; } - void insert(int p_at, const String &p_text); - void remove(int p_at); - int size() const { return text.size(); } - void clear(); - void clear_caches(); - _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; } - Text() { indent_size = 4; } - }; + Map<int, int> color_region_cache; struct TextOperation { @@ -209,9 +224,12 @@ class TextEdit : public Control { void _do_text_op(const TextOperation &p_op, bool p_reverse); //syntax coloring + SyntaxHighlighter *syntax_highlighter; HashMap<String, Color> keywords; HashMap<String, Color> member_keywords; + Map<int, HighlighterInfo> _get_line_syntax_highlighting(int p_line); + Vector<ColorRegion> color_regions; Set<String> completion_prefixes; @@ -250,8 +268,11 @@ class TextEdit : public Control { bool block_caret; bool right_click_moves_caret; + bool wrap_enabled; + int wrap_at; + int wrap_right_offset; + bool setting_row; - bool wrap; bool draw_tabs; bool override_selected_font_color; bool cursor_changed_dirty; @@ -308,19 +329,34 @@ class TextEdit : public Control { int search_result_line; int search_result_col; - double line_scroll_pos; - bool context_menu_enabled; int get_visible_rows() const; - int get_total_unhidden_rows() const; - double get_line_scroll_pos(bool p_recalculate = false) const; - void update_line_scroll_pos(); - + int get_total_visible_rows() const; + + void update_cursor_wrap_offset(); + void update_wrap_at(); + bool line_wraps(int line) const; + int times_line_wraps(int line) const; + Vector<String> get_wrap_rows_text(int p_line) const; + int get_cursor_wrap_index() const; + int get_line_wrap_index_at_col(int p_line, int p_column) const; int get_char_count(); + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; + void set_line_as_first_visible(int p_line, int p_wrap_index = 0); + void set_line_as_center_visible(int p_line, int p_wrap_index = 0); + void set_line_as_last_visible(int p_line, int p_wrap_index = 0); + int get_first_visible_line() const; + int get_last_visible_line() const; + int get_last_visible_line_wrap_index() const; + double get_visible_rows_offset() const; + double get_v_scroll_offset() const; + + int get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; + int get_column_x_offset_for_line(int p_char, int p_line) const; int get_char_pos_for(int p_px, String p_str) const; - int get_column_x_offset(int p_char, String p_str); + int get_column_x_offset(int p_char, String p_str) const; void adjust_viewport_to_cursor(); double get_scroll_line_diff() const; @@ -355,6 +391,7 @@ class TextEdit : public Control { void _update_caches(); void _cursor_changed_emit(); void _text_changed_emit(); + void _line_edited_from(int p_line); void _push_current_op(); @@ -391,6 +428,13 @@ protected: static void _bind_methods(); public: + SyntaxHighlighter *_get_syntax_highlighting(); + void _set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter); + + int _is_line_in_region(int p_line); + ColorRegion _get_color_region(int p_region) const; + Map<int, Text::ColorRegionInfo> _get_line_color_region_info(int p_line) const; + enum MenuItems { MENU_CUT, MENU_COPY, @@ -434,8 +478,10 @@ public: bool is_line_hidden(int p_line) const; void fold_all_lines(); void unhide_all_lines(); - int num_lines_from(int p_line_from, int unhidden_amount) const; - bool is_last_visible_line(int p_line) const; + int num_lines_from(int p_line_from, int visible_amount) const; + int num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const; + int get_last_unhidden_line() const; + bool can_fold(int p_line) const; bool is_folded(int p_line) const; void fold_line(int p_line); @@ -472,7 +518,7 @@ public: void center_viewport_to_cursor(); void cursor_set_column(int p_col, bool p_adjust_viewport = true); - void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true); + void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); int cursor_get_column() const; int cursor_get_line() const; @@ -495,8 +541,8 @@ public: void set_max_chars(int p_max_chars); int get_max_chars() const; - void set_wrap(bool p_wrap); - bool is_wrapping() const; + void set_wrap_enabled(bool p_wrap_enabled); + bool is_wrap_enabled() const; void clear(); @@ -536,6 +582,7 @@ public: void set_indent_using_spaces(const bool p_use_spaces); bool is_indent_using_spaces() const; void set_indent_size(const int p_size); + int get_indent_size(); void set_draw_tabs(bool p_draw); bool is_drawing_tabs() const; void set_override_selected_font_color(bool p_override_selected_font_color); @@ -545,14 +592,19 @@ public: bool is_insert_mode() const; void add_keyword_color(const String &p_keyword, const Color &p_color); + bool has_keyword_color(String p_keyword) const; + Color get_keyword_color(String p_keyword) const; + void add_color_region(const String &p_begin_key = String(), const String &p_end_key = String(), const Color &p_color = Color(), bool p_line_only = false); void clear_colors(); void add_member_keyword(const String &p_keyword, const Color &p_color); + bool has_member_color(String p_member) const; + Color get_member_color(String p_member) const; void clear_member_keywords(); - int get_v_scroll() const; - void set_v_scroll(int p_scroll); + double get_v_scroll() const; + void set_v_scroll(double p_scroll); int get_h_scroll() const; void set_h_scroll(int p_scroll); @@ -621,4 +673,19 @@ public: VARIANT_ENUM_CAST(TextEdit::MenuItems); VARIANT_ENUM_CAST(TextEdit::SearchFlags); +class SyntaxHighlighter { +protected: + TextEdit *text_editor; + +public: + virtual void _update_cache() = 0; + virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line) = 0; + + virtual String get_name() = 0; + virtual List<String> get_supported_languages() = 0; + + void set_text_editor(TextEdit *p_text_editor); + TextEdit *get_text_editor(); +}; + #endif // TEXT_EDIT_H diff --git a/scene/gui/texture_progress.cpp b/scene/gui/texture_progress.cpp index 4a419098a0..82d983184b 100644 --- a/scene/gui/texture_progress.cpp +++ b/scene/gui/texture_progress.cpp @@ -106,6 +106,33 @@ Ref<Texture> TextureProgress::get_progress_texture() const { return progress; } +void TextureProgress::set_tint_under(const Color &p_tint) { + tint_under = p_tint; + update(); +} + +Color TextureProgress::get_tint_under() const { + return tint_under; +} + +void TextureProgress::set_tint_progress(const Color &p_tint) { + tint_progress = p_tint; + update(); +} + +Color TextureProgress::get_tint_progress() const { + return tint_progress; +} + +void TextureProgress::set_tint_over(const Color &p_tint) { + tint_over = p_tint; + update(); +} + +Color TextureProgress::get_tint_over() const { + return tint_over; +} + Point2 TextureProgress::unit_val_to_uv(float val) { if (progress.is_null()) return Point2(); @@ -117,22 +144,45 @@ Point2 TextureProgress::unit_val_to_uv(float val) { Point2 p = get_relative_center(); - if (val < 0.125) - return Point2(p.x + (1 - p.x) * val * 8, 0); - if (val < 0.25) - return Point2(1, p.y * (val - 0.125) * 8); - if (val < 0.375) - return Point2(1, p.y + (1 - p.y) * (val - 0.25) * 8); - if (val < 0.5) - return Point2(1 - (1 - p.x) * (val - 0.375) * 8, 1); - if (val < 0.625) - return Point2(p.x * (1 - (val - 0.5) * 8), 1); - if (val < 0.75) - return Point2(0, 1 - ((1 - p.y) * (val - 0.625) * 8)); - if (val < 0.875) - return Point2(0, p.y - p.y * (val - 0.75) * 8); - else - return Point2(p.x * (val - 0.875) * 8, 0); + // Minimal version of Liang-Barsky clipping algorithm + float angle = (val * Math_TAU) - Math_PI * 0.5; + Point2 dir = Vector2(Math::cos(angle), Math::sin(angle)); + float t1 = 1.0; + float cp; + float cq; + float cr; + float edgeLeft = 0.0; + float edgeRight = 1.0; + float edgeBottom = 0.0; + float edgeTop = 1.0; + + for (int edge = 0; edge < 4; edge++) { + if (edge == 0) { + if (dir.x > 0) + continue; + cp = -dir.x; + cq = -(edgeLeft - p.x); + } else if (edge == 1) { + if (dir.x < 0) + continue; + cp = dir.x; + cq = (edgeRight - p.x); + } else if (edge == 2) { + if (dir.y > 0) + continue; + cp = -dir.y; + cq = -(edgeBottom - p.y); + } else if (edge == 3) { + if (dir.y < 0) + continue; + cp = dir.y; + cq = (edgeTop - p.y); + } + cr = cq / cp; + if (cr >= 0 && cr < t1) + t1 = cr; + } + return (p + t1 * dir); } Point2 TextureProgress::get_relative_center() { @@ -147,7 +197,7 @@ Point2 TextureProgress::get_relative_center() { return p; } -void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio) { +void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio, const Color &p_modulate) { Vector2 texture_size = p_texture->get_size(); Vector2 topleft = Vector2(stretch_margin[MARGIN_LEFT], stretch_margin[MARGIN_TOP]); Vector2 bottomright = Vector2(stretch_margin[MARGIN_RIGHT], stretch_margin[MARGIN_BOTTOM]); @@ -217,7 +267,7 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F } RID ci = get_canvas_item(); - VS::get_singleton()->canvas_item_add_nine_patch(ci, dst_rect, src_rect, p_texture->get_rid(), topleft, bottomright); + VS::get_singleton()->canvas_item_add_nine_patch(ci, dst_rect, src_rect, p_texture->get_rid(), topleft, bottomright, VS::NINE_PATCH_STRETCH, VS::NINE_PATCH_STRETCH, true, p_modulate); } void TextureProgress::_notification(int p_what) { @@ -228,42 +278,42 @@ void TextureProgress::_notification(int p_what) { if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP)) { if (under.is_valid()) { - draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0); + draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0, tint_under); } if (progress.is_valid()) { - draw_nine_patch_stretched(progress, mode, get_as_ratio()); + draw_nine_patch_stretched(progress, mode, get_as_ratio(), tint_progress); } if (over.is_valid()) { - draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0); + draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0, tint_over); } } else { if (under.is_valid()) - draw_texture(under, Point2()); + draw_texture(under, Point2(), tint_under); if (progress.is_valid()) { Size2 s = progress->get_size(); switch (mode) { case FILL_LEFT_TO_RIGHT: { Rect2 region = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region); + draw_texture_rect_region(progress, region, region, tint_progress); } break; case FILL_RIGHT_TO_LEFT: { Rect2 region = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y)); - draw_texture_rect_region(progress, region, region); + draw_texture_rect_region(progress, region, region, tint_progress); } break; case FILL_TOP_TO_BOTTOM: { Rect2 region = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region); + draw_texture_rect_region(progress, region, region, tint_progress); } break; case FILL_BOTTOM_TO_TOP: { Rect2 region = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio())); - draw_texture_rect_region(progress, region, region); + draw_texture_rect_region(progress, region, region, tint_progress); } break; case FILL_CLOCKWISE: case FILL_COUNTER_CLOCKWISE: { float val = get_as_ratio() * rad_max_degrees / 360; if (val == 1) { Rect2 region = Rect2(Point2(), s); - draw_texture_rect_region(progress, region, region); + draw_texture_rect_region(progress, region, region, tint_progress); } else if (val != 0) { Array pts; float direction = mode == FILL_CLOCKWISE ? 1 : -1; @@ -288,7 +338,9 @@ void TextureProgress::_notification(int p_what) { uvs.push_back(uv); points.push_back(Point2(uv.x * s.x, uv.y * s.y)); } - draw_polygon(points, Vector<Color>(), uvs, progress); + Vector<Color> colors; + colors.push_back(tint_progress); + draw_polygon(points, colors, uvs, progress); } if (Engine::get_singleton()->is_editor_hint()) { Point2 p = progress->get_size(); @@ -300,11 +352,11 @@ void TextureProgress::_notification(int p_what) { } } break; default: - draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y))); + draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); } } if (over.is_valid()) - draw_texture(over, Point2()); + draw_texture(over, Point2(), tint_over); } } break; @@ -366,6 +418,15 @@ void TextureProgress::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &TextureProgress::set_fill_mode); ClassDB::bind_method(D_METHOD("get_fill_mode"), &TextureProgress::get_fill_mode); + ClassDB::bind_method(D_METHOD("set_tint_under", "tint"), &TextureProgress::set_tint_under); + ClassDB::bind_method(D_METHOD("get_tint_under"), &TextureProgress::get_tint_under); + + ClassDB::bind_method(D_METHOD("set_tint_progress", "tint"), &TextureProgress::set_tint_progress); + ClassDB::bind_method(D_METHOD("get_tint_progress"), &TextureProgress::get_tint_progress); + + ClassDB::bind_method(D_METHOD("set_tint_over", "tint"), &TextureProgress::set_tint_over); + ClassDB::bind_method(D_METHOD("get_tint_over"), &TextureProgress::get_tint_over); + ClassDB::bind_method(D_METHOD("set_radial_initial_angle", "mode"), &TextureProgress::set_radial_initial_angle); ClassDB::bind_method(D_METHOD("get_radial_initial_angle"), &TextureProgress::get_radial_initial_angle); @@ -386,6 +447,10 @@ void TextureProgress::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_over_texture", "get_over_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_progress_texture", "get_progress_texture"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise"), "set_fill_mode", "get_fill_mode"); + ADD_GROUP("Tint", "tint_"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_under", "get_tint_under"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_over", "get_tint_over"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_progress", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_progress", "get_tint_progress"); ADD_GROUP("Radial Fill", "radial_"); ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_radial_initial_angle", "get_radial_initial_angle"); ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_fill_degrees", "get_fill_degrees"); @@ -417,4 +482,6 @@ TextureProgress::TextureProgress() { stretch_margin[MARGIN_RIGHT] = 0; stretch_margin[MARGIN_BOTTOM] = 0; stretch_margin[MARGIN_TOP] = 0; + + tint_under = tint_progress = tint_over = Color(1, 1, 1); } diff --git a/scene/gui/texture_progress.h b/scene/gui/texture_progress.h index 77c3980e29..34158b5db5 100644 --- a/scene/gui/texture_progress.h +++ b/scene/gui/texture_progress.h @@ -82,6 +82,15 @@ public: void set_nine_patch_stretch(bool p_stretch); bool get_nine_patch_stretch() const; + void set_tint_under(const Color &p_tint); + Color get_tint_under() const; + + void set_tint_progress(const Color &p_tint); + Color get_tint_progress() const; + + void set_tint_over(const Color &p_tint); + Color get_tint_over() const; + Size2 get_minimum_size() const; TextureProgress(); @@ -93,10 +102,11 @@ private: Point2 rad_center_off; bool nine_patch_stretch; int stretch_margin[4]; + Color tint_under, tint_progress, tint_over; Point2 unit_val_to_uv(float val); Point2 get_relative_center(); - void draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio); + void draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio, const Color &p_modulate); }; VARIANT_ENUM_CAST(TextureProgress::FillMode); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index cdbdc9b0d7..1d27612766 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2061,322 +2061,318 @@ void Tree::popup_select(int p_option) { item_edited(popup_edited_item_col, popup_edited_item); } -void Tree::_gui_input(Ref<InputEvent> p_event) { - - Ref<InputEventKey> k = p_event; +void Tree::_go_left() { + if (selected_col == 0) { + if (selected_item->get_children() != NULL && !selected_item->is_collapsed()) { + selected_item->set_collapsed(true); + } else { + if (columns.size() == 1) { // goto parent with one column + TreeItem *parent = selected_item->get_parent(); + if (selected_item != get_root() && parent && parent->is_selectable(selected_col) && !(hide_root && parent == get_root())) { + select_single_item(parent, get_root(), selected_col); + } + } else if (selected_item->get_prev_visible()) { + selected_col = columns.size() - 1; + _go_up(); // go to upper column if possible + } + } + } else { + if (select_mode == SELECT_MULTI) { + selected_col--; + emit_signal("cell_selected"); + } else { - if (k.is_valid()) { + selected_item->select(selected_col - 1); + } + } + update(); + accept_event(); + ensure_cursor_is_visible(); +} - if (!k->is_pressed()) - return; - if (k->get_command() || (k->get_shift() && k->get_unicode() == 0) || k->get_metakey()) - return; - if (!root) +void Tree::_go_right() { + if (selected_col == (columns.size() - 1)) { + if (selected_item->get_children() != NULL && selected_item->is_collapsed()) { + selected_item->set_collapsed(false); + } else if (selected_item->get_next_visible()) { + selected_item->select(0); + _go_down(); return; + } + } else { + if (select_mode == SELECT_MULTI) { + selected_col++; + emit_signal("cell_selected"); + } else { - if (hide_root && !root->get_next_visible()) + selected_item->select(selected_col + 1); + } + } + update(); + ensure_cursor_is_visible(); + accept_event(); +} + +void Tree::_go_up() { + TreeItem *prev = NULL; + if (!selected_item) { + prev = get_last_item(); + selected_col = 0; + } else { + + prev = selected_item->get_prev_visible(); + if (last_keypress != 0) { + //incr search next + int col; + prev = _search_item_text(prev, incr_search, &col, true, true); + if (!prev) { + accept_event(); + return; + } + } + } + + if (select_mode == SELECT_MULTI) { + + if (!prev) return; + selected_item = prev; + emit_signal("cell_selected"); + update(); + } else { - switch (k->get_scancode()) { -#define EXIT_BREAK \ - { \ - if (!cursor_can_exit_tree) accept_event(); \ - break; \ + int col = selected_col < 0 ? 0 : selected_col; + while (prev && !prev->cells[col].selectable) + prev = prev->get_prev_visible(); + if (!prev) + return; // do nothing.. + prev->select(col); } - case KEY_RIGHT: { - bool dobreak = true; - //TreeItem *next = NULL; - if (!selected_item) - break; - if (select_mode == SELECT_ROW) { - EXIT_BREAK; - } - if (selected_col > (columns.size() - 1)) { - EXIT_BREAK; - } - if (k->get_alt()) { - selected_item->set_collapsed(false); - TreeItem *next = selected_item->get_children(); - while (next && next != selected_item->next) { - next->set_collapsed(false); - next = next->get_next_visible(); - } - } else if (selected_col == (columns.size() - 1)) { - if (selected_item->get_children() != NULL && selected_item->is_collapsed()) { - selected_item->set_collapsed(false); - } else { - selected_col = 0; - dobreak = false; // fall through to key_down - } - } else { - if (select_mode == SELECT_MULTI) { - selected_col++; - emit_signal("cell_selected"); - } else { + ensure_cursor_is_visible(); + accept_event(); +} - selected_item->select(selected_col + 1); - } - } - update(); - ensure_cursor_is_visible(); +void Tree::_go_down() { + TreeItem *next = NULL; + if (!selected_item) { + + next = hide_root ? root->get_next_visible() : root; + selected_item = 0; + } else { + + next = selected_item->get_next_visible(); + + if (last_keypress != 0) { + //incr search next + int col; + next = _search_item_text(next, incr_search, &col, true); + if (!next) { accept_event(); - if (dobreak) { - break; - } + return; } - case KEY_DOWN: { + } + } - TreeItem *next = NULL; - if (!selected_item) { + if (select_mode == SELECT_MULTI) { - next = hide_root ? root->get_next_visible() : root; - selected_item = 0; - } else { + if (!next) { + return; + } - next = selected_item->get_next_visible(); + selected_item = next; + emit_signal("cell_selected"); + update(); + } else { - //if (diff < uint64_t(GLOBAL_DEF("gui/incr_search_max_interval_msec",2000))) { - if (last_keypress != 0) { - //incr search next - int col; - next = _search_item_text(next, incr_search, &col, true); - if (!next) { - accept_event(); - return; - } - } - } + int col = selected_col < 0 ? 0 : selected_col; - if (select_mode == SELECT_MULTI) { + while (next && !next->cells[col].selectable) + next = next->get_next_visible(); + if (!next) { + return; // do nothing.. + } + next->select(col); + } - if (!next) - EXIT_BREAK; + ensure_cursor_is_visible(); + accept_event(); +} - selected_item = next; - emit_signal("cell_selected"); - update(); - } else { +void Tree::_gui_input(Ref<InputEvent> p_event) { - int col = selected_col < 0 ? 0 : selected_col; + Ref<InputEventKey> k = p_event; - while (next && !next->cells[col].selectable) - next = next->get_next_visible(); - if (!next) - EXIT_BREAK; // do nothing.. - next->select(col); - } + if (p_event->is_action("ui_right") && p_event->is_pressed()) { - ensure_cursor_is_visible(); - accept_event(); + if (!cursor_can_exit_tree) accept_event(); - } break; - case KEY_LEFT: { - bool dobreak = true; + if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) { + return; + } + if (k.is_valid() && k->get_alt()) { + selected_item->set_collapsed(false); + TreeItem *next = selected_item->get_children(); + while (next && next != selected_item->next) { + next->set_collapsed(false); + next = next->get_next_visible(); + } + } else { + _go_right(); + } + } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { - //TreeItem *next = NULL; - if (!selected_item) - break; - if (select_mode == SELECT_ROW) { - EXIT_BREAK; - } - if (selected_col < 0) { - EXIT_BREAK; - } - if (k->get_alt()) { - selected_item->set_collapsed(true); - TreeItem *next = selected_item->get_children(); - while (next && next != selected_item->next) { - next->set_collapsed(true); - next = next->get_next_visible(); - } - } else if (selected_col == 0) { - if (selected_item->get_children() != NULL && !selected_item->is_collapsed()) { - selected_item->set_collapsed(true); - } else { - if (columns.size() == 1) { // goto parent with one column - TreeItem *parent = selected_item->get_parent(); - if (selected_item != get_root() && parent && parent->is_selectable(selected_col) && !(hide_root && parent == get_root())) { - select_single_item(parent, get_root(), selected_col); - } - } else { - selected_col = columns.size() - 1; - dobreak = false; // fall through to key_up - } - } - } else { - if (select_mode == SELECT_MULTI) { - selected_col--; - emit_signal("cell_selected"); - } else { + if (!cursor_can_exit_tree) accept_event(); - selected_item->select(selected_col - 1); - } - } - update(); - accept_event(); - ensure_cursor_is_visible(); + if (!selected_item || select_mode == SELECT_ROW || selected_col < 0) { + return; + } - if (dobreak) { - break; - } + if (k.is_valid() && k->get_alt()) { + selected_item->set_collapsed(true); + TreeItem *next = selected_item->get_children(); + while (next && next != selected_item->next) { + next->set_collapsed(true); + next = next->get_next_visible(); } - case KEY_UP: { + } else { + _go_left(); + } - TreeItem *prev = NULL; - if (!selected_item) { - prev = get_last_item(); - selected_col = 0; - } else { + } else if (p_event->is_action("ui_up") && p_event->is_pressed()) { - prev = selected_item->get_prev_visible(); - if (last_keypress != 0) { - //incr search next - int col; - prev = _search_item_text(prev, incr_search, &col, true, true); - if (!prev) { - accept_event(); - return; - } - } - } + if (!cursor_can_exit_tree) accept_event(); - if (select_mode == SELECT_MULTI) { + _go_up(); - if (!prev) - break; - selected_item = prev; - emit_signal("cell_selected"); - update(); - } else { + } else if (p_event->is_action("ui_down") && p_event->is_pressed()) { - int col = selected_col < 0 ? 0 : selected_col; - while (prev && !prev->cells[col].selectable) - prev = prev->get_prev_visible(); - if (!prev) - break; // do nothing.. - prev->select(col); - } + if (!cursor_can_exit_tree) accept_event(); - ensure_cursor_is_visible(); - accept_event(); + _go_down(); - } break; - case KEY_PAGEDOWN: { + } else if (p_event->is_action("ui_page_down") && p_event->is_pressed()) { - TreeItem *next = NULL; - if (!selected_item) - break; - next = selected_item; + if (!cursor_can_exit_tree) accept_event(); - for (int i = 0; i < 10; i++) { + TreeItem *next = NULL; + if (!selected_item) + return; + next = selected_item; - TreeItem *_n = next->get_next_visible(); - if (_n) { - next = _n; - } else { + for (int i = 0; i < 10; i++) { - break; - } - } - if (next == selected_item) - break; + TreeItem *_n = next->get_next_visible(); + if (_n) { + next = _n; + } else { - if (select_mode == SELECT_MULTI) { + return; + } + } + if (next == selected_item) + return; - selected_item = next; - emit_signal("cell_selected"); - update(); - } else { + if (select_mode == SELECT_MULTI) { - while (next && !next->cells[selected_col].selectable) - next = next->get_next_visible(); - if (!next) - EXIT_BREAK; // do nothing.. - next->select(selected_col); - } + selected_item = next; + emit_signal("cell_selected"); + update(); + } else { - ensure_cursor_is_visible(); - } break; - case KEY_PAGEUP: { + while (next && !next->cells[selected_col].selectable) + next = next->get_next_visible(); + if (!next) { + return; // do nothing.. + } + next->select(selected_col); + } - TreeItem *prev = NULL; - if (!selected_item) - break; - prev = selected_item; + ensure_cursor_is_visible(); + } else if (p_event->is_action("ui_page_up") && p_event->is_pressed()) { - for (int i = 0; i < 10; i++) { + if (!cursor_can_exit_tree) accept_event(); - TreeItem *_n = prev->get_prev_visible(); - if (_n) { - prev = _n; - } else { + TreeItem *prev = NULL; + if (!selected_item) + return; + prev = selected_item; - break; - } - } - if (prev == selected_item) - break; + for (int i = 0; i < 10; i++) { - if (select_mode == SELECT_MULTI) { + TreeItem *_n = prev->get_prev_visible(); + if (_n) { + prev = _n; + } else { - selected_item = prev; - emit_signal("cell_selected"); - update(); - } else { + return; + } + } + if (prev == selected_item) + return; - while (prev && !prev->cells[selected_col].selectable) - prev = prev->get_prev_visible(); - if (!prev) - EXIT_BREAK; // do nothing.. - prev->select(selected_col); - } + if (select_mode == SELECT_MULTI) { - ensure_cursor_is_visible(); + selected_item = prev; + emit_signal("cell_selected"); + update(); + } else { - } break; - case KEY_F2: - case KEY_ENTER: - case KEY_KP_ENTER: { - - if (selected_item) { - //bring up editor if possible - if (!edit_selected()) { - emit_signal("item_activated"); - incr_search.clear(); - } - } - accept_event(); + while (prev && !prev->cells[selected_col].selectable) + prev = prev->get_prev_visible(); + if (!prev) { + return; // do nothing.. + } + prev->select(selected_col); + } + ensure_cursor_is_visible(); + } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { - } break; - case KEY_SPACE: { - if (select_mode == SELECT_MULTI) { - if (!selected_item) - break; - if (selected_item->is_selected(selected_col)) { - selected_item->deselect(selected_col); - emit_signal("multi_selected", selected_item, selected_col, false); - } else if (selected_item->is_selectable(selected_col)) { - selected_item->select(selected_col); - emit_signal("multi_selected", selected_item, selected_col, true); - } - } - accept_event(); + if (selected_item) { + //bring up editor if possible + if (!edit_selected()) { + emit_signal("item_activated"); + incr_search.clear(); + } + } + accept_event(); + } else if (p_event->is_action("ui_select") && p_event->is_pressed()) { - } break; - default: { + if (select_mode == SELECT_MULTI) { + if (!selected_item) + return; + if (selected_item->is_selected(selected_col)) { + selected_item->deselect(selected_col); + emit_signal("multi_selected", selected_item, selected_col, false); + } else if (selected_item->is_selectable(selected_col)) { + selected_item->select(selected_col); + emit_signal("multi_selected", selected_item, selected_col, true); + } + } + accept_event(); + } + + if (k.is_valid()) { // Incremental search + + if (!k->is_pressed()) + return; + if (k->get_command() || (k->get_shift() && k->get_unicode() == 0) || k->get_metakey()) + return; + if (!root) + return; - if (k->get_unicode() > 0) { + if (hide_root && !root->get_next_visible()) + return; - _do_incr_search(String::chr(k->get_unicode())); - accept_event(); + if (k->get_unicode() > 0) { - return; - } else { - if (k->get_scancode() != KEY_SHIFT) - last_keypress = 0; - } - } break; + _do_incr_search(String::chr(k->get_unicode())); + accept_event(); + + return; + } else { + if (k->get_scancode() != KEY_SHIFT) + last_keypress = 0; } } @@ -2549,7 +2545,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { if (drag_speed == 0) { drag_touching_deaccel = false; drag_touching = false; - set_physics_process(false); + set_physics_process_internal(false); } else { drag_touching_deaccel = true; @@ -2615,7 +2611,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { break; if (drag_touching) { - set_physics_process(false); + set_physics_process_internal(false); drag_touching_deaccel = false; drag_touching = false; drag_speed = 0; @@ -2630,7 +2626,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { drag_touching = OS::get_singleton()->has_touchscreen_ui_hint(); drag_touching_deaccel = false; if (drag_touching) { - set_physics_process(true); + set_physics_process_internal(true); } if (b->get_button_index() == BUTTON_LEFT) { @@ -2833,7 +2829,7 @@ void Tree::_notification(int p_what) { drop_mode_flags = 0; scrolling = false; - set_physics_process(false); + set_physics_process_internal(false); update(); } if (p_what == NOTIFICATION_DRAG_BEGIN) { @@ -2841,10 +2837,10 @@ void Tree::_notification(int p_what) { single_select_defer = NULL; if (cache.scroll_speed > 0 && get_rect().has_point(get_viewport()->get_mouse_position() - get_global_position())) { scrolling = true; - set_physics_process(true); + set_physics_process_internal(true); } } - if (p_what == NOTIFICATION_PHYSICS_PROCESS) { + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { if (drag_touching) { @@ -2857,7 +2853,7 @@ void Tree::_notification(int p_what) { if (pos < 0) { pos = 0; turnoff = true; - set_physics_process(false); + set_physics_process_internal(false); drag_touching = false; drag_touching_deaccel = false; } @@ -2877,7 +2873,7 @@ void Tree::_notification(int p_what) { drag_speed = sgn * val; if (turnoff) { - set_physics_process(false); + set_physics_process_internal(false); drag_touching = false; drag_touching_deaccel = false; } @@ -3911,6 +3907,8 @@ Tree::Tree() { cache.click_id = -1; cache.click_item = NULL; cache.click_column = 0; + cache.hover_cell = -1; + cache.hover_index = -1; last_keypress = 0; focus_in_id = 0; diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 2a8546a743..5af66c5faa 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -507,6 +507,10 @@ private: ValueEvaluator *evaluator; int _count_selected_items(TreeItem *p_from) const; + void _go_left(); + void _go_right(); + void _go_down(); + void _go_up(); protected: static void _bind_methods(); diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp index 4eee0126d8..88e1847533 100644 --- a/scene/gui/video_player.cpp +++ b/scene/gui/video_player.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "video_player.h" +#include "scene/scene_string_names.h" #include "os/os.h" #include "servers/audio_server.h" @@ -159,11 +160,7 @@ void VideoPlayer::_notification(int p_notification) { bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); - if (stream.is_null()) - return; - if (paused) - return; - if (!playback->is_playing()) + if (stream.is_null() || paused || !playback->is_playing()) return; double audio_time = USEC_TO_SEC(OS::get_singleton()->get_ticks_usec()); @@ -174,7 +171,11 @@ void VideoPlayer::_notification(int p_notification) { if (delta == 0) return; - playback->update(delta); + playback->update(delta); // playback->is_playing() returns false in the last video frame + + if (!playback->is_playing()) { + emit_signal(SceneStringNames::get_singleton()->finished); + } } break; @@ -467,6 +468,8 @@ void VideoPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_video_texture"), &VideoPlayer::get_video_texture); + ADD_SIGNAL(MethodInfo("finished")); + ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_track", PROPERTY_HINT_RANGE, "0,128,1"), "set_audio_track", "get_audio_track"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VideoStream"), "set_stream", "get_stream"); //ADD_PROPERTY( PropertyInfo(Variant::BOOL, "stream/loop"), "set_loop", "has_loop") ; diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index 31d45d8e4c..8414210952 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -35,7 +35,7 @@ void CanvasLayer::set_layer(int p_xform) { layer = p_xform; if (viewport.is_valid()) - VisualServer::get_singleton()->viewport_set_canvas_layer(viewport, canvas->get_canvas(), layer); + VisualServer::get_singleton()->viewport_set_canvas_layer(viewport, canvas, layer); } int CanvasLayer::get_layer() const { @@ -48,7 +48,7 @@ void CanvasLayer::set_transform(const Transform2D &p_xform) { transform = p_xform; locrotscale_dirty = true; if (viewport.is_valid()) - VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas->get_canvas(), transform); + VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform); } Transform2D CanvasLayer::get_transform() const { @@ -61,7 +61,7 @@ void CanvasLayer::_update_xform() { transform.set_rotation_and_scale(rot, scale); transform.set_origin(ofs); if (viewport.is_valid()) - VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas->get_canvas(), transform); + VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform); } void CanvasLayer::_update_locrotscale() { @@ -133,11 +133,6 @@ Vector2 CanvasLayer::get_scale() const { return scale; } -Ref<World2D> CanvasLayer::get_world_2d() const { - - return canvas; -} - void CanvasLayer::_notification(int p_what) { switch (p_what) { @@ -153,14 +148,14 @@ void CanvasLayer::_notification(int p_what) { ERR_FAIL_COND(!vp); viewport = vp->get_viewport_rid(); - VisualServer::get_singleton()->viewport_attach_canvas(viewport, canvas->get_canvas()); - VisualServer::get_singleton()->viewport_set_canvas_layer(viewport, canvas->get_canvas(), layer); - VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas->get_canvas(), transform); + VisualServer::get_singleton()->viewport_attach_canvas(viewport, canvas); + VisualServer::get_singleton()->viewport_set_canvas_layer(viewport, canvas, layer); + VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform); } break; case NOTIFICATION_EXIT_TREE: { - VisualServer::get_singleton()->viewport_remove_canvas(viewport, canvas->get_canvas()); + VisualServer::get_singleton()->viewport_remove_canvas(viewport, canvas); viewport = RID(); } break; @@ -184,7 +179,7 @@ RID CanvasLayer::get_viewport() const { void CanvasLayer::set_custom_viewport(Node *p_viewport) { ERR_FAIL_NULL(p_viewport); if (is_inside_tree()) { - VisualServer::get_singleton()->viewport_remove_canvas(viewport, canvas->get_canvas()); + VisualServer::get_singleton()->viewport_remove_canvas(viewport, canvas); viewport = RID(); } @@ -205,9 +200,9 @@ void CanvasLayer::set_custom_viewport(Node *p_viewport) { viewport = vp->get_viewport_rid(); - VisualServer::get_singleton()->viewport_attach_canvas(viewport, canvas->get_canvas()); - VisualServer::get_singleton()->viewport_set_canvas_layer(viewport, canvas->get_canvas(), layer); - VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas->get_canvas(), transform); + VisualServer::get_singleton()->viewport_attach_canvas(viewport, canvas); + VisualServer::get_singleton()->viewport_set_canvas_layer(viewport, canvas, layer); + VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform); } } @@ -225,6 +220,10 @@ int CanvasLayer::get_sort_index() { return sort_index++; } +RID CanvasLayer::get_canvas() const { + + return canvas; +} void CanvasLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer", "layer"), &CanvasLayer::set_layer); @@ -248,7 +247,7 @@ void CanvasLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_custom_viewport", "viewport"), &CanvasLayer::set_custom_viewport); ClassDB::bind_method(D_METHOD("get_custom_viewport"), &CanvasLayer::get_custom_viewport); - ClassDB::bind_method(D_METHOD("get_world_2d"), &CanvasLayer::get_world_2d); + ClassDB::bind_method(D_METHOD("get_canvas"), &CanvasLayer::get_canvas); //ClassDB::bind_method(D_METHOD("get_viewport"),&CanvasLayer::get_viewport); ADD_PROPERTY(PropertyInfo(Variant::INT, "layer", PROPERTY_HINT_RANGE, "-128,128,1"), "set_layer", "get_layer"); @@ -268,8 +267,13 @@ CanvasLayer::CanvasLayer() { rot = 0; locrotscale_dirty = false; layer = 1; - canvas = Ref<World2D>(memnew(World2D)); + canvas = VS::get_singleton()->canvas_create(); custom_viewport = NULL; custom_viewport_id = 0; sort_index = 0; } + +CanvasLayer::~CanvasLayer() { + + VS::get_singleton()->free(canvas); +} diff --git a/scene/main/canvas_layer.h b/scene/main/canvas_layer.h index c3352a6dba..aae23fbb12 100644 --- a/scene/main/canvas_layer.h +++ b/scene/main/canvas_layer.h @@ -45,7 +45,7 @@ class CanvasLayer : public Node { real_t rot; int layer; Transform2D transform; - Ref<World2D> canvas; + RID canvas; ObjectID custom_viewport_id; // to check validity Viewport *custom_viewport; @@ -81,8 +81,6 @@ public: void set_scale(const Size2 &p_scale); Size2 get_scale() const; - Ref<World2D> get_world_2d() const; - Size2 get_viewport_size() const; RID get_viewport() const; @@ -93,7 +91,10 @@ public: void reset_sort_index(); int get_sort_index(); + RID get_canvas() const; + CanvasLayer(); + ~CanvasLayer(); }; #endif // CANVAS_LAYER_H diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 3d58aa65f8..ae21775c55 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -121,7 +121,7 @@ Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_h } if (!has_user_agent) { - headers.push_back("User-Agent: GodotEngine/" + String(VERSION_MKSTRING) + " (" + OS::get_singleton()->get_name() + ")"); + headers.push_back("User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")"); } if (!has_accept) { diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index 24b04c0c4a..1443d5efbf 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -52,6 +52,7 @@ bool InstancePlaceholder::_get(const StringName &p_name, Variant &r_ret) const { } return false; } + void InstancePlaceholder::_get_property_list(List<PropertyInfo> *p_list) const { for (const List<PropSet>::Element *E = stored_values.front(); E; E = E->next()) { @@ -73,13 +74,14 @@ String InstancePlaceholder::get_instance_path() const { return path; } -void InstancePlaceholder::replace_by_instance(const Ref<PackedScene> &p_custom_scene) { - ERR_FAIL_COND(!is_inside_tree()); +Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene> &p_custom_scene) { + + ERR_FAIL_COND_V(!is_inside_tree(), NULL); Node *base = get_parent(); if (!base) - return; + return NULL; Ref<PackedScene> ps; if (p_custom_scene.is_valid()) @@ -88,7 +90,7 @@ void InstancePlaceholder::replace_by_instance(const Ref<PackedScene> &p_custom_s ps = ResourceLoader::load(path, "PackedScene"); if (!ps.is_valid()) - return; + return NULL; Node *scene = ps->instance(); scene->set_name(get_name()); int pos = get_position_in_parent(); @@ -97,11 +99,20 @@ void InstancePlaceholder::replace_by_instance(const Ref<PackedScene> &p_custom_s scene->set(E->get().name, E->get().value); } - queue_delete(); + if (p_replace) { + queue_delete(); + base->remove_child(this); + } - base->remove_child(this); base->add_child(scene); base->move_child(scene, pos); + + return scene; +} + +void InstancePlaceholder::replace_by_instance(const Ref<PackedScene> &p_custom_scene) { + //Deprecated by + create_instance(true, p_custom_scene); } Dictionary InstancePlaceholder::get_stored_values(bool p_with_order) { @@ -124,6 +135,7 @@ Dictionary InstancePlaceholder::get_stored_values(bool p_with_order) { void InstancePlaceholder::_bind_methods() { ClassDB::bind_method(D_METHOD("get_stored_values", "with_order"), &InstancePlaceholder::get_stored_values, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("create_instance", "replace", "custom_scene"), &InstancePlaceholder::create_instance, DEFVAL(false), DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("replace_by_instance", "custom_scene"), &InstancePlaceholder::replace_by_instance, DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("get_instance_path"), &InstancePlaceholder::get_instance_path); } diff --git a/scene/main/instance_placeholder.h b/scene/main/instance_placeholder.h index d70f1318ea..2158257c93 100644 --- a/scene/main/instance_placeholder.h +++ b/scene/main/instance_placeholder.h @@ -60,6 +60,7 @@ public: Dictionary get_stored_values(bool p_with_order = false); + Node *create_instance(bool p_replace = false, const Ref<PackedScene> &p_custom_scene = Ref<PackedScene>()); void replace_by_instance(const Ref<PackedScene> &p_custom_scene = Ref<PackedScene>()); InstancePlaceholder(); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index cf22383e36..6199f52ec5 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -40,7 +40,6 @@ #include "viewport.h" VARIANT_ENUM_CAST(Node::PauseMode); -VARIANT_ENUM_CAST(Node::RPCMode); void Node::_notification(int p_notification) { @@ -136,7 +135,6 @@ void Node::_notification(int p_notification) { get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_ready, NULL, 0); } - //emit_signal(SceneStringNames::get_singleton()->enter_tree); } break; case NOTIFICATION_POSTINITIALIZE: { @@ -177,9 +175,13 @@ void Node::_propagate_ready() { data.children[i]->_propagate_ready(); } data.blocked--; + + notification(NOTIFICATION_POST_ENTER_TREE); + if (data.ready_first) { data.ready_first = false; notification(NOTIFICATION_READY); + emit_signal(SceneStringNames::get_singleton()->ready); } } @@ -477,23 +479,23 @@ bool Node::is_network_master() const { ERR_FAIL_COND_V(!is_inside_tree(), false); - return get_tree()->get_network_unique_id() == data.network_master; + return get_multiplayer()->get_network_unique_id() == data.network_master; } /***** RPC CONFIG ********/ -void Node::rpc_config(const StringName &p_method, RPCMode p_mode) { +void Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode) { - if (p_mode == RPC_MODE_DISABLED) { + if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) { data.rpc_methods.erase(p_method); } else { data.rpc_methods[p_method] = p_mode; }; } -void Node::rset_config(const StringName &p_property, RPCMode p_mode) { +void Node::rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode) { - if (p_mode == RPC_MODE_DISABLED) { + if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) { data.rpc_properties.erase(p_property); } else { data.rpc_properties[p_property] = p_mode; @@ -667,200 +669,16 @@ Variant Node::_rpc_unreliable_id_bind(const Variant **p_args, int p_argcount, Va } void Node::rpcp(int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount) { - ERR_FAIL_COND(!is_inside_tree()); - - bool skip_rpc = false; - bool call_local_native = false; - bool call_local_script = false; - - if (p_peer_id == 0 || p_peer_id == get_tree()->get_network_unique_id() || (p_peer_id < 0 && p_peer_id != -get_tree()->get_network_unique_id())) { - //check that send mode can use local call - - Map<StringName, RPCMode>::Element *E = data.rpc_methods.find(p_method); - if (E) { - - switch (E->get()) { - - case RPC_MODE_DISABLED: { - //do nothing - } break; - case RPC_MODE_REMOTE: { - //do nothing also, no need to call local - } break; - case RPC_MODE_SYNC: { - //call it, sync always results in call - call_local_native = true; - } break; - case RPC_MODE_MASTER: { - call_local_native = is_network_master(); - if (call_local_native) { - skip_rpc = true; //no other master so.. - } - } break; - case RPC_MODE_SLAVE: { - call_local_native = !is_network_master(); - } break; - } - } - - if (call_local_native) { - // done below - } else if (get_script_instance()) { - //attempt with script - ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rpc_mode(p_method); - - switch (rpc_mode) { - - case ScriptInstance::RPC_MODE_DISABLED: { - //do nothing - } break; - case ScriptInstance::RPC_MODE_REMOTE: { - //do nothing also, no need to call local - } break; - case ScriptInstance::RPC_MODE_SYNC: { - //call it, sync always results in call - call_local_script = true; - } break; - case ScriptInstance::RPC_MODE_MASTER: { - call_local_script = is_network_master(); - if (call_local_script) { - skip_rpc = true; //no other master so.. - } - } break; - case ScriptInstance::RPC_MODE_SLAVE: { - call_local_script = !is_network_master(); - } break; - } - } - } - - if (!skip_rpc) { - get_tree()->_rpc(this, p_peer_id, p_unreliable, false, p_method, p_arg, p_argcount); - } - - if (call_local_native) { - Variant::CallError ce; - call(p_method, p_arg, p_argcount, ce); - if (ce.error != Variant::CallError::CALL_OK) { - String error = Variant::get_call_error_text(this, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in local call: - " + error; - ERR_PRINTS(error); - return; - } - } - - if (call_local_script) { - Variant::CallError ce; - ce.error = Variant::CallError::CALL_OK; - get_script_instance()->call(p_method, p_arg, p_argcount, ce); - if (ce.error != Variant::CallError::CALL_OK) { - String error = Variant::get_call_error_text(this, p_method, p_arg, p_argcount, ce); - error = "rpc() aborted in script local call: - " + error; - ERR_PRINTS(error); - return; - } - } + get_multiplayer()->rpcp(this, p_peer_id, p_unreliable, p_method, p_arg, p_argcount); } -/******** RSET *********/ - void Node::rsetp(int p_peer_id, bool p_unreliable, const StringName &p_property, const Variant &p_value) { - ERR_FAIL_COND(!is_inside_tree()); - - bool skip_rset = false; - - if (p_peer_id == 0 || p_peer_id == get_tree()->get_network_unique_id() || (p_peer_id < 0 && p_peer_id != -get_tree()->get_network_unique_id())) { - //check that send mode can use local call - - bool set_local = false; - - Map<StringName, RPCMode>::Element *E = data.rpc_properties.find(p_property); - if (E) { - - switch (E->get()) { - - case RPC_MODE_DISABLED: { - //do nothing - } break; - case RPC_MODE_REMOTE: { - //do nothing also, no need to call local - } break; - case RPC_MODE_SYNC: { - //call it, sync always results in call - set_local = true; - } break; - case RPC_MODE_MASTER: { - set_local = is_network_master(); - if (set_local) { - skip_rset = true; - } - - } break; - case RPC_MODE_SLAVE: { - set_local = !is_network_master(); - } break; - } - } - - if (set_local) { - bool valid; - set(p_property, p_value, &valid); - - if (!valid) { - String error = "rset() aborted in local set, property not found: - " + String(p_property); - ERR_PRINTS(error); - return; - } - } else if (get_script_instance()) { - //attempt with script - ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rset_mode(p_property); - - switch (rpc_mode) { - - case ScriptInstance::RPC_MODE_DISABLED: { - //do nothing - } break; - case ScriptInstance::RPC_MODE_REMOTE: { - //do nothing also, no need to call local - } break; - case ScriptInstance::RPC_MODE_SYNC: { - //call it, sync always results in call - set_local = true; - } break; - case ScriptInstance::RPC_MODE_MASTER: { - set_local = is_network_master(); - if (set_local) { - skip_rset = true; - } - } break; - case ScriptInstance::RPC_MODE_SLAVE: { - set_local = !is_network_master(); - } break; - } - - if (set_local) { - - bool valid = get_script_instance()->set(p_property, p_value); - - if (!valid) { - String error = "rset() aborted in local script set, property not found: - " + String(p_property); - ERR_PRINTS(error); - return; - } - } - } - } - - if (skip_rset) - return; - - const Variant *vptr = &p_value; - - get_tree()->_rpc(this, p_peer_id, p_unreliable, true, p_property, &vptr, 1); + get_multiplayer()->rsetp(this, p_peer_id, p_unreliable, p_property, p_value); } +/******** RSET *********/ void Node::rset(const StringName &p_property, const Variant &p_value) { rsetp(0, false, p_property, p_value); @@ -882,112 +700,29 @@ void Node::rset_unreliable_id(int p_peer_id, const StringName &p_property, const } //////////// end of rpc - -bool Node::can_call_rpc(const StringName &p_method, int p_from) const { - - const Map<StringName, RPCMode>::Element *E = data.rpc_methods.find(p_method); - if (E) { - - switch (E->get()) { - - case RPC_MODE_DISABLED: { - return false; - } break; - case RPC_MODE_REMOTE: { - return true; - } break; - case RPC_MODE_SYNC: { - return true; - } break; - case RPC_MODE_MASTER: { - return is_network_master(); - } break; - case RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } - - if (get_script_instance()) { - //attempt with script - ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rpc_mode(p_method); - - switch (rpc_mode) { - - case ScriptInstance::RPC_MODE_DISABLED: { - return false; - } break; - case ScriptInstance::RPC_MODE_REMOTE: { - return true; - } break; - case ScriptInstance::RPC_MODE_SYNC: { - return true; - } break; - case ScriptInstance::RPC_MODE_MASTER: { - return is_network_master(); - } break; - case ScriptInstance::RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } - - ERR_PRINTS("RPC from " + itos(p_from) + " on unauthorized method attempted: " + String(p_method) + " on base: " + String(Variant(this))); - return false; +Ref<MultiplayerAPI> Node::get_multiplayer() const { + if (multiplayer.is_valid()) + return multiplayer; + if (!is_inside_tree()) + return Ref<MultiplayerAPI>(); + return get_tree()->get_multiplayer(); } -bool Node::can_call_rset(const StringName &p_property, int p_from) const { - - const Map<StringName, RPCMode>::Element *E = data.rpc_properties.find(p_property); - if (E) { - - switch (E->get()) { - - case RPC_MODE_DISABLED: { - return false; - } break; - case RPC_MODE_REMOTE: { - return true; - } break; - case RPC_MODE_SYNC: { - return true; - } break; - case RPC_MODE_MASTER: { - return is_network_master(); - } break; - case RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } - - if (get_script_instance()) { - //attempt with script - ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rset_mode(p_property); +Ref<MultiplayerAPI> Node::get_custom_multiplayer() const { + return multiplayer; +} - switch (rpc_mode) { +void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { - case ScriptInstance::RPC_MODE_DISABLED: { - return false; - } break; - case ScriptInstance::RPC_MODE_REMOTE: { - return true; - } break; - case ScriptInstance::RPC_MODE_SYNC: { - return true; - } break; - case ScriptInstance::RPC_MODE_MASTER: { - return is_network_master(); - } break; - case ScriptInstance::RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } + multiplayer = p_multiplayer; +} - ERR_PRINTS("RSET from " + itos(p_from) + " on unauthorized property attempted: " + String(p_property) + " on base: " + String(Variant(this))); +const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rpc_mode(const StringName &p_method) { + return data.rpc_methods.find(p_method); +} - return false; +const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rset_mode(const StringName &p_property) { + return data.rpc_properties.find(p_property); } bool Node::can_process() const { @@ -1139,9 +874,23 @@ void Node::_set_name_nocheck(const StringName &p_name) { data.name = p_name; } +String Node::invalid_character = ". : @ / \""; + +bool Node::_validate_node_name(String &p_name) { + String name = p_name; + Vector<String> chars = Node::invalid_character.split(" "); + for (int i = 0; i < chars.size(); i++) { + name = name.replace(chars[i], ""); + } + bool is_valid = name == p_name; + p_name = name; + return is_valid; +} + void Node::set_name(const String &p_name) { - String name = p_name.replace(":", "").replace("/", "").replace("@", ""); + String name = p_name; + _validate_node_name(name); ERR_FAIL_COND(name == ""); data.name = name; @@ -1346,6 +1095,10 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) { } void Node::add_child_below_node(Node *p_node, Node *p_child, bool p_legible_unique_name) { + + ERR_FAIL_NULL(p_node); + ERR_FAIL_NULL(p_child); + add_child(p_child, p_legible_unique_name); if (is_a_parent_of(p_node)) { @@ -1868,11 +1621,18 @@ bool Node::has_persistent_groups() const { return false; } -void Node::_print_tree(const Node *p_node) { +void Node::_print_tree_pretty(const String prefix, const bool last) { - print_line(String(p_node->get_path_to(this))); - for (int i = 0; i < data.children.size(); i++) - data.children[i]->_print_tree(p_node); + String new_prefix = last ? String::utf8(" ┖╴") : String::utf8(" ┠╴"); + print_line(prefix + new_prefix + String(get_name())); + for (int i = 0; i < data.children.size(); i++) { + new_prefix = last ? String::utf8(" ") : String::utf8(" ┃ "); + data.children[i]->_print_tree_pretty(prefix + new_prefix, i == data.children.size() - 1); + } +} + +void Node::print_tree_pretty() { + _print_tree_pretty("", true); } void Node::print_tree() { @@ -1880,6 +1640,12 @@ void Node::print_tree() { _print_tree(this); } +void Node::_print_tree(const Node *p_node) { + print_line(String(p_node->get_path_to(this))); + for (int i = 0; i < data.children.size(); i++) + data.children[i]->_print_tree(p_node); +} + void Node::_propagate_reverse_notification(int p_notification) { data.blocked++; @@ -2163,19 +1929,14 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const if (name == script_property_name) continue; - Variant value = N->get()->get(name); - // Duplicate dictionaries and arrays, mainly needed for __meta__ - if (value.get_type() == Variant::DICTIONARY) { - value = Dictionary(value).duplicate(); - } else if (value.get_type() == Variant::ARRAY) { - value = Array(value).duplicate(); - } + Variant value = N->get()->get(name).duplicate(true); if (E->get().usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE) { Resource *res = Object::cast_to<Resource>(value); - if (res) // Duplicate only if it's a resource + if (res) { // Duplicate only if it's a resource current_node->set(name, res->duplicate()); + } } else { @@ -2313,13 +2074,7 @@ void Node::_duplicate_and_reown(Node *p_new_parent, const Map<Node *, Node *> &p continue; String name = E->get().name; - Variant value = get(name); - // Duplicate dictionaries and arrays, mainly needed for __meta__ - if (value.get_type() == Variant::DICTIONARY) { - value = Dictionary(value).duplicate(); - } else if (value.get_type() == Variant::ARRAY) { - value = Array(value).duplicate(); - } + Variant value = get(name).duplicate(true); node->set(name, value); } @@ -2502,7 +2257,10 @@ void Node::replace_by(Node *p_node, bool p_keep_data) { Node *child = get_child(0); remove_child(child); - p_node->add_child(child); + if (!child->is_owned_by_parent()) { + // add the custom children to the p_node + p_node->add_child(child); + } } p_node->set_owner(owner); @@ -2723,18 +2481,21 @@ Array Node::_get_children() const { return arr; } -#ifdef TOOLS_ENABLED void Node::set_import_path(const NodePath &p_import_path) { +#ifdef TOOLS_ENABLED data.import_path = p_import_path; +#endif } NodePath Node::get_import_path() const { +#ifdef TOOLS_ENABLED return data.import_path; -} - +#else + return NodePath(); #endif +} static void _add_nodes_to_options(const Node *p_base, const Node *p_node, List<String> *r_options) { @@ -2837,6 +2598,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_and_skip"), &Node::remove_and_skip); ClassDB::bind_method(D_METHOD("get_index"), &Node::get_index); ClassDB::bind_method(D_METHOD("print_tree"), &Node::print_tree); + ClassDB::bind_method(D_METHOD("print_tree_pretty"), &Node::print_tree_pretty); ClassDB::bind_method(D_METHOD("set_filename", "filename"), &Node::set_filename); ClassDB::bind_method(D_METHOD("get_filename"), &Node::get_filename); ClassDB::bind_method(D_METHOD("propagate_notification", "what"), &Node::propagate_notification); @@ -2886,16 +2648,16 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("is_network_master"), &Node::is_network_master); + ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer); + ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer); + ClassDB::bind_method(D_METHOD("set_custom_multiplayer", "api"), &Node::set_custom_multiplayer); ClassDB::bind_method(D_METHOD("rpc_config", "method", "mode"), &Node::rpc_config); ClassDB::bind_method(D_METHOD("rset_config", "property", "mode"), &Node::rset_config); -#ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("_set_import_path", "import_path"), &Node::set_import_path); ClassDB::bind_method(D_METHOD("_get_import_path"), &Node::get_import_path); ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "_import_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_import_path", "_get_import_path"); -#endif - { MethodInfo mi; @@ -2937,12 +2699,6 @@ void Node::_bind_methods() { BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); - BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); - BIND_ENUM_CONSTANT(RPC_MODE_SYNC); - BIND_ENUM_CONSTANT(RPC_MODE_MASTER); - BIND_ENUM_CONSTANT(RPC_MODE_SLAVE); - BIND_ENUM_CONSTANT(PAUSE_MODE_INHERIT); BIND_ENUM_CONSTANT(PAUSE_MODE_STOP); BIND_ENUM_CONSTANT(PAUSE_MODE_PROCESS); @@ -2952,6 +2708,7 @@ void Node::_bind_methods() { BIND_ENUM_CONSTANT(DUPLICATE_SCRIPTS); BIND_ENUM_CONSTANT(DUPLICATE_USE_INSTANCING); + ADD_SIGNAL(MethodInfo("ready")); ADD_SIGNAL(MethodInfo("renamed")); ADD_SIGNAL(MethodInfo("tree_entered")); ADD_SIGNAL(MethodInfo("tree_exiting")); @@ -2967,6 +2724,8 @@ void Node::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "", 0), "set_name", "get_name"); ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "filename", PROPERTY_HINT_NONE, "", 0), "set_filename", "get_filename"); ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_owner", "get_owner"); + ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "", "get_multiplayer"); + ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "custom_multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "set_custom_multiplayer", "get_custom_multiplayer"); BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::REAL, "delta"))); BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::REAL, "delta"))); diff --git a/scene/main/node.h b/scene/main/node.h index 341869f7ba..341349de79 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -65,15 +65,6 @@ public: #endif }; - enum RPCMode { - - RPC_MODE_DISABLED, //no rpc for this method, calls to this will be blocked (default) - RPC_MODE_REMOTE, // using rpc() on it will call method / set property in all other peers - RPC_MODE_SYNC, // using rpc() on it will call method / set property in all other peers and locally - RPC_MODE_MASTER, // usinc rpc() on it will call method on wherever the master is, be it local or remote - RPC_MODE_SLAVE, // usinc rpc() on it will call method for all slaves, be it local or remote - }; - struct Comparator { bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); } @@ -120,8 +111,8 @@ private: Node *pause_owner; int network_master; - Map<StringName, RPCMode> rpc_methods; - Map<StringName, RPCMode> rpc_properties; + Map<StringName, MultiplayerAPI::RPCMode> rpc_methods; + Map<StringName, MultiplayerAPI::RPCMode> rpc_properties; // variables used to properly sort the node when processing, ignored otherwise //should move all the stuff below to bits @@ -151,6 +142,9 @@ private: NAME_CASING_SNAKE_CASE }; + Ref<MultiplayerAPI> multiplayer; + + void _print_tree_pretty(const String prefix, const bool last); void _print_tree(const Node *p_node); Node *_get_node(const NodePath &p_path) const; @@ -187,6 +181,12 @@ private: void _set_tree(SceneTree *p_tree); +#ifdef TOOLS_ENABLED + friend class SceneTreeEditor; +#endif + static String invalid_character; + static bool _validate_node_name(String &p_name); + protected: void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -228,6 +228,7 @@ public: NOTIFICATION_TRANSLATION_CHANGED = 24, NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, + NOTIFICATION_POST_ENTER_TREE = 27, }; @@ -287,6 +288,7 @@ public: int get_index() const; void print_tree(); + void print_tree_pretty(); void set_filename(const String &p_filename); String get_filename() const; @@ -370,10 +372,8 @@ public: void force_parent_owned() { data.parent_owned = true; } //hack to avoid duplicate nodes -#ifdef TOOLS_ENABLED void set_import_path(const NodePath &p_import_path); //path used when imported, used by scene editors to keep tracking NodePath get_import_path() const; -#endif bool is_owned_by_parent() const; @@ -395,25 +395,27 @@ public: int get_network_master() const; bool is_network_master() const; - void rpc_config(const StringName &p_method, RPCMode p_mode); // config a local method for RPC - void rset_config(const StringName &p_property, RPCMode p_mode); // config a local property for RPC + void rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode); // config a local method for RPC + void rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode); // config a local property for RPC void rpc(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode void rpc_unreliable(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode void rpc_id(int p_peer_id, const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode void rpc_unreliable_id(int p_peer_id, const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode - void rpcp(int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); - void rset(const StringName &p_property, const Variant &p_value); //remote set call, honors RPCMode void rset_unreliable(const StringName &p_property, const Variant &p_value); //remote set call, honors RPCMode void rset_id(int p_peer_id, const StringName &p_property, const Variant &p_value); //remote set call, honors RPCMode void rset_unreliable_id(int p_peer_id, const StringName &p_property, const Variant &p_value); //remote set call, honors RPCMode + void rpcp(int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount); void rsetp(int p_peer_id, bool p_unreliable, const StringName &p_property, const Variant &p_value); - bool can_call_rpc(const StringName &p_method, int p_from) const; - bool can_call_rset(const StringName &p_property, int p_from) const; + Ref<MultiplayerAPI> get_multiplayer() const; + Ref<MultiplayerAPI> get_custom_multiplayer() const; + void set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer); + const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rpc_mode(const StringName &p_method); + const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rset_mode(const StringName &p_property); Node(); ~Node(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 136fd4cfd1..8d6e57b335 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -33,6 +33,7 @@ #include "editor/editor_node.h" #include "io/marshalls.h" #include "io/resource_loader.h" +#include "main/input_default.h" #include "message_queue.h" #include "node.h" #include "os/keyboard.h" @@ -484,7 +485,9 @@ bool SceneTree::idle(float p_time) { idle_process_time = p_time; - _network_poll(); + if (multiplayer_poll) { + multiplayer->poll(); + } emit_signal("idle_frame"); @@ -498,14 +501,14 @@ bool SceneTree::idle(float p_time) { Size2 win_size = Size2(OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height); if (win_size != last_screen_size) { + last_screen_size = win_size; + _update_root_rect(); + if (use_font_oversampling) { DynamicFontAtSize::font_oversampling = OS::get_singleton()->get_window_size().width / root->get_visible_rect().size.width; DynamicFont::update_oversampling(); } - last_screen_size = win_size; - _update_root_rect(); - emit_signal("screen_resized"); } @@ -615,9 +618,18 @@ void SceneTree::_notification(int p_notification) { } } break; case NOTIFICATION_OS_MEMORY_WARNING: + case NOTIFICATION_WM_MOUSE_ENTER: + case NOTIFICATION_WM_MOUSE_EXIT: case NOTIFICATION_WM_FOCUS_IN: case NOTIFICATION_WM_FOCUS_OUT: { + if (p_notification == NOTIFICATION_WM_FOCUS_IN) { + InputDefault *id = Object::cast_to<InputDefault>(Input::get_singleton()); + if (id) { + id->ensure_touch_mouse_raised(); + } + } + get_root()->propagate_notification(p_notification); } break; case NOTIFICATION_TRANSLATION_CHANGED: { @@ -1195,16 +1207,20 @@ void SceneTree::set_screen_stretch(StretchMode p_mode, StretchAspect p_aspect, c _update_root_rect(); } -#ifdef TOOLS_ENABLED void SceneTree::set_edited_scene_root(Node *p_node) { +#ifdef TOOLS_ENABLED edited_scene_root = p_node; +#endif } Node *SceneTree::get_edited_scene_root() const { +#ifdef TOOLS_ENABLED return edited_scene_root; -} +#else + return NULL; #endif +} void SceneTree::set_current_scene(Node *p_scene) { @@ -1631,16 +1647,11 @@ Ref<SceneTreeTimer> SceneTree::create_timer(float p_delay_sec, bool p_process_pa void SceneTree::_network_peer_connected(int p_id) { - connected_peers.insert(p_id); - path_get_cache.insert(p_id, PathGetCache()); - emit_signal("network_peer_connected", p_id); } void SceneTree::_network_peer_disconnected(int p_id) { - connected_peers.erase(p_id); - path_get_cache.erase(p_id); //I no longer need your cache, sorry emit_signal("network_peer_disconnected", p_id); } @@ -1659,471 +1670,78 @@ void SceneTree::_server_disconnected() { emit_signal("server_disconnected"); } -void SceneTree::set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_network_peer) { - if (network_peer.is_valid()) { - network_peer->disconnect("peer_connected", this, "_network_peer_connected"); - network_peer->disconnect("peer_disconnected", this, "_network_peer_disconnected"); - network_peer->disconnect("connection_succeeded", this, "_connected_to_server"); - network_peer->disconnect("connection_failed", this, "_connection_failed"); - network_peer->disconnect("server_disconnected", this, "_server_disconnected"); - connected_peers.clear(); - path_get_cache.clear(); - path_send_cache.clear(); - last_send_cache_id = 1; - } +Ref<MultiplayerAPI> SceneTree::get_multiplayer() const { + return multiplayer; +} + +void SceneTree::set_multiplayer_poll_enabled(bool p_enabled) { + multiplayer_poll = p_enabled; +} - ERR_EXPLAIN("Supplied NetworkedNetworkPeer must be connecting or connected."); - ERR_FAIL_COND(p_network_peer.is_valid() && p_network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED); +bool SceneTree::is_multiplayer_poll_enabled() const { + return multiplayer_poll; +} - network_peer = p_network_peer; +void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { + ERR_FAIL_COND(!p_multiplayer.is_valid()); - if (network_peer.is_valid()) { - network_peer->connect("peer_connected", this, "_network_peer_connected"); - network_peer->connect("peer_disconnected", this, "_network_peer_disconnected"); - network_peer->connect("connection_succeeded", this, "_connected_to_server"); - network_peer->connect("connection_failed", this, "_connection_failed"); - network_peer->connect("server_disconnected", this, "_server_disconnected"); + if (multiplayer.is_valid()) { + multiplayer->disconnect("network_peer_connected", this, "_network_peer_connected"); + multiplayer->disconnect("network_peer_disconnected", this, "_network_peer_disconnected"); + multiplayer->disconnect("connected_to_server", this, "_connected_to_server"); + multiplayer->disconnect("connection_failed", this, "_connection_failed"); + multiplayer->disconnect("server_disconnected", this, "_server_disconnected"); } + + multiplayer = p_multiplayer; + multiplayer->set_root_node(root); + + multiplayer->connect("network_peer_connected", this, "_network_peer_connected"); + multiplayer->connect("network_peer_disconnected", this, "_network_peer_disconnected"); + multiplayer->connect("connected_to_server", this, "_connected_to_server"); + multiplayer->connect("connection_failed", this, "_connection_failed"); + multiplayer->connect("server_disconnected", this, "_server_disconnected"); +} + +void SceneTree::set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_network_peer) { + + multiplayer->set_network_peer(p_network_peer); } Ref<NetworkedMultiplayerPeer> SceneTree::get_network_peer() const { - return network_peer; + return multiplayer->get_network_peer(); } bool SceneTree::is_network_server() const { - ERR_FAIL_COND_V(!network_peer.is_valid(), false); - return network_peer->is_server(); + return multiplayer->is_network_server(); } bool SceneTree::has_network_peer() const { - return network_peer.is_valid(); + return multiplayer->has_network_peer(); } int SceneTree::get_network_unique_id() const { - ERR_FAIL_COND_V(!network_peer.is_valid(), 0); - return network_peer->get_unique_id(); + return multiplayer->get_network_unique_id(); } Vector<int> SceneTree::get_network_connected_peers() const { - ERR_FAIL_COND_V(!network_peer.is_valid(), Vector<int>()); - - Vector<int> ret; - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - ret.push_back(E->get()); - } - return ret; + return multiplayer->get_network_connected_peers(); } int SceneTree::get_rpc_sender_id() const { - return rpc_sender_id; + return multiplayer->get_rpc_sender_id(); } void SceneTree::set_refuse_new_network_connections(bool p_refuse) { - ERR_FAIL_COND(!network_peer.is_valid()); - network_peer->set_refuse_new_connections(p_refuse); + multiplayer->set_refuse_new_network_connections(p_refuse); } bool SceneTree::is_refusing_new_network_connections() const { - - ERR_FAIL_COND_V(!network_peer.is_valid(), false); - - return network_peer->is_refusing_new_connections(); -} - -void SceneTree::_rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount) { - - if (network_peer.is_null()) { - ERR_EXPLAIN("Attempt to remote call/set when networking is not active in SceneTree."); - ERR_FAIL(); - } - - if (network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_CONNECTING) { - ERR_EXPLAIN("Attempt to remote call/set when networking is not connected yet in SceneTree."); - ERR_FAIL(); - } - - if (network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED) { - ERR_EXPLAIN("Attempt to remote call/set when networking is disconnected."); - ERR_FAIL(); - } - - if (p_argcount > 255) { - ERR_EXPLAIN("Too many arguments >255."); - ERR_FAIL(); - } - - if (p_to != 0 && !connected_peers.has(ABS(p_to))) { - if (p_to == get_network_unique_id()) { - ERR_EXPLAIN("Attempt to remote call/set yourself! unique ID: " + itos(get_network_unique_id())); - } else { - ERR_EXPLAIN("Attempt to remote call unexisting ID: " + itos(p_to)); - } - - ERR_FAIL(); - } - - NodePath from_path = p_from->get_path(); - ERR_FAIL_COND(from_path.is_empty()); - - //see if the path is cached - PathSentCache *psc = path_send_cache.getptr(from_path); - if (!psc) { - //path is not cached, create - path_send_cache[from_path] = PathSentCache(); - psc = path_send_cache.getptr(from_path); - psc->id = last_send_cache_id++; - } - - //create base packet, lots of hardcode because it must be tight - - int ofs = 0; - -#define MAKE_ROOM(m_amount) \ - if (packet_cache.size() < m_amount) packet_cache.resize(m_amount); - - //encode type - MAKE_ROOM(1); - packet_cache[0] = p_set ? NETWORK_COMMAND_REMOTE_SET : NETWORK_COMMAND_REMOTE_CALL; - ofs += 1; - - //encode ID - MAKE_ROOM(ofs + 4); - encode_uint32(psc->id, &packet_cache[ofs]); - ofs += 4; - - //encode function name - CharString name = String(p_name).utf8(); - int len = encode_cstring(name.get_data(), NULL); - MAKE_ROOM(ofs + len); - encode_cstring(name.get_data(), &packet_cache[ofs]); - ofs += len; - - if (p_set) { - //set argument - Error err = encode_variant(*p_arg[0], NULL, len); - ERR_FAIL_COND(err != OK); - MAKE_ROOM(ofs + len); - encode_variant(*p_arg[0], &packet_cache[ofs], len); - ofs += len; - - } else { - //call arguments - MAKE_ROOM(ofs + 1); - packet_cache[ofs] = p_argcount; - ofs += 1; - for (int i = 0; i < p_argcount; i++) { - Error err = encode_variant(*p_arg[i], NULL, len); - ERR_FAIL_COND(err != OK); - MAKE_ROOM(ofs + len); - encode_variant(*p_arg[i], &packet_cache[ofs], len); - ofs += len; - } - } - - //see if all peers have cached path (is so, call can be fast) - bool has_all_peers = true; - - List<int> peers_to_add; //if one is missing, take note to add it - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - - if (p_to < 0 && E->get() == -p_to) - continue; //continue, excluded - - if (p_to > 0 && E->get() != p_to) - continue; //continue, not for this peer - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - - if (!F || F->get() == false) { - //path was not cached, or was cached but is unconfirmed - if (!F) { - //not cached at all, take note - peers_to_add.push_back(E->get()); - } - - has_all_peers = false; - } - } - - //those that need to be added, send a message for this - - for (List<int>::Element *E = peers_to_add.front(); E; E = E->next()) { - - //encode function name - CharString pname = String(from_path).utf8(); - int len = encode_cstring(pname.get_data(), NULL); - - Vector<uint8_t> packet; - - packet.resize(1 + 4 + len); - packet[0] = NETWORK_COMMAND_SIMPLIFY_PATH; - encode_uint32(psc->id, &packet[1]); - encode_cstring(pname.get_data(), &packet[5]); - - network_peer->set_target_peer(E->get()); //to all of you - network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->put_packet(packet.ptr(), packet.size()); - - psc->confirmed_peers.insert(E->get(), false); //insert into confirmed, but as false since it was not confirmed - } - - //take chance and set transfer mode, since all send methods will use it - network_peer->set_transfer_mode(p_unreliable ? NetworkedMultiplayerPeer::TRANSFER_MODE_UNRELIABLE : NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); - - if (has_all_peers) { - - //they all have verified paths, so send fast - network_peer->set_target_peer(p_to); //to all of you - network_peer->put_packet(packet_cache.ptr(), ofs); //a message with love - } else { - //not all verified path, so send one by one - - //apend path at the end, since we will need it for some packets - CharString pname = String(from_path).utf8(); - int path_len = encode_cstring(pname.get_data(), NULL); - MAKE_ROOM(ofs + path_len); - encode_cstring(pname.get_data(), &packet_cache[ofs]); - - for (Set<int>::Element *E = connected_peers.front(); E; E = E->next()) { - - if (p_to < 0 && E->get() == -p_to) - continue; //continue, excluded - - if (p_to > 0 && E->get() != p_to) - continue; //continue, not for this peer - - Map<int, bool>::Element *F = psc->confirmed_peers.find(E->get()); - ERR_CONTINUE(!F); //should never happen - - network_peer->set_target_peer(E->get()); //to this one specifically - - if (F->get() == true) { - //this one confirmed path, so use id - encode_uint32(psc->id, &packet_cache[1]); - network_peer->put_packet(packet_cache.ptr(), ofs); - } else { - //this one did not confirm path yet, so use entire path (sorry!) - encode_uint32(0x80000000 | ofs, &packet_cache[1]); //offset to path and flag - network_peer->put_packet(packet_cache.ptr(), ofs + path_len); - } - } - } -} - -void SceneTree::_network_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len) { - - ERR_FAIL_COND(p_packet_len < 5); - - uint8_t packet_type = p_packet[0]; - - switch (packet_type) { - - case NETWORK_COMMAND_REMOTE_CALL: - case NETWORK_COMMAND_REMOTE_SET: { - - ERR_FAIL_COND(p_packet_len < 5); - uint32_t target = decode_uint32(&p_packet[1]); - - Node *node = NULL; - - if (target & 0x80000000) { - //use full path (not cached yet) - - int ofs = target & 0x7FFFFFFF; - ERR_FAIL_COND(ofs >= p_packet_len); - - String paths; - paths.parse_utf8((const char *)&p_packet[ofs], p_packet_len - ofs); - - NodePath np = paths; - - node = get_root()->get_node(np); - if (node == NULL) { - ERR_EXPLAIN("Failed to get path from RPC: " + String(np)); - ERR_FAIL_COND(node == NULL); - } - } else { - //use cached path - int id = target; - - Map<int, PathGetCache>::Element *E = path_get_cache.find(p_from); - ERR_FAIL_COND(!E); - - Map<int, PathGetCache::NodeInfo>::Element *F = E->get().nodes.find(id); - ERR_FAIL_COND(!F); - - PathGetCache::NodeInfo *ni = &F->get(); - //do proper caching later - - node = get_root()->get_node(ni->path); - if (node == NULL) { - ERR_EXPLAIN("Failed to get cached path from RPC: " + String(ni->path)); - ERR_FAIL_COND(node == NULL); - } - } - - ERR_FAIL_COND(p_packet_len < 6); - - //detect cstring end - int len_end = 5; - for (; len_end < p_packet_len; len_end++) { - if (p_packet[len_end] == 0) { - break; - } - } - - ERR_FAIL_COND(len_end >= p_packet_len); - - StringName name = String::utf8((const char *)&p_packet[5]); - - if (packet_type == NETWORK_COMMAND_REMOTE_CALL) { - - if (!node->can_call_rpc(name, p_from)) - return; - - int ofs = len_end + 1; - - ERR_FAIL_COND(ofs >= p_packet_len); - - int argc = p_packet[ofs]; - Vector<Variant> args; - Vector<const Variant *> argp; - args.resize(argc); - argp.resize(argc); - - ofs++; - - for (int i = 0; i < argc; i++) { - - ERR_FAIL_COND(ofs >= p_packet_len); - int vlen; - Error err = decode_variant(args[i], &p_packet[ofs], p_packet_len - ofs, &vlen); - ERR_FAIL_COND(err != OK); - //args[i]=p_packet[3+i]; - argp[i] = &args[i]; - ofs += vlen; - } - - Variant::CallError ce; - - node->call(name, (const Variant **)argp.ptr(), argc, ce); - if (ce.error != Variant::CallError::CALL_OK) { - String error = Variant::get_call_error_text(node, name, (const Variant **)argp.ptr(), argc, ce); - error = "RPC - " + error; - ERR_PRINTS(error); - } - - } else { - - if (!node->can_call_rset(name, p_from)) - return; - - int ofs = len_end + 1; - - ERR_FAIL_COND(ofs >= p_packet_len); - - Variant value; - decode_variant(value, &p_packet[ofs], p_packet_len - ofs); - - bool valid; - - node->set(name, value, &valid); - if (!valid) { - String error = "Error setting remote property '" + String(name) + "', not found in object of type " + node->get_class(); - ERR_PRINTS(error); - } - } - - } break; - case NETWORK_COMMAND_SIMPLIFY_PATH: { - - ERR_FAIL_COND(p_packet_len < 5); - int id = decode_uint32(&p_packet[1]); - - String paths; - paths.parse_utf8((const char *)&p_packet[5], p_packet_len - 5); - - NodePath path = paths; - - if (!path_get_cache.has(p_from)) { - path_get_cache[p_from] = PathGetCache(); - } - - PathGetCache::NodeInfo ni; - ni.path = path; - ni.instance = 0; - - path_get_cache[p_from].nodes[id] = ni; - - { - //send ack - - //encode path - CharString pname = String(path).utf8(); - int len = encode_cstring(pname.get_data(), NULL); - - Vector<uint8_t> packet; - - packet.resize(1 + len); - packet[0] = NETWORK_COMMAND_CONFIRM_PATH; - encode_cstring(pname.get_data(), &packet[1]); - - network_peer->set_transfer_mode(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE); - network_peer->set_target_peer(p_from); - network_peer->put_packet(packet.ptr(), packet.size()); - } - } break; - case NETWORK_COMMAND_CONFIRM_PATH: { - - String paths; - paths.parse_utf8((const char *)&p_packet[1], p_packet_len - 1); - - NodePath path = paths; - - PathSentCache *psc = path_send_cache.getptr(path); - ERR_FAIL_COND(!psc); - - Map<int, bool>::Element *E = psc->confirmed_peers.find(p_from); - ERR_FAIL_COND(!E); - E->get() = true; - } break; - } -} - -void SceneTree::_network_poll() { - - if (!network_peer.is_valid() || network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED) - return; - - network_peer->poll(); - - if (!network_peer.is_valid()) //it's possible that polling might have resulted in a disconnection, so check here - return; - - while (network_peer->get_available_packet_count()) { - - int sender = network_peer->get_packet_peer(); - const uint8_t *packet; - int len; - - Error err = network_peer->get_packet(&packet, len); - if (err != OK) { - ERR_PRINT("Error getting packet!"); - } - - rpc_sender_id = sender; - _network_process_packet(sender, packet, len); - rpc_sender_id = 0; - - if (!network_peer.is_valid()) { - break; //it's also possible that a packet or RPC caused a disconnection, so also check here - } - } + return multiplayer->is_refusing_new_network_connections(); } void SceneTree::_bind_methods() { @@ -2141,10 +1759,8 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_debug_navigation_hint", "enable"), &SceneTree::set_debug_navigation_hint); ClassDB::bind_method(D_METHOD("is_debugging_navigation_hint"), &SceneTree::is_debugging_navigation_hint); -#ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("set_edited_scene_root", "scene"), &SceneTree::set_edited_scene_root); ClassDB::bind_method(D_METHOD("get_edited_scene_root"), &SceneTree::get_edited_scene_root); -#endif ClassDB::bind_method(D_METHOD("set_pause", "enable"), &SceneTree::set_pause); ClassDB::bind_method(D_METHOD("is_paused"), &SceneTree::is_paused); @@ -2194,6 +1810,10 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("_change_scene"), &SceneTree::_change_scene); + ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer"), &SceneTree::set_multiplayer); + ClassDB::bind_method(D_METHOD("get_multiplayer"), &SceneTree::get_multiplayer); + ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled); + ClassDB::bind_method(D_METHOD("is_multiplayer_poll_enabled"), &SceneTree::is_multiplayer_poll_enabled); ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &SceneTree::set_network_peer); ClassDB::bind_method(D_METHOD("get_network_peer"), &SceneTree::get_network_peer); ClassDB::bind_method(D_METHOD("is_network_server"), &SceneTree::is_network_server); @@ -2217,12 +1837,12 @@ void SceneTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_pause", "is_paused"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_network_connections"), "set_refuse_new_network_connections", "is_refusing_new_network_connections"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_font_oversampling"), "set_use_font_oversampling", "is_using_font_oversampling"); -#ifdef TOOLS_ENABLED ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_scene_root", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_edited_scene_root", "get_edited_scene_root"); -#endif ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_current_scene", "get_current_scene"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "NetworkedMultiplayerPeer", 0), "set_network_peer", "get_network_peer"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "", "get_root"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "set_multiplayer", "get_multiplayer"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled"); ADD_SIGNAL(MethodInfo("tree_changed")); ADD_SIGNAL(MethodInfo("node_added", PropertyInfo(Variant::OBJECT, "node"))); @@ -2318,7 +1938,6 @@ SceneTree::SceneTree() { call_lock = 0; root_lock = 0; node_count = 0; - rpc_sender_id = 0; //create with mainloop @@ -2327,6 +1946,10 @@ SceneTree::SceneTree() { if (!root->get_world().is_valid()) root->set_world(Ref<World>(memnew(World))); + // Initialize network state + multiplayer_poll = true; + set_multiplayer(Ref<MultiplayerAPI>(memnew(MultiplayerAPI))); + //root->set_world_2d( Ref<World2D>( memnew( World2D ))); root->set_as_audio_listener(true); root->set_as_audio_listener_2d(true); @@ -2421,8 +2044,6 @@ SceneTree::SceneTree() { live_edit_root = NodePath("/root"); - last_send_cache_id = 1; - #endif use_font_oversampling = false; diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index c5357762ec..aa8d78b1e1 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -31,7 +31,7 @@ #ifndef SCENE_MAIN_LOOP_H #define SCENE_MAIN_LOOP_H -#include "io/networked_multiplayer_peer.h" +#include "io/multiplayer_api.h" #include "os/main_loop.h" #include "os/thread_safe.h" #include "scene/resources/mesh.h" @@ -185,16 +185,9 @@ private: ///network/// - enum NetworkCommands { - NETWORK_COMMAND_REMOTE_CALL, - NETWORK_COMMAND_REMOTE_SET, - NETWORK_COMMAND_SIMPLIFY_PATH, - NETWORK_COMMAND_CONFIRM_PATH, - }; - - Ref<NetworkedMultiplayerPeer> network_peer; + Ref<MultiplayerAPI> multiplayer; + bool multiplayer_poll; - Set<int> connected_peers; void _network_peer_connected(int p_id); void _network_peer_disconnected(int p_id); @@ -202,39 +195,9 @@ private: void _connection_failed(); void _server_disconnected(); - int rpc_sender_id; - - //path sent caches - struct PathSentCache { - Map<int, bool> confirmed_peers; - int id; - }; - - HashMap<NodePath, PathSentCache> path_send_cache; - int last_send_cache_id; - - //path get caches - struct PathGetCache { - struct NodeInfo { - NodePath path; - ObjectID instance; - }; - - Map<int, NodeInfo> nodes; - }; - - Map<int, PathGetCache> path_get_cache; - - Vector<uint8_t> packet_cache; - - void _network_process_packet(int p_from, const uint8_t *p_packet, int p_packet_len); - void _network_poll(); - static SceneTree *singleton; friend class Node; - void _rpc(Node *p_from, int p_to, bool p_unreliable, bool p_set, const StringName &p_name, const Variant **p_arg, int p_argcount); - void tree_changed(); void node_added(Node *p_node); void node_removed(Node *p_node); @@ -428,10 +391,8 @@ public: //void change_scene(const String& p_path); //Node *get_loaded_scene(); -#ifdef TOOLS_ENABLED void set_edited_scene_root(Node *p_node); Node *get_edited_scene_root() const; -#endif void set_current_scene(Node *p_scene); Node *get_current_scene() const; @@ -450,6 +411,10 @@ public: //network API + Ref<MultiplayerAPI> get_multiplayer() const; + void set_multiplayer_poll_enabled(bool p_enabled); + bool is_multiplayer_poll_enabled() const; + void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer); void set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_network_peer); Ref<NetworkedMultiplayerPeer> get_network_peer() const; bool is_network_server() const; diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index ad2cdbfd0f..c285694dfa 100755 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -107,7 +107,10 @@ bool Timer::has_autostart() const { return autostart; } -void Timer::start() { +void Timer::start(float p_time) { + if (p_time > 0) { + set_wait_time(p_time); + } time_left = wait_time; _set_process(true); } @@ -185,7 +188,7 @@ void Timer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autostart", "enable"), &Timer::set_autostart); ClassDB::bind_method(D_METHOD("has_autostart"), &Timer::has_autostart); - ClassDB::bind_method(D_METHOD("start"), &Timer::start); + ClassDB::bind_method(D_METHOD("start", "time_sec"), &Timer::start, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("stop"), &Timer::stop); ClassDB::bind_method(D_METHOD("set_paused", "paused"), &Timer::set_paused); diff --git a/scene/main/timer.h b/scene/main/timer.h index 410d985407..2f42252a7e 100755 --- a/scene/main/timer.h +++ b/scene/main/timer.h @@ -64,7 +64,7 @@ public: void set_autostart(bool p_start); bool has_autostart() const; - void start(); + void start(float p_time = -1); void stop(); void set_paused(bool p_paused); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1ff1f4b6d8..a894b82a94 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -72,6 +72,9 @@ void ViewportTexture::setup_local_to_scene() { vp->viewport_textures.insert(this); VS::get_singleton()->texture_set_proxy(proxy, vp->texture_rid); + + vp->texture_flags = flags; + VS::get_singleton()->texture_set_flags(vp->texture_rid, flags); } void ViewportTexture::set_viewport_path_in_scene(const NodePath &p_path) { @@ -122,20 +125,18 @@ Ref<Image> ViewportTexture::get_data() const { return VS::get_singleton()->texture_get_data(vp->texture_rid); } void ViewportTexture::set_flags(uint32_t p_flags) { + flags = p_flags; if (!vp) return; - vp->texture_flags = p_flags; - VS::get_singleton()->texture_set_flags(vp->texture_rid, p_flags); + vp->texture_flags = flags; + VS::get_singleton()->texture_set_flags(vp->texture_rid, flags); } uint32_t ViewportTexture::get_flags() const { - if (!vp) - return 0; - - return vp->texture_flags; + return flags; } void ViewportTexture::_bind_methods() { @@ -143,12 +144,13 @@ void ViewportTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_viewport_path_in_scene", "path"), &ViewportTexture::set_viewport_path_in_scene); ClassDB::bind_method(D_METHOD("get_viewport_path_in_scene"), &ViewportTexture::get_viewport_path_in_scene); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path"), "set_viewport_path_in_scene", "get_viewport_path_in_scene"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Viewport"), "set_viewport_path_in_scene", "get_viewport_path_in_scene"); } ViewportTexture::ViewportTexture() { vp = NULL; + flags = 0; set_local_to_scene(true); proxy = VS::get_singleton()->texture_create(); } @@ -181,6 +183,7 @@ public: Viewport::GUI::GUI() { mouse_focus = NULL; + mouse_click_grabber = NULL; mouse_focus_button = -1; key_focus = NULL; mouse_over = NULL; @@ -188,10 +191,12 @@ Viewport::GUI::GUI() { tooltip = NULL; tooltip_popup = NULL; tooltip_label = NULL; + subwindow_visibility_dirty = false; subwindow_order_dirty = false; } ///////////////////////////////////// + void Viewport::_update_stretch_transform() { if (size_override_stretch && size_override) { @@ -318,6 +323,11 @@ void Viewport::_notification(int p_what) { first->make_current(); } #endif + + // Enable processing for tooltips, collision debugging, physics object picking, etc. + set_process_internal(true); + set_physics_process_internal(true); + } break; case NOTIFICATION_EXIT_TREE: { @@ -345,15 +355,18 @@ void Viewport::_notification(int p_what) { VS::get_singleton()->viewport_set_active(viewport, false); } break; - case NOTIFICATION_PHYSICS_PROCESS: { + case NOTIFICATION_INTERNAL_PROCESS: { if (gui.tooltip_timer >= 0) { - gui.tooltip_timer -= get_physics_process_delta_time(); + gui.tooltip_timer -= get_process_delta_time(); if (gui.tooltip_timer < 0) { _gui_show_tooltip(); } } + } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (get_tree()->is_debugging_collisions_hint() && contact_2d_debug.is_valid()) { VisualServer::get_singleton()->canvas_item_clear(contact_2d_debug); @@ -1241,6 +1254,24 @@ void Viewport::warp_mouse(const Vector2 &p_pos) { Input::get_singleton()->warp_mouse_position(gpos); } +void Viewport::_gui_prepare_subwindows() { + + if (gui.subwindow_visibility_dirty) { + + gui.subwindows.clear(); + for (List<Control *>::Element *E = gui.all_known_subwindows.front(); E; E = E->next()) { + if (E->get()->is_visible_in_tree()) { + gui.subwindows.push_back(E->get()); + } + } + + gui.subwindow_visibility_dirty = false; + gui.subwindow_order_dirty = true; + } + + _gui_sort_subwindows(); +} + void Viewport::_gui_sort_subwindows() { if (!gui.subwindow_order_dirty) @@ -1277,13 +1308,37 @@ void Viewport::_gui_cancel_tooltip() { } } +String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos) { + + Vector2 pos = p_pos; + String tooltip; + + while (p_control) { + + tooltip = p_control->get_tooltip(pos); + + if (tooltip != String()) + break; + pos = p_control->get_transform().xform(pos); + + if (p_control->data.mouse_filter == Control::MOUSE_FILTER_STOP) + break; + if (p_control->is_set_as_toplevel()) + break; + + p_control = p_control->get_parent_control(); + } + + return tooltip; +} + void Viewport::_gui_show_tooltip() { if (!gui.tooltip) { return; } - String tooltip = gui.tooltip->get_tooltip(gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos)); + String tooltip = _gui_get_tooltip(gui.tooltip, gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos)); if (tooltip.length() == 0) return; // bye @@ -1316,7 +1371,7 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_label->set_anchor_and_margin(MARGIN_RIGHT, Control::ANCHOR_END, -ttp->get_margin(MARGIN_RIGHT)); gui.tooltip_label->set_anchor_and_margin(MARGIN_BOTTOM, Control::ANCHOR_END, -ttp->get_margin(MARGIN_BOTTOM)); gui.tooltip_label->set_text(tooltip.strip_edges()); - Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_label->get_combined_minimum_size() + ttp->get_minimum_size()); + Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_label->get_minimum_size() + ttp->get_minimum_size()); Rect2 vr = gui.tooltip_label->get_viewport_rect(); if (r.size.x + r.position.x > vr.size.x) r.position.x = vr.size.x - r.size.x; @@ -1357,12 +1412,14 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu Control *control = Object::cast_to<Control>(ci); if (control) { - control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev); + + control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); //signal should be first, so it's possible to override an event (and then accept it) if (gui.key_event_accepted) break; if (!control->is_inside_tree()) break; - control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); + control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev); + if (!control->is_inside_tree() || control->is_set_as_toplevel()) break; if (gui.key_event_accepted) @@ -1383,7 +1440,7 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu Control *Viewport::_gui_find_control(const Point2 &p_global) { - _gui_sort_subwindows(); + _gui_prepare_subwindows(); for (List<Control *>::Element *E = gui.subwindows.back(); E; E = E->prev()) { @@ -1531,7 +1588,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (mb->is_pressed()) { Size2 pos = mpos; - if (gui.mouse_focus && mb->get_button_index() != gui.mouse_focus_button && mb->get_button_index() == BUTTON_LEFT) { + if (gui.mouse_focus && mb->get_button_index() != gui.mouse_focus_button) { //do not steal mouse focus and stuff @@ -1804,7 +1861,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } if (!over) { - OS::get_singleton()->set_cursor_shape(OS::CURSOR_ARROW); + OS::get_singleton()->set_cursor_shape((OS::CursorShape)Input::get_singleton()->get_default_cursor_shape()); return; } @@ -1833,7 +1890,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.tooltip_popup) { if (can_tooltip) { - String tooltip = over->get_tooltip(gui.tooltip->get_global_transform().xform_inv(mpos)); + String tooltip = _gui_get_tooltip(over, gui.tooltip->get_global_transform().xform_inv(mpos)); if (tooltip.length() == 0) _gui_cancel_tooltip(); @@ -1855,7 +1912,23 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { mm->set_position(pos); - Control::CursorShape cursor_shape = over->get_cursor_shape(pos); + Control::CursorShape cursor_shape = Control::CURSOR_ARROW; + { + Control *c = over; + Vector2 cpos = pos; + while (c) { + cursor_shape = c->get_cursor_shape(cpos); + cpos = c->get_transform().xform(cpos); + if (cursor_shape != Control::CURSOR_ARROW) + break; + if (c->data.mouse_filter == Control::MOUSE_FILTER_STOP) + break; + if (c->is_set_as_toplevel()) + break; + c = c->get_parent_control(); + } + } + OS::get_singleton()->set_cursor_shape((OS::CursorShape)cursor_shape); if (over->can_process()) { @@ -1925,6 +1998,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { Ref<InputEventGesture> gesture_event = p_event; if (gesture_event.is_valid()) { + gui.key_event_accepted = false; + _gui_cancel_tooltip(); Size2 pos = gesture_event->get_position(); @@ -2017,6 +2092,9 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { top->notification(Control::NOTIFICATION_MODAL_CLOSE); top->_modal_stack_remove(); top->hide(); + // Close modal, set input as handled + get_tree()->set_input_as_handled(); + return; } } @@ -2078,8 +2156,14 @@ List<Control *>::Element *Viewport::_gui_add_root_control(Control *p_control) { List<Control *>::Element *Viewport::_gui_add_subwindow_control(Control *p_control) { - gui.subwindow_order_dirty = true; - return gui.subwindows.push_back(p_control); + p_control->connect("visibility_changed", this, "_subwindow_visibility_changed"); + + if (p_control->is_visible_in_tree()) { + gui.subwindow_order_dirty = true; + gui.subwindows.push_back(p_control); + } + + return gui.all_known_subwindows.push_back(p_control); } void Viewport::_gui_set_subwindow_order_dirty() { @@ -2153,9 +2237,7 @@ void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) { p_control->set_position(gui.last_mouse_pos); p_base->get_root_parent_control()->add_child(p_control); //add as child of viewport p_control->raise(); - if (gui.drag_preview) { - memdelete(gui.drag_preview); - } + gui.drag_preview = p_control; } @@ -2166,7 +2248,17 @@ void Viewport::_gui_remove_root_control(List<Control *>::Element *RI) { void Viewport::_gui_remove_subwindow_control(List<Control *>::Element *SI) { - gui.subwindows.erase(SI); + ERR_FAIL_COND(!SI); + + Control *control = SI->get(); + + control->disconnect("visibility_changed", this, "_subwindow_visibility_changed"); + + List<Control *>::Element *E = gui.subwindows.find(control); + if (E) + gui.subwindows.erase(E); + + gui.all_known_subwindows.erase(SI); } void Viewport::_gui_unfocus_control(Control *p_control) { @@ -2193,7 +2285,7 @@ void Viewport::_gui_hid_control(Control *p_control) { */ if (gui.key_focus == p_control) - gui.key_focus = NULL; + _gui_remove_focus(); if (gui.mouse_over == p_control) gui.mouse_over = NULL; if (gui.tooltip == p_control) @@ -2264,7 +2356,7 @@ List<Control *>::Element *Viewport::_gui_show_modal(Control *p_control) { else p_control->_modal_set_prev_focus_owner(0); - if (gui.mouse_focus && !p_control->is_a_parent_of(gui.mouse_focus)) { + if (gui.mouse_focus && !p_control->is_a_parent_of(gui.mouse_focus) && !gui.mouse_click_grabber) { Ref<InputEventMouseButton> mb; mb.instance(); mb->set_position(gui.mouse_focus->get_local_mouse_position()); @@ -2286,9 +2378,22 @@ Control *Viewport::_gui_get_focus_owner() { void Viewport::_gui_grab_click_focus(Control *p_control) { + gui.mouse_click_grabber = p_control; + call_deferred("_post_gui_grab_click_focus"); +} + +void Viewport::_post_gui_grab_click_focus() { + + Control *focus_grabber = gui.mouse_click_grabber; + if (!focus_grabber) { + // Redundant grab requests were made + return; + } + gui.mouse_click_grabber = NULL; + if (gui.mouse_focus) { - if (gui.mouse_focus == p_control) + if (gui.mouse_focus == focus_grabber) return; Ref<InputEventMouseButton> mb; mb.instance(); @@ -2299,9 +2404,9 @@ void Viewport::_gui_grab_click_focus(Control *p_control) { mb->set_position(click); mb->set_button_index(gui.mouse_focus_button); mb->set_pressed(false); - gui.mouse_focus->call_deferred(SceneStringNames::get_singleton()->_gui_input, mb); + gui.mouse_focus->call_multilevel(SceneStringNames::get_singleton()->_gui_input, mb); - gui.mouse_focus = p_control; + gui.mouse_focus = focus_grabber; gui.focus_inv_xform = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse(); click = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos); mb->set_position(click); @@ -2404,9 +2509,14 @@ Rect2 Viewport::get_attach_to_screen_rect() const { void Viewport::set_physics_object_picking(bool p_enable) { physics_object_picking = p_enable; - set_physics_process(physics_object_picking); - if (!physics_object_picking) + if (!physics_object_picking) { physics_picking_events.clear(); + } +} + +bool Viewport::get_physics_object_picking() { + + return physics_object_picking; } Vector2 Viewport::get_camera_coords(const Vector2 &p_viewport_coords) const { @@ -2420,11 +2530,6 @@ Vector2 Viewport::get_camera_rect_size() const { return size; } -bool Viewport::get_physics_object_picking() { - - return physics_object_picking; -} - bool Viewport::gui_has_modal_stack() const { return gui.modal_stack.size(); @@ -2449,6 +2554,16 @@ bool Viewport::is_3d_disabled() const { return disable_3d; } +void Viewport::set_keep_3d_linear(bool p_keep_3d_linear) { + keep_3d_linear = p_keep_3d_linear; + VS::get_singleton()->viewport_set_keep_3d_linear(viewport, keep_3d_linear); +} + +bool Viewport::get_keep_3d_linear() const { + + return keep_3d_linear; +} + Variant Viewport::gui_get_drag_data() const { return gui.drag_data; } @@ -2632,8 +2747,12 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_disable_3d", "disable"), &Viewport::set_disable_3d); ClassDB::bind_method(D_METHOD("is_3d_disabled"), &Viewport::is_3d_disabled); + ClassDB::bind_method(D_METHOD("set_keep_3d_linear", "keep_3d_linear"), &Viewport::set_keep_3d_linear); + ClassDB::bind_method(D_METHOD("get_keep_3d_linear"), &Viewport::get_keep_3d_linear); + ClassDB::bind_method(D_METHOD("_gui_show_tooltip"), &Viewport::_gui_show_tooltip); ClassDB::bind_method(D_METHOD("_gui_remove_focus"), &Viewport::_gui_remove_focus); + ClassDB::bind_method(D_METHOD("_post_gui_grab_click_focus"), &Viewport::_post_gui_grab_click_focus); ClassDB::bind_method(D_METHOD("set_shadow_atlas_size", "size"), &Viewport::set_shadow_atlas_size); ClassDB::bind_method(D_METHOD("get_shadow_atlas_size"), &Viewport::get_shadow_atlas_size); @@ -2644,6 +2763,8 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shadow_atlas_quadrant_subdiv", "quadrant", "subdiv"), &Viewport::set_shadow_atlas_quadrant_subdiv); ClassDB::bind_method(D_METHOD("get_shadow_atlas_quadrant_subdiv", "quadrant"), &Viewport::get_shadow_atlas_quadrant_subdiv); + ClassDB::bind_method(D_METHOD("_subwindow_visibility_changed"), &Viewport::_subwindow_visibility_changed); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arvr"), "set_use_arvr", "use_arvr"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); @@ -2655,6 +2776,7 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, "Disabled,2x,4x,8x,16x"), "set_msaa", "get_msaa"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hdr"), "set_hdr", "get_hdr"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_3d_linear"), "set_keep_3d_linear", "get_keep_3d_linear"); ADD_PROPERTY(PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_ENUM, "2D,2D No-Sampling,3D,3D No-Effects"), "set_usage", "get_usage"); ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_draw", PROPERTY_HINT_ENUM, "Disabled,Unshaded,Overdraw,Wireframe"), "set_debug_draw", "get_debug_draw"); ADD_GROUP("Render Target", "render_target_"); @@ -2723,6 +2845,13 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(CLEAR_MODE_ONLY_NEXT_FRAME); } +void Viewport::_subwindow_visibility_changed() { + + // unfortunately, we don't know the sender, i.e. which subwindow changed; + // so we have to check them all. + gui.subwindow_visibility_dirty = true; +} + Viewport::Viewport() { world_2d = Ref<World2D>(memnew(World2D)); @@ -2777,6 +2906,7 @@ Viewport::Viewport() { disable_input = false; disable_3d = false; + keep_3d_linear = false; //window tooltip gui.tooltip_timer = -1; @@ -2789,6 +2919,7 @@ Viewport::Viewport() { gui.drag_preview = NULL; gui.drag_attempted = false; gui.canvas_sort_index = 0; + gui.roots_order_dirty = false; msaa = MSAA_DISABLED; hdr = true; diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 07bbd3f1fa..3000398540 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -58,6 +58,7 @@ class ViewportTexture : public Texture { friend class Viewport; Viewport *vp; + uint32_t flags; RID proxy; @@ -226,6 +227,7 @@ private: void _update_global_transform(); bool disable_3d; + bool keep_3d_linear; UpdateMode update_mode; RID texture_rid; uint32_t texture_flags; @@ -248,6 +250,7 @@ private: bool key_event_accepted; Control *mouse_focus; + Control *mouse_click_grabber; int mouse_focus_button; Control *key_focus; Control *mouse_over; @@ -265,7 +268,9 @@ private: List<Control *> modal_stack; Transform2D focus_inv_xform; bool subwindow_order_dirty; - List<Control *> subwindows; + bool subwindow_visibility_dirty; + List<Control *> subwindows; // visible subwindows + List<Control *> all_known_subwindows; bool roots_order_dirty; List<Control *> roots; int canvas_sort_index; //for sorting items with canvas as root @@ -276,6 +281,7 @@ private: bool disable_input; void _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input); + void _gui_prepare_subwindows(); void _gui_sort_subwindows(); void _gui_sort_roots(); void _gui_sort_modal_stack(); @@ -306,6 +312,7 @@ private: void _gui_remove_root_control(List<Control *>::Element *RI); void _gui_remove_subwindow_control(List<Control *>::Element *SI); + String _gui_get_tooltip(Control *p_control, const Vector2 &p_pos); void _gui_cancel_tooltip(); void _gui_show_tooltip(); @@ -323,6 +330,7 @@ private: bool _gui_control_has_focus(const Control *p_control); void _gui_control_grab_focus(Control *p_control); void _gui_grab_click_focus(Control *p_control); + void _post_gui_grab_click_focus(); void _gui_accept_event(); Control *_gui_get_focus_owner(); @@ -431,6 +439,9 @@ public: void set_disable_3d(bool p_disable); bool is_3d_disabled() const; + void set_keep_3d_linear(bool p_keep_3d_linear); + bool get_keep_3d_linear() const; + void set_attach_to_screen_rect(const Rect2 &p_rect); Rect2 get_attach_to_screen_rect() const; @@ -461,6 +472,8 @@ public: void set_snap_controls_to_pixels(bool p_enable); bool is_snap_controls_to_pixels_enabled() const; + void _subwindow_visibility_changed(); + Viewport(); ~Viewport(); }; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 19d8f09ebe..55aa0024c8 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -58,12 +58,19 @@ #include "scene/2d/ray_cast_2d.h" #include "scene/2d/remote_transform_2d.h" #include "scene/2d/screen_button.h" +#include "scene/2d/skeleton_2d.h" #include "scene/2d/sprite.h" #include "scene/2d/tile_map.h" #include "scene/2d/visibility_notifier_2d.h" #include "scene/2d/y_sort.h" +#include "scene/animation/animation_blend_space_1d.h" +#include "scene/animation/animation_blend_space_2d.h" +#include "scene/animation/animation_blend_tree.h" +#include "scene/animation/animation_node_state_machine.h" #include "scene/animation/animation_player.h" +#include "scene/animation/animation_tree.h" #include "scene/animation/animation_tree_player.h" +#include "scene/animation/root_motion_view.h" #include "scene/animation/tween.h" #include "scene/audio/audio_player.h" #include "scene/gui/box_container.h" @@ -381,6 +388,28 @@ void register_scene_types() { ClassDB::register_class<NavigationMesh>(); ClassDB::register_class<Navigation>(); + ClassDB::register_class<RootMotionView>(); + ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor + + ClassDB::register_class<AnimationTree>(); + ClassDB::register_class<AnimationNode>(); + ClassDB::register_class<AnimationRootNode>(); + ClassDB::register_class<AnimationNodeBlendTree>(); + ClassDB::register_class<AnimationNodeBlendSpace1D>(); + ClassDB::register_class<AnimationNodeBlendSpace2D>(); + ClassDB::register_class<AnimationNodeStateMachine>(); + ClassDB::register_class<AnimationNodeStateMachineTransition>(); + ClassDB::register_class<AnimationNodeOutput>(); + ClassDB::register_class<AnimationNodeOneShot>(); + ClassDB::register_class<AnimationNodeAnimation>(); + ClassDB::register_class<AnimationNodeAdd2>(); + ClassDB::register_class<AnimationNodeAdd3>(); + ClassDB::register_class<AnimationNodeBlend2>(); + ClassDB::register_class<AnimationNodeBlend3>(); + ClassDB::register_class<AnimationNodeTimeScale>(); + ClassDB::register_class<AnimationNodeTimeSeek>(); + ClassDB::register_class<AnimationNodeTransition>(); + OS::get_singleton()->yield(); //may take time to init ClassDB::register_virtual_class<CollisionObject>(); @@ -389,6 +418,7 @@ void register_scene_types() { ClassDB::register_class<RigidBody>(); ClassDB::register_class<KinematicCollision>(); ClassDB::register_class<KinematicBody>(); + ClassDB::register_class<PhysicalBone>(); ClassDB::register_class<VehicleBody>(); ClassDB::register_class<VehicleWheel>(); @@ -402,6 +432,7 @@ void register_scene_types() { ClassDB::register_class<Curve3D>(); ClassDB::register_class<Path>(); ClassDB::register_class<PathFollow>(); + ClassDB::register_class<OrientedPathFollow>(); ClassDB::register_class<VisibilityNotifier>(); ClassDB::register_class<VisibilityEnabler>(); ClassDB::register_class<WorldEnvironment>(); @@ -449,6 +480,8 @@ void register_scene_types() { ClassDB::register_class<VisibilityNotifier2D>(); ClassDB::register_class<VisibilityEnabler2D>(); ClassDB::register_class<Polygon2D>(); + ClassDB::register_class<Skeleton2D>(); + ClassDB::register_class<Bone2D>(); ClassDB::register_class<Light2D>(); ClassDB::register_class<LightOccluder2D>(); ClassDB::register_class<OccluderPolygon2D>(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 7a1fffaa26..3185fb6768 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -32,6 +32,8 @@ #include "geometry.h" +#define ANIM_MIN_LENGTH 0.001 + bool Animation::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; @@ -54,6 +56,15 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } else if (type == "method") { add_track(TYPE_METHOD); + } else if (type == "bezier") { + + add_track(TYPE_BEZIER); + } else if (type == "audio") { + + add_track(TYPE_AUDIO); + } else if (type == "animation") { + + add_track(TYPE_ANIMATION); } else { return false; @@ -123,8 +134,8 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { int um = d["update"]; if (um < 0) um = 0; - else if (um > 2) - um = 2; + else if (um > 3) + um = 3; vt->update_mode = UpdateMode(um); } @@ -163,7 +174,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { return true; - } else { + } else if (track_get_type(track) == TYPE_METHOD) { while (track_get_key_count(track)) track_remove_key(track, 0); //well shouldn't be set anyway @@ -201,6 +212,114 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } } } + } else if (track_get_type(track) == TYPE_BEZIER) { + + BezierTrack *bt = static_cast<BezierTrack *>(tracks[track]); + Dictionary d = p_value; + ERR_FAIL_COND_V(!d.has("times"), false); + ERR_FAIL_COND_V(!d.has("points"), false); + + PoolVector<float> times = d["times"]; + PoolRealArray values = d["points"]; + + ERR_FAIL_COND_V(times.size() * 5 != values.size(), false); + + if (times.size()) { + + int valcount = times.size(); + + PoolVector<float>::Read rt = times.read(); + PoolVector<float>::Read rv = values.read(); + + bt->values.resize(valcount); + + for (int i = 0; i < valcount; i++) { + + bt->values[i].time = rt[i]; + bt->values[i].transition = 0; //unused in bezier + bt->values[i].value.value = rv[i * 5 + 0]; + bt->values[i].value.in_handle.x = rv[i * 5 + 1]; + bt->values[i].value.in_handle.y = rv[i * 5 + 2]; + bt->values[i].value.out_handle.x = rv[i * 5 + 3]; + bt->values[i].value.out_handle.y = rv[i * 5 + 4]; + } + } + + return true; + } else if (track_get_type(track) == TYPE_AUDIO) { + + AudioTrack *ad = static_cast<AudioTrack *>(tracks[track]); + Dictionary d = p_value; + ERR_FAIL_COND_V(!d.has("times"), false); + ERR_FAIL_COND_V(!d.has("clips"), false); + + PoolVector<float> times = d["times"]; + Array clips = d["clips"]; + + ERR_FAIL_COND_V(clips.size() != times.size(), false); + + if (times.size()) { + + int valcount = times.size(); + + PoolVector<float>::Read rt = times.read(); + + ad->values.clear(); + + for (int i = 0; i < valcount; i++) { + + Dictionary d = clips[i]; + if (!d.has("start_offset")) + continue; + if (!d.has("end_offset")) + continue; + if (!d.has("stream")) + continue; + + TKey<AudioKey> ak; + ak.time = rt[i]; + ak.value.start_offset = d["start_offset"]; + ak.value.end_offset = d["end_offset"]; + ak.value.stream = d["stream"]; + + ad->values.push_back(ak); + } + } + + return true; + } else if (track_get_type(track) == TYPE_ANIMATION) { + + AnimationTrack *an = static_cast<AnimationTrack *>(tracks[track]); + Dictionary d = p_value; + ERR_FAIL_COND_V(!d.has("times"), false); + ERR_FAIL_COND_V(!d.has("clips"), false); + + PoolVector<float> times = d["times"]; + PoolVector<String> clips = d["clips"]; + + ERR_FAIL_COND_V(clips.size() != times.size(), false); + + if (times.size()) { + + int valcount = times.size(); + + PoolVector<float>::Read rt = times.read(); + PoolVector<String>::Read rc = clips.read(); + + an->values.resize(valcount); + + for (int i = 0; i < valcount; i++) { + + TKey<StringName> ak; + ak.time = rt[i]; + ak.value = rc[i]; + an->values[i] = ak; + } + } + + return true; + } else { + return false; } } else return false; @@ -232,6 +351,9 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { case TYPE_TRANSFORM: r_ret = "transform"; break; case TYPE_VALUE: r_ret = "value"; break; case TYPE_METHOD: r_ret = "method"; break; + case TYPE_BEZIER: r_ret = "bezier"; break; + case TYPE_AUDIO: r_ret = "audio"; break; + case TYPE_ANIMATION: r_ret = "animation"; break; } return true; @@ -329,7 +451,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { return true; - } else { + } else if (track_get_type(track) == TYPE_METHOD) { Dictionary d; @@ -368,6 +490,119 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { r_ret = d; return true; + } else if (track_get_type(track) == TYPE_BEZIER) { + + const BezierTrack *bt = static_cast<const BezierTrack *>(tracks[track]); + + Dictionary d; + + PoolVector<float> key_times; + PoolVector<float> key_points; + + int kk = bt->values.size(); + + key_times.resize(kk); + key_points.resize(kk * 5); + + PoolVector<float>::Write wti = key_times.write(); + PoolVector<float>::Write wpo = key_points.write(); + + int idx = 0; + + const TKey<BezierKey> *vls = bt->values.ptr(); + + for (int i = 0; i < kk; i++) { + + wti[idx] = vls[i].time; + wpo[idx * 5 + 0] = vls[i].value.value; + wpo[idx * 5 + 1] = vls[i].value.in_handle.x; + wpo[idx * 5 + 2] = vls[i].value.in_handle.y; + wpo[idx * 5 + 3] = vls[i].value.out_handle.x; + wpo[idx * 5 + 4] = vls[i].value.out_handle.y; + idx++; + } + + wti = PoolVector<float>::Write(); + wpo = PoolVector<float>::Write(); + + d["times"] = key_times; + d["points"] = key_points; + + r_ret = d; + + return true; + } else if (track_get_type(track) == TYPE_AUDIO) { + + const AudioTrack *ad = static_cast<const AudioTrack *>(tracks[track]); + + Dictionary d; + + PoolVector<float> key_times; + Array clips; + + int kk = ad->values.size(); + + key_times.resize(kk); + + PoolVector<float>::Write wti = key_times.write(); + + int idx = 0; + + const TKey<AudioKey> *vls = ad->values.ptr(); + + for (int i = 0; i < kk; i++) { + + wti[idx] = vls[i].time; + Dictionary clip; + clip["start_offset"] = vls[i].value.start_offset; + clip["end_offset"] = vls[i].value.end_offset; + clip["stream"] = vls[i].value.stream; + clips.push_back(clip); + idx++; + } + + wti = PoolVector<float>::Write(); + + d["times"] = key_times; + d["clips"] = clips; + + r_ret = d; + + return true; + } else if (track_get_type(track) == TYPE_ANIMATION) { + + const AnimationTrack *an = static_cast<const AnimationTrack *>(tracks[track]); + + Dictionary d; + + PoolVector<float> key_times; + PoolVector<String> clips; + + int kk = an->values.size(); + + key_times.resize(kk); + clips.resize(kk); + + PoolVector<float>::Write wti = key_times.write(); + PoolVector<String>::Write wcl = clips.write(); + + const TKey<StringName> *vls = an->values.ptr(); + + for (int i = 0; i < kk; i++) { + + wti[i] = vls[i].time; + wcl[i] = vls[i].value; + } + + wti = PoolVector<float>::Write(); + wcl = PoolVector<String>::Write(); + + d["times"] = key_times; + d["clips"] = clips; + + r_ret = d; + + return true; } } else return false; @@ -412,6 +647,21 @@ int Animation::add_track(TrackType p_type, int p_at_pos) { tracks.insert(p_at_pos, memnew(MethodTrack)); } break; + case TYPE_BEZIER: { + + tracks.insert(p_at_pos, memnew(BezierTrack)); + + } break; + case TYPE_AUDIO: { + + tracks.insert(p_at_pos, memnew(AudioTrack)); + + } break; + case TYPE_ANIMATION: { + + tracks.insert(p_at_pos, memnew(AnimationTrack)); + + } break; default: { ERR_PRINT("Unknown track type"); @@ -446,6 +696,24 @@ void Animation::remove_track(int p_track) { _clear(mt->methods); } break; + case TYPE_BEZIER: { + + BezierTrack *bz = static_cast<BezierTrack *>(t); + _clear(bz->values); + + } break; + case TYPE_AUDIO: { + + AudioTrack *ad = static_cast<AudioTrack *>(t); + _clear(ad->values); + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *an = static_cast<AnimationTrack *>(t); + _clear(an->values); + + } break; } memdelete(t); @@ -642,6 +910,27 @@ void Animation::track_remove_key(int p_track, int p_idx) { mt->methods.remove(p_idx); } break; + case TYPE_BEZIER: { + + BezierTrack *bz = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX(p_idx, bz->values.size()); + bz->values.remove(p_idx); + + } break; + case TYPE_AUDIO: { + + AudioTrack *ad = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX(p_idx, ad->values.size()); + ad->values.remove(p_idx); + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *an = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX(p_idx, an->values.size()); + an->values.remove(p_idx); + + } break; } emit_changed(); @@ -686,6 +975,39 @@ int Animation::track_find_key(int p_track, float p_time, bool p_exact) const { return k; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + int k = _find(bt->values, p_time); + if (k < 0 || k >= bt->values.size()) + return -1; + if (bt->values[k].time != p_time && p_exact) + return -1; + return k; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + int k = _find(at->values, p_time); + if (k < 0 || k >= at->values.size()) + return -1; + if (at->values[k].time != p_time && p_exact) + return -1; + return k; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + int k = _find(at->values, p_time); + if (k < 0 || k >= at->values.size()) + return -1; + if (at->values[k].time != p_time && p_exact) + return -1; + return k; + + } break; } return -1; @@ -748,6 +1070,51 @@ void Animation::track_insert_key(int p_track, float p_time, const Variant &p_key _insert(p_time, mt->methods, k); } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + Array arr = p_key; + ERR_FAIL_COND(arr.size() != 5); + + TKey<BezierKey> k; + k.time = p_time; + k.value.value = arr[0]; + k.value.in_handle.x = arr[1]; + k.value.in_handle.y = arr[2]; + k.value.out_handle.x = arr[3]; + k.value.out_handle.y = arr[4]; + _insert(p_time, bt->values, k); + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + + Dictionary k = p_key; + ERR_FAIL_COND(!k.has("start_offset")); + ERR_FAIL_COND(!k.has("end_offset")); + ERR_FAIL_COND(!k.has("stream")); + + TKey<AudioKey> ak; + ak.time = p_time; + ak.value.start_offset = k["start_offset"]; + ak.value.end_offset = k["end_offset"]; + ak.value.stream = k["stream"]; + _insert(p_time, at->values, ak); + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + TKey<StringName> ak; + ak.time = p_time; + ak.value = p_key; + + _insert(p_time, at->values, ak); + + } break; } emit_changed(); @@ -776,6 +1143,21 @@ int Animation::track_get_key_count(int p_track) const { MethodTrack *mt = static_cast<MethodTrack *>(t); return mt->methods.size(); } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + return bt->values.size(); + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + return at->values.size(); + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + return at->values.size(); + } break; } ERR_FAIL_V(-1); @@ -817,6 +1199,41 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const { return d; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), Variant()); + + Array arr; + arr.resize(5); + arr[0] = bt->values[p_key_idx].value.value; + arr[1] = bt->values[p_key_idx].value.in_handle.x; + arr[2] = bt->values[p_key_idx].value.in_handle.y; + arr[3] = bt->values[p_key_idx].value.out_handle.x; + arr[4] = bt->values[p_key_idx].value.out_handle.y; + return arr; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant()); + + Dictionary k; + k["start_offset"] = at->values[p_key_idx].value.start_offset; + k["end_offset"] = at->values[p_key_idx].value.end_offset; + k["stream"] = at->values[p_key_idx].value.stream; + return k; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant()); + + return at->values[p_key_idx].value; + + } break; } ERR_FAIL_V(Variant()); @@ -849,6 +1266,27 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const { return mt->methods[p_key_idx].time; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), -1); + return bt->values[p_key_idx].time; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1); + return at->values[p_key_idx].time; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1); + return at->values[p_key_idx].time; + + } break; } ERR_FAIL_V(-1); @@ -881,6 +1319,18 @@ float Animation::track_get_key_transition(int p_track, int p_key_idx) const { return mt->methods[p_key_idx].transition; } break; + case TYPE_BEZIER: { + + return 1; //bezier does not really use transitions + } break; + case TYPE_AUDIO: { + + return 1; //audio does not really use transitions + } break; + case TYPE_ANIMATION: { + + return 1; //animation does not really use transitions + } break; } ERR_FAIL_V(0); @@ -923,6 +1373,42 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p if (d.has("args")) mt->methods[p_key_idx].params = d["args"]; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, bt->values.size()); + + Array arr = p_value; + ERR_FAIL_COND(arr.size() != 5); + + bt->values[p_key_idx].value.value = arr[0]; + bt->values[p_key_idx].value.in_handle.x = arr[1]; + bt->values[p_key_idx].value.in_handle.y = arr[2]; + bt->values[p_key_idx].value.out_handle.x = arr[3]; + bt->values[p_key_idx].value.out_handle.y = arr[4]; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + + Dictionary k = p_value; + ERR_FAIL_COND(!k.has("start_offset")); + ERR_FAIL_COND(!k.has("end_offset")); + ERR_FAIL_COND(!k.has("stream")); + + at->values[p_key_idx].value.start_offset = k["start_offset"]; + at->values[p_key_idx].value.end_offset = k["end_offset"]; + at->values[p_key_idx].value.stream = k["stream"]; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + at->values[p_key_idx].value = p_value; + + } break; } } @@ -953,6 +1439,11 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra mt->methods[p_key_idx].transition = p_transition; } break; + case TYPE_BEZIER: + case TYPE_AUDIO: + case TYPE_ANIMATION: { + // they dont use transition + } break; } } @@ -1410,7 +1901,7 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_VALUE); - ERR_FAIL_INDEX(p_mode, 3); + ERR_FAIL_INDEX(p_mode, 4); ValueTrack *vt = static_cast<ValueTrack *>(t); vt->update_mode = p_mode; @@ -1426,6 +1917,161 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const return vt->update_mode; } +template <class T> +void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const { + + if (from_time != length && to_time == length) + to_time = length * 1.01; //include a little more if at the end + + int to = _find(p_array, to_time); + + // can't really send the events == time, will be sent in the next frame. + // if event>=len then it will probably never be requested by the anim player. + + if (to >= 0 && p_array[to].time >= to_time) + to--; + + if (to < 0) + return; // not bother + + int from = _find(p_array, from_time); + + // position in the right first event.+ + if (from < 0 || p_array[from].time < from_time) + from++; + + int max = p_array.size(); + + for (int i = from; i <= to; i++) { + + ERR_CONTINUE(i < 0 || i >= max); // shouldn't happen + p_indices->push_back(i); + } +} + +void Animation::track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const { + + ERR_FAIL_INDEX(p_track, tracks.size()); + const Track *t = tracks[p_track]; + + float from_time = p_time - p_delta; + float to_time = p_time; + + if (from_time > to_time) + SWAP(from_time, to_time); + + if (loop) { + + if (from_time > length || from_time < 0) + from_time = Math::fposmod(from_time, length); + + if (to_time > length || to_time < 0) + to_time = Math::fposmod(to_time, length); + + if (from_time > to_time) { + // handle loop by splitting + + switch (t->type) { + + case TYPE_TRANSFORM: { + + const TransformTrack *tt = static_cast<const TransformTrack *>(t); + _track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices); + _track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices); + + } break; + case TYPE_VALUE: { + + const ValueTrack *vt = static_cast<const ValueTrack *>(t); + _track_get_key_indices_in_range(vt->values, from_time, length, p_indices); + _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices); + + } break; + case TYPE_METHOD: { + + const MethodTrack *mt = static_cast<const MethodTrack *>(t); + _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices); + _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices); + + } break; + case TYPE_BEZIER: { + + const BezierTrack *bz = static_cast<const BezierTrack *>(t); + _track_get_key_indices_in_range(bz->values, from_time, length, p_indices); + _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices); + + } break; + case TYPE_AUDIO: { + + const AudioTrack *ad = static_cast<const AudioTrack *>(t); + _track_get_key_indices_in_range(ad->values, from_time, length, p_indices); + _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices); + + } break; + case TYPE_ANIMATION: { + + const AnimationTrack *an = static_cast<const AnimationTrack *>(t); + _track_get_key_indices_in_range(an->values, from_time, length, p_indices); + _track_get_key_indices_in_range(an->values, 0, to_time, p_indices); + + } break; + } + return; + } + } else { + + if (from_time < 0) + from_time = 0; + if (from_time > length) + from_time = length; + + if (to_time < 0) + to_time = 0; + if (to_time > length) + to_time = length; + } + + switch (t->type) { + + case TYPE_TRANSFORM: { + + const TransformTrack *tt = static_cast<const TransformTrack *>(t); + _track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices); + + } break; + case TYPE_VALUE: { + + const ValueTrack *vt = static_cast<const ValueTrack *>(t); + _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices); + + } break; + case TYPE_METHOD: { + + const MethodTrack *mt = static_cast<const MethodTrack *>(t); + _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices); + + } break; + case TYPE_BEZIER: { + + const BezierTrack *bz = static_cast<const BezierTrack *>(t); + _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices); + + } break; + case TYPE_AUDIO: { + + const AudioTrack *ad = static_cast<const AudioTrack *>(t); + _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices); + + } break; + case TYPE_ANIMATION: { + + const AnimationTrack *an = static_cast<const AnimationTrack *>(t); + _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices); + + } break; + } +} + void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const { if (from_time != length && to_time == length) @@ -1527,9 +2173,362 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const { return pm->methods[p_key_idx].method; } +int Animation::bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + TKey<BezierKey> k; + k.time = p_time; + k.value.value = p_value; + k.value.in_handle = p_in_handle; + if (k.value.in_handle.x > 0) { + k.value.in_handle.x = 0; + } + k.value.out_handle = p_out_handle; + if (k.value.out_handle.x < 0) { + k.value.out_handle.x = 0; + } + + int key = _insert(p_time, bt->values, k); + + emit_changed(); + + return key; +} + +void Animation::bezier_track_set_key_value(int p_track, int p_index, float p_value) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_BEZIER); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX(p_index, bt->values.size()); + + bt->values[p_index].value.value = p_value; + emit_changed(); +} + +void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_BEZIER); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX(p_index, bt->values.size()); + + bt->values[p_index].value.in_handle = p_handle; + if (bt->values[p_index].value.in_handle.x > 0) { + bt->values[p_index].value.in_handle.x = 0; + } + emit_changed(); +} +void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_BEZIER); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX(p_index, bt->values.size()); + + bt->values[p_index].value.out_handle = p_handle; + if (bt->values[p_index].value.out_handle.x < 0) { + bt->values[p_index].value.out_handle.x = 0; + } + emit_changed(); +} +float Animation::bezier_track_get_key_value(int p_track, int p_index) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX_V(p_index, bt->values.size(), 0); + + return bt->values[p_index].value.value; +} +Vector2 Animation::bezier_track_get_key_in_handle(int p_track, int p_index) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2()); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2()); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2()); + + return bt->values[p_index].value.in_handle; +} +Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2()); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2()); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2()); + + return bt->values[p_index].value.out_handle; +} + +static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) { + /* Formula from Wikipedia article on Bezier curves. */ + real_t omt = (1.0 - t); + real_t omt2 = omt * omt; + real_t omt3 = omt2 * omt; + real_t t2 = t * t; + real_t t3 = t2 * t; + + return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; +} + +float Animation::bezier_track_interpolate(int p_track, float p_time) const { + //this uses a different interpolation scheme + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + Track *track = tracks[p_track]; + ERR_FAIL_COND_V(track->type != TYPE_BEZIER, 0); + + BezierTrack *bt = static_cast<BezierTrack *>(track); + + int len = _find(bt->values, length) + 1; // try to find last key (there may be more past the end) + + if (len <= 0) { + // (-1 or -2 returned originally) (plus one above) + return 0; + } else if (len == 1) { // one key found (0+1), return it + return bt->values[0].value.value; + } + + int idx = _find(bt->values, p_time); + + ERR_FAIL_COND_V(idx == -2, 0); + + //there really is no looping interpolation on bezier + + if (idx < 0) { + return bt->values[0].value.value; + } + + if (idx >= bt->values.size() - 1) { + return bt->values[bt->values.size() - 1].value.value; + } + + float t = p_time - bt->values[idx].time; + + int iterations = 10; + + float low = 0; + float high = bt->values[idx + 1].time - bt->values[idx].time; + float middle = 0; + + Vector2 start(0, bt->values[idx].value.value); + Vector2 start_out = start + bt->values[idx].value.out_handle; + Vector2 end(high, bt->values[idx + 1].value.value); + Vector2 end_in = end + bt->values[idx + 1].value.in_handle; + + //narrow high and low as much as possible + for (int i = 0; i < iterations; i++) { + + middle = (low + high) / 2; + + Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end); + + if (interp.x < t) { + low = middle; + } else { + high = middle; + } + } + + //interpolate the result: + Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end); + Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end); + + float c = (t - low_pos.x) / (high_pos.x - low_pos.x); + + return low_pos.linear_interpolate(high_pos, c).y; +} + +int Animation::audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset, float p_end_offset) { + + print_line("really insert key? "); + ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, -1); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + TKey<AudioKey> k; + k.time = p_time; + k.value.stream = p_stream; + k.value.start_offset = p_start_offset; + if (k.value.start_offset < 0) + k.value.start_offset = 0; + k.value.end_offset = p_end_offset; + if (k.value.end_offset < 0) + k.value.end_offset = 0; + + int key = _insert(p_time, at->values, k); + + emit_changed(); + + return key; +} + +void Animation::audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_AUDIO); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + at->values[p_key].value.stream = p_stream; + + emit_changed(); +} + +void Animation::audio_track_set_key_start_offset(int p_track, int p_key, float p_offset) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_AUDIO); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + if (p_offset < 0) + p_offset = 0; + + at->values[p_key].value.start_offset = p_offset; + + emit_changed(); +} + +void Animation::audio_track_set_key_end_offset(int p_track, int p_key, float p_offset) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_AUDIO); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + if (p_offset < 0) + p_offset = 0; + + at->values[p_key].value.end_offset = p_offset; + + emit_changed(); +} + +RES Animation::audio_track_get_key_stream(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), RES()); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, RES()); + + const AudioTrack *at = static_cast<const AudioTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), RES()); + + return at->values[p_key].value.stream; +} +float Animation::audio_track_get_key_start_offset(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0); + + const AudioTrack *at = static_cast<const AudioTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), 0); + + return at->values[p_key].value.start_offset; +} +float Animation::audio_track_get_key_end_offset(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0); + + const AudioTrack *at = static_cast<const AudioTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), 0); + + return at->values[p_key].value.end_offset; +} + +// + +int Animation::animation_track_insert_key(int p_track, float p_time, const StringName &p_animation) { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, -1); + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + TKey<StringName> k; + k.time = p_time; + k.value = p_animation; + + int key = _insert(p_time, at->values, k); + + emit_changed(); + + return key; +} + +void Animation::animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_ANIMATION); + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + at->values[p_key].value = p_animation; + + emit_changed(); +} + +StringName Animation::animation_track_get_key_animation(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), StringName()); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, StringName()); + + const AnimationTrack *at = static_cast<const AnimationTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), StringName()); + + return at->values[p_key].value; +} + void Animation::set_length(float p_length) { - ERR_FAIL_COND(length < 0); + if (p_length < ANIM_MIN_LENGTH) { + p_length = ANIM_MIN_LENGTH; + } length = p_length; emit_changed(); } @@ -1592,6 +2591,16 @@ void Animation::track_move_down(int p_track) { emit_changed(); } +void Animation::track_swap(int p_track, int p_with_track) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + ERR_FAIL_INDEX(p_with_track, tracks.size()); + if (p_track == p_with_track) + return; + SWAP(tracks[p_track], tracks[p_with_track]); + emit_changed(); +} + void Animation::set_step(float p_step) { step = p_step; @@ -1631,6 +2640,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("track_move_up", "idx"), &Animation::track_move_up); ClassDB::bind_method(D_METHOD("track_move_down", "idx"), &Animation::track_move_down); + ClassDB::bind_method(D_METHOD("track_swap", "idx", "with_idx"), &Animation::track_swap); ClassDB::bind_method(D_METHOD("track_set_imported", "idx", "imported"), &Animation::track_set_imported); ClassDB::bind_method(D_METHOD("track_is_imported", "idx"), &Animation::track_is_imported); @@ -1667,6 +2677,30 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("method_track_get_name", "idx", "key_idx"), &Animation::method_track_get_name); ClassDB::bind_method(D_METHOD("method_track_get_params", "idx", "key_idx"), &Animation::method_track_get_params); + ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track", "time", "value", "in_handle", "out_handle"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2())); + + ClassDB::bind_method(D_METHOD("bezier_track_set_key_value", "idx", "key_idx", "value"), &Animation::bezier_track_set_key_value); + ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "idx", "key_idx", "in_handle"), &Animation::bezier_track_set_key_in_handle); + ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "idx", "key_idx", "out_handle"), &Animation::bezier_track_set_key_out_handle); + + ClassDB::bind_method(D_METHOD("bezier_track_get_key_value", "idx", "key_idx"), &Animation::bezier_track_get_key_value); + ClassDB::bind_method(D_METHOD("bezier_track_get_key_in_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_in_handle); + ClassDB::bind_method(D_METHOD("bezier_track_get_key_out_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_out_handle); + + ClassDB::bind_method(D_METHOD("bezier_track_interpolate", "track", "time"), &Animation::bezier_track_interpolate); + + ClassDB::bind_method(D_METHOD("audio_track_insert_key", "track", "time", "stream", "start_offset", "end_offset"), &Animation::audio_track_insert_key, DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("audio_track_set_key_stream", "idx", "key_idx", "stream"), &Animation::audio_track_set_key_stream); + ClassDB::bind_method(D_METHOD("audio_track_set_key_start_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_start_offset); + ClassDB::bind_method(D_METHOD("audio_track_set_key_end_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_end_offset); + ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "idx", "key_idx"), &Animation::audio_track_get_key_stream); + ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "idx", "key_idx"), &Animation::audio_track_get_key_start_offset); + ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "idx", "key_idx"), &Animation::audio_track_get_key_end_offset); + + ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track", "time", "animation"), &Animation::animation_track_insert_key); + ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation); + ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "idx", "key_idx"), &Animation::animation_track_get_key_animation); + ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length); ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length); @@ -1686,6 +2720,9 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_VALUE); BIND_ENUM_CONSTANT(TYPE_TRANSFORM); BIND_ENUM_CONSTANT(TYPE_METHOD); + BIND_ENUM_CONSTANT(TYPE_BEZIER); + BIND_ENUM_CONSTANT(TYPE_AUDIO); + BIND_ENUM_CONSTANT(TYPE_ANIMATION); BIND_ENUM_CONSTANT(INTERPOLATION_NEAREST); BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR); @@ -1694,6 +2731,7 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS); BIND_ENUM_CONSTANT(UPDATE_DISCRETE); BIND_ENUM_CONSTANT(UPDATE_TRIGGER); + BIND_ENUM_CONSTANT(UPDATE_CAPTURE); } void Animation::clear() { diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 73691a69f2..a41e6ea5d7 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -45,6 +45,9 @@ public: TYPE_VALUE, ///< Set a value in a property, can be interpolated. TYPE_TRANSFORM, ///< Transform a node or a bone. TYPE_METHOD, ///< Call any method on a specific node. + TYPE_BEZIER, ///< Bezier curve + TYPE_AUDIO, + TYPE_ANIMATION, }; enum InterpolationType { @@ -57,6 +60,7 @@ public: UPDATE_CONTINUOUS, UPDATE_DISCRETE, UPDATE_TRIGGER, + UPDATE_CAPTURE, }; @@ -137,6 +141,55 @@ private: MethodTrack() { type = TYPE_METHOD; } }; + /* BEZIER TRACK */ + + struct BezierKey { + Vector2 in_handle; //relative (x always <0) + Vector2 out_handle; //relative (x always >0) + float value; + }; + + struct BezierTrack : public Track { + + Vector<TKey<BezierKey> > values; + + BezierTrack() { + type = TYPE_BEZIER; + } + }; + + /* AUDIO TRACK */ + + struct AudioKey { + RES stream; + float start_offset; //offset from start + float end_offset; //offset from end, if 0 then full length or infinite + AudioKey() { + start_offset = 0; + end_offset = 0; + } + }; + + struct AudioTrack : public Track { + + Vector<TKey<AudioKey> > values; + + AudioTrack() { + type = TYPE_AUDIO; + } + }; + + /* AUDIO TRACK */ + + struct AnimationTrack : public Track { + + Vector<TKey<StringName> > values; + + AnimationTrack() { + type = TYPE_ANIMATION; + } + }; + Vector<Track *> tracks; /* @@ -168,6 +221,9 @@ private: template <class T> _FORCE_INLINE_ T _interpolate(const Vector<TKey<T> > &p_keys, float p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const; + template <class T> + _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const; + _FORCE_INLINE_ void _value_track_get_key_indices_in_range(const ValueTrack *vt, float from_time, float to_time, List<int> *p_indices) const; _FORCE_INLINE_ void _method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const; @@ -238,6 +294,7 @@ public: void track_move_up(int p_track); void track_move_down(int p_track); + void track_swap(int p_track, int p_with_track); void track_set_imported(int p_track, bool p_imported); bool track_is_imported(int p_track) const; @@ -245,7 +302,6 @@ public: void track_set_enabled(int p_track, bool p_enabled); bool track_is_enabled(int p_track) const; - int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3()); void track_insert_key(int p_track, float p_time, const Variant &p_key, float p_transition = 1); void track_set_key_transition(int p_track, int p_key_idx, float p_transition); void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value); @@ -257,10 +313,33 @@ public: float track_get_key_time(int p_track, int p_key_idx) const; float track_get_key_transition(int p_track, int p_key_idx) const; + int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3()); Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const; void track_set_interpolation_type(int p_track, InterpolationType p_interp); InterpolationType track_get_interpolation_type(int p_track) const; + int bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle); + void bezier_track_set_key_value(int p_track, int p_index, float p_value); + void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle); + void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle); + float bezier_track_get_key_value(int p_track, int p_index) const; + Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const; + Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const; + + float bezier_track_interpolate(int p_track, float p_time) const; + + int audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset = 0, float p_end_offset = 0); + void audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream); + void audio_track_set_key_start_offset(int p_track, int p_key, float p_offset); + void audio_track_set_key_end_offset(int p_track, int p_key, float p_offset); + RES audio_track_get_key_stream(int p_track, int p_key) const; + float audio_track_get_key_start_offset(int p_track, int p_key) const; + float audio_track_get_key_end_offset(int p_track, int p_key) const; + + int animation_track_insert_key(int p_track, float p_time, const StringName &p_animation); + void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation); + StringName animation_track_get_key_animation(int p_track, int p_key) const; + void track_set_interpolation_loop_wrap(int p_track, bool p_enable); bool track_get_interpolation_loop_wrap(int p_track) const; @@ -277,6 +356,8 @@ public: void copy_track(int p_track, Ref<Animation> p_to_animation); + void track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const; + void set_length(float p_length); float get_length() const; diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index b77143cd9d..02a9e4d69b 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -524,6 +524,9 @@ String AudioStreamSample::get_stream_name() const { void AudioStreamSample::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamSample::set_data); + ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamSample::get_data); + ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioStreamSample::set_format); ClassDB::bind_method(D_METHOD("get_format"), &AudioStreamSample::get_format); @@ -542,16 +545,13 @@ void AudioStreamSample::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stereo", "stereo"), &AudioStreamSample::set_stereo); ClassDB::bind_method(D_METHOD("is_stereo"), &AudioStreamSample::is_stereo); - ClassDB::bind_method(D_METHOD("_set_data", "data"), &AudioStreamSample::set_data); - ClassDB::bind_method(D_METHOD("_get_data"), &AudioStreamSample::get_data); - + ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data"); ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong"), "set_loop_mode", "get_loop_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_rate"), "set_mix_rate", "get_mix_rate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stereo"), "set_stereo", "is_stereo"); - ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); BIND_ENUM_CONSTANT(FORMAT_8_BITS); BIND_ENUM_CONSTANT(FORMAT_16_BITS); diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 5fd6f6c74d..7f902fc982 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -806,6 +806,87 @@ float Curve2D::get_bake_interval() const { return bake_interval; } +Vector2 Curve2D::get_closest_point(const Vector2 &p_to_point) const { + // Brute force method + + if (baked_cache_dirty) + _bake(); + + //validate// + int pc = baked_point_cache.size(); + if (pc == 0) { + ERR_EXPLAIN("No points in Curve2D"); + ERR_FAIL_COND_V(pc == 0, Vector2()); + } + + if (pc == 1) + return baked_point_cache.get(0); + + PoolVector2Array::Read r = baked_point_cache.read(); + + Vector2 nearest; + float nearest_dist = -1.0f; + + for (int i = 0; i < pc - 1; i++) { + Vector2 origin = r[i]; + Vector2 direction = (r[i + 1] - origin) / bake_interval; + + float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval); + Vector2 proj = origin + direction * d; + + float dist = proj.distance_squared_to(p_to_point); + + if (nearest_dist < 0.0f || dist < nearest_dist) { + nearest = proj; + nearest_dist = dist; + } + } + + return nearest; +} + +float Curve2D::get_closest_offset(const Vector2 &p_to_point) const { + // Brute force method + + if (baked_cache_dirty) + _bake(); + + //validate// + int pc = baked_point_cache.size(); + if (pc == 0) { + ERR_EXPLAIN("No points in Curve2D"); + ERR_FAIL_COND_V(pc == 0, 0.0f); + } + + if (pc == 1) + return 0.0f; + + PoolVector2Array::Read r = baked_point_cache.read(); + + float nearest = 0.0f; + float nearest_dist = -1.0f; + float offset = 0.0f; + + for (int i = 0; i < pc - 1; i++) { + Vector2 origin = r[i]; + Vector2 direction = (r[i + 1] - origin) / bake_interval; + + float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval); + Vector2 proj = origin + direction * d; + + float dist = proj.distance_squared_to(p_to_point); + + if (nearest_dist < 0.0f || dist < nearest_dist) { + nearest = offset + d; + nearest_dist = dist; + } + + offset += bake_interval; + } + + return nearest; +} + Dictionary Curve2D::_get_data() const { Dictionary dc; @@ -909,6 +990,8 @@ void Curve2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve2D::get_baked_length); ClassDB::bind_method(D_METHOD("interpolate_baked", "offset", "cubic"), &Curve2D::interpolate_baked, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve2D::get_baked_points); + ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve2D::get_closest_point); + ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve2D::get_closest_offset); ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve2D::tessellate, DEFVAL(5), DEFVAL(4)); ClassDB::bind_method(D_METHOD("_get_data"), &Curve2D::_get_data); @@ -1086,6 +1169,7 @@ void Curve3D::_bake() const { if (points.size() == 0) { baked_point_cache.resize(0); baked_tilt_cache.resize(0); + baked_up_vector_cache.resize(0); return; } @@ -1095,6 +1179,14 @@ void Curve3D::_bake() const { baked_point_cache.set(0, points[0].pos); baked_tilt_cache.resize(1); baked_tilt_cache.set(0, points[0].tilt); + + if (up_vector_enabled) { + + baked_up_vector_cache.resize(1); + baked_up_vector_cache.set(0, Vector3(0, 1, 0)); + } else + baked_up_vector_cache.resize(0); + return; } @@ -1164,10 +1256,51 @@ void Curve3D::_bake() const { baked_tilt_cache.resize(pointlist.size()); PoolRealArray::Write wt = baked_tilt_cache.write(); + baked_up_vector_cache.resize(up_vector_enabled ? pointlist.size() : 0); + PoolVector3Array::Write up_write = baked_up_vector_cache.write(); + + Vector3 sideways; + Vector3 up; + Vector3 forward; + + Vector3 prev_sideways = Vector3(1, 0, 0); + Vector3 prev_up = Vector3(0, 1, 0); + Vector3 prev_forward = Vector3(0, 0, 1); + for (List<Plane>::Element *E = pointlist.front(); E; E = E->next()) { w[idx] = E->get().normal; wt[idx] = E->get().d; + + if (!up_vector_enabled) { + idx++; + continue; + } + + forward = idx > 0 ? (w[idx] - w[idx - 1]).normalized() : prev_forward; + + float y_dot = prev_up.dot(forward); + + if (y_dot > (1.0f - CMP_EPSILON)) { + sideways = prev_sideways; + up = -prev_forward; + } else if (y_dot < -(1.0f - CMP_EPSILON)) { + sideways = prev_sideways; + up = prev_forward; + } else { + sideways = prev_up.cross(forward).normalized(); + up = forward.cross(sideways).normalized(); + } + + if (idx == 1) + up_write[0] = up; + + up_write[idx] = up; + + prev_sideways = sideways; + prev_up = up; + prev_forward = forward; + idx++; } } @@ -1260,6 +1393,53 @@ float Curve3D::interpolate_baked_tilt(float p_offset) const { return Math::lerp(r[idx], r[idx + 1], frac); } +Vector3 Curve3D::interpolate_baked_up_vector(float p_offset, bool p_apply_tilt) const { + + if (baked_cache_dirty) + _bake(); + + //validate// + // curve may not have baked up vectors + int count = baked_up_vector_cache.size(); + if (count == 0) { + ERR_EXPLAIN("No up vectors in Curve3D"); + ERR_FAIL_COND_V(count == 0, Vector3(0, 1, 0)); + } + + if (count == 1) + return baked_up_vector_cache.get(0); + + PoolVector3Array::Read r = baked_up_vector_cache.read(); + PoolVector3Array::Read rp = baked_point_cache.read(); + PoolRealArray::Read rt = baked_tilt_cache.read(); + + float offset = CLAMP(p_offset, 0.0f, baked_max_ofs); + + int idx = Math::floor((double)offset / (double)bake_interval); + float frac = Math::fmod(offset, bake_interval) / bake_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]; + + if (p_apply_tilt) { + up.rotate(forward, rt[idx]); + up1.rotate(idx + 2 >= count ? forward : (rp[idx + 2] - rp[idx + 1]).normalized(), rt[idx + 1]); + } + + Vector3 axis = up.cross(up1); + + if (axis.length_squared() < CMP_EPSILON2) + axis = forward; + else + axis.normalize(); + + return up.rotated(axis, up.angle_to(up1) * frac); +} + PoolVector3Array Curve3D::get_baked_points() const { if (baked_cache_dirty) @@ -1276,6 +1456,95 @@ PoolRealArray Curve3D::get_baked_tilts() const { return baked_tilt_cache; } +PoolVector3Array Curve3D::get_baked_up_vectors() const { + + if (baked_cache_dirty) + _bake(); + + return baked_up_vector_cache; +} + +Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const { + // Brute force method + + if (baked_cache_dirty) + _bake(); + + //validate// + int pc = baked_point_cache.size(); + if (pc == 0) { + ERR_EXPLAIN("No points in Curve3D"); + ERR_FAIL_COND_V(pc == 0, Vector3()); + } + + if (pc == 1) + return baked_point_cache.get(0); + + PoolVector3Array::Read r = baked_point_cache.read(); + + Vector3 nearest; + float nearest_dist = -1.0f; + + for (int i = 0; i < pc - 1; i++) { + Vector3 origin = r[i]; + Vector3 direction = (r[i + 1] - origin) / bake_interval; + + float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval); + Vector3 proj = origin + direction * d; + + float dist = proj.distance_squared_to(p_to_point); + + if (nearest_dist < 0.0f || dist < nearest_dist) { + nearest = proj; + nearest_dist = dist; + } + } + + return nearest; +} + +float Curve3D::get_closest_offset(const Vector3 &p_to_point) const { + // Brute force method + + if (baked_cache_dirty) + _bake(); + + //validate// + int pc = baked_point_cache.size(); + if (pc == 0) { + ERR_EXPLAIN("No points in Curve3D"); + ERR_FAIL_COND_V(pc == 0, 0.0f); + } + + if (pc == 1) + return 0.0f; + + PoolVector3Array::Read r = baked_point_cache.read(); + + float nearest = 0.0f; + float nearest_dist = -1.0f; + float offset = 0.0f; + + for (int i = 0; i < pc - 1; i++) { + Vector3 origin = r[i]; + Vector3 direction = (r[i + 1] - origin) / bake_interval; + + float d = CLAMP((p_to_point - origin).dot(direction), 0.0f, bake_interval); + Vector3 proj = origin + direction * d; + + float dist = proj.distance_squared_to(p_to_point); + + if (nearest_dist < 0.0f || dist < nearest_dist) { + nearest = offset + d; + nearest_dist = dist; + } + + offset += bake_interval; + } + + return nearest; +} + void Curve3D::set_bake_interval(float p_tolerance) { bake_interval = p_tolerance; @@ -1288,6 +1557,18 @@ float Curve3D::get_bake_interval() const { return bake_interval; } +void Curve3D::set_up_vector_enabled(bool p_enable) { + + up_vector_enabled = p_enable; + baked_cache_dirty = true; + emit_signal(CoreStringNames::get_singleton()->changed); +} + +bool Curve3D::is_up_vector_enabled() const { + + return up_vector_enabled; +} + Dictionary Curve3D::_get_data() const { Dictionary dc; @@ -1399,11 +1680,17 @@ void Curve3D::_bind_methods() { //ClassDB::bind_method(D_METHOD("bake","subdivs"),&Curve3D::bake,DEFVAL(10)); ClassDB::bind_method(D_METHOD("set_bake_interval", "distance"), &Curve3D::set_bake_interval); ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve3D::get_bake_interval); + ClassDB::bind_method(D_METHOD("set_up_vector_enabled", "enable"), &Curve3D::set_up_vector_enabled); + ClassDB::bind_method(D_METHOD("is_up_vector_enabled"), &Curve3D::is_up_vector_enabled); ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve3D::get_baked_length); ClassDB::bind_method(D_METHOD("interpolate_baked", "offset", "cubic"), &Curve3D::interpolate_baked, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("interpolate_baked_up_vector", "offset", "apply_tilt"), &Curve3D::interpolate_baked_up_vector, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve3D::get_baked_points); ClassDB::bind_method(D_METHOD("get_baked_tilts"), &Curve3D::get_baked_tilts); + ClassDB::bind_method(D_METHOD("get_baked_up_vectors"), &Curve3D::get_baked_up_vectors); + ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve3D::get_closest_point); + ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve3D::get_closest_offset); ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve3D::tessellate, DEFVAL(5), DEFVAL(4)); ClassDB::bind_method(D_METHOD("_get_data"), &Curve3D::_get_data); @@ -1411,6 +1698,9 @@ void Curve3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_interval", PROPERTY_HINT_RANGE, "0.01,512,0.01"), "set_bake_interval", "get_bake_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); + + ADD_GROUP("Up Vector", "up_vector_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "up_vector_enabled"), "set_up_vector_enabled", "is_up_vector_enabled"); } Curve3D::Curve3D() { @@ -1420,4 +1710,5 @@ Curve3D::Curve3D() { add_point(Vector3(0,2,0)); add_point(Vector3(0,3,5));*/ bake_interval = 0.2; + up_vector_enabled = true; } diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 4f55e4055c..9cb12a4345 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -199,6 +199,8 @@ public: float get_baked_length() const; Vector2 interpolate_baked(float p_offset, bool p_cubic = false) const; PoolVector2Array get_baked_points() const; //useful for going through + Vector2 get_closest_point(const Vector2 &p_to_point) const; + float get_closest_offset(const Vector2 &p_to_point) const; PoolVector2Array tessellate(int p_max_stages = 5, float p_tolerance = 4) const; //useful for display @@ -230,11 +232,13 @@ class Curve3D : public Resource { mutable bool baked_cache_dirty; mutable PoolVector3Array baked_point_cache; mutable PoolRealArray baked_tilt_cache; + mutable PoolVector3Array baked_up_vector_cache; mutable float baked_max_ofs; void _bake() const; float bake_interval; + bool up_vector_enabled; void _bake_segment3d(Map<float, Vector3> &r_bake, float p_begin, float p_end, const Vector3 &p_a, const Vector3 &p_out, const Vector3 &p_b, const Vector3 &p_in, int p_depth, int p_max_depth, float p_tol) const; Dictionary _get_data() const; @@ -262,12 +266,18 @@ public: void set_bake_interval(float p_tolerance); float get_bake_interval() const; + void set_up_vector_enabled(bool p_enable); + bool is_up_vector_enabled() const; float get_baked_length() const; Vector3 interpolate_baked(float p_offset, bool p_cubic = false) const; float interpolate_baked_tilt(float p_offset) const; + Vector3 interpolate_baked_up_vector(float p_offset, bool p_apply_tilt = false) const; PoolVector3Array get_baked_points() const; //useful for going through PoolRealArray get_baked_tilts() const; //useful for going through + PoolVector3Array get_baked_up_vectors() const; + Vector3 get_closest_point(const Vector3 &p_to_point) const; + float get_closest_offset(const Vector3 &p_to_point) const; PoolVector3Array tessellate(int p_max_stages = 5, float p_tolerance = 4) const; //useful for display diff --git a/scene/resources/default_theme/arrow_down.png b/scene/resources/default_theme/arrow_down.png Binary files differindex fc837d120a..bfb87a4761 100644 --- a/scene/resources/default_theme/arrow_down.png +++ b/scene/resources/default_theme/arrow_down.png diff --git a/scene/resources/default_theme/arrow_right.png b/scene/resources/default_theme/arrow_right.png Binary files differindex ebe6e26ace..1e4c8e5529 100644 --- a/scene/resources/default_theme/arrow_right.png +++ b/scene/resources/default_theme/arrow_right.png diff --git a/scene/resources/default_theme/background.png b/scene/resources/default_theme/background.png Binary files differindex 03cfab1de3..6c5f43e3ce 100644 --- a/scene/resources/default_theme/background.png +++ b/scene/resources/default_theme/background.png diff --git a/scene/resources/default_theme/base_green.png b/scene/resources/default_theme/base_green.png Binary files differindex d03d6f08d8..03a5b313d7 100644 --- a/scene/resources/default_theme/base_green.png +++ b/scene/resources/default_theme/base_green.png diff --git a/scene/resources/default_theme/button_disabled.png b/scene/resources/default_theme/button_disabled.png Binary files differindex d75e76989d..708748dfe9 100644 --- a/scene/resources/default_theme/button_disabled.png +++ b/scene/resources/default_theme/button_disabled.png diff --git a/scene/resources/default_theme/button_focus.png b/scene/resources/default_theme/button_focus.png Binary files differindex 44e354be95..70e16b953b 100644 --- a/scene/resources/default_theme/button_focus.png +++ b/scene/resources/default_theme/button_focus.png diff --git a/scene/resources/default_theme/button_hover.png b/scene/resources/default_theme/button_hover.png Binary files differindex 6e609f435f..ef226e3caf 100644 --- a/scene/resources/default_theme/button_hover.png +++ b/scene/resources/default_theme/button_hover.png diff --git a/scene/resources/default_theme/button_normal.png b/scene/resources/default_theme/button_normal.png Binary files differindex 92482aaf28..7d0bd16221 100644 --- a/scene/resources/default_theme/button_normal.png +++ b/scene/resources/default_theme/button_normal.png diff --git a/scene/resources/default_theme/checked.png b/scene/resources/default_theme/checked.png Binary files differindex 93e291a29e..bde031b6a2 100644 --- a/scene/resources/default_theme/checked.png +++ b/scene/resources/default_theme/checked.png diff --git a/scene/resources/default_theme/checker_bg.png b/scene/resources/default_theme/checker_bg.png Binary files differindex f58dfed29c..3eff2f0e08 100644 --- a/scene/resources/default_theme/checker_bg.png +++ b/scene/resources/default_theme/checker_bg.png diff --git a/scene/resources/default_theme/close.png b/scene/resources/default_theme/close.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/close.png +++ b/scene/resources/default_theme/close.png diff --git a/scene/resources/default_theme/close_hl.png b/scene/resources/default_theme/close_hl.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/close_hl.png +++ b/scene/resources/default_theme/close_hl.png diff --git a/scene/resources/default_theme/color_picker_sample.png b/scene/resources/default_theme/color_picker_sample.png Binary files differindex b145a3e384..e6ec28d307 100644 --- a/scene/resources/default_theme/color_picker_sample.png +++ b/scene/resources/default_theme/color_picker_sample.png diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 3e244aa8f8..702953fa40 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -415,6 +415,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color", "Label", Color(1, 1, 1)); theme->set_color("font_color_shadow", "Label", Color(0, 0, 0, 0)); + theme->set_color("font_outline_modulate", "Label", Color(1, 1, 1)); theme->set_constant("shadow_offset_x", "Label", 1 * scale); theme->set_constant("shadow_offset_y", "Label", 1 * scale); @@ -543,6 +544,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("updown", "SpinBox", make_icon(spinbox_updown_png)); + //scroll container + Ref<StyleBoxEmpty> empty; + empty.instance(); + theme->set_stylebox("bg", "ScrollContainer", empty); + // WindowDialog theme->set_stylebox("panel", "WindowDialog", sb_expand(make_stylebox(popup_window_png, 10, 26, 10, 8), 8, 24, 8, 6)); @@ -582,6 +588,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("checked", "PopupMenu", make_icon(checked_png)); theme->set_icon("unchecked", "PopupMenu", make_icon(unchecked_png)); + theme->set_icon("radio_checked", "PopupMenu", make_icon(radio_checked_png)); + theme->set_icon("radio_unchecked", "PopupMenu", make_icon(radio_unchecked_png)); theme->set_icon("submenu", "PopupMenu", make_icon(submenu_png)); theme->set_font("font", "PopupMenu", default_font); @@ -816,6 +824,12 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color_selected", "RichTextLabel", font_color_selection); theme->set_color("selection_color", "RichTextLabel", Color(0.1, 0.1, 1, 0.8)); + theme->set_color("font_color_shadow", "RichTextLabel", Color(0, 0, 0, 0)); + + theme->set_constant("shadow_offset_x", "RichTextLabel", 1 * scale); + theme->set_constant("shadow_offset_y", "RichTextLabel", 1 * scale); + theme->set_constant("shadow_as_outline", "RichTextLabel", 0 * scale); + theme->set_constant("line_separation", "RichTextLabel", 1 * scale); theme->set_constant("table_hseparation", "RichTextLabel", 3 * scale); theme->set_constant("table_vseparation", "RichTextLabel", 3 * scale); @@ -830,7 +844,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("separation", "HBoxContainer", 4 * scale); theme->set_constant("separation", "VBoxContainer", 4 * scale); - theme->set_constant("margin_left", "MarginContainer", 8 * scale); + theme->set_constant("margin_left", "MarginContainer", 0 * scale); theme->set_constant("margin_top", "MarginContainer", 0 * scale); theme->set_constant("margin_right", "MarginContainer", 0 * scale); theme->set_constant("margin_bottom", "MarginContainer", 0 * scale); @@ -860,6 +874,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 4, 4, 4, 5)); theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05)); theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2)); + theme->set_color("activity", "GraphEdit", Color(1, 1, 1)); theme->set_constant("bezier_len_pos", "GraphEdit", 80 * scale); theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale); diff --git a/scene/resources/default_theme/dosfont.png b/scene/resources/default_theme/dosfont.png Binary files differindex 814d2e9060..e2739b94ea 100644 --- a/scene/resources/default_theme/dosfont.png +++ b/scene/resources/default_theme/dosfont.png diff --git a/scene/resources/default_theme/dropdown.png b/scene/resources/default_theme/dropdown.png Binary files differindex 3a6a2ed778..b5d9ffbbb4 100644 --- a/scene/resources/default_theme/dropdown.png +++ b/scene/resources/default_theme/dropdown.png diff --git a/scene/resources/default_theme/error_icon.png b/scene/resources/default_theme/error_icon.png Binary files differindex f291362350..7741d00749 100644 --- a/scene/resources/default_theme/error_icon.png +++ b/scene/resources/default_theme/error_icon.png diff --git a/scene/resources/default_theme/focus.png b/scene/resources/default_theme/focus.png Binary files differindex 5d37028f2d..f51ea89e8f 100644 --- a/scene/resources/default_theme/focus.png +++ b/scene/resources/default_theme/focus.png diff --git a/scene/resources/default_theme/frame_focus.png b/scene/resources/default_theme/frame_focus.png Binary files differindex 9170db38ed..1b24ba47d8 100644 --- a/scene/resources/default_theme/frame_focus.png +++ b/scene/resources/default_theme/frame_focus.png diff --git a/scene/resources/default_theme/full_panel_bg.png b/scene/resources/default_theme/full_panel_bg.png Binary files differindex 7f02dc7259..85f753cc13 100644 --- a/scene/resources/default_theme/full_panel_bg.png +++ b/scene/resources/default_theme/full_panel_bg.png diff --git a/scene/resources/default_theme/graph_node_breakpoint.png b/scene/resources/default_theme/graph_node_breakpoint.png Binary files differindex 0e36f31bd4..e18c6f42e1 100644 --- a/scene/resources/default_theme/graph_node_breakpoint.png +++ b/scene/resources/default_theme/graph_node_breakpoint.png diff --git a/scene/resources/default_theme/graph_node_close.png b/scene/resources/default_theme/graph_node_close.png Binary files differindex 144a8b9c4c..5c962ae1c6 100644 --- a/scene/resources/default_theme/graph_node_close.png +++ b/scene/resources/default_theme/graph_node_close.png diff --git a/scene/resources/default_theme/graph_node_comment.png b/scene/resources/default_theme/graph_node_comment.png Binary files differindex f2d6daa259..cdec1d1eac 100644 --- a/scene/resources/default_theme/graph_node_comment.png +++ b/scene/resources/default_theme/graph_node_comment.png diff --git a/scene/resources/default_theme/graph_node_comment_focus.png b/scene/resources/default_theme/graph_node_comment_focus.png Binary files differindex a4b7b5a618..472a6b6f53 100644 --- a/scene/resources/default_theme/graph_node_comment_focus.png +++ b/scene/resources/default_theme/graph_node_comment_focus.png diff --git a/scene/resources/default_theme/graph_node_default.png b/scene/resources/default_theme/graph_node_default.png Binary files differindex e3a220301f..359bbdc205 100644 --- a/scene/resources/default_theme/graph_node_default.png +++ b/scene/resources/default_theme/graph_node_default.png diff --git a/scene/resources/default_theme/graph_node_default_focus.png b/scene/resources/default_theme/graph_node_default_focus.png Binary files differindex 9972b07593..204dd16ac0 100644 --- a/scene/resources/default_theme/graph_node_default_focus.png +++ b/scene/resources/default_theme/graph_node_default_focus.png diff --git a/scene/resources/default_theme/graph_node_position.png b/scene/resources/default_theme/graph_node_position.png Binary files differindex 7ec15e2ff4..24c2759be6 100644 --- a/scene/resources/default_theme/graph_node_position.png +++ b/scene/resources/default_theme/graph_node_position.png diff --git a/scene/resources/default_theme/graph_node_selected.png b/scene/resources/default_theme/graph_node_selected.png Binary files differindex f76c9703dd..cc4eb7f753 100644 --- a/scene/resources/default_theme/graph_node_selected.png +++ b/scene/resources/default_theme/graph_node_selected.png diff --git a/scene/resources/default_theme/graph_port.png b/scene/resources/default_theme/graph_port.png Binary files differindex 9d5082cfdb..f33ae3baf3 100644 --- a/scene/resources/default_theme/graph_port.png +++ b/scene/resources/default_theme/graph_port.png diff --git a/scene/resources/default_theme/hseparator.png b/scene/resources/default_theme/hseparator.png Binary files differindex 99609ac118..d4fd71ace5 100644 --- a/scene/resources/default_theme/hseparator.png +++ b/scene/resources/default_theme/hseparator.png diff --git a/scene/resources/default_theme/hslider_bg.png b/scene/resources/default_theme/hslider_bg.png Binary files differindex 9c2a2df62a..b402bd370d 100644 --- a/scene/resources/default_theme/hslider_bg.png +++ b/scene/resources/default_theme/hslider_bg.png diff --git a/scene/resources/default_theme/hslider_grabber.png b/scene/resources/default_theme/hslider_grabber.png Binary files differindex 2acd33879a..d273b491ee 100644 --- a/scene/resources/default_theme/hslider_grabber.png +++ b/scene/resources/default_theme/hslider_grabber.png diff --git a/scene/resources/default_theme/hslider_grabber_disabled.png b/scene/resources/default_theme/hslider_grabber_disabled.png Binary files differindex 0d75182b8f..dddd1a468e 100644 --- a/scene/resources/default_theme/hslider_grabber_disabled.png +++ b/scene/resources/default_theme/hslider_grabber_disabled.png diff --git a/scene/resources/default_theme/hslider_grabber_hl.png b/scene/resources/default_theme/hslider_grabber_hl.png Binary files differindex f8a011e64b..e3defb3610 100644 --- a/scene/resources/default_theme/hslider_grabber_hl.png +++ b/scene/resources/default_theme/hslider_grabber_hl.png diff --git a/scene/resources/default_theme/hslider_tick.png b/scene/resources/default_theme/hslider_tick.png Binary files differindex f7afd78529..1ba19c37a1 100644 --- a/scene/resources/default_theme/hslider_tick.png +++ b/scene/resources/default_theme/hslider_tick.png diff --git a/scene/resources/default_theme/hsplit_bg.png b/scene/resources/default_theme/hsplit_bg.png Binary files differindex 7dd1d48b29..a5749f6d5c 100644 --- a/scene/resources/default_theme/hsplit_bg.png +++ b/scene/resources/default_theme/hsplit_bg.png diff --git a/scene/resources/default_theme/hsplitter.png b/scene/resources/default_theme/hsplitter.png Binary files differindex 71a3914d7e..2287753c9d 100644 --- a/scene/resources/default_theme/hsplitter.png +++ b/scene/resources/default_theme/hsplitter.png diff --git a/scene/resources/default_theme/icon_add.png b/scene/resources/default_theme/icon_add.png Binary files differindex fa675045bc..eccb69b363 100644 --- a/scene/resources/default_theme/icon_add.png +++ b/scene/resources/default_theme/icon_add.png diff --git a/scene/resources/default_theme/icon_close.png b/scene/resources/default_theme/icon_close.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/icon_close.png +++ b/scene/resources/default_theme/icon_close.png diff --git a/scene/resources/default_theme/icon_color_pick.png b/scene/resources/default_theme/icon_color_pick.png Binary files differindex 15679a9558..46953febb8 100644 --- a/scene/resources/default_theme/icon_color_pick.png +++ b/scene/resources/default_theme/icon_color_pick.png diff --git a/scene/resources/default_theme/icon_folder.png b/scene/resources/default_theme/icon_folder.png Binary files differindex cc05e98ebb..d1b308e88d 100644 --- a/scene/resources/default_theme/icon_folder.png +++ b/scene/resources/default_theme/icon_folder.png diff --git a/scene/resources/default_theme/icon_parent_folder.png b/scene/resources/default_theme/icon_parent_folder.png Binary files differindex 47fee1ad81..35d218722e 100644 --- a/scene/resources/default_theme/icon_parent_folder.png +++ b/scene/resources/default_theme/icon_parent_folder.png diff --git a/scene/resources/default_theme/icon_play.png b/scene/resources/default_theme/icon_play.png Binary files differindex 864e4e4fb9..b9ed6e6d5b 100644 --- a/scene/resources/default_theme/icon_play.png +++ b/scene/resources/default_theme/icon_play.png diff --git a/scene/resources/default_theme/icon_reload.png b/scene/resources/default_theme/icon_reload.png Binary files differindex 9303fabb9c..bec5f3f4f9 100644 --- a/scene/resources/default_theme/icon_reload.png +++ b/scene/resources/default_theme/icon_reload.png diff --git a/scene/resources/default_theme/icon_snap_grid.png b/scene/resources/default_theme/icon_snap_grid.png Binary files differindex 44db9bdfdc..0680317d86 100644 --- a/scene/resources/default_theme/icon_snap_grid.png +++ b/scene/resources/default_theme/icon_snap_grid.png diff --git a/scene/resources/default_theme/icon_stop.png b/scene/resources/default_theme/icon_stop.png Binary files differindex 1f194d0e14..0c1371ceb9 100644 --- a/scene/resources/default_theme/icon_stop.png +++ b/scene/resources/default_theme/icon_stop.png diff --git a/scene/resources/default_theme/icon_zoom_less.png b/scene/resources/default_theme/icon_zoom_less.png Binary files differindex 888ddc995d..03119c60ca 100644 --- a/scene/resources/default_theme/icon_zoom_less.png +++ b/scene/resources/default_theme/icon_zoom_less.png diff --git a/scene/resources/default_theme/icon_zoom_more.png b/scene/resources/default_theme/icon_zoom_more.png Binary files differindex fa675045bc..31467ec3de 100644 --- a/scene/resources/default_theme/icon_zoom_more.png +++ b/scene/resources/default_theme/icon_zoom_more.png diff --git a/scene/resources/default_theme/icon_zoom_reset.png b/scene/resources/default_theme/icon_zoom_reset.png Binary files differindex 953ae47d24..cac68c09fa 100644 --- a/scene/resources/default_theme/icon_zoom_reset.png +++ b/scene/resources/default_theme/icon_zoom_reset.png diff --git a/scene/resources/default_theme/line_edit.png b/scene/resources/default_theme/line_edit.png Binary files differindex bf2b91f1be..2b0c506f34 100644 --- a/scene/resources/default_theme/line_edit.png +++ b/scene/resources/default_theme/line_edit.png diff --git a/scene/resources/default_theme/line_edit_disabled.png b/scene/resources/default_theme/line_edit_disabled.png Binary files differindex a0fa505e4c..69d78febd9 100644 --- a/scene/resources/default_theme/line_edit_disabled.png +++ b/scene/resources/default_theme/line_edit_disabled.png diff --git a/scene/resources/default_theme/line_edit_focus.png b/scene/resources/default_theme/line_edit_focus.png Binary files differindex e66d7b60e3..1d74b74068 100644 --- a/scene/resources/default_theme/line_edit_focus.png +++ b/scene/resources/default_theme/line_edit_focus.png diff --git a/scene/resources/default_theme/logo.png b/scene/resources/default_theme/logo.png Binary files differindex 2161402438..d0ef9d8aa7 100644 --- a/scene/resources/default_theme/logo.png +++ b/scene/resources/default_theme/logo.png diff --git a/scene/resources/default_theme/mini_checkerboard.png b/scene/resources/default_theme/mini_checkerboard.png Binary files differindex 3e53183847..d8279bda80 100644 --- a/scene/resources/default_theme/mini_checkerboard.png +++ b/scene/resources/default_theme/mini_checkerboard.png diff --git a/scene/resources/default_theme/option_arrow.png b/scene/resources/default_theme/option_arrow.png Binary files differindex 007de16bfa..40590fd60a 100644 --- a/scene/resources/default_theme/option_arrow.png +++ b/scene/resources/default_theme/option_arrow.png diff --git a/scene/resources/default_theme/option_button_disabled.png b/scene/resources/default_theme/option_button_disabled.png Binary files differindex ce727d56e1..1961b673cd 100644 --- a/scene/resources/default_theme/option_button_disabled.png +++ b/scene/resources/default_theme/option_button_disabled.png diff --git a/scene/resources/default_theme/option_button_focus.png b/scene/resources/default_theme/option_button_focus.png Binary files differindex c76d91287e..402670f9a2 100644 --- a/scene/resources/default_theme/option_button_focus.png +++ b/scene/resources/default_theme/option_button_focus.png diff --git a/scene/resources/default_theme/option_button_hover.png b/scene/resources/default_theme/option_button_hover.png Binary files differindex fd1e987ceb..826fe1c9ca 100644 --- a/scene/resources/default_theme/option_button_hover.png +++ b/scene/resources/default_theme/option_button_hover.png diff --git a/scene/resources/default_theme/option_button_normal.png b/scene/resources/default_theme/option_button_normal.png Binary files differindex 9d7fb98d1c..2dadb40338 100644 --- a/scene/resources/default_theme/option_button_normal.png +++ b/scene/resources/default_theme/option_button_normal.png diff --git a/scene/resources/default_theme/option_button_pressed.png b/scene/resources/default_theme/option_button_pressed.png Binary files differindex 28b1d93468..68796f9d85 100644 --- a/scene/resources/default_theme/option_button_pressed.png +++ b/scene/resources/default_theme/option_button_pressed.png diff --git a/scene/resources/default_theme/panel_bg.png b/scene/resources/default_theme/panel_bg.png Binary files differindex 320819ad6d..b496e2177e 100644 --- a/scene/resources/default_theme/panel_bg.png +++ b/scene/resources/default_theme/panel_bg.png diff --git a/scene/resources/default_theme/popup_bg.png b/scene/resources/default_theme/popup_bg.png Binary files differindex 63f5994441..023029f936 100644 --- a/scene/resources/default_theme/popup_bg.png +++ b/scene/resources/default_theme/popup_bg.png diff --git a/scene/resources/default_theme/popup_bg_disabled.png b/scene/resources/default_theme/popup_bg_disabled.png Binary files differindex 611d949380..8eab5f27bc 100644 --- a/scene/resources/default_theme/popup_bg_disabled.png +++ b/scene/resources/default_theme/popup_bg_disabled.png diff --git a/scene/resources/default_theme/popup_checked.png b/scene/resources/default_theme/popup_checked.png Binary files differindex a24e0543c0..b7b05640e1 100644 --- a/scene/resources/default_theme/popup_checked.png +++ b/scene/resources/default_theme/popup_checked.png diff --git a/scene/resources/default_theme/popup_hover.png b/scene/resources/default_theme/popup_hover.png Binary files differindex 85d4e48475..bdb6ae8bd0 100644 --- a/scene/resources/default_theme/popup_hover.png +++ b/scene/resources/default_theme/popup_hover.png diff --git a/scene/resources/default_theme/popup_unchecked.png b/scene/resources/default_theme/popup_unchecked.png Binary files differindex c1137e6fbf..ff922335c3 100644 --- a/scene/resources/default_theme/popup_unchecked.png +++ b/scene/resources/default_theme/popup_unchecked.png diff --git a/scene/resources/default_theme/popup_window.png b/scene/resources/default_theme/popup_window.png Binary files differindex 59362a8ffd..174a29ef45 100644 --- a/scene/resources/default_theme/popup_window.png +++ b/scene/resources/default_theme/popup_window.png diff --git a/scene/resources/default_theme/progress_bar.png b/scene/resources/default_theme/progress_bar.png Binary files differindex bf81e3adea..057557e567 100644 --- a/scene/resources/default_theme/progress_bar.png +++ b/scene/resources/default_theme/progress_bar.png diff --git a/scene/resources/default_theme/progress_fill.png b/scene/resources/default_theme/progress_fill.png Binary files differindex 3a34dfdda6..e39bb2a021 100644 --- a/scene/resources/default_theme/progress_fill.png +++ b/scene/resources/default_theme/progress_fill.png diff --git a/scene/resources/default_theme/radio_checked.png b/scene/resources/default_theme/radio_checked.png Binary files differindex 95d472022f..0ce575c15f 100644 --- a/scene/resources/default_theme/radio_checked.png +++ b/scene/resources/default_theme/radio_checked.png diff --git a/scene/resources/default_theme/radio_unchecked.png b/scene/resources/default_theme/radio_unchecked.png Binary files differindex 7f0535c3a4..fe5bcf6ab1 100644 --- a/scene/resources/default_theme/radio_unchecked.png +++ b/scene/resources/default_theme/radio_unchecked.png diff --git a/scene/resources/default_theme/reference_border.png b/scene/resources/default_theme/reference_border.png Binary files differindex 96219676bf..6a680f393c 100644 --- a/scene/resources/default_theme/reference_border.png +++ b/scene/resources/default_theme/reference_border.png diff --git a/scene/resources/default_theme/scroll_bg.png b/scene/resources/default_theme/scroll_bg.png Binary files differindex cefadb2c08..fb151a48b1 100644 --- a/scene/resources/default_theme/scroll_bg.png +++ b/scene/resources/default_theme/scroll_bg.png diff --git a/scene/resources/default_theme/scroll_button_down.png b/scene/resources/default_theme/scroll_button_down.png Binary files differindex caeac9b286..1df4ef5b6b 100644 --- a/scene/resources/default_theme/scroll_button_down.png +++ b/scene/resources/default_theme/scroll_button_down.png diff --git a/scene/resources/default_theme/scroll_button_down_hl.png b/scene/resources/default_theme/scroll_button_down_hl.png Binary files differindex 48036e0297..ba79087393 100644 --- a/scene/resources/default_theme/scroll_button_down_hl.png +++ b/scene/resources/default_theme/scroll_button_down_hl.png diff --git a/scene/resources/default_theme/scroll_button_left.png b/scene/resources/default_theme/scroll_button_left.png Binary files differindex 3b50938d97..e430cb4673 100644 --- a/scene/resources/default_theme/scroll_button_left.png +++ b/scene/resources/default_theme/scroll_button_left.png diff --git a/scene/resources/default_theme/scroll_button_left_hl.png b/scene/resources/default_theme/scroll_button_left_hl.png Binary files differindex b3d348c24f..2a6ef17a34 100644 --- a/scene/resources/default_theme/scroll_button_left_hl.png +++ b/scene/resources/default_theme/scroll_button_left_hl.png diff --git a/scene/resources/default_theme/scroll_button_right.png b/scene/resources/default_theme/scroll_button_right.png Binary files differindex 1c622a41ad..4f61687aa4 100644 --- a/scene/resources/default_theme/scroll_button_right.png +++ b/scene/resources/default_theme/scroll_button_right.png diff --git a/scene/resources/default_theme/scroll_button_right_hl.png b/scene/resources/default_theme/scroll_button_right_hl.png Binary files differindex 108796ca02..10e2722509 100644 --- a/scene/resources/default_theme/scroll_button_right_hl.png +++ b/scene/resources/default_theme/scroll_button_right_hl.png diff --git a/scene/resources/default_theme/scroll_button_up.png b/scene/resources/default_theme/scroll_button_up.png Binary files differindex 2c8238ae4c..f425412f50 100644 --- a/scene/resources/default_theme/scroll_button_up.png +++ b/scene/resources/default_theme/scroll_button_up.png diff --git a/scene/resources/default_theme/scroll_button_up_hl.png b/scene/resources/default_theme/scroll_button_up_hl.png Binary files differindex 4283bd114a..615a236c52 100644 --- a/scene/resources/default_theme/scroll_button_up_hl.png +++ b/scene/resources/default_theme/scroll_button_up_hl.png diff --git a/scene/resources/default_theme/scroll_grabber.png b/scene/resources/default_theme/scroll_grabber.png Binary files differindex 1d625a9b7b..732725a28f 100644 --- a/scene/resources/default_theme/scroll_grabber.png +++ b/scene/resources/default_theme/scroll_grabber.png diff --git a/scene/resources/default_theme/scroll_grabber_hl.png b/scene/resources/default_theme/scroll_grabber_hl.png Binary files differindex 99eb24b7e7..006cfa3361 100644 --- a/scene/resources/default_theme/scroll_grabber_hl.png +++ b/scene/resources/default_theme/scroll_grabber_hl.png diff --git a/scene/resources/default_theme/scroll_grabber_pressed.png b/scene/resources/default_theme/scroll_grabber_pressed.png Binary files differindex a46d242ddd..f4886158fa 100644 --- a/scene/resources/default_theme/scroll_grabber_pressed.png +++ b/scene/resources/default_theme/scroll_grabber_pressed.png diff --git a/scene/resources/default_theme/selection.png b/scene/resources/default_theme/selection.png Binary files differindex 501877a8b4..7d1c985b35 100644 --- a/scene/resources/default_theme/selection.png +++ b/scene/resources/default_theme/selection.png diff --git a/scene/resources/default_theme/selection_oof.png b/scene/resources/default_theme/selection_oof.png Binary files differindex 9594fe0913..2da0538389 100644 --- a/scene/resources/default_theme/selection_oof.png +++ b/scene/resources/default_theme/selection_oof.png diff --git a/scene/resources/default_theme/spinbox_updown.png b/scene/resources/default_theme/spinbox_updown.png Binary files differindex b40b1e9fd2..74fab19f34 100644 --- a/scene/resources/default_theme/spinbox_updown.png +++ b/scene/resources/default_theme/spinbox_updown.png diff --git a/scene/resources/default_theme/submenu.png b/scene/resources/default_theme/submenu.png Binary files differindex ec727eecf1..8f7de446d4 100644 --- a/scene/resources/default_theme/submenu.png +++ b/scene/resources/default_theme/submenu.png diff --git a/scene/resources/default_theme/tab.png b/scene/resources/default_theme/tab.png Binary files differindex 3e4d792a48..895daa65e2 100644 --- a/scene/resources/default_theme/tab.png +++ b/scene/resources/default_theme/tab.png diff --git a/scene/resources/default_theme/tab_behind.png b/scene/resources/default_theme/tab_behind.png Binary files differindex 12f07c3a91..2803d9db65 100644 --- a/scene/resources/default_theme/tab_behind.png +++ b/scene/resources/default_theme/tab_behind.png diff --git a/scene/resources/default_theme/tab_close.png b/scene/resources/default_theme/tab_close.png Binary files differindex 20d9b5c810..af2775a132 100644 --- a/scene/resources/default_theme/tab_close.png +++ b/scene/resources/default_theme/tab_close.png diff --git a/scene/resources/default_theme/tab_container_bg.png b/scene/resources/default_theme/tab_container_bg.png Binary files differindex 92482aaf28..7d0bd16221 100644 --- a/scene/resources/default_theme/tab_container_bg.png +++ b/scene/resources/default_theme/tab_container_bg.png diff --git a/scene/resources/default_theme/tab_current.png b/scene/resources/default_theme/tab_current.png Binary files differindex 7289e032da..520d147217 100644 --- a/scene/resources/default_theme/tab_current.png +++ b/scene/resources/default_theme/tab_current.png diff --git a/scene/resources/default_theme/tab_menu.png b/scene/resources/default_theme/tab_menu.png Binary files differindex 148b64b8aa..fa4421a28a 100644 --- a/scene/resources/default_theme/tab_menu.png +++ b/scene/resources/default_theme/tab_menu.png diff --git a/scene/resources/default_theme/tab_menu_hl.png b/scene/resources/default_theme/tab_menu_hl.png Binary files differindex 148b64b8aa..fa4421a28a 100644 --- a/scene/resources/default_theme/tab_menu_hl.png +++ b/scene/resources/default_theme/tab_menu_hl.png diff --git a/scene/resources/default_theme/toggle_off.png b/scene/resources/default_theme/toggle_off.png Binary files differindex 71cd64b001..64b51c8c9d 100644 --- a/scene/resources/default_theme/toggle_off.png +++ b/scene/resources/default_theme/toggle_off.png diff --git a/scene/resources/default_theme/toggle_on.png b/scene/resources/default_theme/toggle_on.png Binary files differindex 6ea1b589c7..f0c699c181 100644 --- a/scene/resources/default_theme/toggle_on.png +++ b/scene/resources/default_theme/toggle_on.png diff --git a/scene/resources/default_theme/tool_button_pressed.png b/scene/resources/default_theme/tool_button_pressed.png Binary files differindex bcf70b486d..5494475792 100644 --- a/scene/resources/default_theme/tool_button_pressed.png +++ b/scene/resources/default_theme/tool_button_pressed.png diff --git a/scene/resources/default_theme/tooltip_bg.png b/scene/resources/default_theme/tooltip_bg.png Binary files differindex eca0675a98..07b7d942ca 100644 --- a/scene/resources/default_theme/tooltip_bg.png +++ b/scene/resources/default_theme/tooltip_bg.png diff --git a/scene/resources/default_theme/tree_bg.png b/scene/resources/default_theme/tree_bg.png Binary files differindex 839a6a272a..2b0c506f34 100644 --- a/scene/resources/default_theme/tree_bg.png +++ b/scene/resources/default_theme/tree_bg.png diff --git a/scene/resources/default_theme/tree_bg_disabled.png b/scene/resources/default_theme/tree_bg_disabled.png Binary files differindex a0fa505e4c..69d78febd9 100644 --- a/scene/resources/default_theme/tree_bg_disabled.png +++ b/scene/resources/default_theme/tree_bg_disabled.png diff --git a/scene/resources/default_theme/tree_bg_focus.png b/scene/resources/default_theme/tree_bg_focus.png Binary files differindex 692cf71926..aadc6b0db4 100644 --- a/scene/resources/default_theme/tree_bg_focus.png +++ b/scene/resources/default_theme/tree_bg_focus.png diff --git a/scene/resources/default_theme/tree_cursor.png b/scene/resources/default_theme/tree_cursor.png Binary files differindex 94d2a08818..2b8722d066 100644 --- a/scene/resources/default_theme/tree_cursor.png +++ b/scene/resources/default_theme/tree_cursor.png diff --git a/scene/resources/default_theme/tree_cursor_unfocus.png b/scene/resources/default_theme/tree_cursor_unfocus.png Binary files differindex 3f023bbabe..bfaebbea85 100644 --- a/scene/resources/default_theme/tree_cursor_unfocus.png +++ b/scene/resources/default_theme/tree_cursor_unfocus.png diff --git a/scene/resources/default_theme/tree_title.png b/scene/resources/default_theme/tree_title.png Binary files differindex b0ddcffbbe..e5f3f49695 100644 --- a/scene/resources/default_theme/tree_title.png +++ b/scene/resources/default_theme/tree_title.png diff --git a/scene/resources/default_theme/tree_title_pressed.png b/scene/resources/default_theme/tree_title_pressed.png Binary files differindex 746d10039e..35e2bb3008 100644 --- a/scene/resources/default_theme/tree_title_pressed.png +++ b/scene/resources/default_theme/tree_title_pressed.png diff --git a/scene/resources/default_theme/unchecked.png b/scene/resources/default_theme/unchecked.png Binary files differindex d6f790cbc2..8c818af755 100644 --- a/scene/resources/default_theme/unchecked.png +++ b/scene/resources/default_theme/unchecked.png diff --git a/scene/resources/default_theme/updown.png b/scene/resources/default_theme/updown.png Binary files differindex 916284a3cf..56f81921e8 100644 --- a/scene/resources/default_theme/updown.png +++ b/scene/resources/default_theme/updown.png diff --git a/scene/resources/default_theme/vseparator.png b/scene/resources/default_theme/vseparator.png Binary files differindex 498768c05b..51e79f3ac5 100644 --- a/scene/resources/default_theme/vseparator.png +++ b/scene/resources/default_theme/vseparator.png diff --git a/scene/resources/default_theme/vslider_bg.png b/scene/resources/default_theme/vslider_bg.png Binary files differindex 8d9ead3c5a..ba3244e3e5 100644 --- a/scene/resources/default_theme/vslider_bg.png +++ b/scene/resources/default_theme/vslider_bg.png diff --git a/scene/resources/default_theme/vslider_grabber.png b/scene/resources/default_theme/vslider_grabber.png Binary files differindex afc490be45..6c6bf93e68 100644 --- a/scene/resources/default_theme/vslider_grabber.png +++ b/scene/resources/default_theme/vslider_grabber.png diff --git a/scene/resources/default_theme/vslider_grabber_disabled.png b/scene/resources/default_theme/vslider_grabber_disabled.png Binary files differindex c830359f45..49cced5055 100644 --- a/scene/resources/default_theme/vslider_grabber_disabled.png +++ b/scene/resources/default_theme/vslider_grabber_disabled.png diff --git a/scene/resources/default_theme/vslider_grabber_hl.png b/scene/resources/default_theme/vslider_grabber_hl.png Binary files differindex 548972e115..28774fdbf8 100644 --- a/scene/resources/default_theme/vslider_grabber_hl.png +++ b/scene/resources/default_theme/vslider_grabber_hl.png diff --git a/scene/resources/default_theme/vslider_tick.png b/scene/resources/default_theme/vslider_tick.png Binary files differindex 873ebb00d8..bde788b563 100644 --- a/scene/resources/default_theme/vslider_tick.png +++ b/scene/resources/default_theme/vslider_tick.png diff --git a/scene/resources/default_theme/vsplit_bg.png b/scene/resources/default_theme/vsplit_bg.png Binary files differindex 7dd1d48b29..a5749f6d5c 100644 --- a/scene/resources/default_theme/vsplit_bg.png +++ b/scene/resources/default_theme/vsplit_bg.png diff --git a/scene/resources/default_theme/vsplitter.png b/scene/resources/default_theme/vsplitter.png Binary files differindex ec5542bf69..dde1f390df 100644 --- a/scene/resources/default_theme/vsplitter.png +++ b/scene/resources/default_theme/vsplitter.png diff --git a/scene/resources/default_theme/window_resizer.png b/scene/resources/default_theme/window_resizer.png Binary files differindex ed51968c4e..b06e6f5366 100644 --- a/scene/resources/default_theme/window_resizer.png +++ b/scene/resources/default_theme/window_resizer.png diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index 23c6dc200b..e5d463d391 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -33,8 +33,12 @@ #include "os/file_access.h" #include "os/os.h" -bool DynamicFontData::CacheID::operator<(CacheID right) const { +#include FT_STROKER_H + +#define __STDC_LIMIT_MACROS +#include <stdint.h> +bool DynamicFontData::CacheID::operator<(CacheID right) const { return key < right.key; } @@ -80,6 +84,14 @@ void DynamicFontData::set_force_autohinter(bool p_force) { void DynamicFontData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_font_path", "path"), &DynamicFontData::set_font_path); ClassDB::bind_method(D_METHOD("get_font_path"), &DynamicFontData::get_font_path); + ClassDB::bind_method(D_METHOD("set_hinting", "mode"), &DynamicFontData::set_hinting); + ClassDB::bind_method(D_METHOD("get_hinting"), &DynamicFontData::get_hinting); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); + + BIND_ENUM_CONSTANT(HINTING_NONE); + BIND_ENUM_CONSTANT(HINTING_LIGHT); + BIND_ENUM_CONSTANT(HINTING_NORMAL); ADD_PROPERTY(PropertyInfo(Variant::STRING, "font_path", PROPERTY_HINT_FILE, "*.ttf,*.otf"), "set_font_path", "get_font_path"); } @@ -87,6 +99,7 @@ void DynamicFontData::_bind_methods() { DynamicFontData::DynamicFontData() { force_autohinter = false; + hinting = DynamicFontData::HINTING_NORMAL; font_mem = NULL; font_mem_size = 0; } @@ -203,8 +216,8 @@ Error DynamicFontAtSize::_load() { error = FT_Set_Pixel_Sizes(face, 0, id.size * oversampling); } - ascent = (face->size->metrics.ascender >> 6) / oversampling * scale_color_font; - descent = (-face->size->metrics.descender >> 6) / oversampling * scale_color_font; + ascent = (face->size->metrics.ascender / 64.0) / oversampling * scale_color_font; + descent = (-face->size->metrics.descender / 64.0) / oversampling * scale_color_font; linegap = 0; texture_flags = 0; if (id.mipmaps) @@ -212,8 +225,6 @@ Error DynamicFontAtSize::_load() { if (id.filter) texture_flags |= Texture::FLAG_FILTER; - //print_line("ASCENT: "+itos(ascent)+" descent "+itos(descent)+" hinted: "+itos(face->face_flags&FT_FACE_FLAG_HINTER)); - valid = true; return OK; } @@ -234,18 +245,11 @@ float DynamicFontAtSize::get_descent() const { return descent; } -Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const { - - if (!valid) - return Size2(1, 1); - const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); - - const Character *c = char_map.getptr(p_char); - ERR_FAIL_COND_V(!c, Size2()); +const Pair<const DynamicFontAtSize::Character *, DynamicFontAtSize *> DynamicFontAtSize::_find_char_with_font(CharType p_char, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const { + const Character *chr = char_map.getptr(p_char); + ERR_FAIL_COND_V(!chr, (Pair<const Character *, DynamicFontAtSize *>(NULL, NULL))); - Size2 ret(0, get_height()); - - if (!c->found) { + if (!chr->found) { //not found, try in fallbacks for (int i = 0; i < p_fallbacks.size(); i++) { @@ -255,52 +259,54 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const V continue; fb->_update_char(p_char); - const Character *ch = fb->char_map.getptr(p_char); - ERR_CONTINUE(!ch); + const Character *fallback_chr = fb->char_map.getptr(p_char); + ERR_CONTINUE(!fallback_chr); - if (!ch->found) + if (!fallback_chr->found) continue; - c = ch; - break; + return Pair<const Character *, DynamicFontAtSize *>(fallback_chr, fb); } - //not found, try 0xFFFD to display 'not found'. - - if (!c->found) { - const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD); - c = char_map.getptr(0xFFFD); - ERR_FAIL_COND_V(!c, Size2()); - } + //not found, try 0xFFFD to display 'not found'. + const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD); + chr = char_map.getptr(0xFFFD); + ERR_FAIL_COND_V(!chr, (Pair<const Character *, DynamicFontAtSize *>(NULL, NULL))); } - if (c->found) { - ret.x = c->advance; - } + return Pair<const Character *, DynamicFontAtSize *>(chr, const_cast<DynamicFontAtSize *>(this)); +} + +float DynamicFontAtSize::_get_kerning_advance(const DynamicFontAtSize *font, CharType p_char, CharType p_next) const { + float advance = 0.0; if (p_next) { FT_Vector delta; - FT_Get_Kerning(face, p_char, p_next, FT_KERNING_DEFAULT, &delta); + FT_Get_Kerning(font->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); + advance = (delta.x / 64.0) / oversampling; + } - if (delta.x == 0) { - for (int i = 0; i < p_fallbacks.size(); i++) { + return advance; +} - DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(p_fallbacks[i].ptr()); - if (!fb->valid) - continue; +Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const { - FT_Get_Kerning(fb->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); + if (!valid) + return Size2(1, 1); + const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); - if (delta.x == 0) - continue; + Pair<const Character *, DynamicFontAtSize *> char_pair_with_font = _find_char_with_font(p_char, p_fallbacks); + const Character *ch = char_pair_with_font.first; + DynamicFontAtSize *font = char_pair_with_font.second; + ERR_FAIL_COND_V(!ch, Size2()); - ret.x += (delta.x >> 6) / oversampling; - break; - } - } else { - ret.x += (delta.x >> 6) / oversampling; - } + Size2 ret(0, get_height()); + + if (ch->found) { + ret.x = ch->advance; } + ret.x += _get_kerning_advance(font, p_char, p_next); + return ret; } @@ -314,102 +320,42 @@ void DynamicFontAtSize::set_texture_flags(uint32_t p_flags) { } } -float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const { +float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only) const { if (!valid) return 0; const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); - const Character *c = char_map.getptr(p_char); + Pair<const Character *, DynamicFontAtSize *> char_pair_with_font = _find_char_with_font(p_char, p_fallbacks); + const Character *ch = char_pair_with_font.first; + DynamicFontAtSize *font = char_pair_with_font.second; - float advance = 0; + ERR_FAIL_COND_V(!ch, 0.0); - if (!c->found) { + float advance = 0.0; - //not found, try in fallbacks - bool used_fallback = false; + if (ch->found) { + ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0); - for (int i = 0; i < p_fallbacks.size(); i++) { - - DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(p_fallbacks[i].ptr()); - if (!fb->valid) - continue; - - fb->_update_char(p_char); - const Character *ch = fb->char_map.getptr(p_char); - ERR_CONTINUE(!ch); - - if (!ch->found) - continue; + if (!p_advance_only && ch->texture_idx != -1) { Point2 cpos = p_pos; cpos.x += ch->h_align; - cpos.y -= fb->get_ascent(); + cpos.y -= font->get_ascent(); cpos.y += ch->v_align; - ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= fb->textures.size(), 0); - if (ch->texture_idx != -1) { - Color modulate = p_modulate; - if (FT_HAS_COLOR(fb->face)) { - modulate.r = modulate.g = modulate.b = 1; - } - VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size * Vector2(fb->scale_color_font, fb->scale_color_font)), fb->textures[ch->texture_idx].texture->get_rid(), ch->rect_uv, modulate, false, RID(), false); - } - advance = ch->advance; - used_fallback = true; - break; - } - //not found, try 0xFFFD to display 'not found'. - - if (!used_fallback) { - - const_cast<DynamicFontAtSize *>(this)->_update_char(0xFFFD); - c = char_map.getptr(0xFFFD); - } - } - - if (c->found) { - - Point2 cpos = p_pos; - cpos.x += c->h_align; - cpos.y -= get_ascent(); - cpos.y += c->v_align; - ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0); - if (c->texture_idx != -1) { Color modulate = p_modulate; if (FT_HAS_COLOR(face)) { - modulate.r = modulate.g = modulate.b = 1; + modulate.r = modulate.g = modulate.b = 1.0; } - VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size * Vector2(scale_color_font, scale_color_font)), textures[c->texture_idx].texture->get_rid(), c->rect_uv, modulate, false, RID(), false); + RID texture = font->textures[ch->texture_idx].texture->get_rid(); + VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, ch->rect.size * Vector2(font->scale_color_font, font->scale_color_font)), texture, ch->rect_uv, modulate, false, RID(), false); } - advance = c->advance; - //textures[c->texture_idx].texture->draw(p_canvas_item,Vector2()); - } - - if (p_next) { - FT_Vector delta; - FT_Get_Kerning(face, p_char, p_next, FT_KERNING_DEFAULT, &delta); - - if (delta.x == 0) { - for (int i = 0; i < p_fallbacks.size(); i++) { - - DynamicFontAtSize *fb = const_cast<DynamicFontAtSize *>(p_fallbacks[i].ptr()); - if (!fb->valid) - continue; - - FT_Get_Kerning(fb->face, p_char, p_next, FT_KERNING_DEFAULT, &delta); - - if (delta.x == 0) - continue; - - advance += (delta.x >> 6) / oversampling; - break; - } - } else { - advance += (delta.x >> 6) / oversampling; - } + advance = ch->advance; } + advance += _get_kerning_advance(font, p_char, p_next); + return advance; } @@ -433,84 +379,37 @@ void DynamicFontAtSize::_ft_stream_close(FT_Stream stream) { memdelete(f); } -void DynamicFontAtSize::_update_char(CharType p_char) { - - if (char_map.has(p_char)) - return; - - _THREAD_SAFE_METHOD_ - - FT_GlyphSlot slot = face->glyph; - - if (FT_Get_Char_Index(face, p_char) == 0) { - //not found - Character ch; - ch.texture_idx = -1; - ch.advance = 0; - ch.h_align = 0; - ch.v_align = 0; - ch.found = false; - - char_map[p_char] = ch; - return; - } - int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); - if (!error) { - error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); - } - if (error) { - - int advance = 0; - //stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); - //print_line("char has no bitmap: "+itos(p_char)+" but advance is "+itos(advance*scale)); - Character ch; - ch.texture_idx = -1; - ch.advance = advance; - ch.h_align = 0; - ch.v_align = 0; - ch.found = false; - - char_map[p_char] = ch; - - return; - } - - int w = slot->bitmap.width; - int h = slot->bitmap.rows; - //int p = slot->bitmap.pitch; - int yofs = slot->bitmap_top; - int xofs = slot->bitmap_left; - int advance = slot->advance.x >> 6; - - int mw = w + rect_margin * 2; - int mh = h + rect_margin * 2; - - if (mw > 4096 || mh > 4096) { - - ERR_FAIL_COND(mw > 4096); - ERR_FAIL_COND(mh > 4096); - } +DynamicFontAtSize::Character DynamicFontAtSize::Character::not_found() { + Character ch; + ch.texture_idx = -1; + ch.advance = 0; + ch.h_align = 0; + ch.v_align = 0; + ch.found = false; + return ch; +} - //find a texture to fit this... +DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height) { + TexturePosition ret; + ret.index = -1; + ret.x = 0; + ret.y = 0; - int color_size = slot->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; - Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; - int tex_index = -1; - int tex_x = 0; - int tex_y = 0; + int mw = p_width; + int mh = p_height; for (int i = 0; i < textures.size(); i++) { CharTexture &ct = textures[i]; - if (ct.texture->get_format() != require_format) + if (ct.texture->get_format() != p_image_format) continue; if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture continue; - tex_y = 0x7FFFFFFF; - tex_x = 0; + ret.y = 0x7FFFFFFF; + ret.x = 0; for (int j = 0; j < ct.texture_size - mw; j++) { @@ -523,27 +422,27 @@ void DynamicFontAtSize::_update_char(CharType p_char) { max_y = y; } - if (max_y < tex_y) { - tex_y = max_y; - tex_x = j; + if (max_y < ret.y) { + ret.y = max_y; + ret.x = j; } } - if (tex_y == 0x7FFFFFFF || tex_y + mh > ct.texture_size) + if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_size) continue; //fail, could not fit it here - tex_index = i; + ret.index = i; break; } //print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" X: "+itos(tex_x)+" Y: "+itos(tex_y)); - if (tex_index == -1) { + if (ret.index == -1) { //could not find texture to fit, create one - tex_x = 0; - tex_y = 0; + ret.x = 0; + ret.y = 0; - int texsize = MAX(id.size * 8, 256); + int texsize = MAX(id.size * oversampling * 8, 256); if (mw > texsize) texsize = mw; //special case, adapt to it? if (mh > texsize) @@ -555,13 +454,13 @@ void DynamicFontAtSize::_update_char(CharType p_char) { CharTexture tex; tex.texture_size = texsize; - tex.imgdata.resize(texsize * texsize * color_size); + tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha { //zero texture PoolVector<uint8_t>::Write w = tex.imgdata.write(); - ERR_FAIL_COND(texsize * texsize * color_size > tex.imgdata.size()); - for (int i = 0; i < texsize * texsize * color_size; i++) { + ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret); + for (int i = 0; i < texsize * texsize * p_color_size; i++) { w[i] = 0; } } @@ -570,12 +469,31 @@ void DynamicFontAtSize::_update_char(CharType p_char) { tex.offsets[i] = 0; textures.push_back(tex); - tex_index = textures.size() - 1; + ret.index = textures.size() - 1; } + return ret; +} + +DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance) { + int w = bitmap.width; + int h = bitmap.rows; + + int mw = w + rect_margin * 2; + int mh = h + rect_margin * 2; + + ERR_FAIL_COND_V(mw > 4096, Character::not_found()); + ERR_FAIL_COND_V(mh > 4096, Character::not_found()); + + int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; + Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; + + TexturePosition tex_pos = _find_texture_pos_for_glyph(color_size, require_format, mw, mh); + ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found()); + //fit character in char texture - CharTexture &tex = textures[tex_index]; + CharTexture &tex = textures[tex_pos.index]; { PoolVector<uint8_t>::Write wr = tex.imgdata.write(); @@ -583,30 +501,30 @@ void DynamicFontAtSize::_update_char(CharType p_char) { for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { - int ofs = ((i + tex_y + rect_margin) * tex.texture_size + j + tex_x + rect_margin) * color_size; - ERR_FAIL_COND(ofs >= tex.imgdata.size()); - switch (slot->bitmap.pixel_mode) { + int ofs = ((i + tex_pos.y + rect_margin) * tex.texture_size + j + tex_pos.x + rect_margin) * color_size; + ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), Character::not_found()); + switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: { - int byte = i * slot->bitmap.pitch + (j >> 3); + int byte = i * bitmap.pitch + (j >> 3); int bit = 1 << (7 - (j % 8)); wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = slot->bitmap.buffer[byte] & bit ? 255 : 0; + wr[ofs + 1] = bitmap.buffer[byte] & bit ? 255 : 0; } break; case FT_PIXEL_MODE_GRAY: wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = slot->bitmap.buffer[i * slot->bitmap.pitch + j]; + wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j]; break; case FT_PIXEL_MODE_BGRA: { - int ofs_color = i * slot->bitmap.pitch + (j << 2); - wr[ofs + 2] = slot->bitmap.buffer[ofs_color + 0]; - wr[ofs + 1] = slot->bitmap.buffer[ofs_color + 1]; - wr[ofs + 0] = slot->bitmap.buffer[ofs_color + 2]; - wr[ofs + 3] = slot->bitmap.buffer[ofs_color + 3]; + int ofs_color = i * bitmap.pitch + (j << 2); + wr[ofs + 2] = bitmap.buffer[ofs_color + 0]; + wr[ofs + 1] = bitmap.buffer[ofs_color + 1]; + wr[ofs + 0] = bitmap.buffer[ofs_color + 2]; + wr[ofs + 3] = bitmap.buffer[ofs_color + 3]; } break; // TODO: FT_PIXEL_MODE_LCD default: - ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(slot->bitmap.pixel_mode)); - ERR_FAIL(); + ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(bitmap.pixel_mode)); + ERR_FAIL_V(Character::not_found()); break; } } @@ -628,33 +546,105 @@ void DynamicFontAtSize::_update_char(CharType p_char) { // update height array - for (int k = tex_x; k < tex_x + mw; k++) { - - tex.offsets[k] = tex_y + mh; + for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { + tex.offsets[k] = tex_pos.y + mh; } Character chr; chr.h_align = xofs * scale_color_font / oversampling; chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent; chr.advance = advance * scale_color_font / oversampling; - chr.texture_idx = tex_index; + chr.texture_idx = tex_pos.index; chr.found = true; - chr.rect_uv = Rect2(tex_x + rect_margin, tex_y + rect_margin, w, h); + chr.rect_uv = Rect2(tex_pos.x + rect_margin, tex_pos.y + rect_margin, w, h); chr.rect = chr.rect_uv; chr.rect.position /= oversampling; - chr.rect.size /= oversampling; + chr.rect.size = chr.rect.size * scale_color_font / oversampling; + return chr; +} + +DynamicFontAtSize::Character DynamicFontAtSize::_make_outline_char(CharType p_char) { + Character ret = Character::not_found(); + + if (FT_Load_Char(face, p_char, FT_LOAD_NO_BITMAP | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)) != 0) + return ret; + + FT_Stroker stroker; + if (FT_Stroker_New(library, &stroker) != 0) + return ret; + + FT_Stroker_Set(stroker, (int)(id.outline_size * oversampling * 64.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Glyph glyph; + FT_BitmapGlyph glyph_bitmap; + + if (FT_Get_Glyph(face->glyph, &glyph) != 0) + goto cleanup_stroker; + if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) + goto cleanup_glyph; + if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1) != 0) + goto cleanup_glyph; - //print_line("CHAR: "+String::chr(p_char)+" TEX INDEX: "+itos(tex_index)+" RECT: "+chr.rect+" X OFS: "+itos(xofs)+" Y OFS: "+itos(yofs)); + glyph_bitmap = (FT_BitmapGlyph)glyph; + ret = _bitmap_to_character(glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, glyph->advance.x / 65536.0); - char_map[p_char] = chr; +cleanup_glyph: + FT_Done_Glyph(glyph); +cleanup_stroker: + FT_Stroker_Done(stroker); + return ret; } -bool DynamicFontAtSize::update_oversampling() { - if (oversampling == font_oversampling) - return false; - if (!valid) - return false; +void DynamicFontAtSize::_update_char(CharType p_char) { + + if (char_map.has(p_char)) + return; + + _THREAD_SAFE_METHOD_ + + Character character = Character::not_found(); + + FT_GlyphSlot slot = face->glyph; + + if (FT_Get_Char_Index(face, p_char) == 0) { + char_map[p_char] = character; + return; + } + + int ft_hinting; + + switch (font->hinting) { + case DynamicFontData::HINTING_NONE: + ft_hinting = FT_LOAD_NO_HINTING; + break; + case DynamicFontData::HINTING_LIGHT: + ft_hinting = FT_LOAD_TARGET_LIGHT; + break; + default: + ft_hinting = FT_LOAD_TARGET_NORMAL; + break; + } + + int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting); + if (error) { + char_map[p_char] = character; + return; + } + + if (id.outline_size > 0) { + character = _make_outline_char(p_char); + } else { + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if (!error) + character = _bitmap_to_character(slot->bitmap, slot->bitmap_top, slot->bitmap_left, slot->advance.x / 64.0); + } + + char_map[p_char] = character; +} + +void DynamicFontAtSize::update_oversampling() { + if (oversampling == font_oversampling || !valid) + return; FT_Done_FreeType(library); textures.clear(); @@ -662,8 +652,6 @@ bool DynamicFontAtSize::update_oversampling() { oversampling = font_oversampling; valid = false; _load(); - - return true; } DynamicFontAtSize::DynamicFontAtSize() { @@ -692,11 +680,27 @@ DynamicFontAtSize::~DynamicFontAtSize() { void DynamicFont::_reload_cache() { ERR_FAIL_COND(cache_id.size < 1); - if (!data.is_valid()) + if (!data.is_valid()) { + data_at_size.unref(); + outline_data_at_size.unref(); + fallback_data_at_size.resize(0); + fallback_outline_data_at_size.resize(0); return; + } + data_at_size = data->_get_dynamic_font_at_size(cache_id); + if (outline_cache_id.outline_size > 0) { + outline_data_at_size = data->_get_dynamic_font_at_size(outline_cache_id); + fallback_outline_data_at_size.resize(fallback_data_at_size.size()); + } else { + outline_data_at_size.unref(); + fallback_outline_data_at_size.resize(0); + } + for (int i = 0; i < fallbacks.size(); i++) { fallback_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(cache_id); + if (outline_cache_id.outline_size > 0) + fallback_outline_data_at_size[i] = fallbacks[i]->_get_dynamic_font_at_size(outline_cache_id); } emit_changed(); @@ -706,12 +710,10 @@ void DynamicFont::_reload_cache() { void DynamicFont::set_font_data(const Ref<DynamicFontData> &p_data) { data = p_data; - if (data.is_valid()) - data_at_size = data->_get_dynamic_font_at_size(cache_id); - else - data_at_size = Ref<DynamicFontAtSize>(); + _reload_cache(); emit_changed(); + _change_notify(); } Ref<DynamicFontData> DynamicFont::get_font_data() const { @@ -724,6 +726,7 @@ void DynamicFont::set_size(int p_size) { if (cache_id.size == p_size) return; cache_id.size = p_size; + outline_cache_id.size = p_size; _reload_cache(); } @@ -732,6 +735,30 @@ int DynamicFont::get_size() const { return cache_id.size; } +void DynamicFont::set_outline_size(int p_size) { + if (outline_cache_id.outline_size == p_size) + return; + ERR_FAIL_COND(p_size < 0 || p_size > UINT8_MAX); + outline_cache_id.outline_size = p_size; + _reload_cache(); +} + +int DynamicFont::get_outline_size() const { + return outline_cache_id.outline_size; +} + +void DynamicFont::set_outline_color(Color p_color) { + if (p_color != outline_color) { + outline_color = p_color; + emit_changed(); + _change_notify(); + } +} + +Color DynamicFont::get_outline_color() const { + return outline_color; +} + bool DynamicFont::get_use_mipmaps() const { return cache_id.mipmaps; @@ -742,6 +769,7 @@ void DynamicFont::set_use_mipmaps(bool p_enable) { if (cache_id.mipmaps == p_enable) return; cache_id.mipmaps = p_enable; + outline_cache_id.mipmaps = p_enable; _reload_cache(); } @@ -755,9 +783,22 @@ void DynamicFont::set_use_filter(bool p_enable) { if (cache_id.filter == p_enable) return; cache_id.filter = p_enable; + outline_cache_id.filter = p_enable; _reload_cache(); } +DynamicFontData::Hinting DynamicFontData::get_hinting() const { + + return hinting; +} + +void DynamicFontData::set_hinting(Hinting p_hinting) { + + if (hinting == p_hinting) + return; + hinting = p_hinting; +} + int DynamicFont::get_spacing(int p_type) const { if (p_type == SPACING_TOP) { @@ -832,13 +873,24 @@ bool DynamicFont::is_distance_field_hint() const { return false; } -float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const { +bool DynamicFont::has_outline() const { + return outline_cache_id.outline_size > 0; +} - if (!data_at_size.is_valid()) +float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { + const Ref<DynamicFontAtSize> &font_at_size = p_outline && outline_cache_id.outline_size > 0 ? outline_data_at_size : data_at_size; + + if (!font_at_size.is_valid()) return 0; - return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, fallback_data_at_size) + spacing_char; + const Vector<Ref<DynamicFontAtSize> > &fallbacks = p_outline && outline_cache_id.outline_size > 0 ? fallback_outline_data_at_size : fallback_data_at_size; + Color color = p_outline && outline_cache_id.outline_size > 0 ? p_modulate * outline_color : p_modulate; + + // If requested outline draw, but no outline is present, simply return advance without drawing anything + bool advance_only = p_outline && outline_cache_id.outline_size == 0; + return font_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, color, fallbacks, advance_only) + spacing_char; } + void DynamicFont::set_fallback(int p_idx, const Ref<DynamicFontData> &p_data) { ERR_FAIL_COND(p_data.is_null()); @@ -852,6 +904,8 @@ void DynamicFont::add_fallback(const Ref<DynamicFontData> &p_data) { ERR_FAIL_COND(p_data.is_null()); fallbacks.push_back(p_data); fallback_data_at_size.push_back(fallbacks[fallbacks.size() - 1]->_get_dynamic_font_at_size(cache_id)); //const.. + if (outline_cache_id.outline_size > 0) + fallback_outline_data_at_size.push_back(fallbacks[fallbacks.size() - 1]->_get_dynamic_font_at_size(outline_cache_id)); _change_notify(); emit_changed(); @@ -936,6 +990,12 @@ void DynamicFont::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "data"), &DynamicFont::set_size); ClassDB::bind_method(D_METHOD("get_size"), &DynamicFont::get_size); + ClassDB::bind_method(D_METHOD("set_outline_size", "size"), &DynamicFont::set_outline_size); + ClassDB::bind_method(D_METHOD("get_outline_size"), &DynamicFont::get_outline_size); + + ClassDB::bind_method(D_METHOD("set_outline_color", "color"), &DynamicFont::set_outline_color); + ClassDB::bind_method(D_METHOD("get_outline_color"), &DynamicFont::get_outline_color); + ClassDB::bind_method(D_METHOD("set_use_mipmaps", "enable"), &DynamicFont::set_use_mipmaps); ClassDB::bind_method(D_METHOD("get_use_mipmaps"), &DynamicFont::get_use_mipmaps); ClassDB::bind_method(D_METHOD("set_use_filter", "enable"), &DynamicFont::set_use_filter); @@ -951,6 +1011,8 @@ void DynamicFont::_bind_methods() { ADD_GROUP("Settings", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_size"), "set_outline_size", "get_outline_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "outline_color"), "set_outline_color", "get_outline_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_mipmaps"), "set_use_mipmaps", "get_use_mipmaps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_filter"), "set_use_filter", "get_use_filter"); ADD_GROUP("Extra Spacing", "extra_spacing"); @@ -978,6 +1040,7 @@ DynamicFont::DynamicFont() : spacing_bottom = 0; spacing_char = 0; spacing_space = 0; + outline_color = Color(1, 1, 1); if (dynamic_font_mutex) dynamic_font_mutex->lock(); dynamic_fonts.add(&font_list); @@ -1013,7 +1076,13 @@ void DynamicFont::update_oversampling() { SelfList<DynamicFont> *E = dynamic_fonts.first(); while (E) { - if (E->self()->data_at_size.is_valid() && E->self()->data_at_size->update_oversampling()) { + if (E->self()->data_at_size.is_valid()) { + E->self()->data_at_size->update_oversampling(); + + if (E->self()->outline_data_at_size.is_valid()) { + E->self()->outline_data_at_size->update_oversampling(); + } + changed.push_back(Ref<DynamicFont>(E->self())); } E = E->next(); diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h index d8adf35b6b..f460bca2d4 100644 --- a/scene/resources/dynamic_font.h +++ b/scene/resources/dynamic_font.h @@ -35,6 +35,7 @@ #include "io/resource_loader.h" #include "os/mutex.h" #include "os/thread_safe.h" +#include "pair.h" #include "scene/resources/font.h" #include <ft2build.h> @@ -49,10 +50,10 @@ class DynamicFontData : public Resource { public: struct CacheID { - union { struct { uint32_t size : 16; + uint32_t outline_size : 8; bool mipmaps : 1; bool filter : 1; }; @@ -64,10 +65,20 @@ public: } }; + enum Hinting { + HINTING_NONE, + HINTING_LIGHT, + HINTING_NORMAL + }; + + Hinting get_hinting() const; + void set_hinting(Hinting p_hinting); + private: const uint8_t *font_mem; int font_mem_size; bool force_autohinter; + Hinting hinting; String font_path; Map<CacheID, DynamicFontAtSize *> size_cache; @@ -91,6 +102,8 @@ public: ~DynamicFontData(); }; +VARIANT_ENUM_CAST(DynamicFontData::Hinting); + class DynamicFontAtSize : public Reference { GDCLASS(DynamicFontAtSize, Reference) @@ -136,8 +149,22 @@ class DynamicFontAtSize : public Reference { texture_idx = 0; v_align = 0; } + + static Character not_found(); + }; + + struct TexturePosition { + int index; + int x; + int y; }; + const Pair<const Character *, DynamicFontAtSize *> _find_char_with_font(CharType p_char, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const; + Character _make_outline_char(CharType p_char); + float _get_kerning_advance(const DynamicFontAtSize *font, CharType p_char, CharType p_next) const; + TexturePosition _find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height); + Character _bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance); + static unsigned long _ft_stream_io(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count); static void _ft_stream_close(FT_Stream stream); @@ -162,10 +189,10 @@ public: Size2 get_char_size(CharType p_char, CharType p_next, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks) const; + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, const Vector<Ref<DynamicFontAtSize> > &p_fallbacks, bool p_advance_only = false) const; void set_texture_flags(uint32_t p_flags); - bool update_oversampling(); + void update_oversampling(); DynamicFontAtSize(); ~DynamicFontAtSize(); @@ -188,17 +215,23 @@ public: private: Ref<DynamicFontData> data; Ref<DynamicFontAtSize> data_at_size; + Ref<DynamicFontAtSize> outline_data_at_size; Vector<Ref<DynamicFontData> > fallbacks; Vector<Ref<DynamicFontAtSize> > fallback_data_at_size; + Vector<Ref<DynamicFontAtSize> > fallback_outline_data_at_size; DynamicFontData::CacheID cache_id; + DynamicFontData::CacheID outline_cache_id; + bool valid; int spacing_top; int spacing_bottom; int spacing_char; int spacing_space; + Color outline_color; + protected: void _reload_cache(); @@ -215,6 +248,12 @@ public: void set_size(int p_size); int get_size() const; + void set_outline_size(int p_size); + int get_outline_size() const; + + void set_outline_color(Color p_color); + Color get_outline_color() const; + bool get_use_mipmaps() const; void set_use_mipmaps(bool p_enable); @@ -239,7 +278,9 @@ public: virtual bool is_distance_field_hint() const; - virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + virtual bool has_outline() const; + + virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; SelfList<DynamicFont> font_list; diff --git a/scene/resources/dynamic_font_stb.cpp b/scene/resources/dynamic_font_stb.cpp index 098b794a95..29f1106d16 100644 --- a/scene/resources/dynamic_font_stb.cpp +++ b/scene/resources/dynamic_font_stb.cpp @@ -162,7 +162,7 @@ Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next) const { return ret; } -float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const { +float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); @@ -172,13 +172,15 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharT return 0; } - Point2 cpos = p_pos; - cpos.x += c->h_align; - cpos.y -= get_ascent(); - cpos.y += c->v_align; - ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0); - if (c->texture_idx != -1) - VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx].texture->get_rid(), c->rect, p_modulate); + if (!p_outline) { + Point2 cpos = p_pos; + cpos.x += c->h_align; + cpos.y -= get_ascent(); + cpos.y += c->v_align; + ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0); + if (c->texture_idx != -1) + VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx].texture->get_rid(), c->rect, p_modulate); + } //textures[c->texture_idx].texture->draw(p_canvas_item,Vector2()); @@ -459,12 +461,12 @@ bool DynamicFont::is_distance_field_hint() const { return false; } -float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const { +float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { if (!data_at_size.is_valid()) return 0; - return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate); + return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline); } DynamicFont::DynamicFont() { diff --git a/scene/resources/dynamic_font_stb.h b/scene/resources/dynamic_font_stb.h index 4c1097d28b..feae29c0c2 100644 --- a/scene/resources/dynamic_font_stb.h +++ b/scene/resources/dynamic_font_stb.h @@ -136,7 +136,7 @@ public: Size2 get_char_size(CharType p_char, CharType p_next = 0) const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; DynamicFontAtSize(); ~DynamicFontAtSize(); @@ -171,7 +171,7 @@ public: virtual bool is_distance_field_hint() const; - virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; DynamicFont(); ~DynamicFont(); diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index 3fab4d3cfc..d3da842b79 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -378,7 +378,7 @@ bool Environment::is_ssr_rough() const { void Environment::set_ssao_enabled(bool p_enable) { ssao_enabled = p_enable; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); _change_notify(); } @@ -390,7 +390,7 @@ bool Environment::is_ssao_enabled() const { void Environment::set_ssao_radius(float p_radius) { ssao_radius = p_radius; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_radius() const { @@ -400,7 +400,7 @@ float Environment::get_ssao_radius() const { void Environment::set_ssao_intensity(float p_intensity) { ssao_intensity = p_intensity; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_intensity() const { @@ -411,7 +411,7 @@ float Environment::get_ssao_intensity() const { void Environment::set_ssao_radius2(float p_radius) { ssao_radius2 = p_radius; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_radius2() const { @@ -421,7 +421,7 @@ float Environment::get_ssao_radius2() const { void Environment::set_ssao_intensity2(float p_intensity) { ssao_intensity2 = p_intensity; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_intensity2() const { @@ -431,7 +431,7 @@ float Environment::get_ssao_intensity2() const { void Environment::set_ssao_bias(float p_bias) { ssao_bias = p_bias; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_bias() const { @@ -441,17 +441,27 @@ float Environment::get_ssao_bias() const { void Environment::set_ssao_direct_light_affect(float p_direct_light_affect) { ssao_direct_light_affect = p_direct_light_affect; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_direct_light_affect() const { return ssao_direct_light_affect; } +void Environment::set_ssao_ao_channel_affect(float p_ao_channel_affect) { + + ssao_ao_channel_affect = p_ao_channel_affect; + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); +} +float Environment::get_ssao_ao_channel_affect() const { + + return ssao_ao_channel_affect; +} + void Environment::set_ssao_color(const Color &p_color) { ssao_color = p_color; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Color Environment::get_ssao_color() const { @@ -462,7 +472,7 @@ Color Environment::get_ssao_color() const { void Environment::set_ssao_blur(SSAOBlur p_blur) { ssao_blur = p_blur; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Environment::SSAOBlur Environment::get_ssao_blur() const { @@ -472,7 +482,7 @@ Environment::SSAOBlur Environment::get_ssao_blur() const { void Environment::set_ssao_quality(SSAOQuality p_quality) { ssao_quality = p_quality; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Environment::SSAOQuality Environment::get_ssao_quality() const { @@ -483,7 +493,7 @@ Environment::SSAOQuality Environment::get_ssao_quality() const { void Environment::set_ssao_edge_sharpness(float p_edge_sharpness) { ssao_edge_sharpness = p_edge_sharpness; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_edge_sharpness() const { @@ -1008,6 +1018,9 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_ssao_direct_light_affect", "amount"), &Environment::set_ssao_direct_light_affect); ClassDB::bind_method(D_METHOD("get_ssao_direct_light_affect"), &Environment::get_ssao_direct_light_affect); + ClassDB::bind_method(D_METHOD("set_ssao_ao_channel_affect", "amount"), &Environment::set_ssao_ao_channel_affect); + ClassDB::bind_method(D_METHOD("get_ssao_ao_channel_affect"), &Environment::get_ssao_ao_channel_affect); + ClassDB::bind_method(D_METHOD("set_ssao_color", "color"), &Environment::set_ssao_color); ClassDB::bind_method(D_METHOD("get_ssao_color"), &Environment::get_ssao_color); @@ -1028,6 +1041,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_intensity2", PROPERTY_HINT_RANGE, "0.0,128,0.1"), "set_ssao_intensity2", "get_ssao_intensity2"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_bias", PROPERTY_HINT_RANGE, "0.001,8,0.001"), "set_ssao_bias", "get_ssao_bias"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_light_affect", PROPERTY_HINT_RANGE, "0.00,1,0.01"), "set_ssao_direct_light_affect", "get_ssao_direct_light_affect"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_ao_channel_affect", PROPERTY_HINT_RANGE, "0.00,1,0.01"), "set_ssao_ao_channel_affect", "get_ssao_ao_channel_affect"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ssao_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ssao_color", "get_ssao_color"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ssao_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_ssao_quality", "get_ssao_quality"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ssao_blur", PROPERTY_HINT_ENUM, "Disabled,1x1,2x2,3x3"), "set_ssao_blur", "get_ssao_blur"); @@ -1220,6 +1234,7 @@ Environment::Environment() { ssao_intensity2 = 1; ssao_bias = 0.01; ssao_direct_light_affect = 0.0; + ssao_ao_channel_affect = 0.0; ssao_blur = SSAO_BLUR_3x3; set_ssao_edge_sharpness(4); set_ssao_quality(SSAO_QUALITY_LOW); diff --git a/scene/resources/environment.h b/scene/resources/environment.h index 27fd57aa09..7d66c7e60b 100644 --- a/scene/resources/environment.h +++ b/scene/resources/environment.h @@ -127,6 +127,7 @@ private: float ssao_intensity2; float ssao_bias; float ssao_direct_light_affect; + float ssao_ao_channel_affect; Color ssao_color; SSAOBlur ssao_blur; float ssao_edge_sharpness; @@ -274,6 +275,9 @@ public: void set_ssao_direct_light_affect(float p_direct_light_affect); float get_ssao_direct_light_affect() const; + void set_ssao_ao_channel_affect(float p_ao_channel_affect); + float get_ssao_ao_channel_affect() const; + void set_ssao_color(const Color &p_color); Color get_ssao_color() const; diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 6fc5778dd8..0bae9d9b2d 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -32,12 +32,12 @@ #include "core/io/resource_loader.h" #include "core/os/file_access.h" +#include "method_bind_ext.gen.inc" -void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate) const { - +void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate, const Color &p_outline_modulate) const { float length = get_string_size(p_text).width; if (length >= p_width) { - draw(p_canvas_item, p_pos, p_text, p_modulate, p_width); + draw(p_canvas_item, p_pos, p_text, p_modulate, p_width, p_outline_modulate); return; } @@ -56,13 +56,14 @@ void Font::draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, f ERR_PRINT("Unknown halignment type"); } break; } - draw(p_canvas_item, p_pos + Point2(ofs, 0), p_text, p_modulate, p_width); + draw(p_canvas_item, p_pos + Point2(ofs, 0), p_text, p_modulate, p_width, p_outline_modulate); } -void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w) const { - +void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w, const Color &p_outline_modulate) const { Vector2 ofs; + int chars_drawn = 0; + bool with_outline = has_outline(); for (int i = 0; i < p_text.length(); i++) { int width = get_char_size(p_text[i]).width; @@ -70,7 +71,15 @@ void Font::draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, co if (p_clip_w >= 0 && (ofs.x + width) > p_clip_w) break; //clip - ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate); + ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], with_outline ? p_outline_modulate : p_modulate, with_outline); + ++chars_drawn; + } + + if (has_outline()) { + ofs = Vector2(0, 0); + for (int i = 0; i < chars_drawn; i++) { + ofs.x += draw_char(p_canvas_item, p_pos + ofs, p_text[i], p_text[i + 1], p_modulate, false); + } } } @@ -81,13 +90,14 @@ void Font::update_changes() { void Font::_bind_methods() { - ClassDB::bind_method(D_METHOD("draw", "canvas_item", "position", "string", "modulate", "clip_w"), &Font::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("draw", "canvas_item", "position", "string", "modulate", "clip_w", "outline_modulate"), &Font::draw, DEFVAL(Color(1, 1, 1)), DEFVAL(-1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("get_ascent"), &Font::get_ascent); ClassDB::bind_method(D_METHOD("get_descent"), &Font::get_descent); ClassDB::bind_method(D_METHOD("get_height"), &Font::get_height); ClassDB::bind_method(D_METHOD("is_distance_field_hint"), &Font::is_distance_field_hint); ClassDB::bind_method(D_METHOD("get_string_size", "string"), &Font::get_string_size); - ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "position", "char", "next", "modulate"), &Font::draw_char, DEFVAL(-1), DEFVAL(Color(1, 1, 1))); + ClassDB::bind_method(D_METHOD("has_outline"), &Font::has_outline); + ClassDB::bind_method(D_METHOD("draw_char", "canvas_item", "position", "char", "next", "modulate", "outline"), &Font::draw_char, DEFVAL(-1), DEFVAL(Color(1, 1, 1)), DEFVAL(false)); ClassDB::bind_method(D_METHOD("update_changes"), &Font::update_changes); } @@ -494,23 +504,24 @@ Ref<BitmapFont> BitmapFont::get_fallback() const { return fallback; } -float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate) const { +float BitmapFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { const Character *c = char_map.getptr(p_char); if (!c) { if (fallback.is_valid()) - return fallback->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate); + return fallback->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline); return 0; } - Point2 cpos = p_pos; - cpos.x += c->h_align; - cpos.y -= ascent; - cpos.y += c->v_align; ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0); - if (c->texture_idx != -1) + if (!p_outline && c->texture_idx != -1) { + Point2 cpos = p_pos; + cpos.x += c->h_align; + cpos.y -= ascent; + cpos.y += c->v_align; VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx]->get_rid(), c->rect, p_modulate, false, RID(), false); + } return get_char_size(p_char, p_next).width; } diff --git a/scene/resources/font.h b/scene/resources/font.h index ae08890be3..4e295b6035 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -56,14 +56,55 @@ public: virtual bool is_distance_field_hint() const = 0; - void draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1) const; - void draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate = Color(1, 1, 1)) const; - virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const = 0; + void draw(RID p_canvas_item, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1, const Color &p_outline_modulate = Color(1, 1, 1)) const; + void draw_halign(RID p_canvas_item, const Point2 &p_pos, HAlign p_align, float p_width, const String &p_text, const Color &p_modulate = Color(1, 1, 1), const Color &p_outline_modulate = Color(1, 1, 1)) const; + + virtual bool has_outline() const { return false; } + virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const = 0; void update_changes(); Font(); }; +// Helper class to that draws outlines immediately and draws characters in its destructor. +class FontDrawer { + const Ref<Font> &font; + Color outline_color; + bool has_outline; + + struct PendingDraw { + RID canvas_item; + Point2 pos; + CharType chr; + CharType next; + Color modulate; + }; + + Vector<PendingDraw> pending_draws; + +public: + FontDrawer(const Ref<Font> &p_font, const Color &p_outline_color) : + font(p_font), + outline_color(p_outline_color) { + has_outline = p_font->has_outline(); + } + + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) { + if (has_outline) { + PendingDraw draw = { p_canvas_item, p_pos, p_char, p_next, p_modulate }; + pending_draws.push_back(draw); + } + return font->draw_char(p_canvas_item, p_pos, p_char, p_next, has_outline ? outline_color : p_modulate, has_outline); + } + + ~FontDrawer() { + for (int i = 0; i < pending_draws.size(); ++i) { + const PendingDraw &draw = pending_draws[i]; + font->draw_char(draw.canvas_item, draw.pos, draw.chr, draw.next, draw.modulate, false); + } + } +}; + class BitmapFont : public Font { GDCLASS(BitmapFont, Font); @@ -153,7 +194,7 @@ public: void set_distance_field_hint(bool p_distance_field); bool is_distance_field_hint() const; - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1)) const; + float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; BitmapFont(); ~BitmapFont(); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index a83ef198fb..5e7569586a 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -387,10 +387,12 @@ void SpatialMaterial::_update_shader() { if (flags[FLAG_USE_VERTEX_LIGHTING]) { code += ",vertex_lighting"; } - if (flags[FLAG_TRIPLANAR_USE_WORLD] && (flags[FLAG_UV1_USE_TRIPLANAR] || flags[FLAG_UV2_USE_TRIPLANAR])) { code += ",world_vertex_coords"; } + if (flags[FLAG_DONT_RECEIVE_SHADOWS]) { + code += ",shadows_disabled"; + } code += ";\n"; code += "uniform vec4 albedo : hint_color;\n"; @@ -550,7 +552,7 @@ void SpatialMaterial::_update_shader() { //handle animation code += "\tint particle_total_frames = particles_anim_h_frames * particles_anim_v_frames;\n"; - code += "\tint particle_frame = int(INSTANCE_CUSTOM.y * float(particle_total_frames));\n"; + code += "\tint particle_frame = int(INSTANCE_CUSTOM.z * float(particle_total_frames));\n"; code += "\tif (particles_anim_loop) particle_frame=clamp(particle_frame,0,particle_total_frames-1); else particle_frame=abs(particle_frame)%particle_total_frames;\n"; code += "\tUV /= vec2(float(particles_anim_h_frames),float(particles_anim_v_frames));\n"; code += "\tUV += vec2(float(particle_frame % particles_anim_h_frames) / float(particles_anim_h_frames),float(particle_frame / particles_anim_h_frames) / float(particles_anim_v_frames));\n"; @@ -1849,6 +1851,7 @@ void SpatialMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_world_triplanar"), "set_flag", "get_flag", FLAG_TRIPLANAR_USE_WORLD); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_fixed_size"), "set_flag", "get_flag", FLAG_FIXED_SIZE); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_albedo_tex_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_do_not_receive_shadows"), "set_flag", "get_flag", FLAG_DONT_RECEIVE_SHADOWS); ADD_GROUP("Vertex Color", "vertex_color"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_use_as_albedo"), "set_flag", "get_flag", FLAG_ALBEDO_FROM_VERTEX_COLOR); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_is_srgb"), "set_flag", "get_flag", FLAG_SRGB_VERTEX_COLOR); @@ -2038,6 +2041,7 @@ void SpatialMaterial::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_USE_ALPHA_SCISSOR); BIND_ENUM_CONSTANT(FLAG_TRIPLANAR_USE_WORLD); BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_FORCE_SRGB); + BIND_ENUM_CONSTANT(FLAG_DONT_RECEIVE_SHADOWS); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(DIFFUSE_BURLEY); diff --git a/scene/resources/material.h b/scene/resources/material.h index 2c297cda41..ce733bfb8d 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -188,6 +188,7 @@ public: FLAG_EMISSION_ON_UV2, FLAG_USE_ALPHA_SCISSOR, FLAG_ALBEDO_TEXTURE_FORCE_SRGB, + FLAG_DONT_RECEIVE_SHADOWS, FLAG_MAX }; @@ -236,7 +237,7 @@ private: uint64_t blend_mode : 2; uint64_t depth_draw_mode : 2; uint64_t cull_mode : 2; - uint64_t flags : 14; + uint64_t flags : 15; uint64_t detail_blend_mode : 2; uint64_t diffuse_mode : 3; uint64_t specular_mode : 2; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 949ba12a4c..b0620d3363 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -222,7 +222,6 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { continue; Array a = surface_get_arrays(i); - int vcount = 0; if (i == 0) { arrays = a; @@ -230,6 +229,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { index_accum += v.size(); } else { + int vcount = 0; for (int j = 0; j < arrays.size(); j++) { if (arrays[j].get_type() == Variant::NIL || a[j].get_type() == Variant::NIL) { @@ -315,6 +315,8 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { } } + ERR_FAIL_COND_V(arrays.size() != ARRAY_MAX, Ref<ArrayMesh>()); + { PoolVector<int>::Write ir; PoolVector<int> indices = arrays[ARRAY_INDEX]; @@ -910,6 +912,7 @@ void ArrayMesh::surface_set_material(int p_idx, const Ref<Material> &p_material) VisualServer::get_singleton()->mesh_surface_set_material(mesh, p_idx, p_material.is_null() ? RID() : p_material->get_rid()); _change_notify("material"); + emit_changed(); } void ArrayMesh::surface_set_name(int p_idx, const String &p_name) { @@ -917,6 +920,7 @@ void ArrayMesh::surface_set_name(int p_idx, const String &p_name) { ERR_FAIL_INDEX(p_idx, surfaces.size()); surfaces[p_idx].name = p_name; + emit_changed(); } String ArrayMesh::surface_get_name(int p_idx) const { @@ -929,6 +933,7 @@ void ArrayMesh::surface_update_region(int p_surface, int p_offset, const PoolVec ERR_FAIL_INDEX(p_surface, surfaces.size()); VS::get_singleton()->mesh_surface_update_region(mesh, p_surface, p_offset, p_data); + emit_changed(); } void ArrayMesh::surface_set_custom_aabb(int p_idx, const AABB &p_aabb) { @@ -936,6 +941,7 @@ void ArrayMesh::surface_set_custom_aabb(int p_idx, const AABB &p_aabb) { ERR_FAIL_INDEX(p_idx, surfaces.size()); surfaces[p_idx].aabb = p_aabb; // set custom aabb too? + emit_changed(); } Ref<Material> ArrayMesh::surface_get_material(int p_idx) const { @@ -984,6 +990,7 @@ void ArrayMesh::set_custom_aabb(const AABB &p_custom) { custom_aabb = p_custom; VS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb); + emit_changed(); } AABB ArrayMesh::get_custom_aabb() const { @@ -1187,8 +1194,6 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe for (int j = 0; j < 3; j++) { - int vertex_idx = gen_vertices[gen_indices[i + j]]; - SurfaceTool::Vertex v = surfaces[surface].vertices[uv_index[gen_vertices[gen_indices[i + j]]].second]; if (surfaces[surface].format & ARRAY_FORMAT_COLOR) { diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index e8b7ecaf9a..a3fb068569 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -100,7 +100,7 @@ public: ARRAY_FLAG_USE_16_BIT_BONES = ARRAY_COMPRESS_INDEX << 2, ARRAY_FLAG_USE_DYNAMIC_UPDATE = ARRAY_COMPRESS_INDEX << 3, - ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_VERTEX | ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS + ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS }; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 3df9285bb6..846f6e356e 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -249,6 +249,8 @@ Node *SceneState::instance(GenEditState p_edit_state) const { //must make a copy, because this res is local to scene } } + } else if (p_edit_state == GEN_EDIT_STATE_INSTANCE) { + value = value.duplicate(true); // Duplicate arrays and dictionaries for the editor } node->set(snames[nprops[j].name], value, &valid); } diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 94c54c91d3..28aa6f1aa7 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -57,6 +57,31 @@ void PrimitiveMesh::_update() const { } } + if (flip_faces) { + PoolVector<Vector3> normals = arr[VS::ARRAY_NORMAL]; + PoolVector<int> indices = arr[VS::ARRAY_INDEX]; + if (normals.size() && indices.size()) { + + { + int nc = normals.size(); + PoolVector<Vector3>::Write w = normals.write(); + for (int i = 0; i < nc; i++) { + w[i] = -w[i]; + } + } + + { + int ic = indices.size(); + PoolVector<int>::Write w = indices.write(); + for (int i = 0; i < ic; i += 3) { + SWAP(w[i + 0], w[i + 1]); + } + } + arr[VS::ARRAY_NORMAL] = normals; + arr[VS::ARRAY_INDEX] = indices; + } + } + // in with the new VisualServer::get_singleton()->mesh_clear(mesh); VisualServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (VisualServer::PrimitiveType)primitive_type, arr); @@ -65,6 +90,8 @@ void PrimitiveMesh::_update() const { pending_request = false; _clear_triangle_mesh(); + + const_cast<PrimitiveMesh *>(this)->emit_changed(); } void PrimitiveMesh::_request_update() { @@ -164,7 +191,15 @@ void PrimitiveMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mesh_arrays"), &PrimitiveMesh::get_mesh_arrays); + ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &PrimitiveMesh::set_custom_aabb); + ClassDB::bind_method(D_METHOD("get_custom_aabb"), &PrimitiveMesh::get_custom_aabb); + + ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &PrimitiveMesh::set_flip_faces); + ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, ""), "set_custom_aabb", "get_custom_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); } void PrimitiveMesh::set_material(const Ref<Material> &p_material) { @@ -185,7 +220,30 @@ Array PrimitiveMesh::get_mesh_arrays() const { return surface_get_arrays(0); } +void PrimitiveMesh::set_custom_aabb(const AABB &p_custom) { + + custom_aabb = p_custom; + VS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb); + emit_changed(); +} + +AABB PrimitiveMesh::get_custom_aabb() const { + + return custom_aabb; +} + +void PrimitiveMesh::set_flip_faces(bool p_enable) { + flip_faces = p_enable; + _request_update(); +} + +bool PrimitiveMesh::get_flip_faces() const { + return flip_faces; +} + PrimitiveMesh::PrimitiveMesh() { + + flip_faces = false; // defaults mesh = VisualServer::get_singleton()->mesh_create(); @@ -361,10 +419,10 @@ void CapsuleMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CapsuleMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CapsuleMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "mid_height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_mid_height", "get_mid_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "mid_height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_mid_height", "get_mid_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); } void CapsuleMesh::set_radius(const float p_radius) { @@ -618,10 +676,10 @@ void CubeMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &CubeMesh::set_subdivide_depth); ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &CubeMesh::get_subdivide_depth); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_height", "get_subdivide_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); + 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_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); } void CubeMesh::set_size(const Vector3 &p_size) { @@ -823,11 +881,11 @@ void CylinderMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_top_radius", "get_top_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_bottom_radius", "get_bottom_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_top_radius", "get_top_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); } void CylinderMesh::set_top_radius(const float p_radius) { @@ -959,8 +1017,8 @@ void PlaneMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PlaneMesh::get_subdivide_depth); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth"); + 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"); } void PlaneMesh::set_size(const Size2 &p_size) { @@ -1225,9 +1283,9 @@ void PrismMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "left_to_right", PROPERTY_HINT_RANGE, "-2.0,2.0,0.1"), "set_left_to_right", "get_left_to_right"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_height", "get_subdivide_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth"); + 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_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); } void PrismMesh::set_left_to_right(const float p_left_to_right) { @@ -1294,10 +1352,10 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { PoolVector<float> tangents; PoolVector<Vector2> uvs; - faces.resize(4); - normals.resize(4); - tangents.resize(4 * 4); - uvs.resize(4); + faces.resize(6); + normals.resize(6); + tangents.resize(6 * 4); + uvs.resize(6); Vector2 _size = Vector2(size.x / 2.0f, size.y / 2.0f); @@ -1308,9 +1366,15 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { Vector3(_size.x, -_size.y, 0), }; - for (int i = 0; i < 4; i++) { + static const int indices[6] = { + 0, 1, 2, + 0, 2, 3 + }; - faces.set(i, quad_faces[i]); + for (int i = 0; i < 6; i++) { + + int j = indices[i]; + faces.set(i, quad_faces[j]); normals.set(i, Vector3(0, 0, 1)); tangents.set(i * 4 + 0, 1.0); tangents.set(i * 4 + 1, 0.0); @@ -1324,14 +1388,14 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { Vector2(1, 1), }; - uvs.set(i, quad_uv[i]); + uvs.set(i, quad_uv[j]); } p_arr[VS::ARRAY_VERTEX] = faces; p_arr[VS::ARRAY_NORMAL] = normals; p_arr[VS::ARRAY_TANGENT] = tangents; p_arr[VS::ARRAY_TEX_UV] = uvs; -}; +} void QuadMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadMesh::set_size); @@ -1340,7 +1404,7 @@ void QuadMesh::_bind_methods() { } QuadMesh::QuadMesh() { - primitive_type = PRIMITIVE_TRIANGLE_FAN; + primitive_type = PRIMITIVE_TRIANGLES; size = Size2(1.0, 1.0); } @@ -1441,10 +1505,10 @@ void SphereMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_is_hemisphere", "is_hemisphere"), &SphereMesh::set_is_hemisphere); ClassDB::bind_method(D_METHOD("get_is_hemisphere"), &SphereMesh::get_is_hemisphere); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_hemisphere"), "set_is_hemisphere", "get_is_hemisphere"); } diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 94a7a055a3..a91aa09ffe 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -48,8 +48,10 @@ class PrimitiveMesh : public Mesh { private: RID mesh; mutable AABB aabb; + AABB custom_aabb; Ref<Material> material; + bool flip_faces; mutable bool pending_request; void _update() const; @@ -81,6 +83,12 @@ public: Array get_mesh_arrays() const; + void set_custom_aabb(const AABB &p_custom); + AABB get_custom_aabb() const; + + void set_flip_faces(bool p_enable); + bool get_flip_faces() const; + PrimitiveMesh(); ~PrimitiveMesh(); }; diff --git a/scene/resources/scene_format_text.cpp b/scene/resources/scene_format_text.cpp index 91c801c016..597866eb74 100644 --- a/scene/resources/scene_format_text.cpp +++ b/scene/resources/scene_format_text.cpp @@ -1445,8 +1445,15 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, static String _valprop(const String &p_name) { - if (p_name.find("\"") != -1 || p_name.find("=") != -1 || p_name.find(" ") != -1) - return "\"" + p_name.c_escape_multiline() + "\""; + // Escape and quote strings with extended ASCII or further Unicode characters + // as well as '"', '=' or ' ' (32) + const CharType *cstr = p_name.c_str(); + for (int i = 0; cstr[i]; i++) { + if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] < 33 || cstr[i] > 126) { + return "\"" + p_name.c_escape_multiline() + "\""; + } + } + // Keep as is return p_name; } @@ -1506,7 +1513,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r title += "load_steps=" + itos(load_steps) + " "; } title += "format=" + itos(FORMAT_VERSION) + ""; - //title+="engine_version=\""+itos(VERSION_MAJOR)+"."+itos(VERSION_MINOR)+"\""; f->store_string(title); f->store_line("]\n"); //one empty line @@ -1666,7 +1672,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r f->store_string(vars); } - f->store_line("]\n"); + f->store_line("]"); for (int j = 0; j < state->get_node_property_count(i); j++) { @@ -1676,10 +1682,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r f->store_string(_valprop(String(state->get_node_property_name(i, j))) + " = " + vars + "\n"); } - if (state->get_node_property_count(i)) { - //add space - f->store_line(String()); - } + f->store_line(String()); } for (int i = 0; i < state->get_connection_count(); i++) { @@ -1702,14 +1705,12 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r f->store_string(" binds= " + vars); } - f->store_line("]\n"); + f->store_line("]"); } - f->store_line(String()); - Vector<NodePath> editable_instances = state->get_editable_instances(); for (int i = 0; i < editable_instances.size(); i++) { - f->store_line("[editable path=\"" + editable_instances[i].operator String() + "\"]"); + f->store_line("\n[editable path=\"" + editable_instances[i].operator String() + "\"]"); } } diff --git a/scene/resources/shape_2d.h b/scene/resources/shape_2d.h index ed655b8a93..7eb0406bd8 100644 --- a/scene/resources/shape_2d.h +++ b/scene/resources/shape_2d.h @@ -45,7 +45,7 @@ protected: Shape2D(const RID &p_rid); public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return true; } + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return get_rect().has_point(p_point); } void set_custom_solver_bias(real_t p_bias); real_t get_custom_solver_bias() const; diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index f256e0397d..ebad00b068 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -101,23 +101,27 @@ StyleBox::StyleBox() { } } -void StyleBoxTexture::set_texture(RES p_texture) { +void StyleBoxTexture::set_texture(Ref<Texture> p_texture) { if (texture == p_texture) return; texture = p_texture; - region_rect = Rect2(Point2(), texture->get_size()); + if (p_texture.is_null()) { + region_rect = Rect2(0, 0, 0, 0); + } else { + region_rect = Rect2(Point2(), texture->get_size()); + } emit_signal("texture_changed"); emit_changed(); _change_notify("texture"); } -RES StyleBoxTexture::get_texture() const { +Ref<Texture> StyleBoxTexture::get_texture() const { return texture; } -void StyleBoxTexture::set_normal_map(RES p_normal_map) { +void StyleBoxTexture::set_normal_map(Ref<Texture> p_normal_map) { if (normal_map == p_normal_map) return; @@ -125,15 +129,24 @@ void StyleBoxTexture::set_normal_map(RES p_normal_map) { emit_changed(); } -RES StyleBoxTexture::get_normal_map() const { +Ref<Texture> StyleBoxTexture::get_normal_map() const { return normal_map; } void StyleBoxTexture::set_margin_size(Margin p_margin, float p_size) { + ERR_FAIL_INDEX(p_margin, 4); + margin[p_margin] = p_size; emit_changed(); + static const char *margin_prop[4] = { + "content_margin_left", + "content_margin_top", + "content_margin_right", + "content_margin_bottom", + }; + _change_notify(margin_prop[p_margin]); } float StyleBoxTexture::get_margin_size(Margin p_margin) const { @@ -182,7 +195,7 @@ Size2 StyleBoxTexture::get_center_size() const { if (texture.is_null()) return Size2(); - return texture->get_size() - get_minimum_size(); + return region_rect.size - get_minimum_size(); } void StyleBoxTexture::set_expand_margin_size(Margin p_expand_margin, float p_size) { diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index fb79aa360e..c1d84fe19f 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -112,11 +112,11 @@ public: void set_region_rect(const Rect2 &p_region_rect); Rect2 get_region_rect() const; - void set_texture(RES p_texture); - RES get_texture() const; + void set_texture(Ref<Texture> p_texture); + Ref<Texture> get_texture() const; - void set_normal_map(RES p_normal_map); - RES get_normal_map() const; + void set_normal_map(Ref<Texture> p_normal_map); + Ref<Texture> get_normal_map() const; void set_draw_center(bool p_enabled); bool is_draw_center_enabled() const; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 07c1036a10..5a42873d79 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -861,7 +861,7 @@ void SurfaceTool::generate_tangents() { } } -void SurfaceTool::generate_normals() { +void SurfaceTool::generate_normals(bool p_flip) { ERR_FAIL_COND(primitive != Mesh::PRIMITIVE_TRIANGLES); @@ -887,7 +887,11 @@ void SurfaceTool::generate_normals() { ERR_FAIL_COND(!v[2]); E = v[2]->next(); - Vector3 normal = Plane(v[0]->get().vertex, v[1]->get().vertex, v[2]->get().vertex).normal; + Vector3 normal; + if (!p_flip) + normal = Plane(v[0]->get().vertex, v[1]->get().vertex, v[2]->get().vertex).normal; + else + normal = Plane(v[2]->get().vertex, v[1]->get().vertex, v[0]->get().vertex).normal; if (smooth) { @@ -980,7 +984,7 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("index"), &SurfaceTool::index); ClassDB::bind_method(D_METHOD("deindex"), &SurfaceTool::deindex); - ClassDB::bind_method(D_METHOD("generate_normals"), &SurfaceTool::generate_normals); + ClassDB::bind_method(D_METHOD("generate_normals", "flip"), &SurfaceTool::generate_normals, DEFVAL(false)); ClassDB::bind_method(D_METHOD("generate_tangents"), &SurfaceTool::generate_tangents); ClassDB::bind_method(D_METHOD("add_to_format", "flags"), &SurfaceTool::add_to_format); diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index 7a9aa349bb..459d399380 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -116,7 +116,7 @@ public: void index(); void deindex(); - void generate_normals(); + void generate_normals(bool p_flip = false); void generate_tangents(); void add_to_format(int p_flags) { format |= p_flags; } diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index c0f6756fd1..54f5aea160 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -76,7 +76,9 @@ void Texture::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_rect_region", "canvas_item", "rect", "src_rect", "modulate", "transpose", "normal_map", "clip_uv"), &Texture::draw_rect_region, DEFVAL(Color(1, 1, 1)), DEFVAL(false), DEFVAL(Variant()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("get_data"), &Texture::get_data); + ADD_GROUP("Flags", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter,Anisotropic Linear,Convert to Linear,Mirrored Repeat,Video Surface"), "set_flags", "get_flags"); + ADD_GROUP("", ""); BIND_ENUM_CONSTANT(FLAGS_DEFAULT); BIND_ENUM_CONSTANT(FLAG_MIPMAPS); @@ -220,12 +222,15 @@ Image::Format ImageTexture::get_format() const { return format; } -void ImageTexture::load(const String &p_path) { +Error ImageTexture::load(const String &p_path) { Ref<Image> img; img.instance(); - img->load(p_path); - create_from_image(img); + Error err = img->load(p_path); + if (err == OK) { + create_from_image(img); + } + return err; } void ImageTexture::set_data(const Ref<Image> &p_image) { diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 93d7ec4ef9..d81fd3b19b 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -124,7 +124,7 @@ public: void set_flags(uint32_t p_flags); uint32_t get_flags() const; Image::Format get_format() const; - void load(const String &p_path); + Error load(const String &p_path); void set_data(const Ref<Image> &p_image); Ref<Image> get_data() const; diff --git a/scene/resources/theme.h b/scene/resources/theme.h index c23f237c75..e0d4038e7e 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -55,12 +55,12 @@ class Theme : public Resource { void _unref_font(Ref<Font> p_sc); void _emit_theme_changed(); - HashMap<StringName, HashMap<StringName, Ref<Texture>, StringNameHasher>, StringNameHasher> icon_map; - HashMap<StringName, HashMap<StringName, Ref<StyleBox>, StringNameHasher>, StringNameHasher> style_map; - HashMap<StringName, HashMap<StringName, Ref<Font>, StringNameHasher>, StringNameHasher> font_map; - HashMap<StringName, HashMap<StringName, Ref<Shader>, StringNameHasher>, StringNameHasher> shader_map; - HashMap<StringName, HashMap<StringName, Color, StringNameHasher>, StringNameHasher> color_map; - HashMap<StringName, HashMap<StringName, int, StringNameHasher>, StringNameHasher> constant_map; + HashMap<StringName, HashMap<StringName, Ref<Texture> > > icon_map; + HashMap<StringName, HashMap<StringName, Ref<StyleBox> > > style_map; + HashMap<StringName, HashMap<StringName, Ref<Font> > > font_map; + HashMap<StringName, HashMap<StringName, Ref<Shader> > > shader_map; + HashMap<StringName, HashMap<StringName, Color> > color_map; + HashMap<StringName, HashMap<StringName, int> > constant_map; protected: bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 4463f98b07..58057cda0c 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -142,6 +142,8 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { tile_set_navigation_polygon(id, p_value); else if (what == "navigation_offset") tile_set_navigation_polygon_offset(id, p_value); + else if (what == "z_index") + tile_set_z_index(id, p_value); else return false; @@ -239,6 +241,8 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { r_ret = tile_get_navigation_polygon(id); else if (what == "navigation_offset") r_ret = tile_get_navigation_polygon_offset(id); + else if (what == "z_index") + r_ret = tile_get_z_index(id); else return false; @@ -260,7 +264,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::RECT2, pre + "region")); p_list->push_back(PropertyInfo(Variant::INT, pre + "tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE")); if (tile_get_tile_mode(id) == AUTO_TILE) { - p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/bitmask_flags", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/icon_coordinate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "autotile/tile_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); @@ -278,6 +282,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::OBJECT, pre + "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape2D", PROPERTY_USAGE_EDITOR)); p_list->push_back(PropertyInfo(Variant::BOOL, pre + "shape_one_way", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "shapes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, pre + "z_index", PROPERTY_HINT_RANGE, itos(VS::CANVAS_ITEM_Z_MIN) + "," + itos(VS::CANVAS_ITEM_Z_MAX) + ",1")); } } @@ -347,6 +352,7 @@ void TileSet::tile_set_modulate(int p_id, const Color &p_modulate) { ERR_FAIL_COND(!tile_map.has(p_id)); tile_map[p_id].modulate = p_modulate; emit_changed(); + _change_notify("modulate"); } Color TileSet::tile_get_modulate(int p_id) const { @@ -747,6 +753,19 @@ Vector<TileSet::ShapeData> TileSet::tile_get_shapes(int p_id) const { return tile_map[p_id].shapes_data; } +int TileSet::tile_get_z_index(int p_id) const { + + ERR_FAIL_COND_V(!tile_map.has(p_id), 0); + return tile_map[p_id].z_index; +} + +void TileSet::tile_set_z_index(int p_id, int p_z_index) { + + ERR_FAIL_COND(!tile_map.has(p_id)); + tile_map[p_id].z_index = p_z_index; + emit_changed(); +} + void TileSet::_tile_set_shapes(int p_id, const Array &p_shapes) { ERR_FAIL_COND(!tile_map.has(p_id)); @@ -918,6 +937,8 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("tile_get_shape_count", "id"), &TileSet::tile_get_shape_count); ClassDB::bind_method(D_METHOD("tile_set_shapes", "id", "shapes"), &TileSet::_tile_set_shapes); ClassDB::bind_method(D_METHOD("tile_get_shapes", "id"), &TileSet::_tile_get_shapes); + ClassDB::bind_method(D_METHOD("tile_set_tile_mode", "id", "tilemode"), &TileSet::tile_set_tile_mode); + ClassDB::bind_method(D_METHOD("tile_get_tile_mode", "id"), &TileSet::tile_get_tile_mode); ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon", "id", "navigation_polygon"), &TileSet::tile_set_navigation_polygon); ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon", "id"), &TileSet::tile_get_navigation_polygon); ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon_offset", "id", "navigation_polygon_offset"), &TileSet::tile_set_navigation_polygon_offset); @@ -926,6 +947,8 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("tile_get_light_occluder", "id"), &TileSet::tile_get_light_occluder); ClassDB::bind_method(D_METHOD("tile_set_occluder_offset", "id", "occluder_offset"), &TileSet::tile_set_occluder_offset); ClassDB::bind_method(D_METHOD("tile_get_occluder_offset", "id"), &TileSet::tile_get_occluder_offset); + ClassDB::bind_method(D_METHOD("tile_set_z_index", "id", "z_index"), &TileSet::tile_set_z_index); + ClassDB::bind_method(D_METHOD("tile_get_z_index", "id"), &TileSet::tile_get_z_index); ClassDB::bind_method(D_METHOD("remove_tile", "id"), &TileSet::remove_tile); ClassDB::bind_method(D_METHOD("clear"), &TileSet::clear); @@ -937,6 +960,7 @@ void TileSet::_bind_methods() { BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_subtile_selection", PropertyInfo(Variant::INT, "autotile_id"), PropertyInfo(Variant::INT, "bitmask"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "TileMap"), PropertyInfo(Variant::VECTOR2, "tile_location"))); BIND_ENUM_CONSTANT(BITMASK_2X2); + BIND_ENUM_CONSTANT(BITMASK_3X3_MINIMAL); BIND_ENUM_CONSTANT(BITMASK_3X3); BIND_ENUM_CONSTANT(BIND_TOPLEFT); @@ -947,6 +971,10 @@ void TileSet::_bind_methods() { BIND_ENUM_CONSTANT(BIND_BOTTOMLEFT); BIND_ENUM_CONSTANT(BIND_BOTTOM); BIND_ENUM_CONSTANT(BIND_BOTTOMRIGHT); + + BIND_ENUM_CONSTANT(SINGLE_TILE); + BIND_ENUM_CONSTANT(AUTO_TILE); + BIND_ENUM_CONSTANT(ANIMATED_TILE); } TileSet::TileSet() { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 46f34b6252..ec635ee5cc 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -56,6 +56,7 @@ public: enum BitmaskMode { BITMASK_2X2, + BITMASK_3X3_MINIMAL, BITMASK_3X3 }; @@ -113,11 +114,13 @@ private: Color modulate; TileMode tile_mode; AutotileData autotile_data; + int z_index; // Default modulate for back-compat explicit TileData() : tile_mode(SINGLE_TILE), - modulate(1, 1, 1) {} + modulate(1, 1, 1), + z_index(0) {} }; Map<int, TileData> tile_map; @@ -220,6 +223,9 @@ public: Ref<NavigationPolygon> autotile_get_navigation_polygon(int p_id, const Vector2 &p_coord) const; const Map<Vector2, Ref<NavigationPolygon> > &autotile_get_navigation_map(int p_id) const; + void tile_set_z_index(int p_id, int p_z_index); + int tile_get_z_index(int p_id) const; + void remove_tile(int p_id); bool has_tile(int p_id) const; @@ -238,5 +244,6 @@ public: VARIANT_ENUM_CAST(TileSet::AutotileBindings); VARIANT_ENUM_CAST(TileSet::BitmaskMode); +VARIANT_ENUM_CAST(TileSet::TileMode); #endif // TILE_SET_H diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index fdce0eb2b6..bf765385d0 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -51,6 +51,7 @@ SceneStringNames::SceneStringNames() { tree_entered = StaticCString::create("tree_entered"); tree_exiting = StaticCString::create("tree_exiting"); tree_exited = StaticCString::create("tree_exited"); + ready = StaticCString::create("ready"); item_rect_changed = StaticCString::create("item_rect_changed"); size_flags_changed = StaticCString::create("size_flags_changed"); minimum_size_changed = StaticCString::create("minimum_size_changed"); @@ -186,6 +187,8 @@ SceneStringNames::SceneStringNames() { node_configuration_warning_changed = StaticCString::create("node_configuration_warning_changed"); + output = StaticCString::create("output"); + path_pp = NodePath(".."); _default = StaticCString::create("default"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 63a9a5db4d..b88cf7d8d7 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -71,6 +71,7 @@ public: StringName tree_entered; StringName tree_exiting; StringName tree_exited; + StringName ready; StringName size_flags_changed; StringName minimum_size_changed; StringName sleeping_state_changed; @@ -198,6 +199,8 @@ public: StringName node_configuration_warning_changed; + StringName output; + enum { MAX_MATERIALS = 32 }; |