diff options
Diffstat (limited to 'scene')
318 files changed, 13103 insertions, 4766 deletions
diff --git a/scene/2d/animated_sprite.cpp b/scene/2d/animated_sprite.cpp index 707e95e271..0b20b781f0 100644 --- a/scene/2d/animated_sprite.cpp +++ b/scene/2d/animated_sprite.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "animated_sprite.h" + #include "core/os/os.h" #include "scene/scene_string_names.h" @@ -69,10 +70,7 @@ bool AnimatedSprite::_edit_use_rect() const { Ref<Texture> t; if (animation) t = frames->get_frame(animation, frame); - if (t.is_null()) - return false; - - return true; + return t.is_valid(); } Rect2 AnimatedSprite::get_anchorable_rect() const { @@ -93,7 +91,7 @@ Rect2 AnimatedSprite::_get_rect() const { Point2 ofs = offset; if (centered) - ofs -= s / 2; + ofs -= Size2(s) / 2; if (s == Size2(0, 0)) s = Size2(1, 1); @@ -276,9 +274,9 @@ void SpriteFrames::_set_animations(const Array &p_animations) { anim.speed = d["speed"]; anim.loop = d["loop"]; Array frames = d["frames"]; - for (int i = 0; i < frames.size(); i++) { + for (int j = 0; j < frames.size(); j++) { - RES res = frames[i]; + RES res = frames[j]; anim.frames.push_back(res); } @@ -359,12 +357,11 @@ void AnimatedSprite::_validate_property(PropertyInfo &property) const { } if (property.name == "frame") { - - property.hint = PROPERTY_HINT_SPRITE_FRAME; - + property.hint = PROPERTY_HINT_RANGE; if (frames->has_animation(animation) && frames->get_frame_count(animation) > 1) { property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; } + property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } } @@ -393,19 +390,30 @@ void AnimatedSprite::_notification(int p_what) { timeout = _get_frame_duration(); int fc = frames->get_frame_count(animation); - if (frame >= fc - 1) { + if ((!backwards && frame >= fc - 1) || (backwards && frame <= 0)) { if (frames->get_animation_loop(animation)) { + if (backwards) + frame = fc - 1; + else + frame = 0; + emit_signal(SceneStringNames::get_singleton()->animation_finished); - frame = 0; } else { - frame = fc - 1; + if (backwards) + frame = 0; + else + frame = fc - 1; + if (!is_over) { is_over = true; emit_signal(SceneStringNames::get_singleton()->animation_finished); } } } else { - frame++; + if (backwards) + frame--; + else + frame++; } update(); @@ -594,10 +602,12 @@ bool AnimatedSprite::_is_playing() const { return playing; } -void AnimatedSprite::play(const StringName &p_animation) { +void AnimatedSprite::play(const StringName &p_animation, const bool p_backwards) { if (p_animation) set_animation(p_animation); + + backwards = p_backwards; _set_playing(true); } @@ -632,6 +642,9 @@ void AnimatedSprite::_reset_timeout() { void AnimatedSprite::set_animation(const StringName &p_animation) { + ERR_FAIL_COND_MSG(frames == NULL, vformat("There is no animation with name '%s'.", p_animation)); + ERR_FAIL_COND_MSG(frames->get_animation_names().find(p_animation) == -1, vformat("There is no animation with name '%s'.", p_animation)); + if (animation == p_animation) return; @@ -649,7 +662,7 @@ StringName AnimatedSprite::get_animation() const { String AnimatedSprite::get_configuration_warning() const { if (frames.is_null()) { - return TTR("A SpriteFrames resource must be created or set in the 'Frames' property in order for AnimatedSprite to display frames."); + return TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite to display frames."); } return String(); @@ -666,7 +679,7 @@ void AnimatedSprite::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_playing", "playing"), &AnimatedSprite::_set_playing); ClassDB::bind_method(D_METHOD("_is_playing"), &AnimatedSprite::_is_playing); - ClassDB::bind_method(D_METHOD("play", "anim"), &AnimatedSprite::play, DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("play", "anim", "backwards"), &AnimatedSprite::play, DEFVAL(StringName()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("stop"), &AnimatedSprite::stop); ClassDB::bind_method(D_METHOD("is_playing"), &AnimatedSprite::is_playing); @@ -695,7 +708,7 @@ void AnimatedSprite::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "frame", PROPERTY_HINT_SPRITE_FRAME), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "speed_scale"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "_set_playing", "_is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered"), "set_centered", "is_centered"); @@ -713,6 +726,7 @@ AnimatedSprite::AnimatedSprite() { frame = 0; speed_scale = 1.0f; playing = false; + backwards = false; animation = "default"; timeout = 0; is_over = false; diff --git a/scene/2d/animated_sprite.h b/scene/2d/animated_sprite.h index 8753f88799..2cc372bd93 100644 --- a/scene/2d/animated_sprite.h +++ b/scene/2d/animated_sprite.h @@ -128,6 +128,7 @@ class AnimatedSprite : public Node2D { Ref<SpriteFrames> frames; bool playing; + bool backwards; StringName animation; int frame; float speed_scale; @@ -169,7 +170,7 @@ public: void set_sprite_frames(const Ref<SpriteFrames> &p_frames); Ref<SpriteFrames> get_sprite_frames() const; - void play(const StringName &p_animation = StringName()); + void play(const StringName &p_animation = StringName(), const bool p_backwards = false); void stop(); bool is_playing() const; diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index 63c4758a34..a636eea285 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -261,8 +261,9 @@ void Area2D::_area_inout(int p_status, const RID &p_area, int p_instance, int p_ Map<ObjectID, AreaState>::Element *E = area_map.find(objid); - ERR_FAIL_COND(!area_in && !E); - + if (!area_in && !E) { + return; //likely removed from the tree + } locked = true; if (area_in) { @@ -320,10 +321,7 @@ void Area2D::_area_inout(int p_status, const RID &p_area, int p_instance, int p_ void Area2D::_clear_monitoring() { - if (locked) { - ERR_EXPLAIN("This function can't be used during the in/out signal."); - } - ERR_FAIL_COND(locked); + ERR_FAIL_COND_MSG(locked, "This function can't be used during the in/out signal."); { Map<ObjectID, BodyState> bmcopy = body_map; @@ -400,10 +398,7 @@ void Area2D::set_monitoring(bool p_enable) { if (p_enable == monitoring) return; - if (locked) { - ERR_EXPLAIN("Function blocked during in/out signal. Use set_deferred(\"monitoring\",true/false)"); - } - ERR_FAIL_COND(locked); + ERR_FAIL_COND_MSG(locked, "Function blocked during in/out signal. Use set_deferred(\"monitoring\", true/false)."); monitoring = p_enable; @@ -426,10 +421,7 @@ bool Area2D::is_monitoring() const { void Area2D::set_monitorable(bool p_enable) { - if (locked || Physics2DServer::get_singleton()->is_flushing_queries()) { - ERR_EXPLAIN("Function blocked during in/out signal. Use set_deferred(\"monitorable\",true/false)"); - } - ERR_FAIL_COND(locked || Physics2DServer::get_singleton()->is_flushing_queries()); + ERR_FAIL_COND_MSG(locked || (is_inside_tree() && Physics2DServer::get_singleton()->is_flushing_queries()), "Function blocked during in/out signal. Use set_deferred(\"monitorable\", true/false)."); if (p_enable == monitorable) return; @@ -656,10 +648,10 @@ void Area2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_body_inout"), &Area2D::_body_inout); ClassDB::bind_method(D_METHOD("_area_inout"), &Area2D::_area_inout); - ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsBody2D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "area_shape"))); - ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsBody2D"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "area_shape"))); - ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsBody2D"))); - ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsBody2D"))); + ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "area_shape"))); + ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "area_shape"))); + ADD_SIGNAL(MethodInfo("body_entered", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("body_exited", PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("area_shape_entered", PropertyInfo(Variant::INT, "area_id"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "self_shape"))); ADD_SIGNAL(MethodInfo("area_shape_exited", PropertyInfo(Variant::INT, "area_id"), PropertyInfo(Variant::OBJECT, "area", PROPERTY_HINT_RESOURCE_TYPE, "Area2D"), PropertyInfo(Variant::INT, "area_shape"), PropertyInfo(Variant::INT, "self_shape"))); @@ -671,8 +663,8 @@ void Area2D::_bind_methods() { 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::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,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_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitoring"), "set_monitoring", "is_monitoring"); ADD_PROPERTY(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 961d2b00ef..932af469d0 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -457,8 +457,8 @@ void AudioStreamPlayer2D::set_stream_paused(bool p_pause) { if (p_pause != stream_paused) { stream_paused = p_pause; - stream_paused_fade_in = p_pause ? false : true; - stream_paused_fade_out = p_pause ? true : false; + stream_paused_fade_in = !p_pause; + stream_paused_fade_out = p_pause; } } @@ -467,6 +467,10 @@ bool AudioStreamPlayer2D::get_stream_paused() const { return stream_paused; } +Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() { + return stream_playback; +} + void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer2D::set_stream); @@ -506,6 +510,8 @@ void AudioStreamPlayer2D::_bind_methods() { 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("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback); + 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"); diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h index cc00b59010..ffa3d4edb4 100644 --- a/scene/2d/audio_stream_player_2d.h +++ b/scene/2d/audio_stream_player_2d.h @@ -37,7 +37,7 @@ class AudioStreamPlayer2D : public Node2D { - GDCLASS(AudioStreamPlayer2D, Node2D) + GDCLASS(AudioStreamPlayer2D, Node2D); private: enum { @@ -129,6 +129,8 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + Ref<AudioStreamPlayback> get_stream_playback(); + AudioStreamPlayer2D(); ~AudioStreamPlayer2D(); }; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 11846654c5..3e8902314c 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -29,10 +29,11 @@ /*************************************************************************/ #include "camera_2d.h" + +#include "core/engine.h" #include "core/math/math_funcs.h" #include "scene/scene_string_names.h" #include "servers/visual_server.h" -#include <editor/editor_node.h> void Camera2D::_update_scroll() { @@ -44,15 +45,16 @@ void Camera2D::_update_scroll() { return; } + if (!viewport) + return; + if (current) { ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id)); Transform2D xform = get_camera_transform(); - if (viewport) { - viewport->set_canvas_transform(xform); - } + viewport->set_canvas_transform(xform); Size2 screen_size = viewport->get_visible_rect().size; Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5) : Point2()); @@ -104,7 +106,7 @@ Transform2D Camera2D::get_camera_transform() { if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) { - if (h_drag_enabled && !Engine::get_singleton()->is_editor_hint()) { + if (h_drag_enabled && !Engine::get_singleton()->is_editor_hint() && !h_offset_changed) { 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 { @@ -114,9 +116,11 @@ Transform2D Camera2D::get_camera_transform() { } else { camera_pos.x = new_camera_pos.x + screen_size.x * 0.5 * drag_margin[MARGIN_LEFT] * h_ofs; } + + h_offset_changed = false; } - if (v_drag_enabled && !Engine::get_singleton()->is_editor_hint()) { + if (v_drag_enabled && !Engine::get_singleton()->is_editor_hint() && !v_offset_changed) { 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])); @@ -128,6 +132,8 @@ Transform2D Camera2D::get_camera_transform() { } else { camera_pos.y = new_camera_pos.y + screen_size.y * 0.5 * drag_margin[MARGIN_BOTTOM] * v_ofs; } + + v_offset_changed = false; } } else if (anchor_mode == ANCHOR_MODE_FIXED_TOP_LEFT) { @@ -138,9 +144,6 @@ Transform2D Camera2D::get_camera_transform() { Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom) : Point2()); Rect2 screen_rect(-screen_offset + camera_pos, screen_size * zoom); - if (offset != Vector2()) - screen_rect.position += offset; - if (limit_smoothing_enabled) { if (screen_rect.position.x < limit[MARGIN_LEFT]) camera_pos.x -= screen_rect.position.x - limit[MARGIN_LEFT]; @@ -191,21 +194,8 @@ Transform2D Camera2D::get_camera_transform() { if (screen_rect.position.y < limit[MARGIN_TOP]) screen_rect.position.y = limit[MARGIN_TOP]; - if (offset != Vector2()) { - + if (offset != Vector2()) screen_rect.position += offset; - if (screen_rect.position.x + screen_rect.size.x > limit[MARGIN_RIGHT]) - screen_rect.position.x = limit[MARGIN_RIGHT] - screen_rect.size.x; - - if (screen_rect.position.y + screen_rect.size.y > limit[MARGIN_BOTTOM]) - screen_rect.position.y = limit[MARGIN_BOTTOM] - screen_rect.size.y; - - if (screen_rect.position.x < limit[MARGIN_LEFT]) - screen_rect.position.x = limit[MARGIN_LEFT]; - - if (screen_rect.position.y < limit[MARGIN_TOP]) - screen_rect.position.y = limit[MARGIN_TOP]; - } camera_screen_center = screen_rect.position + screen_rect.size * 0.5; @@ -568,6 +558,7 @@ bool Camera2D::is_v_drag_enabled() const { void Camera2D::set_v_offset(float p_offset) { v_ofs = p_offset; + v_offset_changed = true; _update_scroll(); } @@ -579,6 +570,7 @@ float Camera2D::get_v_offset() const { void Camera2D::set_h_offset(float p_offset) { h_ofs = p_offset; + h_offset_changed = true; _update_scroll(); } float Camera2D::get_h_offset() const { @@ -764,8 +756,8 @@ void Camera2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "smoothing_speed"), "set_follow_smoothing", "get_follow_smoothing"); ADD_GROUP("Offset", "offset_"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset_v", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_v_offset", "get_v_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset_h", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_h_offset", "get_h_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset_v", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_v_offset", "get_v_offset"); ADD_GROUP("Drag Margin", "drag_margin_"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "drag_margin_left", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_margin", "get_drag_margin", MARGIN_LEFT); @@ -813,9 +805,12 @@ Camera2D::Camera2D() { limit_drawing_enabled = false; margin_drawing_enabled = false; - h_drag_enabled = true; - v_drag_enabled = true; + h_drag_enabled = false; + v_drag_enabled = false; h_ofs = 0; v_ofs = 0; + h_offset_changed = false; + v_offset_changed = false; + set_notify_transform(true); } diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 7f16ecff41..bb3c76b30c 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -77,6 +77,9 @@ protected: float h_ofs; float v_ofs; + bool h_offset_changed; + bool v_offset_changed; + Point2 camera_screen_center; void _update_process_mode(); void _update_scroll(); diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index dced688899..b605be47df 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -131,19 +131,15 @@ void CanvasItemMaterial::_update_shader() { code += "\tVERTEX.xy /= vec2(h_frames, v_frames);\n"; - code += "\tint total_frames = particles_anim_h_frames * particles_anim_v_frames;\n"; - code += "\tint frame = int(float(total_frames) * INSTANCE_CUSTOM.z);\n"; - code += "\tif (particles_anim_loop) {\n"; - code += "\t\tframe = abs(frame) % total_frames;\n"; + code += "\tfloat particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n"; + code += "\tfloat particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n"; + code += "\tif (!particles_anim_loop) {\n"; + code += "\t\tparticle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n"; code += "\t} else {\n"; - code += "\t\tframe = clamp(frame, 0, total_frames - 1);\n"; - code += "\t}\n"; - - code += "\tfloat frame_w = 1.0 / h_frames;\n"; - code += "\tfloat frame_h = 1.0 / v_frames;\n"; - code += "\tUV.x = UV.x * frame_w + frame_w * float(frame % particles_anim_h_frames);\n"; - code += "\tUV.y = UV.y * frame_h + frame_h * float(frame / particles_anim_h_frames);\n"; - + code += "\t\tparticle_frame = mod(particle_frame, particle_total_frames);\n"; + code += "\t}"; + code += "\tUV /= vec2(h_frames, v_frames);\n"; + code += "\tUV += vec2(mod(particle_frame, h_frames) / h_frames, floor(particle_frame / h_frames) / v_frames);\n"; code += "}\n"; } @@ -711,20 +707,14 @@ void CanvasItem::item_rect_changed(bool p_size_changed) { void CanvasItem::draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, float p_width, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_from, p_to, p_color, p_width, p_antialiased); } void CanvasItem::draw_polyline(const Vector<Point2> &p_points, const Color &p_color, float p_width, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); Vector<Color> colors; colors.push_back(p_color); @@ -733,20 +723,14 @@ void CanvasItem::draw_polyline(const Vector<Point2> &p_points, const Color &p_co void CanvasItem::draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); VisualServer::get_singleton()->canvas_item_add_polyline(canvas_item, p_points, p_colors, p_width, p_antialiased); } void CanvasItem::draw_multiline(const Vector<Point2> &p_points, const Color &p_color, float p_width, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); Vector<Color> colors; colors.push_back(p_color); @@ -755,48 +739,76 @@ void CanvasItem::draw_multiline(const Vector<Point2> &p_points, const Color &p_c void CanvasItem::draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); VisualServer::get_singleton()->canvas_item_add_multiline(canvas_item, p_points, p_colors, p_width, p_antialiased); } -void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled) { +void CanvasItem::draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled, float p_width, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); if (p_filled) { + if (p_width != 1.0) { + WARN_PRINT("The draw_rect() \"width\" argument has no effect when \"filled\" is \"true\"."); + } + + if (p_antialiased) { + WARN_PRINT("The draw_rect() \"antialiased\" argument has no effect when \"filled\" is \"true\"."); + } VisualServer::get_singleton()->canvas_item_add_rect(canvas_item, p_rect, p_color); } else { - VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position, p_rect.position + Size2(p_rect.size.width, 0), p_color); - VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position, p_rect.position + Size2(0, p_rect.size.height), p_color); - VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position + Point2(0, p_rect.size.height), p_rect.position + p_rect.size, p_color); - VisualServer::get_singleton()->canvas_item_add_line(canvas_item, p_rect.position + Point2(p_rect.size.width, 0), p_rect.position + p_rect.size, p_color); + // Thick lines are offset depending on their width to avoid partial overlapping. + // Thin lines don't require an offset, so don't apply one in this case + float offset; + if (p_width >= 2) { + offset = p_width / 2.0; + } else { + offset = 0.0; + } + + VisualServer::get_singleton()->canvas_item_add_line( + canvas_item, + p_rect.position + Point2(-offset, 0), + p_rect.position + Size2(p_rect.size.width + offset, 0), + p_color, + p_width, + p_antialiased); + VisualServer::get_singleton()->canvas_item_add_line( + canvas_item, + p_rect.position + Point2(0, offset), + p_rect.position + Size2(0, p_rect.size.height - offset), + p_color, + p_width, + p_antialiased); + VisualServer::get_singleton()->canvas_item_add_line( + canvas_item, + p_rect.position + Point2(-offset, p_rect.size.height), + p_rect.position + Size2(p_rect.size.width + offset, p_rect.size.height), + p_color, + p_width, + p_antialiased); + VisualServer::get_singleton()->canvas_item_add_line( + canvas_item, + p_rect.position + Point2(p_rect.size.width, offset), + p_rect.position + Size2(p_rect.size.width, p_rect.size.height - offset), + p_color, + p_width, + p_antialiased); } } void CanvasItem::draw_circle(const Point2 &p_pos, float p_radius, const Color &p_color) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); VisualServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color); } void CanvasItem::draw_texture(const Ref<Texture> &p_texture, const Point2 &p_pos, const Color &p_modulate, const Ref<Texture> &p_normal_map) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); ERR_FAIL_COND(p_texture.is_null()); @@ -805,29 +817,20 @@ void CanvasItem::draw_texture(const Ref<Texture> &p_texture, const Point2 &p_pos void CanvasItem::draw_texture_rect(const Ref<Texture> &p_texture, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose, const Ref<Texture> &p_normal_map) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); ERR_FAIL_COND(p_texture.is_null()); p_texture->draw_rect(canvas_item, p_rect, p_tile, p_modulate, p_transpose, p_normal_map); } void CanvasItem::draw_texture_rect_region(const Ref<Texture> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, const Ref<Texture> &p_normal_map, bool p_clip_uv) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); ERR_FAIL_COND(p_texture.is_null()); p_texture->draw_rect_region(canvas_item, p_rect, p_src_rect, p_modulate, p_transpose, p_normal_map, p_clip_uv); } void CanvasItem::draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); ERR_FAIL_COND(p_style_box.is_null()); @@ -835,10 +838,7 @@ void CanvasItem::draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p } void CanvasItem::draw_primitive(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, Ref<Texture> p_texture, float p_width, const Ref<Texture> &p_normal_map) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); RID rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); RID rid_normal = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); @@ -847,10 +847,7 @@ void CanvasItem::draw_primitive(const Vector<Point2> &p_points, const Vector<Col } void CanvasItem::draw_set_transform(const Point2 &p_offset, float p_rot, const Size2 &p_scale) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); Transform2D xform(p_rot, p_offset); xform.scale_basis(p_scale); @@ -859,20 +856,14 @@ void CanvasItem::draw_set_transform(const Point2 &p_offset, float p_rot, const S void CanvasItem::draw_set_transform_matrix(const Transform2D &p_matrix) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); VisualServer::get_singleton()->canvas_item_add_set_transform(canvas_item, p_matrix); } void CanvasItem::draw_polygon(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, Ref<Texture> p_texture, const Ref<Texture> &p_normal_map, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); RID rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); RID rid_normal = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); @@ -882,10 +873,7 @@ void CanvasItem::draw_polygon(const Vector<Point2> &p_points, const Vector<Color void CanvasItem::draw_colored_polygon(const Vector<Point2> &p_points, const Color &p_color, const Vector<Point2> &p_uvs, Ref<Texture> p_texture, const Ref<Texture> &p_normal_map, bool p_antialiased) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); Vector<Color> colors; colors.push_back(p_color); @@ -895,13 +883,13 @@ void CanvasItem::draw_colored_polygon(const Vector<Point2> &p_points, const Colo VisualServer::get_singleton()->canvas_item_add_polygon(canvas_item, p_points, colors, p_uvs, rid, rid_normal, p_antialiased); } -void CanvasItem::draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map) { +void CanvasItem::draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map, const Transform2D &p_transform, const Color &p_modulate) { ERR_FAIL_COND(p_mesh.is_null()); RID texture_rid = p_texture.is_valid() ? p_texture->get_rid() : RID(); RID normal_map_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); - VisualServer::get_singleton()->canvas_item_add_mesh(canvas_item, p_mesh->get_rid(), texture_rid, normal_map_rid); + VisualServer::get_singleton()->canvas_item_add_mesh(canvas_item, p_mesh->get_rid(), p_transform, p_modulate, texture_rid, normal_map_rid); } void CanvasItem::draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map) { @@ -913,10 +901,7 @@ void CanvasItem::draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Tex void CanvasItem::draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); ERR_FAIL_COND(p_font.is_null()); p_font->draw(canvas_item, p_pos, p_text, p_modulate, p_clip_w); @@ -924,14 +909,14 @@ void CanvasItem::draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const float CanvasItem::draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next, const Color &p_modulate) { - if (!drawing) { - ERR_EXPLAIN("Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); - ERR_FAIL_V(0); - } + ERR_FAIL_COND_V_MSG(!drawing, 0, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal."); ERR_FAIL_COND_V(p_char.length() != 1, 0); ERR_FAIL_COND_V(p_font.is_null(), 0); + if (p_font->has_outline()) { + p_font->draw_char(canvas_item, p_pos, p_char[0], p_next.c_str()[0], Color(1, 1, 1), true); + } return p_font->draw_char(canvas_item, p_pos, p_char[0], p_next.c_str()[0], p_modulate); } @@ -1078,6 +1063,7 @@ Vector2 CanvasItem::make_canvas_position_local(const Vector2 &screen_point) cons Ref<InputEvent> CanvasItem::make_input_local(const Ref<InputEvent> &p_event) const { + ERR_FAIL_COND_V(p_event.is_null(), p_event); ERR_FAIL_COND_V(!is_inside_tree(), p_event); return p_event->xformed_by((get_canvas_transform() * get_global_transform()).affine_inverse()); @@ -1161,7 +1147,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_polyline_colors", "points", "colors", "width", "antialiased"), &CanvasItem::draw_polyline_colors, DEFVAL(1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_multiline", "points", "color", "width", "antialiased"), &CanvasItem::draw_multiline, DEFVAL(1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_multiline_colors", "points", "colors", "width", "antialiased"), &CanvasItem::draw_multiline_colors, DEFVAL(1.0), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled"), &CanvasItem::draw_rect, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("draw_rect", "rect", "color", "filled", "width", "antialiased"), &CanvasItem::draw_rect, DEFVAL(true), DEFVAL(1.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_circle", "position", "radius", "color"), &CanvasItem::draw_circle); ClassDB::bind_method(D_METHOD("draw_texture", "texture", "position", "modulate", "normal_map"), &CanvasItem::draw_texture, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(Variant())); ClassDB::bind_method(D_METHOD("draw_texture_rect", "texture", "rect", "tile", "modulate", "transpose", "normal_map"), &CanvasItem::draw_texture_rect, DEFVAL(Color(1, 1, 1)), DEFVAL(false), DEFVAL(Variant())); @@ -1172,7 +1158,7 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_colored_polygon", "points", "color", "uvs", "texture", "normal_map", "antialiased"), &CanvasItem::draw_colored_polygon, DEFVAL(PoolVector2Array()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("draw_string", "font", "position", "text", "modulate", "clip_w"), &CanvasItem::draw_string, DEFVAL(Color(1, 1, 1)), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("draw_char", "font", "position", "char", "next", "modulate"), &CanvasItem::draw_char, DEFVAL(Color(1, 1, 1))); - ClassDB::bind_method(D_METHOD("draw_mesh", "mesh", "texture", "normal_map"), &CanvasItem::draw_mesh, DEFVAL(Ref<Texture>())); + ClassDB::bind_method(D_METHOD("draw_mesh", "mesh", "texture", "normal_map", "transform", "modulate"), &CanvasItem::draw_mesh, DEFVAL(Ref<Texture>()), DEFVAL(Transform2D()), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("draw_multimesh", "multimesh", "texture", "normal_map"), &CanvasItem::draw_multimesh, DEFVAL(Ref<Texture>())); ClassDB::bind_method(D_METHOD("draw_set_transform", "position", "rotation", "scale"), &CanvasItem::draw_set_transform); diff --git a/scene/2d/canvas_item.h b/scene/2d/canvas_item.h index bf7cfa8e75..9c6799a441 100644 --- a/scene/2d/canvas_item.h +++ b/scene/2d/canvas_item.h @@ -46,7 +46,7 @@ class StyleBox; class CanvasItemMaterial : public Material { - GDCLASS(CanvasItemMaterial, Material) + GDCLASS(CanvasItemMaterial, Material); public: enum BlendMode { @@ -143,7 +143,7 @@ public: void set_particles_anim_v_frames(int p_frames); int get_particles_anim_v_frames() const; - void set_particles_anim_loop(bool p_frames); + void set_particles_anim_loop(bool p_loop); bool get_particles_anim_loop() const; static void init_shaders(); @@ -307,7 +307,7 @@ public: void draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width = 1.0, bool p_antialiased = false); void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, float p_width = 1.0, bool p_antialiased = false); void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width = 1.0, bool p_antialiased = false); - void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true); + void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, float p_width = 1.0, bool p_antialiased = false); void draw_circle(const Point2 &p_pos, float p_radius, const Color &p_color); void draw_texture(const Ref<Texture> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1), const Ref<Texture> &p_normal_map = Ref<Texture>()); void draw_texture_rect(const Ref<Texture> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref<Texture> &p_normal_map = Ref<Texture>()); @@ -317,7 +317,7 @@ public: void draw_polygon(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture> p_texture = Ref<Texture>(), const Ref<Texture> &p_normal_map = Ref<Texture>(), bool p_antialiased = false); void draw_colored_polygon(const Vector<Point2> &p_points, const Color &p_color, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture> p_texture = Ref<Texture>(), const Ref<Texture> &p_normal_map = Ref<Texture>(), bool p_antialiased = false); - void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map); + void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1)); void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map); void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1); diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index bd7bb97b03..009d664462 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -70,7 +70,7 @@ void CanvasModulate::_bind_methods() { void CanvasModulate::set_color(const Color &p_color) { color = p_color; - if (is_inside_tree()) { + if (is_visible_in_tree()) { VS::get_singleton()->canvas_set_modulate(get_canvas(), color); } } diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp index d54070df8d..202c7c9cf2 100644 --- a/scene/2d/collision_object_2d.cpp +++ b/scene/2d/collision_object_2d.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "collision_object_2d.h" + #include "scene/scene_string_names.h" #include "servers/physics_2d_server.h" @@ -56,7 +57,7 @@ void CollisionObject2D::_notification(int p_what) { _update_pickable(); //get space - } + } break; case NOTIFICATION_ENTER_CANVAS: { @@ -64,7 +65,7 @@ void CollisionObject2D::_notification(int p_what) { Physics2DServer::get_singleton()->area_attach_canvas_instance_id(rid, get_canvas_layer_instance_id()); else Physics2DServer::get_singleton()->body_attach_canvas_instance_id(rid, get_canvas_layer_instance_id()); - } + } break; case NOTIFICATION_VISIBILITY_CHANGED: { @@ -101,7 +102,7 @@ void CollisionObject2D::_notification(int p_what) { Physics2DServer::get_singleton()->area_attach_canvas_instance_id(rid, 0); else Physics2DServer::get_singleton()->body_attach_canvas_instance_id(rid, 0); - } + } break; } } @@ -217,12 +218,13 @@ void CollisionObject2D::shape_owner_set_transform(uint32_t p_owner, const Transf ERR_FAIL_COND(!shapes.has(p_owner)); ShapeData &sd = shapes[p_owner]; + sd.xform = p_transform; for (int i = 0; i < sd.shapes.size(); i++) { if (area) { - Physics2DServer::get_singleton()->area_set_shape_transform(rid, sd.shapes[i].index, p_transform); + Physics2DServer::get_singleton()->area_set_shape_transform(rid, sd.shapes[i].index, sd.xform); } else { - Physics2DServer::get_singleton()->body_set_shape_transform(rid, sd.shapes[i].index, p_transform); + Physics2DServer::get_singleton()->body_set_shape_transform(rid, sd.shapes[i].index, sd.xform); } } } @@ -250,9 +252,9 @@ void CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2 s.index = total_subshapes; s.shape = p_shape; if (area) { - Physics2DServer::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform); + Physics2DServer::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled); } else { - Physics2DServer::get_singleton()->body_add_shape(rid, p_shape->get_rid(), sd.xform); + Physics2DServer::get_singleton()->body_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled); } sd.shapes.push_back(s); @@ -386,8 +388,8 @@ String CollisionObject2D::get_configuration_warning() const { String warning = Node2D::get_configuration_warning(); if (shapes.empty()) { - if (warning == String()) { - warning += "\n"; + if (!warning.empty()) { + warning += "\n\n"; } warning += TTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape."); } diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 8aa3330f37..4e7d01c8e6 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -36,7 +36,7 @@ class CollisionObject2D : public Node2D { - GDCLASS(CollisionObject2D, Node2D) + GDCLASS(CollisionObject2D, Node2D); bool area; RID rid; diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index 5edd49b3a3..bb144dda96 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -70,7 +70,7 @@ void CollisionPolygon2D::_build_polygon() { w[(i << 1) + 1] = polygon[(i + 1) % polygon.size()]; } - w = PoolVector<Vector2>::Write(); + w.release(); concave->set_segments(segments); parent->shape_owner_add_shape(owner_id, concave); @@ -78,40 +78,7 @@ void CollisionPolygon2D::_build_polygon() { } Vector<Vector<Vector2> > CollisionPolygon2D::_decompose_in_convex() { - - Vector<Vector<Vector2> > decomp; - List<TriangulatorPoly> in_poly, out_poly; - - TriangulatorPoly inp; - inp.Init(polygon.size()); - for (int i = 0; i < polygon.size(); i++) { - inp.GetPoint(i) = polygon[i]; - } - inp.SetOrientation(TRIANGULATOR_CCW); - in_poly.push_back(inp); - TriangulatorPartition tpart; - if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { //failed! - ERR_PRINT("Convex decomposing failed!"); - return decomp; - } - - decomp.resize(out_poly.size()); - int idx = 0; - - for (List<TriangulatorPoly>::Element *I = out_poly.front(); I; I = I->next()) { - - TriangulatorPoly &tp = I->get(); - - decomp.write[idx].resize(tp.GetNumPoints()); - - for (int i = 0; i < tp.GetNumPoints(); i++) { - - decomp.write[idx].write[i] = tp.GetPoint(i); - } - - idx++; - } - + Vector<Vector<Vector2> > decomp = Geometry::decompose_polygon_in_convex(polygon); return decomp; } diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index c756f49bbd..f79d79d039 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -36,9 +36,9 @@ #include "scene/resources/circle_shape_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/line_shape_2d.h" #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/segment_shape_2d.h" -#include "scene/resources/shape_line_2d.h" void CollisionShape2D::_shape_changed() { @@ -97,15 +97,8 @@ void CollisionShape2D::_notification(int p_what) { } owner_id = 0; parent = NULL; - } break; - /* - case NOTIFICATION_TRANSFORM_CHANGED: { - - if (!is_inside_scene()) - break; - _update_parent(); - } break;*/ + } break; case NOTIFICATION_DRAW: { if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_collisions_hint()) { @@ -131,10 +124,13 @@ void CollisionShape2D::_notification(int p_what) { rect = rect.grow(3); if (one_way_collision) { - Color dcol = get_tree()->get_debug_collisions_color(); //0.9,0.2,0.2,0.4); - dcol.a = 1.0; + // Draw an arrow indicating the one-way collision direction + draw_col = get_tree()->get_debug_collisions_color().inverted(); + if (disabled) { + draw_col = draw_col.darkened(0.25); + } Vector2 line_to(0, 20); - draw_line(Vector2(), line_to, dcol, 3); + draw_line(Vector2(), line_to, draw_col, 2, true); Vector<Vector2> pts; float tsize = 8; pts.push_back(line_to + (Vector2(0, tsize))); @@ -142,9 +138,9 @@ void CollisionShape2D::_notification(int p_what) { pts.push_back(line_to + (Vector2(-0.707 * tsize, 0))); Vector<Color> cols; for (int i = 0; i < 3; i++) - cols.push_back(dcol); + cols.push_back(draw_col); - draw_primitive(pts, cols, Vector<Vector2>()); //small arrow + draw_primitive(pts, cols, Vector<Vector2>()); } } break; } diff --git a/scene/2d/collision_shape_2d.h b/scene/2d/collision_shape_2d.h index e913b4a866..26c61f47bd 100644 --- a/scene/2d/collision_shape_2d.h +++ b/scene/2d/collision_shape_2d.h @@ -38,7 +38,7 @@ class CollisionObject2D; class CollisionShape2D : public Node2D { - GDCLASS(CollisionShape2D, Node2D) + GDCLASS(CollisionShape2D, Node2D); Ref<Shape2D> shape; Rect2 rect; uint32_t owner_id; diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 21feb6e226..f9f273d494 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -29,28 +29,17 @@ /*************************************************************************/ #include "cpu_particles_2d.h" -#include "particles_2d.h" + #include "scene/2d/canvas_item.h" +#include "scene/2d/particles_2d.h" #include "scene/resources/particles_material.h" #include "servers/visual_server.h" void CPUParticles2D::set_emitting(bool p_emitting) { emitting = p_emitting; - if (!is_processing_internal()) { + if (emitting) set_process_internal(true); - if (is_inside_tree()) { -#ifndef NO_THREADS - update_mutex->lock(); -#endif - VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), true); - -#ifndef NO_THREADS - update_mutex->unlock(); -#endif - } - } } void CPUParticles2D::set_amount(int p_amount) { @@ -94,10 +83,16 @@ void CPUParticles2D::set_randomness_ratio(float p_ratio) { randomness_ratio = p_ratio; } +void CPUParticles2D::set_lifetime_randomness(float p_random) { + + lifetime_randomness = p_random; +} void CPUParticles2D::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; + set_notify_transform(!p_enable); } + void CPUParticles2D::set_speed_scale(float p_scale) { speed_scale = p_scale; @@ -132,6 +127,10 @@ float CPUParticles2D::get_randomness_ratio() const { return randomness_ratio; } +float CPUParticles2D::get_lifetime_randomness() const { + + return lifetime_randomness; +} bool CPUParticles2D::get_use_local_coordinates() const { @@ -259,6 +258,8 @@ void CPUParticles2D::restart() { frame_remainder = 0; cycle = 0; + set_emitting(true); + { int pc = particles.size(); PoolVector<Particle>::Write w = particles.write(); @@ -269,6 +270,16 @@ void CPUParticles2D::restart() { } } +void CPUParticles2D::set_direction(Vector2 p_direction) { + + direction = p_direction; +} + +Vector2 CPUParticles2D::get_direction() const { + + return direction; +} + void CPUParticles2D::set_spread(float p_spread) { spread = p_spread; @@ -336,9 +347,9 @@ void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv case PARAM_ANGULAR_VELOCITY: { _adjust_curve_range(p_curve, -360, 360); } break; - /*case PARAM_ORBIT_VELOCITY: { + case PARAM_ORBIT_VELOCITY: { _adjust_curve_range(p_curve, -500, 500); - } break;*/ + } break; case PARAM_LINEAR_ACCEL: { _adjust_curve_range(p_curve, -200, 200); } break; @@ -365,7 +376,8 @@ void CPUParticles2D::set_param_curve(Parameter p_param, const Ref<Curve> &p_curv } break; case PARAM_ANIM_OFFSET: { } break; - default: {} + default: { + } } } Ref<Curve> CPUParticles2D::get_param_curve(Parameter p_param) const { @@ -477,7 +489,7 @@ void CPUParticles2D::_validate_property(PropertyInfo &property) const { property.usage = 0; } - if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_CIRCLE) { + if (property.name == "emission_sphere_radius" && emission_shape != EMISSION_SHAPE_SPHERE) { property.usage = 0; } @@ -500,12 +512,6 @@ void CPUParticles2D::_validate_property(PropertyInfo &property) const { if (property.name == "emission_colors" && emission_shape != EMISSION_SHAPE_POINTS && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { property.usage = 0; } - - /* - if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) { - property.usage = 0; - } - */ } static uint32_t idhash(uint32_t x) { @@ -544,7 +550,8 @@ void CPUParticles2D::_particles_process(float p_delta) { time = Math::fmod(time, lifetime); cycle++; if (one_shot && cycle > 0) { - emitting = false; + set_emitting(false); + _change_notify(); } } @@ -556,6 +563,8 @@ void CPUParticles2D::_particles_process(float p_delta) { velocity_xform[2] = Vector2(); } + float system_phase = time / lifetime; + for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -563,21 +572,26 @@ void CPUParticles2D::_particles_process(float p_delta) { if (!emitting && !p.active) continue; - float restart_time = (float(i) / float(pcount)) * lifetime; float local_delta = p_delta; + // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. + // While we use time in tests later on, for randomness we use the phase as done in the + // original shader code, and we later multiply by lifetime to get the time. + float restart_phase = float(i) / float(pcount); + if (randomness_ratio > 0.0) { uint32_t seed = cycle; - if (restart_time >= time) { + if (restart_phase >= system_phase) { seed -= uint32_t(1); } seed *= uint32_t(pcount); seed += uint32_t(i); float random = float(idhash(seed) % uint32_t(65536)) / 65536.0; - restart_time += randomness_ratio * random * 1.0 / float(pcount); + restart_phase += randomness_ratio * random * 1.0 / float(pcount); } - restart_time *= (1.0 - explosiveness_ratio); + restart_phase *= (1.0 - explosiveness_ratio); + float restart_time = restart_phase * lifetime; bool restart = false; if (time > prev_time) { @@ -586,7 +600,7 @@ void CPUParticles2D::_particles_process(float p_delta) { if (restart_time >= prev_time && restart_time < time) { restart = true; if (fractional_delta) { - local_delta = (time - restart_time) * lifetime; + local_delta = time - restart_time; } } @@ -594,17 +608,21 @@ void CPUParticles2D::_particles_process(float p_delta) { if (restart_time >= prev_time) { restart = true; if (fractional_delta) { - local_delta = (1.0 - restart_time + time) * lifetime; + local_delta = lifetime - restart_time + time; } } else if (restart_time < time) { restart = true; if (fractional_delta) { - local_delta = (time - restart_time) * lifetime; + local_delta = time - restart_time; } } } + if (p.time * (1.0 - explosiveness_ratio) > p.lifetime) { + restart = true; + } + if (restart) { if (!emitting) { @@ -635,7 +653,7 @@ void CPUParticles2D::_particles_process(float p_delta) { p.hue_rot_rand = Math::randf(); p.anim_offset_rand = Math::randf(); - float angle1_rad = (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; + float angle1_rad = Math::atan2(direction.y, direction.x) + (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); @@ -648,14 +666,17 @@ void CPUParticles2D::_particles_process(float p_delta) { p.custom[3] = 0.0; p.transform = Transform2D(); p.time = 0; + p.lifetime = lifetime * (1.0 - Math::randf() * lifetime_randomness); p.base_color = Color(1, 1, 1, 1); switch (emission_shape) { case EMISSION_SHAPE_POINT: { //do none } break; - case EMISSION_SHAPE_CIRCLE: { - p.transform[2] = Vector2(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0).normalized() * emission_sphere_radius; + case EMISSION_SHAPE_SPHERE: { + float s = Math::randf(), t = 2.0 * Math_PI * Math::randf(); + float radius = emission_sphere_radius * Math::sqrt(1.0 - s * s); + p.transform[2] = Vector2(Math::cos(t), Math::sin(t)) * radius; } break; case EMISSION_SHAPE_RECTANGLE: { p.transform[2] = Vector2(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * emission_rect_extents; @@ -688,6 +709,8 @@ void CPUParticles2D::_particles_process(float p_delta) { } else if (!p.active) { continue; + } else if (p.time > p.lifetime) { + p.active = false; } else { uint32_t alt_seed = p.seed; @@ -699,16 +722,12 @@ void CPUParticles2D::_particles_process(float p_delta) { if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]); } - /* - float tex_orbit_velocity = 0.0; - - if (flags[FLAG_DISABLE_Z]) { - if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) { - tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]); - } + float tex_orbit_velocity = 0.0; + if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { + tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(p.custom[1]); } -*/ + float tex_angular_velocity = 0.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]); @@ -759,22 +778,19 @@ void CPUParticles2D::_particles_process(float p_delta) { force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector2(); //apply tangential acceleration; Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2(); + force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector2(); //apply attractor forces p.velocity += force * local_delta; //orbit velocity -#if 0 - if (flags[FLAG_DISABLE_Z]) { - - float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random); - if (orbit_amount != 0.0) { - float ang = orbit_amount * DELTA * pi * 2.0; - mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang))); - TRANSFORM[3].xy -= diff.xy; - TRANSFORM[3].xy += rot * diff.xy; - } + float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + if (orbit_amount != 0.0) { + float ang = orbit_amount * local_delta * Math_PI * 2.0; + // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, + // but we use -ang here to reproduce its behavior. + Transform2D rot = Transform2D(-ang, Vector2()); + p.transform[2] -= diff; + p.transform[2] += rot.basis_xform(diff); } -#endif if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } @@ -876,11 +892,6 @@ void CPUParticles2D::_update_particle_data_buffer() { PoolVector<Particle>::Read r = particles.read(); float *ptr = w.ptr(); - Transform2D un_transform; - if (!local_coords) { - un_transform = get_global_transform().affine_inverse(); - } - if (draw_order != DRAW_ORDER_INDEX) { ow = particle_order.write(); order = ow.ptr(); @@ -902,7 +913,7 @@ void CPUParticles2D::_update_particle_data_buffer() { Transform2D t = r[idx].transform; if (!local_coords) { - t = un_transform * t; + t = inv_emission_transform * t; } if (r[idx].active) { @@ -941,54 +952,56 @@ void CPUParticles2D::_update_particle_data_buffer() { #endif } -void CPUParticles2D::_update_render_thread() { - +void CPUParticles2D::_set_redraw(bool p_redraw) { + if (redraw == p_redraw) + return; + redraw = p_redraw; #ifndef NO_THREADS update_mutex->lock(); #endif + if (redraw) { + VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), true); - VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data); + VS::get_singleton()->multimesh_set_visible_instances(multimesh, -1); + } else { + VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), false); + VS::get_singleton()->multimesh_set_visible_instances(multimesh, 0); + } #ifndef NO_THREADS update_mutex->unlock(); #endif + update(); // redraw to update render list } -void CPUParticles2D::_notification(int p_what) { - - if (p_what == NOTIFICATION_ENTER_TREE) { - if (is_processing_internal()) { +void CPUParticles2D::_update_render_thread() { #ifndef NO_THREADS - update_mutex->lock(); + update_mutex->lock(); #endif - VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), true); + + VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data); #ifndef NO_THREADS - update_mutex->unlock(); + update_mutex->unlock(); #endif - } - } +} - if (p_what == NOTIFICATION_EXIT_TREE) { - if (is_processing_internal()) { +void CPUParticles2D::_notification(int p_what) { -#ifndef NO_THREADS - update_mutex->lock(); -#endif - VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), false); -#ifndef NO_THREADS - update_mutex->unlock(); -#endif - } + if (p_what == NOTIFICATION_ENTER_TREE) { + set_process_internal(emitting); } - if (p_what == NOTIFICATION_PAUSED || p_what == NOTIFICATION_UNPAUSED) { + if (p_what == NOTIFICATION_EXIT_TREE) { + _set_redraw(false); } if (p_what == NOTIFICATION_DRAW) { + if (!redraw) + return; // don't add to render list RID texrid; if (texture.is_valid()) { @@ -1005,26 +1018,20 @@ void CPUParticles2D::_notification(int p_what) { if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (particles.size() == 0 || !is_visible_in_tree()) + if (particles.size() == 0 || !is_visible_in_tree()) { + _set_redraw(false); return; + } float delta = get_process_delta_time(); if (emitting) { - inactive_time = 0; } else { inactive_time += delta; if (inactive_time > lifetime * 1.2) { set_process_internal(false); -#ifndef NO_THREADS - update_mutex->lock(); -#endif - VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->canvas_item_set_update_when_visible(get_canvas_item(), false); + _set_redraw(false); -#ifndef NO_THREADS - update_mutex->unlock(); -#endif //reset variables time = 0; inactive_time = 0; @@ -1033,6 +1040,7 @@ void CPUParticles2D::_notification(int p_what) { return; } } + _set_redraw(true); if (time == 0 && pre_process_time > 0.0) { @@ -1075,6 +1083,42 @@ void CPUParticles2D::_notification(int p_what) { _update_particle_data_buffer(); } + + if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { + + inv_emission_transform = get_global_transform().affine_inverse(); + + if (!local_coords) { + + int pc = particles.size(); + + PoolVector<float>::Write w = particle_data.write(); + PoolVector<Particle>::Read r = particles.read(); + float *ptr = w.ptr(); + + for (int i = 0; i < pc; i++) { + + Transform2D t = inv_emission_transform * r[i].transform; + + if (r[i].active) { + + ptr[0] = t.elements[0][0]; + ptr[1] = t.elements[1][0]; + ptr[2] = 0; + ptr[3] = t.elements[2][0]; + ptr[4] = t.elements[0][1]; + ptr[5] = t.elements[1][1]; + ptr[6] = 0; + ptr[7] = t.elements[2][1]; + + } else { + zeromem(ptr, sizeof(float) * 8); + } + + ptr += 13; + } + } + } } void CPUParticles2D::convert_from_particles(Node *p_particles) { @@ -1104,6 +1148,8 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { if (material.is_null()) return; + Vector3 dir = material->get_direction(); + set_direction(Vector2(dir.x, dir.y)); set_spread(material->get_spread()); set_flatness(material->get_flatness()); @@ -1123,6 +1169,7 @@ void CPUParticles2D::convert_from_particles(Node *p_particles) { Vector2 gravity = Vector2(material->get_gravity().x, material->get_gravity().y); set_gravity(gravity); + set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ @@ -1157,6 +1204,7 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &CPUParticles2D::set_pre_process_time); ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &CPUParticles2D::set_explosiveness_ratio); ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &CPUParticles2D::set_randomness_ratio); + ClassDB::bind_method(D_METHOD("set_lifetime_randomness", "random"), &CPUParticles2D::set_lifetime_randomness); ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &CPUParticles2D::set_use_local_coordinates); ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &CPUParticles2D::set_fixed_fps); ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &CPUParticles2D::set_fractional_delta); @@ -1169,6 +1217,7 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pre_process_time"), &CPUParticles2D::get_pre_process_time); ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &CPUParticles2D::get_explosiveness_ratio); ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &CPUParticles2D::get_randomness_ratio); + ClassDB::bind_method(D_METHOD("get_lifetime_randomness"), &CPUParticles2D::get_lifetime_randomness); ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &CPUParticles2D::get_use_local_coordinates); ClassDB::bind_method(D_METHOD("get_fixed_fps"), &CPUParticles2D::get_fixed_fps); ClassDB::bind_method(D_METHOD("get_fractional_delta"), &CPUParticles2D::get_fractional_delta); @@ -1189,15 +1238,17 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); 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_EXP_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_EXP_RANGE, "0.00,600.0,0.01"), "set_pre_process_time", "get_pre_process_time"); + 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,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"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Drawing", ""); + // No visibility_rect property contrarily to Particles2D, it's updated automatically. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime"), "set_draw_order", "get_draw_order"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); @@ -1208,6 +1259,9 @@ void CPUParticles2D::_bind_methods() { //////////////////////////////// + ClassDB::bind_method(D_METHOD("set_direction", "direction"), &CPUParticles2D::set_direction); + ClassDB::bind_method(D_METHOD("get_direction"), &CPUParticles2D::get_direction); + ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles2D::set_spread); ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles2D::get_spread); @@ -1266,7 +1320,8 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); ADD_GROUP("Flags", "flag_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_align_y"), "set_particle_flag", "get_particle_flag", FLAG_ALIGN_Y_TO_VELOCITY); - ADD_GROUP("Spread", ""); + ADD_GROUP("Direction", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "direction"), "set_direction", "get_direction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "flatness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_flatness", "get_flatness"); ADD_GROUP("Gravity", ""); @@ -1278,12 +1333,10 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "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, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); - /* ADD_GROUP("Orbit Velocity", "orbit_"); 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, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); -*/ ADD_GROUP("Linear Accel", "linear_"); 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); @@ -1339,10 +1392,12 @@ void CPUParticles2D::_bind_methods() { BIND_ENUM_CONSTANT(PARAM_MAX); BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY); + BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); // Unused, but exposed for consistency with 3D. + BIND_ENUM_CONSTANT(FLAG_DISABLE_Z); // Unused, but exposed for consistency with 3D. BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT); - BIND_ENUM_CONSTANT(EMISSION_SHAPE_CIRCLE); + BIND_ENUM_CONSTANT(EMISSION_SHAPE_SPHERE); BIND_ENUM_CONSTANT(EMISSION_SHAPE_RECTANGLE); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINTS); BIND_ENUM_CONSTANT(EMISSION_SHAPE_DIRECTED_POINTS); @@ -1354,6 +1409,7 @@ CPUParticles2D::CPUParticles2D() { inactive_time = 0; frame_remainder = 0; cycle = 0; + redraw = false; mesh = VisualServer::get_singleton()->mesh_create(); multimesh = VisualServer::get_singleton()->multimesh_create(); @@ -1368,17 +1424,19 @@ CPUParticles2D::CPUParticles2D() { set_pre_process_time(0); set_explosiveness_ratio(0); set_randomness_ratio(0); + set_lifetime_randomness(0); set_use_local_coordinates(true); set_draw_order(DRAW_ORDER_INDEX); set_speed_scale(1); + set_direction(Vector2(1, 0)); set_spread(45); set_flatness(0); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1); - //set_param(PARAM_ORBIT_VELOCITY, 0); - set_param(PARAM_LINEAR_ACCEL, 0); + set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); set_param(PARAM_ANGULAR_VELOCITY, 0); + set_param(PARAM_ORBIT_VELOCITY, 0); + set_param(PARAM_LINEAR_ACCEL, 0); set_param(PARAM_RADIAL_ACCEL, 0); set_param(PARAM_TANGENTIAL_ACCEL, 0); set_param(PARAM_DAMPING, 0); @@ -1391,7 +1449,7 @@ CPUParticles2D::CPUParticles2D() { set_emission_sphere_radius(1); set_emission_rect_extents(Vector2(1, 1)); - set_gravity(Vector2(0, 98.8)); + set_gravity(Vector2(0, 98)); for (int i = 0; i < PARAM_MAX; i++) { set_param_randomness(Parameter(i), 0); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index b1adf62980..da668664b9 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -35,10 +35,6 @@ #include "scene/2d/node_2d.h" #include "scene/resources/texture.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class CPUParticles2D : public Node2D { private: GDCLASS(CPUParticles2D, Node2D); @@ -68,12 +64,14 @@ public: enum Flags { FLAG_ALIGN_Y_TO_VELOCITY, + FLAG_ROTATE_Y, // Unused, but exposed for consistency with 3D. + FLAG_DISABLE_Z, // Unused, but exposed for consistency with 3D. FLAG_MAX }; enum EmissionShape { EMISSION_SHAPE_POINT, - EMISSION_SHAPE_CIRCLE, + EMISSION_SHAPE_SPHERE, EMISSION_SHAPE_RECTANGLE, EMISSION_SHAPE_POINTS, EMISSION_SHAPE_DIRECTED_POINTS, @@ -94,6 +92,7 @@ private: float hue_rot_rand; float anim_offset_rand; float time; + float lifetime; Color base_color; uint32_t seed; @@ -103,6 +102,7 @@ private: float inactive_time; float frame_remainder; int cycle; + bool redraw; RID mesh; RID multimesh; @@ -115,7 +115,7 @@ private: const Particle *particles; bool operator()(int p_a, int p_b) const { - return particles[p_a].time < particles[p_b].time; + return particles[p_a].time > particles[p_b].time; } }; @@ -136,11 +136,14 @@ private: float pre_process_time; float explosiveness_ratio; float randomness_ratio; + float lifetime_randomness; float speed_scale; bool local_coords; int fixed_fps; bool fractional_delta; + Transform2D inv_emission_transform; + DrawOrder draw_order; Ref<Texture> texture; @@ -148,6 +151,7 @@ private: //////// + Vector2 direction; float spread; float flatness; @@ -179,6 +183,8 @@ private: void _update_mesh_texture(); + void _set_redraw(bool p_redraw); + protected: static void _bind_methods(); void _notification(int p_what); @@ -192,6 +198,7 @@ public: void set_pre_process_time(float p_time); void set_explosiveness_ratio(float p_ratio); void set_randomness_ratio(float p_ratio); + void set_lifetime_randomness(float p_random); void set_visibility_aabb(const Rect2 &p_aabb); void set_use_local_coordinates(bool p_enable); void set_speed_scale(float p_scale); @@ -203,6 +210,7 @@ public: float get_pre_process_time() const; float get_explosiveness_ratio() const; float get_randomness_ratio() const; + float get_lifetime_randomness() const; Rect2 get_visibility_aabb() const; bool get_use_local_coordinates() const; float get_speed_scale() const; @@ -227,6 +235,9 @@ public: /////////////////// + void set_direction(Vector2 p_direction); + Vector2 get_direction() const; + void set_spread(float p_spread); float get_spread() const; @@ -245,7 +256,7 @@ public: void set_color(const Color &p_color); Color get_color() const; - void set_color_ramp(const Ref<Gradient> &p_texture); + void set_color_ramp(const Ref<Gradient> &p_ramp); Ref<Gradient> get_color_ramp() const; void set_particle_flag(Flags p_flag, bool p_enable); diff --git a/scene/2d/joints_2d.cpp b/scene/2d/joints_2d.cpp index 5b14b3e8e1..d8156a0afe 100644 --- a/scene/2d/joints_2d.cpp +++ b/scene/2d/joints_2d.cpp @@ -61,9 +61,7 @@ void Joint2D::_update_joint(bool p_only_free) { if (!body_a || !body_b) return; - if (!body_a) { - SWAP(body_a, body_b); - } + SWAP(body_a, body_b); joint = _configure_joint(body_a, body_b); diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index d7ed6f8f49..7b3eab175a 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -347,7 +347,7 @@ void Light2D::_notification(int p_what) { String Light2D::get_configuration_warning() const { if (!texture.is_valid()) { - return TTR("A texture with the shape of the light must be supplied to the 'texture' property."); + return TTR("A texture with the shape of the light must be supplied to the \"Texture\" property."); } return String(); @@ -431,14 +431,14 @@ void Light2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture,ImageTexture"), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_texture_offset", "get_texture_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_scale", PROPERTY_HINT_RANGE, "0.01,50,0.01"), "set_texture_scale", "get_texture_scale"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0.01,100,0.01"), "set_energy", "get_energy"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Add,Sub,Mix,Mask"), "set_mode", "get_mode"); ADD_GROUP("Range", "range_"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "range_height", PROPERTY_HINT_RANGE, "-100,100,0.1"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "range_height", PROPERTY_HINT_RANGE, "-2048,2048,0.1,or_lesser,or_greater"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "range_z_min", PROPERTY_HINT_RANGE, itos(VS::CANVAS_ITEM_Z_MIN) + "," + itos(VS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_range_min", "get_z_range_min"); ADD_PROPERTY(PropertyInfo(Variant::INT, "range_z_max", PROPERTY_HINT_RANGE, itos(VS::CANVAS_ITEM_Z_MIN) + "," + itos(VS::CANVAS_ITEM_Z_MAX) + ",1"), "set_z_range_max", "get_z_range_max"); ADD_PROPERTY(PropertyInfo(Variant::INT, "range_layer_min", PROPERTY_HINT_RANGE, "-512,512,1"), "set_layer_range_min", "get_layer_range_min"); diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 5334caed67..313b23b9d4 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -32,9 +32,59 @@ #include "core/engine.h" +#define LINE_GRAB_WIDTH 8 +Rect2 OccluderPolygon2D::_edit_get_rect() const { + + if (rect_cache_dirty) { + if (closed) { + PoolVector<Vector2>::Read r = polygon.read(); + item_rect = Rect2(); + for (int i = 0; i < polygon.size(); i++) { + Vector2 pos = r[i]; + if (i == 0) + item_rect.position = pos; + else + item_rect.expand_to(pos); + } + rect_cache_dirty = false; + } else { + if (polygon.size() == 0) { + item_rect = Rect2(); + } else { + Vector2 d = Vector2(LINE_GRAB_WIDTH, LINE_GRAB_WIDTH); + item_rect = Rect2(polygon[0] - d, 2 * d); + for (int i = 1; i < polygon.size(); i++) { + item_rect.expand_to(polygon[i] - d); + item_rect.expand_to(polygon[i] + d); + } + } + } + } + + return item_rect; +} + +bool OccluderPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + + if (closed) { + return Geometry::is_point_in_polygon(p_point, Variant(polygon)); + } else { + const real_t d = LINE_GRAB_WIDTH / 2 + p_tolerance; + PoolVector<Vector2>::Read points = polygon.read(); + for (int i = 0; i < polygon.size() - 1; i++) { + Vector2 p = Geometry::get_closest_point_to_segment_2d(p_point, &points[i]); + if (p.distance_to(p_point) <= d) + return true; + } + + return false; + } +} + void OccluderPolygon2D::set_polygon(const PoolVector<Vector2> &p_polygon) { polygon = p_polygon; + rect_cache_dirty = true; VS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, p_polygon, closed); emit_changed(); } @@ -100,6 +150,7 @@ OccluderPolygon2D::OccluderPolygon2D() { occ_polygon = VS::get_singleton()->canvas_occluder_polygon_create(); closed = true; cull = CULL_DISABLED; + rect_cache_dirty = true; } OccluderPolygon2D::~OccluderPolygon2D() { @@ -164,6 +215,16 @@ void LightOccluder2D::_notification(int p_what) { } } +Rect2 LightOccluder2D::_edit_get_rect() const { + + return occluder_polygon.is_valid() ? occluder_polygon->_edit_get_rect() : Rect2(); +} + +bool LightOccluder2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + + return occluder_polygon.is_valid() ? occluder_polygon->_edit_is_selected_on_click(p_point, p_tolerance) : false; +} + void LightOccluder2D::set_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon) { #ifdef DEBUG_ENABLED @@ -207,7 +268,7 @@ String LightOccluder2D::get_configuration_warning() const { } if (occluder_polygon.is_valid() && occluder_polygon->get_polygon().size() == 0) { - return TTR("The occluder polygon for this occluder is empty. Please draw a polygon!"); + return TTR("The occluder polygon for this occluder is empty. Please draw a polygon."); } return String(); diff --git a/scene/2d/light_occluder_2d.h b/scene/2d/light_occluder_2d.h index 498d65d764..7931cd0b30 100644 --- a/scene/2d/light_occluder_2d.h +++ b/scene/2d/light_occluder_2d.h @@ -50,10 +50,16 @@ private: bool closed; CullMode cull; + mutable Rect2 item_rect; + mutable bool rect_cache_dirty; + 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_polygon(const PoolVector<Vector2> &p_polygon); PoolVector<Vector2> get_polygon() const; @@ -85,6 +91,9 @@ 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_occluder_polygon(const Ref<OccluderPolygon2D> &p_polygon); Ref<OccluderPolygon2D> get_occluder_polygon() const; diff --git a/scene/2d/line_2d.cpp b/scene/2d/line_2d.cpp index 105eb82afb..ad405fabbb 100644 --- a/scene/2d/line_2d.cpp +++ b/scene/2d/line_2d.cpp @@ -38,13 +38,13 @@ VARIANT_ENUM_CAST(Line2D::LineJointMode) VARIANT_ENUM_CAST(Line2D::LineCapMode) VARIANT_ENUM_CAST(Line2D::LineTextureMode) -Line2D::Line2D() : - Node2D() { +Line2D::Line2D() { _joint_mode = LINE_JOINT_SHARP; _begin_cap_mode = LINE_CAP_NONE; _end_cap_mode = LINE_CAP_NONE; _width = 10; _default_color = Color(0.4, 0.5, 1); + _texture_mode = LINE_TEXTURE_NONE; _sharp_limit = 2.f; _round_precision = 8; } @@ -84,10 +84,10 @@ void Line2D::set_points(const PoolVector<Vector2> &p_points) { update(); } -void Line2D::set_width(float width) { - if (width < 0.0) - width = 0.0; - _width = width; +void Line2D::set_width(float p_width) { + if (p_width < 0.0) + p_width = 0.0; + _width = p_width; update(); } @@ -95,16 +95,37 @@ float Line2D::get_width() const { return _width; } +void Line2D::set_curve(const Ref<Curve> &p_curve) { + // Cleanup previous connection if any + if (_curve.is_valid()) { + _curve->disconnect(CoreStringNames::get_singleton()->changed, this, "_curve_changed"); + } + + _curve = p_curve; + + // Connect to the curve so the line will update when it is changed + if (_curve.is_valid()) { + _curve->connect(CoreStringNames::get_singleton()->changed, this, "_curve_changed"); + } + + update(); +} + +Ref<Curve> Line2D::get_curve() const { + return _curve; +} + PoolVector<Vector2> Line2D::get_points() const { return _points; } -void Line2D::set_point_position(int i, Vector2 pos) { - _points.set(i, pos); +void Line2D::set_point_position(int i, Vector2 p_pos) { + _points.set(i, p_pos); update(); } Vector2 Line2D::get_point_position(int i) const { + ERR_FAIL_INDEX_V(i, _points.size(), Vector2()); return _points.get(i); } @@ -112,8 +133,20 @@ int Line2D::get_point_count() const { return _points.size(); } -void Line2D::add_point(Vector2 pos) { - _points.append(pos); +void Line2D::clear_points() { + int count = _points.size(); + if (count > 0) { + _points.resize(0); + update(); + } +} + +void Line2D::add_point(Vector2 p_pos, int p_atpos) { + if (p_atpos < 0 || _points.size() < p_atpos) { + _points.append(p_pos); + } else { + _points.insert(p_atpos, p_pos); + } update(); } @@ -122,8 +155,8 @@ void Line2D::remove_point(int i) { update(); } -void Line2D::set_default_color(Color color) { - _default_color = color; +void Line2D::set_default_color(Color p_color) { + _default_color = p_color; update(); } @@ -131,18 +164,18 @@ Color Line2D::get_default_color() const { return _default_color; } -void Line2D::set_gradient(const Ref<Gradient> &gradient) { +void Line2D::set_gradient(const Ref<Gradient> &p_gradient) { // Cleanup previous connection if any if (_gradient.is_valid()) { - (**_gradient).disconnect(CoreStringNames::get_singleton()->changed, this, "_gradient_changed"); + _gradient->disconnect(CoreStringNames::get_singleton()->changed, this, "_gradient_changed"); } - _gradient = gradient; + _gradient = p_gradient; // Connect to the gradient so the line will update when the ColorRamp is changed if (_gradient.is_valid()) { - (**_gradient).connect(CoreStringNames::get_singleton()->changed, this, "_gradient_changed"); + _gradient->connect(CoreStringNames::get_singleton()->changed, this, "_gradient_changed"); } update(); @@ -152,8 +185,8 @@ Ref<Gradient> Line2D::get_gradient() const { return _gradient; } -void Line2D::set_texture(const Ref<Texture> &texture) { - _texture = texture; +void Line2D::set_texture(const Ref<Texture> &p_texture) { + _texture = p_texture; update(); } @@ -161,8 +194,8 @@ Ref<Texture> Line2D::get_texture() const { return _texture; } -void Line2D::set_texture_mode(const LineTextureMode mode) { - _texture_mode = mode; +void Line2D::set_texture_mode(const LineTextureMode p_mode) { + _texture_mode = p_mode; update(); } @@ -170,8 +203,8 @@ Line2D::LineTextureMode Line2D::get_texture_mode() const { return _texture_mode; } -void Line2D::set_joint_mode(LineJointMode mode) { - _joint_mode = mode; +void Line2D::set_joint_mode(LineJointMode p_mode) { + _joint_mode = p_mode; update(); } @@ -179,8 +212,8 @@ Line2D::LineJointMode Line2D::get_joint_mode() const { return _joint_mode; } -void Line2D::set_begin_cap_mode(LineCapMode mode) { - _begin_cap_mode = mode; +void Line2D::set_begin_cap_mode(LineCapMode p_mode) { + _begin_cap_mode = p_mode; update(); } @@ -188,8 +221,8 @@ Line2D::LineCapMode Line2D::get_begin_cap_mode() const { return _begin_cap_mode; } -void Line2D::set_end_cap_mode(LineCapMode mode) { - _end_cap_mode = mode; +void Line2D::set_end_cap_mode(LineCapMode p_mode) { + _end_cap_mode = p_mode; update(); } @@ -205,10 +238,10 @@ void Line2D::_notification(int p_what) { } } -void Line2D::set_sharp_limit(float limit) { - if (limit < 0.f) - limit = 0.f; - _sharp_limit = limit; +void Line2D::set_sharp_limit(float p_limit) { + if (p_limit < 0.f) + p_limit = 0.f; + _sharp_limit = p_limit; update(); } @@ -216,10 +249,10 @@ float Line2D::get_sharp_limit() const { return _sharp_limit; } -void Line2D::set_round_precision(int precision) { - if (precision < 1) - precision = 1; - _round_precision = precision; +void Line2D::set_round_precision(int p_precision) { + if (p_precision < 1) + p_precision = 1; + _round_precision = p_precision; update(); } @@ -255,10 +288,11 @@ void Line2D::_draw() { lb.round_precision = _round_precision; lb.sharp_limit = _sharp_limit; lb.width = _width; + lb.curve = *_curve; RID texture_rid; if (_texture.is_valid()) { - texture_rid = (**_texture).get_rid(); + texture_rid = _texture->get_rid(); lb.tile_aspect = _texture->get_size().aspect(); } @@ -299,6 +333,10 @@ void Line2D::_gradient_changed() { update(); } +void Line2D::_curve_changed() { + update(); +} + // static void Line2D::_bind_methods() { @@ -310,12 +348,17 @@ void Line2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_point_count"), &Line2D::get_point_count); - ClassDB::bind_method(D_METHOD("add_point", "position"), &Line2D::add_point); + ClassDB::bind_method(D_METHOD("add_point", "position", "at_position"), &Line2D::add_point, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("remove_point", "i"), &Line2D::remove_point); + ClassDB::bind_method(D_METHOD("clear_points"), &Line2D::clear_points); + ClassDB::bind_method(D_METHOD("set_width", "width"), &Line2D::set_width); ClassDB::bind_method(D_METHOD("get_width"), &Line2D::get_width); + ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Line2D::set_curve); + ClassDB::bind_method(D_METHOD("get_curve"), &Line2D::get_curve); + ClassDB::bind_method(D_METHOD("set_default_color", "color"), &Line2D::set_default_color); ClassDB::bind_method(D_METHOD("get_default_color"), &Line2D::get_default_color); @@ -345,6 +388,7 @@ void Line2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "points"), "set_points", "get_points"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "width"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "width_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_color"), "set_default_color", "get_default_color"); ADD_GROUP("Fill", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "gradient", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_gradient", "get_gradient"); @@ -371,4 +415,5 @@ void Line2D::_bind_methods() { BIND_ENUM_CONSTANT(LINE_TEXTURE_STRETCH); ClassDB::bind_method(D_METHOD("_gradient_changed"), &Line2D::_gradient_changed); + ClassDB::bind_method(D_METHOD("_curve_changed"), &Line2D::_curve_changed); } diff --git a/scene/2d/line_2d.h b/scene/2d/line_2d.h index 5bbd38e460..14afa463ba 100644 --- a/scene/2d/line_2d.h +++ b/scene/2d/line_2d.h @@ -35,7 +35,7 @@ class Line2D : public Node2D { - GDCLASS(Line2D, Node2D) + GDCLASS(Line2D, Node2D); public: enum LineJointMode { @@ -70,12 +70,17 @@ public: int get_point_count() const; - void add_point(Vector2 pos); + void clear_points(); + + void add_point(Vector2 pos, int atpos = -1); void remove_point(int i); void set_width(float width); float get_width() const; + void set_curve(const Ref<Curve> &curve); + Ref<Curve> get_curve() const; + void set_default_color(Color color); Color get_default_color() const; @@ -111,6 +116,7 @@ protected: private: void _gradient_changed(); + void _curve_changed(); private: PoolVector<Vector2> _points; @@ -118,6 +124,7 @@ private: LineCapMode _begin_cap_mode; LineCapMode _end_cap_mode; float _width; + Ref<Curve> _curve; Color _default_color; Ref<Gradient> _gradient; Ref<Texture> _texture; diff --git a/scene/2d/line_builder.cpp b/scene/2d/line_builder.cpp index eb09d3c9d3..9fe5fb98b6 100644 --- a/scene/2d/line_builder.cpp +++ b/scene/2d/line_builder.cpp @@ -95,6 +95,7 @@ static inline Vector2 interpolate(const Rect2 &r, const Vector2 &v) { LineBuilder::LineBuilder() { joint_mode = Line2D::LINE_JOINT_SHARP; width = 10; + curve = NULL; default_color = Color(0.4, 0.5, 1); gradient = NULL; sharp_limit = 2.f; @@ -136,8 +137,8 @@ void LineBuilder::build() { Vector2 pos1 = points[1]; Vector2 f0 = (pos1 - pos0).normalized(); Vector2 u0 = rotate90(f0); - Vector2 pos_up0 = pos0 + u0 * hw; - Vector2 pos_down0 = pos0 - u0 * hw; + Vector2 pos_up0 = pos0; + Vector2 pos_down0 = pos0; Color color0; Color color1; @@ -145,12 +146,30 @@ void LineBuilder::build() { float current_distance0 = 0.f; float current_distance1 = 0.f; float total_distance = 0.f; + float width_factor = 1.f; _interpolate_color = gradient != NULL; + bool retrieve_curve = curve != NULL; bool distance_required = _interpolate_color || + retrieve_curve || texture_mode == Line2D::LINE_TEXTURE_TILE || texture_mode == Line2D::LINE_TEXTURE_STRETCH; - if (distance_required) + if (distance_required) { total_distance = calculate_total_distance(points); + //Ajust totalDistance. + // The line's outer length will be a little higher due to begin and end caps + if (begin_cap_mode == Line2D::LINE_CAP_BOX || begin_cap_mode == Line2D::LINE_CAP_ROUND) { + if (retrieve_curve) + total_distance += width * curve->interpolate_baked(0.f) * 0.5f; + else + total_distance += width * 0.5f; + } + if (end_cap_mode == Line2D::LINE_CAP_BOX || end_cap_mode == Line2D::LINE_CAP_ROUND) { + if (retrieve_curve) + total_distance += width * curve->interpolate_baked(1.f) * 0.5f; + else + total_distance += width * 0.5f; + } + } if (_interpolate_color) color0 = gradient->get_color(0); else @@ -159,22 +178,28 @@ void LineBuilder::build() { float uvx0 = 0.f; float uvx1 = 0.f; + if (retrieve_curve) + width_factor = curve->interpolate_baked(0.f); + + pos_up0 += u0 * hw * width_factor; + pos_down0 -= u0 * hw * width_factor; + // Begin cap if (begin_cap_mode == Line2D::LINE_CAP_BOX) { // Push back first vertices a little bit - pos_up0 -= f0 * hw; - pos_down0 -= f0 * hw; - // The line's outer length will be a little higher due to begin and end caps - total_distance += width; - current_distance0 += hw; + pos_up0 -= f0 * hw * width_factor; + pos_down0 -= f0 * hw * width_factor; + + current_distance0 += hw * width_factor; current_distance1 = current_distance0; } else if (begin_cap_mode == Line2D::LINE_CAP_ROUND) { if (texture_mode == Line2D::LINE_TEXTURE_TILE) { - uvx0 = 0.5f / tile_aspect; + uvx0 = width_factor * 0.5f / tile_aspect; + } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { + uvx0 = width * width_factor / total_distance; } - new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, fmin(uvx0 * 2, 1.f), 1.f)); - total_distance += width; - current_distance0 += hw; + new_arc(pos0, pos_up0 - pos0, -Math_PI, color0, Rect2(0.f, 0.f, uvx0 * 2, 1.f)); + current_distance0 += hw * width_factor; current_distance1 = current_distance0; } @@ -206,13 +231,23 @@ void LineBuilder::build() { const float dp = u0.dot(f1); const Orientation orientation = (dp > 0.f ? UP : DOWN); + if (distance_required) { + current_distance1 += pos0.distance_to(pos1); + } + if (_interpolate_color) { + color1 = gradient->get_color_at_offset(current_distance1 / total_distance); + } + if (retrieve_curve) { + width_factor = curve->interpolate_baked(current_distance1 / total_distance); + } + Vector2 inner_normal0, inner_normal1; if (orientation == UP) { - inner_normal0 = u0 * hw; - inner_normal1 = u1 * hw; + inner_normal0 = u0 * hw * width_factor; + inner_normal1 = u1 * hw * width_factor; } else { - inner_normal0 = -u0 * hw; - inner_normal1 = -u1 * hw; + inner_normal0 = -u0 * hw * width_factor; + inner_normal1 = -u1 * hw * width_factor; } /* @@ -259,7 +294,8 @@ void LineBuilder::build() { Vector2 pos_up1, pos_down1; if (intersection_result == SEGMENT_INTERSECT) { // Fallback on bevel if sharp angle is too high (because it would produce very long miters) - if (current_joint_mode == Line2D::LINE_JOINT_SHARP && corner_pos_out.distance_squared_to(pos1) / hw_sq > sharp_limit_sq) { + float width_factor_sq = width_factor * width_factor; + if (current_joint_mode == Line2D::LINE_JOINT_SHARP && corner_pos_out.distance_squared_to(pos1) / (hw_sq * width_factor_sq) > sharp_limit_sq) { current_joint_mode = Line2D::LINE_JOINT_BEVEL; } if (current_joint_mode == Line2D::LINE_JOINT_SHARP) { @@ -271,9 +307,9 @@ void LineBuilder::build() { // Bevel or round if (orientation == UP) { pos_up1 = corner_pos_up; - pos_down1 = pos1 - u0 * hw; + pos_down1 = pos1 - u0 * hw * width_factor; } else { - pos_up1 = pos1 + u0 * hw; + pos_up1 = pos1 + u0 * hw * width_factor; pos_down1 = corner_pos_down; } } @@ -289,12 +325,6 @@ void LineBuilder::build() { // Add current line body quad // Triangles are clockwise - if (distance_required) { - current_distance1 += pos0.distance_to(pos1); - } - if (_interpolate_color) { - color1 = gradient->get_color_at_offset(current_distance1 / total_distance); - } if (texture_mode == Line2D::LINE_TEXTURE_TILE) { uvx1 = current_distance1 / (width * tile_aspect); } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { @@ -315,15 +345,15 @@ void LineBuilder::build() { } else { if (orientation == UP) { pos_up0 = corner_pos_up; - pos_down0 = pos1 - u1 * hw; + pos_down0 = pos1 - u1 * hw * width_factor; } else { - pos_up0 = pos1 + u1 * hw; + pos_up0 = pos1 + u1 * hw * width_factor; pos_down0 = corner_pos_down; } } } else { - pos_up0 = pos1 + u1 * hw; - pos_down0 = pos1 - u1 * hw; + pos_up0 = pos1 + u1 * hw * width_factor; + pos_down0 = pos1 - u1 * hw * width_factor; } // From this point, bu0 and bd0 concern the next segment @@ -362,26 +392,28 @@ void LineBuilder::build() { strip_begin(pos_up0, pos_down0, color1, uvx1); } } - // Last (or only) segment - pos1 = points[points.size() - 1]; - Vector2 pos_up1 = pos1 + u0 * hw; - Vector2 pos_down1 = pos1 - u0 * hw; - - // End cap (box) - if (end_cap_mode == Line2D::LINE_CAP_BOX) { - pos_up1 += f0 * hw; - pos_down1 += f0 * hw; - } - if (distance_required) { current_distance1 += pos0.distance_to(pos1); } if (_interpolate_color) { color1 = gradient->get_color(gradient->get_points_count() - 1); } + if (retrieve_curve) { + width_factor = curve->interpolate_baked(1.f); + } + + Vector2 pos_up1 = pos1 + u0 * hw * width_factor; + Vector2 pos_down1 = pos1 - u0 * hw * width_factor; + + // End cap (box) + if (end_cap_mode == Line2D::LINE_CAP_BOX) { + pos_up1 += f0 * hw * width_factor; + pos_down1 += f0 * hw * width_factor; + } + if (texture_mode == Line2D::LINE_TEXTURE_TILE) { uvx1 = current_distance1 / (width * tile_aspect); } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { @@ -394,7 +426,13 @@ void LineBuilder::build() { if (end_cap_mode == Line2D::LINE_CAP_ROUND) { // Note: color is not used in case we don't interpolate... Color color = _interpolate_color ? gradient->get_color(gradient->get_points_count() - 1) : Color(0, 0, 0); - new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1 - 0.5f / tile_aspect, 0.f, 1.0f / tile_aspect, 1.f)); + float dist = 0; + if (texture_mode == Line2D::LINE_TEXTURE_TILE) { + dist = width_factor / tile_aspect; + } else if (texture_mode == Line2D::LINE_TEXTURE_STRETCH) { + dist = width * width_factor / total_distance; + } + new_arc(pos1, pos_up1 - pos1, Math_PI, color, Rect2(uvx1 - 0.5f * dist, 0.f, dist, 1.f)); } } diff --git a/scene/2d/line_builder.h b/scene/2d/line_builder.h index 2ca28d09c4..91b4518f9b 100644 --- a/scene/2d/line_builder.h +++ b/scene/2d/line_builder.h @@ -34,7 +34,7 @@ #include "core/color.h" #include "core/math/vector2.h" #include "line_2d.h" -#include "scene/resources/color_ramp.h" +#include "scene/resources/gradient.h" class LineBuilder { public: @@ -45,6 +45,7 @@ public: Line2D::LineCapMode begin_cap_mode; Line2D::LineCapMode end_cap_mode; float width; + Curve *curve; Color default_color; Gradient *gradient; Line2D::LineTextureMode texture_mode; diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp index b382ca7b33..bcd4bca940 100644 --- a/scene/2d/mesh_instance_2d.cpp +++ b/scene/2d/mesh_instance_2d.cpp @@ -50,6 +50,8 @@ void MeshInstance2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_normal_map", "normal_map"), &MeshInstance2D::set_normal_map); ClassDB::bind_method(D_METHOD("get_normal_map"), &MeshInstance2D::get_normal_map); + ADD_SIGNAL(MethodInfo("texture_changed")); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_normal_map", "get_normal_map"); diff --git a/scene/2d/mesh_instance_2d.h b/scene/2d/mesh_instance_2d.h index 4d81c8088a..af552415ca 100644 --- a/scene/2d/mesh_instance_2d.h +++ b/scene/2d/mesh_instance_2d.h @@ -34,7 +34,7 @@ #include "scene/2d/node_2d.h" class MeshInstance2D : public Node2D { - GDCLASS(MeshInstance2D, Node2D) + GDCLASS(MeshInstance2D, Node2D); Ref<Mesh> mesh; diff --git a/scene/2d/multimesh_instance_2d.cpp b/scene/2d/multimesh_instance_2d.cpp new file mode 100644 index 0000000000..ca75302163 --- /dev/null +++ b/scene/2d/multimesh_instance_2d.cpp @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* multimesh_instance_2d.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "multimesh_instance_2d.h" + +void MultiMeshInstance2D::_notification(int p_what) { + + if (p_what == NOTIFICATION_DRAW) { + if (multimesh.is_valid()) { + draw_multimesh(multimesh, texture, normal_map); + } + } +} + +void MultiMeshInstance2D::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance2D::set_multimesh); + ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance2D::get_multimesh); + + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &MultiMeshInstance2D::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &MultiMeshInstance2D::get_texture); + + ClassDB::bind_method(D_METHOD("set_normal_map", "normal_map"), &MultiMeshInstance2D::set_normal_map); + ClassDB::bind_method(D_METHOD("get_normal_map"), &MultiMeshInstance2D::get_normal_map); + + ADD_SIGNAL(MethodInfo("texture_changed")); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "normal_map", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_normal_map", "get_normal_map"); +} + +void MultiMeshInstance2D::set_multimesh(const Ref<MultiMesh> &p_multimesh) { + + multimesh = p_multimesh; + update(); +} + +Ref<MultiMesh> MultiMeshInstance2D::get_multimesh() const { + + return multimesh; +} + +void MultiMeshInstance2D::set_texture(const Ref<Texture> &p_texture) { + + if (p_texture == texture) + return; + texture = p_texture; + update(); + emit_signal("texture_changed"); + _change_notify("texture"); +} + +Ref<Texture> MultiMeshInstance2D::get_texture() const { + + return texture; +} + +void MultiMeshInstance2D::set_normal_map(const Ref<Texture> &p_texture) { + + normal_map = p_texture; + update(); +} + +Ref<Texture> MultiMeshInstance2D::get_normal_map() const { + + return normal_map; +} + +Rect2 MultiMeshInstance2D::_edit_get_rect() const { + + if (multimesh.is_valid()) { + AABB aabb = multimesh->get_aabb(); + return Rect2(aabb.position.x, aabb.position.y, aabb.size.x, aabb.size.y); + } + + return Node2D::_edit_get_rect(); +} + +MultiMeshInstance2D::MultiMeshInstance2D() { +} + +MultiMeshInstance2D::~MultiMeshInstance2D() { +} diff --git a/scene/resources/bounds.cpp b/scene/2d/multimesh_instance_2d.h index e6fa5b818d..3795497183 100644 --- a/scene/resources/bounds.cpp +++ b/scene/2d/multimesh_instance_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* bounds.cpp */ +/* multimesh_instance_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,38 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "bounds.h" +#ifndef MULTIMESH_INSTANCE_2D_H +#define MULTIMESH_INSTANCE_2D_H -void Bounds::_bind_methods() { +#include "scene/2d/node_2d.h" +#include "scene/resources/multimesh.h" - ClassDB::bind_method(D_METHOD("set_bsp_tree", "bsp_tree"), &Bounds::set_bsp_tree); - ClassDB::bind_method(D_METHOD("get_bsp_tree"), &Bounds::get_bsp_tree); +class MultiMeshInstance2D : public Node2D { + GDCLASS(MultiMeshInstance2D, Node2D); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bsp_tree"), "set_bsp_tree", "get_bsp_tree"); -} + Ref<MultiMesh> multimesh; -void Bounds::set_bsp_tree(const BSP_Tree &p_bsp_tree) { + Ref<Texture> texture; + Ref<Texture> normal_map; - bsp_tree = p_bsp_tree; -} +protected: + void _notification(int p_what); + static void _bind_methods(); -BSP_Tree Bounds::get_bsp_tree() const { +public: + void set_multimesh(const Ref<MultiMesh> &p_multimesh); + Ref<MultiMesh> get_multimesh() const; - return bsp_tree; -} + void set_texture(const Ref<Texture> &p_texture); + Ref<Texture> get_texture() const; -Bounds::Bounds() { -} + void set_normal_map(const Ref<Texture> &p_texture); + Ref<Texture> get_normal_map() const; + + virtual Rect2 _edit_get_rect() const; + + MultiMeshInstance2D(); + ~MultiMeshInstance2D(); +}; + +#endif // MULTIMESH_INSTANCE_2D_H diff --git a/scene/2d/navigation2d.cpp b/scene/2d/navigation_2d.cpp index 1c0e924433..5cf28d6c89 100644 --- a/scene/2d/navigation2d.cpp +++ b/scene/2d/navigation_2d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* navigation2d.cpp */ +/* navigation_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "navigation2d.h" +#include "navigation_2d.h" #define USE_ENTRY_POINT @@ -92,7 +92,6 @@ void Navigation2D::_navpoly_link(int p_id) { if (!valid) { nm.polygons.pop_back(); ERR_CONTINUE(!valid); - continue; } p.center = center / plen; @@ -542,7 +541,7 @@ Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vect if (CLOCK_TANGENT(apex_point, portal_left, left) >= 0) { //process - if (portal_left.distance_squared_to(apex_point) < CMP_EPSILON || CLOCK_TANGENT(apex_point, left, portal_right) > 0) { + if (Math::is_zero_approx(portal_left.distance_squared_to(apex_point)) || CLOCK_TANGENT(apex_point, left, portal_right) > 0) { left_poly = p; portal_left = left; } else { @@ -552,7 +551,7 @@ Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vect left_poly = p; portal_left = apex_point; portal_right = apex_point; - if (!path.size() || path[path.size() - 1].distance_to(apex_point) > CMP_EPSILON) + if (!path.size() || path[path.size() - 1] != apex_point) path.push_back(apex_point); skip = true; } @@ -560,7 +559,7 @@ Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vect if (!skip && CLOCK_TANGENT(apex_point, portal_right, right) <= 0) { //process - if (portal_right.distance_squared_to(apex_point) < CMP_EPSILON || CLOCK_TANGENT(apex_point, right, portal_left) < 0) { + if (Math::is_zero_approx(portal_right.distance_squared_to(apex_point)) || CLOCK_TANGENT(apex_point, right, portal_left) < 0) { right_poly = p; portal_right = right; } else { @@ -570,7 +569,7 @@ Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vect right_poly = p; portal_right = apex_point; portal_left = apex_point; - if (!path.size() || path[path.size() - 1].distance_to(apex_point) > CMP_EPSILON) + if (!path.size() || path[path.size() - 1] != apex_point) path.push_back(apex_point); } } @@ -596,7 +595,7 @@ Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vect } } - if (!path.size() || path[path.size() - 1].distance_squared_to(begin_point) > CMP_EPSILON) { + if (!path.size() || !Math::is_zero_approx(path[path.size() - 1].distance_squared_to(begin_point))) { path.push_back(begin_point); // Add the begin point } else { path.write[path.size() - 1] = begin_point; // Replace first midpoint by the exact begin point @@ -604,7 +603,7 @@ Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vect path.invert(); - if (path.size() <= 1 || path[path.size() - 1].distance_squared_to(end_point) > CMP_EPSILON) { + if (path.size() <= 1 || !Math::is_zero_approx(path[path.size() - 1].distance_squared_to(end_point))) { path.push_back(end_point); // Add the end point } else { path.write[path.size() - 1] = end_point; // Replace last midpoint by the exact end point diff --git a/scene/2d/navigation2d.h b/scene/2d/navigation_2d.h index fc1762221c..b4d659ff5c 100644 --- a/scene/2d/navigation2d.h +++ b/scene/2d/navigation_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* navigation2d.h */ +/* navigation_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -171,4 +171,4 @@ public: Navigation2D(); }; -#endif // Navigation2D2D_H +#endif // NAVIGATION_2D_H diff --git a/scene/2d/navigation_polygon.cpp b/scene/2d/navigation_polygon.cpp index 50618c6baa..e389d5f98f 100644 --- a/scene/2d/navigation_polygon.cpp +++ b/scene/2d/navigation_polygon.cpp @@ -32,7 +32,7 @@ #include "core/core_string_names.h" #include "core/engine.h" -#include "navigation2d.h" +#include "navigation_2d.h" #include "thirdparty/misc/triangulator.h" @@ -312,7 +312,7 @@ void NavigationPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_outlines", "outlines"), &NavigationPolygon::_set_outlines); ClassDB::bind_method(D_METHOD("_get_outlines"), &NavigationPolygon::_get_outlines); - ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines"); } diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index 9a94092840..f4430d93f6 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -376,7 +376,7 @@ void Node2D::look_at(const Vector2 &p_pos) { float Node2D::get_angle_to(const Vector2 &p_pos) const { - return (get_global_transform().affine_inverse().xform(p_pos)).angle(); + return (to_local(p_pos) * get_scale()).angle(); } Point2 Node2D::to_local(Point2 p_global) const { diff --git a/scene/2d/parallax_background.cpp b/scene/2d/parallax_background.cpp index 96e13396c5..4ead1bbd1e 100644 --- a/scene/2d/parallax_background.cpp +++ b/scene/2d/parallax_background.cpp @@ -207,7 +207,7 @@ void ParallaxBackground::_bind_methods() { ParallaxBackground::ParallaxBackground() { scale = 1.0; - set_layer(-1); //behind all by default + set_layer(-100); //behind all by default base_scale = Vector2(1, 1); ignore_camera_zoom = false; diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index baf5b5967b..0823e09110 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -69,6 +69,9 @@ Size2 ParallaxLayer::get_motion_offset() const { void ParallaxLayer::_update_mirroring() { + if (!is_inside_tree()) + return; + ParallaxBackground *pb = Object::cast_to<ParallaxBackground>(get_parent()); if (pb) { @@ -105,6 +108,11 @@ void ParallaxLayer::_notification(int p_what) { orig_scale = get_scale(); _update_mirroring(); } break; + case NOTIFICATION_EXIT_TREE: { + + set_position(orig_offset); + set_scale(orig_scale); + } break; } } diff --git a/scene/2d/particles_2d.cpp b/scene/2d/particles_2d.cpp index c005c33a19..93c12f0103 100644 --- a/scene/2d/particles_2d.cpp +++ b/scene/2d/particles_2d.cpp @@ -30,6 +30,7 @@ #include "particles_2d.h" +#include "core/os/os.h" #include "scene/resources/particles_material.h" #include "scene/scene_string_names.h" @@ -40,6 +41,12 @@ void Particles2D::set_emitting(bool p_emitting) { VS::get_singleton()->particles_set_emitting(particles, p_emitting); + + if (p_emitting && one_shot) { + set_process_internal(true); + } else if (!p_emitting) { + set_process_internal(false); + } } void Particles2D::set_amount(int p_amount) { @@ -59,8 +66,16 @@ void Particles2D::set_one_shot(bool p_enable) { one_shot = p_enable; VS::get_singleton()->particles_set_one_shot(particles, one_shot); - if (!one_shot && is_emitting()) - VisualServer::get_singleton()->particles_restart(particles); + + if (is_emitting()) { + + set_process_internal(true); + if (!one_shot) + VisualServer::get_singleton()->particles_restart(particles); + } + + if (!one_shot) + set_process_internal(false); } void Particles2D::set_pre_process_time(float p_time) { @@ -213,6 +228,10 @@ bool Particles2D::get_fractional_delta() const { String Particles2D::get_configuration_warning() const { + if (OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { + return TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles2D node instead. You can use the \"Convert to CPUParticles\" option for this purpose."); + } + String warnings; if (process_material.is_null()) { @@ -273,6 +292,7 @@ void Particles2D::_validate_property(PropertyInfo &property) const { void Particles2D::restart() { VS::get_singleton()->particles_restart(particles); + VS::get_singleton()->particles_set_emitting(particles, true); } void Particles2D::_notification(int p_what) { @@ -308,6 +328,14 @@ void Particles2D::_notification(int p_what) { if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { _update_particle_emission_transform(); } + + if (p_what == NOTIFICATION_INTERNAL_PROCESS) { + + if (one_shot && !is_emitting()) { + _change_notify(); + set_process_internal(false); + } + } } void Particles2D::_bind_methods() { @@ -382,6 +410,7 @@ Particles2D::Particles2D() { particles = VS::get_singleton()->particles_create(); + one_shot = false; // Needed so that set_emitting doesn't access uninitialized values set_emitting(true); set_one_shot(false); set_amount(8); diff --git a/scene/2d/particles_2d.h b/scene/2d/particles_2d.h index a0104b4b16..0276978f83 100644 --- a/scene/2d/particles_2d.h +++ b/scene/2d/particles_2d.h @@ -37,7 +37,7 @@ class Particles2D : public Node2D { private: - GDCLASS(Particles2D, Node2D) + GDCLASS(Particles2D, Node2D); public: enum DrawOrder { diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 271e132002..55c8c7f229 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -58,7 +58,7 @@ Rect2 Path2D::_edit_get_rect() const { } bool Path2D::_edit_use_rect() const { - return true; + return curve.is_valid() && curve->get_point_count() != 0; } bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { @@ -95,7 +95,7 @@ void Path2D::_notification(int p_what) { return; } -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED const float line_width = 2 * EDSCALE; #else const float line_width = 2; @@ -110,7 +110,7 @@ void Path2D::_notification(int p_what) { real_t frac = j / 8.0; Vector2 p = curve->interpolate(i, frac); - draw_line(prev_p, p, color, line_width); + draw_line(prev_p, p, color, line_width, true); prev_p = p; } } @@ -170,6 +170,9 @@ void PathFollow2D::_update_transform() { return; float path_length = c->get_baked_length(); + if (path_length == 0) { + return; + } float bounded_offset = offset; if (loop) bounded_offset = Math::fposmod(bounded_offset, path_length); @@ -261,7 +264,7 @@ void PathFollow2D::_validate_property(PropertyInfo &property) const { if (path && path->get_curve().is_valid()) max = path->get_curve()->get_baked_length(); - property.hint_string = "0," + rtos(max) + ",0.01,or_greater"; + property.hint_string = "0," + rtos(max) + ",0.01,or_lesser"; } } @@ -303,8 +306,8 @@ 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,or_greater"), "set_offset", "get_offset"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001,or_greater", PROPERTY_USAGE_EDITOR), "set_unit_offset", "get_unit_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "offset", PROPERTY_HINT_RANGE, "0,10000,0.01,or_lesser"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_offset", PROPERTY_HINT_RANGE, "0,1,0.0001,or_lesser", 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, "rotate"), "set_rotate", "is_rotating"); @@ -316,8 +319,24 @@ void PathFollow2D::_bind_methods() { void PathFollow2D::set_offset(float p_offset) { offset = p_offset; - if (path) + if (path) { + if (path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { + float path_length = path->get_curve()->get_baked_length(); + + if (loop) { + while (offset > path_length) + offset -= path_length; + + while (offset < 0) + offset += path_length; + + } else { + offset = CLAMP(offset, 0, path_length); + } + } + _update_transform(); + } _change_notify("offset"); _change_notify("unit_offset"); } diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index f4bc8ad6b9..7cc937a64a 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -157,10 +157,7 @@ void PhysicsBody2D::add_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(p_node); - if (!physics_body) { - ERR_EXPLAIN("Collision exception only works between two objects of PhysicsBody type"); - } - ERR_FAIL_COND(!physics_body); + ERR_FAIL_COND_MSG(!physics_body, "Collision exception only works between two objects of PhysicsBody type."); Physics2DServer::get_singleton()->body_add_collision_exception(get_rid(), physics_body->get_rid()); } @@ -168,10 +165,7 @@ void PhysicsBody2D::remove_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); PhysicsBody2D *physics_body = Object::cast_to<PhysicsBody2D>(p_node); - if (!physics_body) { - ERR_EXPLAIN("Collision exception only works between two objects of PhysicsBody type"); - } - ERR_FAIL_COND(!physics_body); + ERR_FAIL_COND_MSG(!physics_body, "Collision exception only works between two objects of PhysicsBody type."); Physics2DServer::get_singleton()->body_remove_collision_exception(get_rid(), physics_body->get_rid()); } @@ -199,12 +193,11 @@ real_t StaticBody2D::get_constant_angular_velocity() const { #ifndef DISABLE_DEPRECATED void StaticBody2D::set_friction(real_t p_friction) { - if (p_friction == 1.0) { // default value, don't create an override for that + if (p_friction == 1.0 && physics_material_override.is_null()) { // default value, don't create an override for that return; } - ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_friction has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_friction < 0 || p_friction > 1); @@ -217,8 +210,7 @@ void StaticBody2D::set_friction(real_t p_friction) { real_t StaticBody2D::get_friction() const { - ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_friction has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 1; @@ -229,12 +221,11 @@ real_t StaticBody2D::get_friction() const { void StaticBody2D::set_bounce(real_t p_bounce) { - if (p_bounce == 0.0) { // default value, don't create an override for that + if (p_bounce == 0.0 && physics_material_override.is_null()) { // default value, don't create an override for that return; } - ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_bounce has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); @@ -247,8 +238,7 @@ void StaticBody2D::set_bounce(real_t p_bounce) { real_t StaticBody2D::get_bounce() const { - ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_bounce has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 0; @@ -604,7 +594,7 @@ real_t RigidBody2D::get_mass() const { void RigidBody2D::set_inertia(real_t p_inertia) { - ERR_FAIL_COND(p_inertia <= 0); + ERR_FAIL_COND(p_inertia < 0); Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_INERTIA, p_inertia); } @@ -626,12 +616,11 @@ real_t RigidBody2D::get_weight() const { #ifndef DISABLE_DEPRECATED void RigidBody2D::set_friction(real_t p_friction) { - if (p_friction == 1.0) { // default value, don't create an override for that + if (p_friction == 1.0 && physics_material_override.is_null()) { // default value, don't create an override for that return; } - ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_friction has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_friction < 0 || p_friction > 1); @@ -643,8 +632,7 @@ void RigidBody2D::set_friction(real_t p_friction) { } real_t RigidBody2D::get_friction() const { - ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_friction has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 1; @@ -655,12 +643,11 @@ real_t RigidBody2D::get_friction() const { void RigidBody2D::set_bounce(real_t p_bounce) { - if (p_bounce == 0.0) { // default value, don't create an override for that + if (p_bounce == 0.0 && physics_material_override.is_null()) { // default value, don't create an override for that return; } - ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_bounce has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); @@ -672,8 +659,7 @@ void RigidBody2D::set_bounce(real_t p_bounce) { } real_t RigidBody2D::get_bounce() const { - ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_bounce has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 0; @@ -905,10 +891,7 @@ void RigidBody2D::set_contact_monitor(bool p_enabled) { if (!p_enabled) { - if (contact_monitor->locked) { - ERR_EXPLAIN("Can't disable contact monitoring during in/out callback. Use call_deferred(\"set_contact_monitor\",false) instead"); - } - ERR_FAIL_COND(contact_monitor->locked); + ERR_FAIL_COND_MSG(contact_monitor->locked, "Can't disable contact monitoring during in/out callback. Use call_deferred(\"set_contact_monitor\", false) instead."); for (Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.front(); E; E = E->next()) { @@ -963,7 +946,7 @@ String RigidBody2D::get_configuration_warning() const { if ((get_mode() == MODE_RIGID || get_mode() == MODE_CHARACTER) && (ABS(t.elements[0].length() - 1.0) > 0.05 || ABS(t.elements[1].length() - 1.0) > 0.05)) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } warning += TTR("Size changes to RigidBody2D (in character or rigid modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead."); } @@ -1074,10 +1057,10 @@ void RigidBody2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_sleep"), "set_can_sleep", "is_able_to_sleep"); ADD_GROUP("Linear", "linear_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "-1,128,0.01"), "set_linear_damp", "get_linear_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); ADD_GROUP("Angular", "angular_"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "-1,128,0.01"), "set_angular_damp", "get_angular_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); ADD_GROUP("Applied Forces", "applied_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "applied_force"), "set_applied_force", "get_applied_force"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "applied_torque"), "set_applied_torque", "get_applied_torque"); @@ -1198,6 +1181,9 @@ bool KinematicBody2D::separate_raycast_shapes(bool p_infinite_inertia, Collision bool KinematicBody2D::move_and_collide(const Vector2 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes, bool p_test_only) { + if (sync_to_physics) { + ERR_PRINT("Functions move_and_slide and move_and_collide do not work together with 'sync to physics' option. Please read the documentation."); + } Transform2D gt = get_global_transform(); Physics2DServer::MotionResult result; bool colliding = Physics2DServer::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, margin, &result, p_exclude_raycast_shapes); @@ -1272,9 +1258,6 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const if (collided) { found_collision = true; - } - - if (collided) { colliders.push_back(collision); motion = collision.remainder; @@ -1283,14 +1266,14 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const //all is a wall on_wall = true; } else { - if (collision.normal.dot(p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //floor + if (Math::acos(collision.normal.dot(p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; on_floor_body = collision.collider_rid; floor_velocity = collision.collider_vel; if (p_stop_on_slope) { - if (Vector2() == lv_n + p_floor_direction && collision.travel.length() < 1) { + if ((lv_n + p_floor_direction).length() < 0.01 && collision.travel.length() < 1) { Transform2D gt = get_global_transform(); gt.elements[2] -= collision.travel.project(p_floor_direction.tangent()); set_global_transform(gt); @@ -1298,7 +1281,7 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const } } - } else if (collision.normal.dot(-p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //ceiling + } else if (Math::acos(collision.normal.dot(-p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; @@ -1309,9 +1292,6 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const motion = motion.slide(n); lv = lv.slide(n); } - - if (p_stop_on_slope) - break; } if (!found_collision) { @@ -1338,13 +1318,27 @@ Vector2 KinematicBody2D::move_and_slide_with_snap(const Vector2 &p_linear_veloci Transform2D gt = get_global_transform(); if (move_and_collide(p_snap, p_infinite_inertia, col, false, true)) { - gt.elements[2] += col.travel; - if (p_floor_direction != Vector2() && Math::acos(p_floor_direction.normalized().dot(col.normal)) < p_floor_max_angle) { - on_floor = true; - on_floor_body = col.collider_rid; - floor_velocity = col.collider_vel; + bool apply = true; + if (p_floor_direction != Vector2()) { + if (Math::acos(p_floor_direction.normalized().dot(col.normal)) < p_floor_max_angle) { + on_floor = true; + on_floor_body = col.collider_rid; + floor_velocity = col.collider_vel; + if (p_stop_on_slope) { + // move and collide may stray the object a bit because of pre un-stucking, + // so only ensure that motion happens on floor direction in this case. + col.travel = p_floor_direction * p_floor_direction.dot(col.travel); + } + + } else { + apply = false; + } + } + + if (apply) { + gt.elements[2] += col.travel; + set_global_transform(gt); } - set_global_transform(gt); } return ret; @@ -1530,7 +1524,7 @@ Vector2 KinematicCollision2D::get_remainder() const { return collision.remainder; } Object *KinematicCollision2D::get_local_shape() const { - ERR_FAIL_COND_V(!owner, NULL); + if (!owner) return NULL; uint32_t ownerid = owner->shape_find_owner(collision.local_shape); return owner->shape_owner_get_owner(ownerid); } diff --git a/scene/2d/physics_body_2d.h b/scene/2d/physics_body_2d.h index 89dd1e5341..66e5ce250f 100644 --- a/scene/2d/physics_body_2d.h +++ b/scene/2d/physics_body_2d.h @@ -157,8 +157,8 @@ private: bool operator<(const ShapePair &p_sp) const { if (body_shape == p_sp.body_shape) return local_shape < p_sp.local_shape; - else - return body_shape < p_sp.body_shape; + + return body_shape < p_sp.body_shape; } ShapePair() {} diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index 54b304f851..32a0b732c0 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -76,7 +76,7 @@ Rect2 Polygon2D::_edit_get_rect() const { } bool Polygon2D::_edit_use_rect() const { - return true; + return polygon.size() > 0; } bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { @@ -88,6 +88,10 @@ bool Polygon2D::_edit_is_selected_on_click(const Point2 &p_point, double p_toler return Geometry::is_point_in_polygon(p_point - get_offset(), polygon2d); } +void Polygon2D::_skeleton_bone_setup_changed() { + update(); +} + void Polygon2D::_notification(int p_what) { switch (p_what) { @@ -102,10 +106,27 @@ void Polygon2D::_notification(int p_what) { skeleton_node = Object::cast_to<Skeleton2D>(get_node(skeleton)); } - if (skeleton_node) + ObjectID new_skeleton_id = 0; + + if (skeleton_node) { VS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), skeleton_node->get_skeleton()); - else + new_skeleton_id = skeleton_node->get_instance_id(); + } else { VS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID()); + } + + if (new_skeleton_id != current_skeleton_id) { + Object *old_skeleton = ObjectDB::get_instance(current_skeleton_id); + if (old_skeleton) { + old_skeleton->disconnect("bone_setup_changed", this, "_skeleton_bone_setup_changed"); + } + + if (skeleton_node) { + skeleton_node->connect("bone_setup_changed", this, "_skeleton_bone_setup_changed"); + } + + current_skeleton_id = new_skeleton_id; + } Vector<Vector2> points; Vector<Vector2> uvs; @@ -286,7 +307,9 @@ void Polygon2D::_notification(int p_what) { if (invert || polygons.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()); + if (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()); + } } else { //draw individual polygons Vector<int> total_indices; @@ -809,6 +832,8 @@ void Polygon2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_bones", "bones"), &Polygon2D::_set_bones); ClassDB::bind_method(D_METHOD("_get_bones"), &Polygon2D::_get_bones); + ClassDB::bind_method(D_METHOD("_skeleton_bone_setup_changed"), &Polygon2D::_skeleton_bone_setup_changed); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "antialiased"), "set_antialiased", "get_antialiased"); @@ -846,4 +871,5 @@ Polygon2D::Polygon2D() { color = Color(1, 1, 1); rect_cache_dirty = true; internal_vertices = 0; + current_skeleton_id = 0; } diff --git a/scene/2d/polygon_2d.h b/scene/2d/polygon_2d.h index c1d6ebe46e..f25b3885b0 100644 --- a/scene/2d/polygon_2d.h +++ b/scene/2d/polygon_2d.h @@ -65,10 +65,13 @@ class Polygon2D : public Node2D { mutable Rect2 item_rect; NodePath skeleton; + ObjectID current_skeleton_id; Array _get_bones() const; void _set_bones(const Array &p_bones); + void _skeleton_bone_setup_changed(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/2d/position_2d.cpp b/scene/2d/position_2d.cpp index bed6f8a816..f0c46a5fb7 100644 --- a/scene/2d/position_2d.cpp +++ b/scene/2d/position_2d.cpp @@ -33,15 +33,19 @@ #include "core/engine.h" #include "scene/resources/texture.h" +const float DEFAULT_GIZMO_EXTENTS = 10.0; + void Position2D::_draw_cross() { - draw_line(Point2(-10, 0), Point2(+10, 0), Color(1, 0.5, 0.5)); - draw_line(Point2(0, -10), Point2(0, +10), Color(0.5, 1, 0.5)); + float extents = get_gizmo_extents(); + draw_line(Point2(-extents, 0), Point2(+extents, 0), Color(1, 0.5, 0.5)); + draw_line(Point2(0, -extents), Point2(0, +extents), Color(0.5, 1, 0.5)); } Rect2 Position2D::_edit_get_rect() const { - return Rect2(Point2(-10, -10), Size2(20, 20)); + float extents = get_gizmo_extents(); + return Rect2(Point2(-extents, -extents), Size2(extents * 2, extents * 2)); } bool Position2D::_edit_use_rect() const { @@ -66,5 +70,31 @@ void Position2D::_notification(int p_what) { } } +void Position2D::set_gizmo_extents(float p_extents) { + if (p_extents == DEFAULT_GIZMO_EXTENTS) { + set_meta("_gizmo_extents_", Variant()); + } else { + set_meta("_gizmo_extents_", p_extents); + } + + update(); +} + +float Position2D::get_gizmo_extents() const { + if (has_meta("_gizmo_extents_")) { + return get_meta("_gizmo_extents_"); + } else { + return DEFAULT_GIZMO_EXTENTS; + } +} + +void Position2D::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_set_gizmo_extents", "extents"), &Position2D::set_gizmo_extents); + ClassDB::bind_method(D_METHOD("_get_gizmo_extents"), &Position2D::get_gizmo_extents); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "gizmo_extents", PROPERTY_HINT_RANGE, "0,1000,0.1,or_greater", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_gizmo_extents", "_get_gizmo_extents"); +} + Position2D::Position2D() { } diff --git a/scene/2d/position_2d.h b/scene/2d/position_2d.h index c95315fea3..f5ec3ef01a 100644 --- a/scene/2d/position_2d.h +++ b/scene/2d/position_2d.h @@ -35,16 +35,21 @@ class Position2D : public Node2D { - GDCLASS(Position2D, Node2D) + GDCLASS(Position2D, Node2D); void _draw_cross(); protected: void _notification(int p_what); + static void _bind_methods(); public: virtual Rect2 _edit_get_rect() const; virtual bool _edit_use_rect() const; + + void set_gizmo_extents(float p_extents); + float get_gizmo_extents() const; + Position2D(); }; diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 57dfe5176d..bf8d008bb2 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -100,6 +100,7 @@ Vector2 RayCast2D::get_collision_normal() const { void RayCast2D::set_enabled(bool p_enabled) { enabled = p_enabled; + update(); if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) set_physics_process_internal(p_enabled); if (!p_enabled) @@ -167,19 +168,25 @@ void RayCast2D::_notification(int p_what) { xf.rotate(cast_to.angle()); xf.translate(Vector2(cast_to.length(), 0)); - //Vector2 tip = Vector2(0,s->get_length()); - Color dcol = get_tree()->get_debug_collisions_color(); //0.9,0.2,0.2,0.4); - draw_line(Vector2(), cast_to, dcol, 3); + // Draw an arrow indicating where the RayCast is pointing to + Color draw_col = get_tree()->get_debug_collisions_color(); + if (!enabled) { + float g = draw_col.get_v(); + draw_col.r = g; + draw_col.g = g; + draw_col.b = g; + } + draw_line(Vector2(), cast_to, draw_col, 2, true); Vector<Vector2> pts; - float tsize = 4; + float tsize = 8; pts.push_back(xf.xform(Vector2(tsize, 0))); pts.push_back(xf.xform(Vector2(0, 0.707 * tsize))); pts.push_back(xf.xform(Vector2(0, -0.707 * tsize))); Vector<Color> cols; for (int i = 0; i < 3; i++) - cols.push_back(dcol); + cols.push_back(draw_col); - draw_primitive(pts, cols, Vector<Vector2>()); //small arrow + draw_primitive(pts, cols, Vector<Vector2>()); } break; diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index 1c38a91877..fe8cc5a5fc 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -110,7 +110,7 @@ void RemoteTransform2D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { + case NOTIFICATION_ENTER_TREE: { _update_cache(); @@ -180,6 +180,10 @@ bool RemoteTransform2D::get_update_scale() const { return update_remote_scale; } +void RemoteTransform2D::force_update_cache() { + _update_cache(); +} + String RemoteTransform2D::get_configuration_warning() const { if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { @@ -193,6 +197,7 @@ void RemoteTransform2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_remote_node", "path"), &RemoteTransform2D::set_remote_node); ClassDB::bind_method(D_METHOD("get_remote_node"), &RemoteTransform2D::get_remote_node); + ClassDB::bind_method(D_METHOD("force_update_cache"), &RemoteTransform2D::force_update_cache); ClassDB::bind_method(D_METHOD("set_use_global_coordinates", "use_global_coordinates"), &RemoteTransform2D::set_use_global_coordinates); ClassDB::bind_method(D_METHOD("get_use_global_coordinates"), &RemoteTransform2D::get_use_global_coordinates); diff --git a/scene/2d/remote_transform_2d.h b/scene/2d/remote_transform_2d.h index 16a5417592..e85fe33fc6 100644 --- a/scene/2d/remote_transform_2d.h +++ b/scene/2d/remote_transform_2d.h @@ -69,6 +69,8 @@ public: void set_update_scale(const bool p_update); bool get_update_scale() const; + void force_update_cache(); + virtual String get_configuration_warning() const; RemoteTransform2D(); diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 2a674e64e6..bf43fca864 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -137,7 +137,7 @@ String Bone2D::get_configuration_warning() const { String warning = Node2D::get_configuration_warning(); if (!skeleton) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } if (parent_bone) { warning += TTR("This Bone2D chain should end at a Skeleton2D node."); @@ -148,7 +148,7 @@ String Bone2D::get_configuration_warning() const { if (rest == Transform2D(0, 0, 0, 0, 0, 0)) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."); } @@ -203,6 +203,7 @@ void Skeleton2D::_update_bone_setup() { transform_dirty = true; _update_transform(); + emit_signal("bone_setup_changed"); } void Skeleton2D::_make_transform_dirty() { @@ -291,6 +292,8 @@ void Skeleton2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone", "idx"), &Skeleton2D::get_bone); ClassDB::bind_method(D_METHOD("get_skeleton"), &Skeleton2D::get_skeleton); + + ADD_SIGNAL(MethodInfo("bone_setup_changed")); } Skeleton2D::Skeleton2D() { diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index cf9877e6f8..0f48b44387 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -36,9 +36,12 @@ class Skeleton2D; class Bone2D : public Node2D { - GDCLASS(Bone2D, Node2D) + GDCLASS(Bone2D, Node2D); friend class Skeleton2D; +#ifdef TOOLS_ENABLED + friend class AnimatedValuesBackup; +#endif Bone2D *parent_bone; Skeleton2D *skeleton; @@ -71,6 +74,9 @@ class Skeleton2D : public Node2D { GDCLASS(Skeleton2D, Node2D); friend class Bone2D; +#ifdef TOOLS_ENABLED + friend class AnimatedValuesBackup; +#endif struct Bone { bool operator<(const Bone &p_bone) const { diff --git a/scene/2d/sprite.cpp b/scene/2d/sprite.cpp index ba103a8bf0..d7a8005187 100644 --- a/scene/2d/sprite.cpp +++ b/scene/2d/sprite.cpp @@ -63,10 +63,7 @@ Rect2 Sprite::_edit_get_rect() const { } bool Sprite::_edit_use_rect() const { - if (texture.is_null()) - return false; - - return true; + return texture.is_valid(); } Rect2 Sprite::get_anchorable_rect() const { @@ -262,6 +259,7 @@ void Sprite::set_frame(int p_frame) { frame = p_frame; _change_notify("frame"); + _change_notify("frame_coords"); emit_signal(SceneStringNames::get_singleton()->frame_changed); } @@ -270,6 +268,17 @@ int Sprite::get_frame() const { return frame; } +void Sprite::set_frame_coords(const Vector2 &p_coord) { + ERR_FAIL_INDEX(int(p_coord.x), vframes); + ERR_FAIL_INDEX(int(p_coord.y), hframes); + + set_frame(int(p_coord.y) * hframes + int(p_coord.x)); +} + +Vector2 Sprite::get_frame_coords() const { + return Vector2(frame % hframes, frame / hframes); +} + void Sprite::set_vframes(int p_amount) { ERR_FAIL_COND(p_amount < 1); @@ -363,7 +372,7 @@ Rect2 Sprite::get_rect() const { Point2 ofs = offset; if (centered) - ofs -= s / 2; + ofs -= Size2(s) / 2; if (s == Size2(0, 0)) s = Size2(1, 1); @@ -374,10 +383,9 @@ Rect2 Sprite::get_rect() const { void Sprite::_validate_property(PropertyInfo &property) const { if (property.name == "frame") { - - property.hint = PROPERTY_HINT_SPRITE_FRAME; - + property.hint = PROPERTY_HINT_RANGE; property.hint_string = "0," + itos(vframes * hframes - 1) + ",1"; + property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } } @@ -424,6 +432,9 @@ void Sprite::_bind_methods() { ClassDB::bind_method(D_METHOD("set_frame", "frame"), &Sprite::set_frame); ClassDB::bind_method(D_METHOD("get_frame"), &Sprite::get_frame); + ClassDB::bind_method(D_METHOD("set_frame_coords", "coords"), &Sprite::set_frame_coords); + ClassDB::bind_method(D_METHOD("get_frame_coords"), &Sprite::get_frame_coords); + ClassDB::bind_method(D_METHOD("set_vframes", "vframes"), &Sprite::set_vframes); ClassDB::bind_method(D_METHOD("get_vframes"), &Sprite::get_vframes); @@ -445,7 +456,8 @@ void Sprite::_bind_methods() { ADD_GROUP("Animation", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "frame", PROPERTY_HINT_SPRITE_FRAME), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region", "is_region"); diff --git a/scene/2d/sprite.h b/scene/2d/sprite.h index e38db3a299..5e6717a3f5 100644 --- a/scene/2d/sprite.h +++ b/scene/2d/sprite.h @@ -110,6 +110,9 @@ public: void set_frame(int p_frame); int get_frame() const; + void set_frame_coords(const Vector2 &p_coord); + Vector2 get_frame_coords() const; + void set_vframes(int p_amount); int get_vframes() const; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 44730062c4..2cd05b5c50 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -30,9 +30,11 @@ #include "tile_map.h" +#include "collision_object_2d.h" #include "core/io/marshalls.h" #include "core/method_bind_ext.gen.inc" #include "core/os/os.h" +#include "scene/2d/area_2d.h" #include "servers/physics_2d_server.h" int TileMap::_get_quadrant_size() const { @@ -60,14 +62,21 @@ void TileMap::_notification(int p_what) { c = Object::cast_to<Node2D>(c->get_parent()); } + if (use_parent) { + _clear_quadrants(); + collision_parent = Object::cast_to<CollisionObject2D>(get_parent()); + } + pending_update = true; _recreate_quadrants(); update_dirty_quadrants(); RID space = get_world_2d()->get_space(); _update_quadrant_transform(); _update_quadrant_space(space); + update_configuration_warning(); } break; + case NOTIFICATION_EXIT_TREE: { _update_quadrant_space(RID()); @@ -75,37 +84,53 @@ void TileMap::_notification(int p_what) { Quadrant &q = E->get(); if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *E = q.navpoly_ids.front(); E; E = E->next()) { + for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) { - navigation->navpoly_remove(E->get().id); + navigation->navpoly_remove(F->get().id); } q.navpoly_ids.clear(); } - for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) { - VS::get_singleton()->free(E->get().id); + if (collision_parent) { + collision_parent->remove_shape_owner(q.shape_owner_id); + q.shape_owner_id = -1; + } + + for (Map<PosKey, Quadrant::Occluder>::Element *F = q.occluder_instances.front(); F; F = F->next()) { + VS::get_singleton()->free(F->get().id); } q.occluder_instances.clear(); } + collision_parent = NULL; navigation = NULL; } break; + case NOTIFICATION_TRANSFORM_CHANGED: { //move stuff _update_quadrant_transform(); } break; + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + + if (use_parent) { + _recreate_quadrants(); + } + + } break; } } void TileMap::_update_quadrant_space(const RID &p_space) { - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + if (!use_parent) { + for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Quadrant &q = E->get(); - Physics2DServer::get_singleton()->body_set_space(q.body, p_space); + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_space(q.body, p_space); + } } } @@ -116,6 +141,10 @@ void TileMap::_update_quadrant_transform() { Transform2D global_transform = get_global_transform(); + Transform2D local_transform; + if (collision_parent) + local_transform = get_transform(); + Transform2D nav_rel; if (navigation) nav_rel = get_relative_transform_to_parent(navigation); @@ -125,18 +154,21 @@ void TileMap::_update_quadrant_transform() { Quadrant &q = E->get(); Transform2D xform; xform.set_origin(q.pos); - xform = global_transform * xform; - Physics2DServer::get_singleton()->body_set_state(q.body, Physics2DServer::BODY_STATE_TRANSFORM, xform); + + if (!use_parent) { + xform = global_transform * xform; + Physics2DServer::get_singleton()->body_set_state(q.body, Physics2DServer::BODY_STATE_TRANSFORM, xform); + } if (navigation) { - for (Map<PosKey, Quadrant::NavPoly>::Element *E = q.navpoly_ids.front(); E; E = E->next()) { + for (Map<PosKey, Quadrant::NavPoly>::Element *F = q.navpoly_ids.front(); F; F = F->next()) { - navigation->navpoly_set_transform(E->get().id, nav_rel * E->get().xform); + navigation->navpoly_set_transform(F->get().id, nav_rel * F->get().xform); } } - for (Map<PosKey, Quadrant::Occluder>::Element *E = q.occluder_instances.front(); E; E = E->next()) { - VS::get_singleton()->canvas_light_occluder_set_transform(E->get().id, global_transform * E->get().xform); + for (Map<PosKey, Quadrant::Occluder>::Element *F = q.occluder_instances.front(); F; F = F->next()) { + VS::get_singleton()->canvas_light_occluder_set_transform(F->get().id, global_transform * F->get().xform); } } } @@ -202,18 +234,23 @@ void TileMap::_fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Size2 s = p_sc; Vector2 offset = p_offset; - if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) - offset.y += cell_size.y; - else if (tile_origin == TILE_ORIGIN_CENTER) { - offset += cell_size / 2; - } + if (compatibility_mode && !centered_textures) { - if (s.y > s.x) { - if ((p_cell.flip_h && (p_cell.flip_v || p_cell.transpose)) || (p_cell.flip_v && !p_cell.transpose)) - offset.y += s.y - s.x; - } else if (s.y < s.x) { - if ((p_cell.flip_v && (p_cell.flip_h || p_cell.transpose)) || (p_cell.flip_h && !p_cell.transpose)) - offset.x += s.x - s.y; + if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + offset.y += cell_size.y; + } else if (tile_origin == TILE_ORIGIN_CENTER) { + offset += cell_size / 2; + } + + if (s.y > s.x) { + if ((p_cell.flip_h && (p_cell.flip_v || p_cell.transpose)) || (p_cell.flip_v && !p_cell.transpose)) { + offset.y += s.y - s.x; + } + } else if (s.y < s.x) { + if ((p_cell.flip_v && (p_cell.flip_h || p_cell.transpose)) || (p_cell.flip_h && !p_cell.transpose)) { + offset.x += s.x - s.y; + } + } } if (p_cell.transpose) { @@ -222,27 +259,69 @@ void TileMap::_fix_cell_transform(Transform2D &xform, const Cell &p_cell, const SWAP(offset.x, offset.y); SWAP(s.x, s.y); } + if (p_cell.flip_h) { xform.elements[0].x = -xform.elements[0].x; xform.elements[1].x = -xform.elements[1].x; - if (tile_origin == TILE_ORIGIN_TOP_LEFT || tile_origin == TILE_ORIGIN_BOTTOM_LEFT) + if (compatibility_mode && !centered_textures) { + if (tile_origin == TILE_ORIGIN_TOP_LEFT || tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + offset.x = s.x - offset.x; + } else if (tile_origin == TILE_ORIGIN_CENTER) { + offset.x = s.x - offset.x / 2; + } + } else { offset.x = s.x - offset.x; - else if (tile_origin == TILE_ORIGIN_CENTER) - offset.x = s.x - offset.x / 2; + } } + if (p_cell.flip_v) { xform.elements[0].y = -xform.elements[0].y; xform.elements[1].y = -xform.elements[1].y; - if (tile_origin == TILE_ORIGIN_TOP_LEFT) + if (compatibility_mode && !centered_textures) { + if (tile_origin == TILE_ORIGIN_TOP_LEFT) { + offset.y = s.y - offset.y; + } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + offset.y += s.y; + } else if (tile_origin == TILE_ORIGIN_CENTER) { + offset.y += s.y; + } + } else { offset.y = s.y - offset.y; - else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { - offset.y += s.y; - } else if (tile_origin == TILE_ORIGIN_CENTER) { - offset.y += s.y; } } - xform.elements[2].x += offset.x; - xform.elements[2].y += offset.y; + + if (centered_textures) { + offset += cell_size / 2 - s / 2; + } + xform.elements[2] += offset; +} + +void TileMap::_add_shape(int &shape_idx, const Quadrant &p_q, const Ref<Shape2D> &p_shape, const TileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata) { + Physics2DServer *ps = Physics2DServer::get_singleton(); + + if (!use_parent) { + ps->body_add_shape(p_q.body, p_shape->get_rid(), p_xform); + ps->body_set_shape_metadata(p_q.body, shape_idx, p_metadata); + ps->body_set_shape_as_one_way_collision(p_q.body, shape_idx, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin); + + } else if (collision_parent) { + Transform2D xform = p_xform; + xform.set_origin(xform.get_origin() + p_q.pos); + + collision_parent->shape_owner_add_shape(p_q.shape_owner_id, p_shape); + + int real_index = collision_parent->shape_owner_get_shape_index(p_q.shape_owner_id, shape_idx); + RID rid = collision_parent->get_rid(); + + if (Object::cast_to<Area2D>(collision_parent) != NULL) { + ps->area_set_shape_transform(rid, real_index, get_transform() * xform); + } else { + ps->body_set_shape_transform(rid, real_index, get_transform() * xform); + ps->body_set_shape_metadata(rid, real_index, p_metadata); + ps->body_set_shape_as_one_way_collision(rid, real_index, p_shape_data.one_way_collision, p_shape_data.one_way_collision_margin); + } + } + shape_idx++; } void TileMap::update_dirty_quadrants() { @@ -288,7 +367,11 @@ void TileMap::update_dirty_quadrants() { q.canvas_items.clear(); - ps->body_clear_shapes(q.body); + if (!use_parent) { + ps->body_clear_shapes(q.body); + } else if (collision_parent) { + collision_parent->shape_owner_clear_shapes(q.shape_owner_id); + } int shape_idx = 0; if (navigation) { @@ -390,63 +473,72 @@ void TileMap::update_dirty_quadrants() { rect.size.x += fp_adjust; rect.size.y += fp_adjust; - if (rect.size.y > rect.size.x) { - if ((c.flip_h && (c.flip_v || c.transpose)) || (c.flip_v && !c.transpose)) - tile_ofs.y += rect.size.y - rect.size.x; - } else if (rect.size.y < rect.size.x) { - if ((c.flip_v && (c.flip_h || c.transpose)) || (c.flip_h && !c.transpose)) - tile_ofs.x += rect.size.x - rect.size.y; + if (compatibility_mode && !centered_textures) { + if (rect.size.y > rect.size.x) { + if ((c.flip_h && (c.flip_v || c.transpose)) || (c.flip_v && !c.transpose)) + tile_ofs.y += rect.size.y - rect.size.x; + } else if (rect.size.y < rect.size.x) { + if ((c.flip_v && (c.flip_h || c.transpose)) || (c.flip_h && !c.transpose)) + tile_ofs.x += rect.size.x - rect.size.y; + } } - /* rect.size.x+=fp_adjust; - rect.size.y+=fp_adjust;*/ - - if (c.transpose) + if (c.transpose) { SWAP(tile_ofs.x, tile_ofs.y); + if (centered_textures) { + rect.position.x += cell_size.x / 2 - rect.size.y / 2; + rect.position.y += cell_size.y / 2 - rect.size.x / 2; + } + } else if (centered_textures) { + rect.position += cell_size / 2 - rect.size / 2; + } if (c.flip_h) { rect.size.x = -rect.size.x; tile_ofs.x = -tile_ofs.x; } + if (c.flip_v) { rect.size.y = -rect.size.y; tile_ofs.y = -tile_ofs.y; } - Vector2 center_ofs; + if (compatibility_mode && !centered_textures) { + if (tile_origin == TILE_ORIGIN_TOP_LEFT) { + rect.position += tile_ofs; - if (tile_origin == TILE_ORIGIN_TOP_LEFT) { - rect.position += tile_ofs; + } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { - } else if (tile_origin == TILE_ORIGIN_BOTTOM_LEFT) { + rect.position += tile_ofs; - rect.position += tile_ofs; + if (c.transpose) { + if (c.flip_h) + rect.position.x -= cell_size.x; + else + rect.position.x += cell_size.x; + } else { + if (c.flip_v) + rect.position.y -= cell_size.y; + else + rect.position.y += cell_size.y; + } + + } else if (tile_origin == TILE_ORIGIN_CENTER) { + + rect.position += tile_ofs; - if (c.transpose) { if (c.flip_h) - rect.position.x -= cell_size.x; + rect.position.x -= cell_size.x / 2; else - rect.position.x += cell_size.x; - } else { + rect.position.x += cell_size.x / 2; + if (c.flip_v) - rect.position.y -= cell_size.y; + rect.position.y -= cell_size.y / 2; else - rect.position.y += cell_size.y; + rect.position.y += cell_size.y / 2; } - - } else if (tile_origin == TILE_ORIGIN_CENTER) { - + } else { rect.position += tile_ofs; - - if (c.flip_h) - rect.position.x -= cell_size.x / 2; - else - rect.position.x += cell_size.x / 2; - - if (c.flip_v) - rect.position.y -= cell_size.y / 2; - else - rect.position.y += cell_size.y / 2; } Ref<Texture> normal_map = tile_set->tile_get_normal_map(c.id); @@ -462,27 +554,39 @@ void TileMap::update_dirty_quadrants() { Vector<TileSet::ShapeData> shapes = tile_set->tile_get_shapes(c.id); - for (int i = 0; i < shapes.size(); i++) { - Ref<Shape2D> shape = shapes[i].shape; + for (int j = 0; j < shapes.size(); j++) { + Ref<Shape2D> shape = shapes[j].shape; if (shape.is_valid()) { - if (tile_set->tile_get_tile_mode(c.id) == TileSet::SINGLE_TILE || (shapes[i].autotile_coord.x == c.autotile_coord_x && shapes[i].autotile_coord.y == c.autotile_coord_y)) { + if (tile_set->tile_get_tile_mode(c.id) == TileSet::SINGLE_TILE || (shapes[j].autotile_coord.x == c.autotile_coord_x && shapes[j].autotile_coord.y == c.autotile_coord_y)) { Transform2D xform; xform.set_origin(offset.floor()); - Vector2 shape_ofs = shapes[i].shape_transform.get_origin(); + Vector2 shape_ofs = shapes[j].shape_transform.get_origin(); - _fix_cell_transform(xform, c, shape_ofs + center_ofs, s); + _fix_cell_transform(xform, c, shape_ofs, s); - xform *= shapes[i].shape_transform.untranslated(); + xform *= shapes[j].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); } - ps->body_add_shape(q.body, shape->get_rid(), xform); - ps->body_set_shape_metadata(q.body, shape_idx, Vector2(E->key().x, E->key().y)); - ps->body_set_shape_as_one_way_collision(q.body, shape_idx, shapes[i].one_way_collision, shapes[i].one_way_collision_margin); - shape_idx++; + + if (shape->has_meta("decomposed")) { + Array _shapes = shape->get_meta("decomposed"); + for (int k = 0; k < _shapes.size(); k++) { + Ref<ConvexPolygonShape2D> convex = _shapes[k]; + if (convex.is_valid()) { + _add_shape(shape_idx, q, convex, shapes[j], xform, Vector2(E->key().x, E->key().y)); +#ifdef DEBUG_ENABLED + } else { + print_error("The TileSet assigned to the TileMap " + get_name() + " has an invalid convex shape."); +#endif + } + } + } else { + _add_shape(shape_idx, q, shape, shapes[j], xform, Vector2(E->key().x, E->key().y)); + } } } } @@ -505,7 +609,7 @@ void TileMap::update_dirty_quadrants() { if (navpoly.is_valid()) { Transform2D xform; xform.set_origin(offset.floor() + q.pos); - _fix_cell_transform(xform, c, npoly_ofs + center_ofs, s); + _fix_cell_transform(xform, c, npoly_ofs, s); int pid = navigation->navpoly_add(navpoly, nav_rel * xform); @@ -531,23 +635,23 @@ void TileMap::update_dirty_quadrants() { colors.resize(vsize); { PoolVector<Vector2>::Read vr = navigation_polygon_vertices.read(); - for (int i = 0; i < vsize; i++) { - vertices.write[i] = vr[i]; - colors.write[i] = debug_navigation_color; + for (int j = 0; j < vsize; j++) { + vertices.write[j] = vr[j]; + colors.write[j] = 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 = 0; j < navpoly->get_polygon_count(); j++) { + Vector<int> polygon = navpoly->get_polygon(j); - for (int j = 2; j < polygon.size(); j++) { + for (int k = 2; k < polygon.size(); k++) { - int kofs[3] = { 0, j - 1, j }; - for (int k = 0; k < 3; k++) { + int kofs[3] = { 0, k - 1, k }; + for (int l = 0; l < 3; l++) { - int idx = polygon[kofs[k]]; + int idx = polygon[kofs[l]]; ERR_FAIL_INDEX(idx, vsize); indices.push_back(idx); } @@ -555,7 +659,7 @@ void TileMap::update_dirty_quadrants() { } Transform2D navxform; navxform.set_origin(offset.floor()); - _fix_cell_transform(navxform, c, npoly_ofs + center_ofs, s); + _fix_cell_transform(navxform, c, npoly_ofs, s); vs->canvas_item_set_transform(debug_navigation_item, navxform); vs->canvas_item_add_triangle_array(debug_navigation_item, indices, vertices, colors); @@ -575,7 +679,7 @@ void TileMap::update_dirty_quadrants() { Vector2 occluder_ofs = tile_set->tile_get_occluder_offset(c.id); Transform2D xform; xform.set_origin(offset.floor() + q.pos); - _fix_cell_transform(xform, c, occluder_ofs + center_ofs, s); + _fix_cell_transform(xform, c, occluder_ofs, s); RID orid = VS::get_singleton()->canvas_light_occluder_create(); VS::get_singleton()->canvas_light_occluder_set_transform(orid, get_global_transform() * xform); @@ -601,9 +705,9 @@ void TileMap::update_dirty_quadrants() { for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { Quadrant &q = E->get(); - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { + for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { - VS::get_singleton()->canvas_item_set_draw_index(E->get(), index++); + VS::get_singleton()->canvas_item_set_draw_index(F->get(), index++); } } @@ -656,22 +760,29 @@ Map<TileMap::PosKey, TileMap::Quadrant>::Element *TileMap::_create_quadrant(cons xform.set_origin(q.pos); //q.canvas_item = VisualServer::get_singleton()->canvas_item_create(); - q.body = Physics2DServer::get_singleton()->body_create(); - Physics2DServer::get_singleton()->body_set_mode(q.body, use_kinematic ? Physics2DServer::BODY_MODE_KINEMATIC : Physics2DServer::BODY_MODE_STATIC); - - Physics2DServer::get_singleton()->body_attach_object_instance_id(q.body, get_instance_id()); - Physics2DServer::get_singleton()->body_set_collision_layer(q.body, collision_layer); - Physics2DServer::get_singleton()->body_set_collision_mask(q.body, collision_mask); - Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_FRICTION, friction); - Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_BOUNCE, bounce); - - if (is_inside_tree()) { - xform = get_global_transform() * xform; - RID space = get_world_2d()->get_space(); - Physics2DServer::get_singleton()->body_set_space(q.body, space); - } + if (!use_parent) { + q.body = Physics2DServer::get_singleton()->body_create(); + Physics2DServer::get_singleton()->body_set_mode(q.body, use_kinematic ? Physics2DServer::BODY_MODE_KINEMATIC : Physics2DServer::BODY_MODE_STATIC); + + Physics2DServer::get_singleton()->body_attach_object_instance_id(q.body, get_instance_id()); + Physics2DServer::get_singleton()->body_set_collision_layer(q.body, collision_layer); + Physics2DServer::get_singleton()->body_set_collision_mask(q.body, collision_mask); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_FRICTION, friction); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_BOUNCE, bounce); - Physics2DServer::get_singleton()->body_set_state(q.body, Physics2DServer::BODY_STATE_TRANSFORM, xform); + if (is_inside_tree()) { + xform = get_global_transform() * xform; + RID space = get_world_2d()->get_space(); + Physics2DServer::get_singleton()->body_set_space(q.body, space); + } + + Physics2DServer::get_singleton()->body_set_state(q.body, Physics2DServer::BODY_STATE_TRANSFORM, xform); + } else if (collision_parent) { + xform = get_transform() * xform; + q.shape_owner_id = collision_parent->create_shape_owner(this); + } else { + q.shape_owner_id = -1; + } rect_cache_dirty = true; quadrant_order_dirty = true; @@ -681,7 +792,12 @@ Map<TileMap::PosKey, TileMap::Quadrant>::Element *TileMap::_create_quadrant(cons void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) { Quadrant &q = Q->get(); - Physics2DServer::get_singleton()->free(q.body); + if (!use_parent) { + Physics2DServer::get_singleton()->free(q.body); + } else if (collision_parent) { + collision_parent->remove_shape_owner(q.shape_owner_id); + } + for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { VisualServer::get_singleton()->free(E->get()); @@ -731,7 +847,10 @@ void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_ 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"]); + Variant v_pos_x = p_pos.x, v_pos_y = p_pos.y, v_tile = p_data["id"], v_flip_h = p_data["flip_h"], v_flip_v = p_data["flip_y"], v_transpose = p_data["transpose"], v_autotile_coord = p_data["auto_coord"]; + const Variant *args[7] = { &v_pos_x, &v_pos_y, &v_tile, &v_flip_h, &v_flip_v, &v_transpose, &v_autotile_coord }; + Variant::CallError ce; + call("set_cell", args, 7, ce); } 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) { @@ -742,7 +861,7 @@ void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_ if (!E && p_tile == INVALID_CELL) return; //nothing to do - PosKey qk(p_x / _get_quadrant_size(), p_y / _get_quadrant_size()); + PosKey qk = pk.to_quadrant(_get_quadrant_size()); if (p_tile == INVALID_CELL) { //erase existing tile_map.erase(pk); @@ -755,6 +874,7 @@ void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_ else _make_quadrant_dirty(Q); + used_size_cache_dirty = true; return; } @@ -900,13 +1020,22 @@ void TileMap::update_cell_bitmask(int p_x, int p_y) { E->get().autotile_coord_x = (int)coord.x; E->get().autotile_coord_y = (int)coord.y; - PosKey qk(p_x / _get_quadrant_size(), p_y / _get_quadrant_size()); + PosKey qk = p.to_quadrant(_get_quadrant_size()); Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); _make_quadrant_dirty(Q); } else if (tile_set->tile_get_tile_mode(id) == TileSet::SINGLE_TILE) { + E->get().autotile_coord_x = 0; E->get().autotile_coord_y = 0; + } else if (tile_set->tile_get_tile_mode(id) == TileSet::ATLAS_TILE) { + + if (tile_set->autotile_get_bitmask(id, Vector2(p_x, p_y)) == TileSet::BIND_CENTER) { + Vector2 coord = tile_set->atlastile_get_subtile_by_priority(id, this, Vector2(p_x, p_y)); + + E->get().autotile_coord_x = (int)coord.x; + E->get().autotile_coord_y = (int)coord.y; + } } } } @@ -988,7 +1117,7 @@ void TileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord) c.autotile_coord_y = p_coord.y; tile_map[pk] = c; - PosKey qk(p_x / _get_quadrant_size(), p_y / _get_quadrant_size()); + PosKey qk = pk.to_quadrant(_get_quadrant_size()); Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); if (!Q) @@ -1015,7 +1144,7 @@ void TileMap::_recreate_quadrants() { for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { - PosKey qk(E->key().x / _get_quadrant_size(), E->key().y / _get_quadrant_size()); + PosKey qk = PosKey(E->key().x, E->key().y).to_quadrant(_get_quadrant_size()); Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); if (!Q) { @@ -1053,9 +1182,9 @@ void TileMap::_update_all_items_material_state() { for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { Quadrant &q = E->get(); - for (List<RID>::Element *E = q.canvas_items.front(); E; E = E->next()) { + for (List<RID>::Element *F = q.canvas_items.front(); F; F = F->next()) { - _update_item_material_state(E->get()); + _update_item_material_state(F->get()); } } } @@ -1074,6 +1203,8 @@ void TileMap::clear() { void TileMap::_set_tile_data(const PoolVector<int> &p_data) { + ERR_FAIL_COND(format > FORMAT_2); + int c = p_data.size(); PoolVector<int>::Read r = p_data.read(); @@ -1113,14 +1244,9 @@ void TileMap::_set_tile_data(const PoolVector<int> &p_data) { coord_x = decode_uint16(&local[8]); coord_y = decode_uint16(&local[10]); } - /* - if (x<-20 || y <-20 || x>4000 || y>4000) - continue; - */ + set_cell(x, y, v, flip_h, flip_v, transpose, Vector2(coord_x, coord_y)); } - - format = FORMAT_2; } PoolVector<int> TileMap::_get_tile_data() const { @@ -1129,7 +1255,7 @@ PoolVector<int> TileMap::_get_tile_data() const { data.resize(tile_map.size() * 3); PoolVector<int>::Write w = data.write(); - format = FORMAT_2; + // Save in highest format int idx = 0; for (const Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { @@ -1149,33 +1275,41 @@ PoolVector<int> TileMap::_get_tile_data() const { idx += 3; } - w = PoolVector<int>::Write(); + w.release(); return data; } Rect2 TileMap::_edit_get_rect() const { - const_cast<TileMap *>(this)->update_dirty_quadrants(); + if (pending_update) { + const_cast<TileMap *>(this)->update_dirty_quadrants(); + } else { + const_cast<TileMap *>(this)->_recompute_rect_cache(); + } return rect_cache; } void TileMap::set_collision_layer(uint32_t p_layer) { collision_layer = p_layer; - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + if (!use_parent) { + for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Quadrant &q = E->get(); - Physics2DServer::get_singleton()->body_set_collision_layer(q.body, collision_layer); + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_collision_layer(q.body, collision_layer); + } } } void TileMap::set_collision_mask(uint32_t p_mask) { collision_mask = p_mask; - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + if (!use_parent) { + for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Quadrant &q = E->get(); - Physics2DServer::get_singleton()->body_set_collision_mask(q.body, collision_mask); + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_collision_mask(q.body, collision_mask); + } } } @@ -1211,13 +1345,40 @@ void TileMap::set_collision_use_kinematic(bool p_use_kinematic) { _recreate_quadrants(); } +bool TileMap::get_collision_use_parent() const { + + return use_parent; +} + +void TileMap::set_collision_use_parent(bool p_use_parent) { + + if (use_parent == p_use_parent) return; + + _clear_quadrants(); + + use_parent = p_use_parent; + set_notify_local_transform(use_parent); + + if (use_parent && is_inside_tree()) { + collision_parent = Object::cast_to<CollisionObject2D>(get_parent()); + } else { + collision_parent = NULL; + } + + _recreate_quadrants(); + _change_notify(); + update_configuration_warning(); +} + void TileMap::set_collision_friction(float p_friction) { friction = p_friction; - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + if (!use_parent) { + for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Quadrant &q = E->get(); - Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_FRICTION, p_friction); + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_FRICTION, p_friction); + } } } @@ -1229,10 +1390,12 @@ float TileMap::get_collision_friction() const { void TileMap::set_collision_bounce(float p_bounce) { bounce = p_bounce; - for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { + if (!use_parent) { + for (Map<PosKey, Quadrant>::Element *E = quadrant_map.front(); E; E = E->next()) { - Quadrant &q = E->get(); - Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_BOUNCE, p_bounce); + Quadrant &q = E->get(); + Physics2DServer::get_singleton()->body_set_param(q.body, Physics2DServer::BODY_PARAM_BOUNCE, p_bounce); + } } } float TileMap::get_collision_bounce() const { @@ -1373,18 +1536,21 @@ Vector2 TileMap::_map_to_world(int p_x, int p_y, bool p_ignore_ofs) const { if (!p_ignore_ofs) { switch (half_offset) { - case HALF_OFFSET_X: { + case HALF_OFFSET_X: + case HALF_OFFSET_NEGATIVE_X: { if (ABS(p_y) & 1) { - ret += get_cell_transform()[0] * 0.5; + ret += get_cell_transform()[0] * (half_offset == HALF_OFFSET_X ? 0.5 : -0.5); } } break; - case HALF_OFFSET_Y: { + case HALF_OFFSET_Y: + case HALF_OFFSET_NEGATIVE_Y: { if (ABS(p_x) & 1) { - ret += get_cell_transform()[1] * 0.5; + ret += get_cell_transform()[1] * (half_offset == HALF_OFFSET_Y ? 0.5 : -0.5); } } break; - default: {} + default: { + } } } return ret; @@ -1394,7 +1560,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { if (p_name == "format") { if (p_value.get_type() == Variant::INT) { - format = (DataFormat)(p_value.operator int64_t()); + format = (DataFormat)(p_value.operator int64_t()); // Set format used for loading return true; } } else if (p_name == "tile_data") { @@ -1410,7 +1576,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == "format") { - r_ret = format; + r_ret = FORMAT_2; // When saving, always save highest format return true; } else if (p_name == "tile_data") { r_ret = _get_tile_data(); @@ -1428,6 +1594,12 @@ void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(p); } +void TileMap::_validate_property(PropertyInfo &property) const { + if (use_parent && property.name != "collision_use_parent" && property.name.begins_with("collision_")) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } +} + Vector2 TileMap::map_to_world(const Vector2 &p_pos, bool p_ignore_ofs) const { return _map_to_world(p_pos.x, p_pos.y, p_ignore_ofs); @@ -1444,16 +1616,27 @@ Vector2 TileMap::world_to_map(const Vector2 &p_pos) const { ret.x -= 0.5; } } break; + case HALF_OFFSET_NEGATIVE_X: { + if (ret.y > 0 ? int(ret.y) & 1 : (int(ret.y) - 1) & 1) { + ret.x += 0.5; + } + } break; case HALF_OFFSET_Y: { if (ret.x > 0 ? int(ret.x) & 1 : (int(ret.x) - 1) & 1) { ret.y -= 0.5; } } break; - default: {} + case HALF_OFFSET_NEGATIVE_Y: { + if (ret.x > 0 ? int(ret.x) & 1 : (int(ret.x) - 1) & 1) { + ret.y += 0.5; + } + } break; + default: { + } } // Account for precision errors on the border (GH-23250). - // 0.00005 is 5*CMP_EPSILON, results would start being unpredictible if + // 0.00005 is 5*CMP_EPSILON, results would start being unpredictable if // cell size is > 15,000, but we can hardly have more precision anyway with // floating point. ret += Vector2(0.00005, 0.00005); @@ -1474,6 +1657,32 @@ bool TileMap::is_y_sort_mode_enabled() const { return y_sort_mode; } +void TileMap::set_compatibility_mode(bool p_enable) { + + _clear_quadrants(); + compatibility_mode = p_enable; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +bool TileMap::is_compatibility_mode_enabled() const { + + return compatibility_mode; +} + +void TileMap::set_centered_textures(bool p_enable) { + + _clear_quadrants(); + centered_textures = p_enable; + _recreate_quadrants(); + emit_signal("settings_changed"); +} + +bool TileMap::is_centered_textures_enabled() const { + + return centered_textures; +} + Array TileMap::get_used_cells() const { Array a; @@ -1565,6 +1774,20 @@ bool TileMap::get_clip_uv() const { return clip_uv; } +String TileMap::get_configuration_warning() const { + + String warning = Node2D::get_configuration_warning(); + + if (use_parent && !collision_parent) { + if (!warning.empty()) { + warning += "\n\n"; + } + return TTR("TileMap with Use Parent on needs a parent CollisionObject2D to give shapes to. Please use it as a child of Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape."); + } + + return warning; +} + void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tileset", "tileset"), &TileMap::set_tileset); @@ -1597,9 +1820,18 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_y_sort_mode", "enable"), &TileMap::set_y_sort_mode); ClassDB::bind_method(D_METHOD("is_y_sort_mode_enabled"), &TileMap::is_y_sort_mode_enabled); + ClassDB::bind_method(D_METHOD("set_compatibility_mode", "enable"), &TileMap::set_compatibility_mode); + ClassDB::bind_method(D_METHOD("is_compatibility_mode_enabled"), &TileMap::is_compatibility_mode_enabled); + + ClassDB::bind_method(D_METHOD("set_centered_textures", "enable"), &TileMap::set_centered_textures); + ClassDB::bind_method(D_METHOD("is_centered_textures_enabled"), &TileMap::is_centered_textures_enabled); + ClassDB::bind_method(D_METHOD("set_collision_use_kinematic", "use_kinematic"), &TileMap::set_collision_use_kinematic); ClassDB::bind_method(D_METHOD("get_collision_use_kinematic"), &TileMap::get_collision_use_kinematic); + ClassDB::bind_method(D_METHOD("set_collision_use_parent", "use_parent"), &TileMap::set_collision_use_parent); + ClassDB::bind_method(D_METHOD("get_collision_use_parent"), &TileMap::get_collision_use_parent); + ClassDB::bind_method(D_METHOD("set_collision_layer", "layer"), &TileMap::set_collision_layer); ClassDB::bind_method(D_METHOD("get_collision_layer"), &TileMap::get_collision_layer); @@ -1659,12 +1891,15 @@ void TileMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size", PROPERTY_HINT_RANGE, "1,8192,1"), "set_cell_size", "get_cell_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_quadrant_size", PROPERTY_HINT_RANGE, "1,128,1"), "set_quadrant_size", "get_quadrant_size"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "cell_custom_transform"), "set_custom_transform", "get_custom_transform"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_half_offset", PROPERTY_HINT_ENUM, "Offset X,Offset Y,Disabled"), "set_half_offset", "get_half_offset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_half_offset", PROPERTY_HINT_ENUM, "Offset X,Offset Y,Disabled,Offset Negative X,Offset Negative Y"), "set_half_offset", "get_half_offset"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_tile_origin", PROPERTY_HINT_ENUM, "Top Left,Center,Bottom Left"), "set_tile_origin", "get_tile_origin"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_y_sort"), "set_y_sort_mode", "is_y_sort_mode_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "compatibility_mode"), "set_compatibility_mode", "is_compatibility_mode_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "centered_textures"), "set_centered_textures", "is_centered_textures_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cell_clip_uv"), "set_clip_uv", "get_clip_uv"); ADD_GROUP("Collision", "collision_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_parent", PROPERTY_HINT_NONE, ""), "set_collision_use_parent", "get_collision_use_parent"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_kinematic", PROPERTY_HINT_NONE, ""), "set_collision_use_kinematic", "get_collision_use_kinematic"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce"); @@ -1674,6 +1909,8 @@ void TileMap::_bind_methods() { ADD_GROUP("Occluder", "occluder_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "occluder_light_mask", PROPERTY_HINT_LAYERS_2D_RENDER), "set_occluder_light_mask", "get_occluder_light_mask"); + ADD_PROPERTY_DEFAULT("format", FORMAT_1); + ADD_SIGNAL(MethodInfo("settings_changed")); BIND_CONSTANT(INVALID_CELL); @@ -1685,6 +1922,8 @@ void TileMap::_bind_methods() { BIND_ENUM_CONSTANT(HALF_OFFSET_X); BIND_ENUM_CONSTANT(HALF_OFFSET_Y); BIND_ENUM_CONSTANT(HALF_OFFSET_DISABLED); + BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_X); + BIND_ENUM_CONSTANT(HALF_OFFSET_NEGATIVE_Y); BIND_ENUM_CONSTANT(TILE_ORIGIN_TOP_LEFT); BIND_ENUM_CONSTANT(TILE_ORIGIN_CENTER); @@ -1711,16 +1950,21 @@ TileMap::TileMap() { bounce = 0; mode = MODE_SQUARE; half_offset = HALF_OFFSET_DISABLED; + use_parent = false; + collision_parent = NULL; use_kinematic = false; navigation = NULL; y_sort_mode = false; + compatibility_mode = false; + centered_textures = false; occluder_light_mask = 1; clip_uv = false; - format = FORMAT_1; //Always initialize with the lowest format + format = FORMAT_1; // Assume lowest possible format if none is present fp_adjust = 0.00001; tile_origin = TILE_ORIGIN_TOP_LEFT; set_notify_transform(true); + set_notify_local_transform(false); } TileMap::~TileMap() { diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index a44098fd77..e30b7eff83 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -33,10 +33,12 @@ #include "core/self_list.h" #include "core/vset.h" -#include "scene/2d/navigation2d.h" +#include "scene/2d/navigation_2d.h" #include "scene/2d/node_2d.h" #include "scene/resources/tile_set.h" +class CollisionObject2D; + class TileMap : public Node2D { GDCLASS(TileMap, Node2D); @@ -52,6 +54,8 @@ public: HALF_OFFSET_X, HALF_OFFSET_Y, HALF_OFFSET_DISABLED, + HALF_OFFSET_NEGATIVE_X, + HALF_OFFSET_NEGATIVE_Y, }; enum TileOrigin { @@ -72,6 +76,8 @@ private: Mode mode; Transform2D custom_transform; HalfOffset half_offset; + bool use_parent; + CollisionObject2D *collision_parent; bool use_kinematic; Navigation2D *navigation; @@ -88,6 +94,13 @@ private: bool operator==(const PosKey &p_k) const { return (y == p_k.y && x == p_k.x); } + PosKey to_quadrant(const int &p_quadrant_size) const { + // rounding down, instead of simply rounding towards zero (truncating) + return PosKey( + x > 0 ? x / p_quadrant_size : (x - (p_quadrant_size - 1)) / p_quadrant_size, + y > 0 ? y / p_quadrant_size : (y - (p_quadrant_size - 1)) / p_quadrant_size); + } + PosKey(int16_t p_x, int16_t p_y) { x = p_x; y = p_y; @@ -121,6 +134,7 @@ private: Vector2 pos; List<RID> canvas_items; RID body; + uint32_t shape_owner_id; SelfList<Quadrant> dirty_list; @@ -143,6 +157,7 @@ private: pos = q.pos; canvas_items = q.canvas_items; body = q.body; + shape_owner_id = q.shape_owner_id; cells = q.cells; navpoly_ids = q.navpoly_ids; occluder_instances = q.occluder_instances; @@ -152,6 +167,7 @@ private: pos = q.pos; canvas_items = q.canvas_items; body = q.body; + shape_owner_id = q.shape_owner_id; cells = q.cells; occluder_instances = q.occluder_instances; navpoly_ids = q.navpoly_ids; @@ -172,6 +188,8 @@ private: bool used_size_cache_dirty; bool quadrant_order_dirty; bool y_sort_mode; + bool compatibility_mode; + bool centered_textures; bool clip_uv; float fp_adjust; float friction; @@ -186,6 +204,8 @@ private: void _fix_cell_transform(Transform2D &xform, const Cell &p_cell, const Vector2 &p_offset, const Size2 &p_sc); + void _add_shape(int &shape_idx, const Quadrant &p_q, const Ref<Shape2D> &p_shape, const TileSet::ShapeData &p_shape_data, const Transform2D &p_xform, const Vector2 &p_metadata); + 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, bool update = true); @@ -216,6 +236,7 @@ protected: void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const; virtual void _changed_callback(Object *p_changed, const char *p_prop); public: @@ -269,6 +290,9 @@ public: void set_collision_use_kinematic(bool p_use_kinematic); bool get_collision_use_kinematic() const; + void set_collision_use_parent(bool p_use_parent); + bool get_collision_use_parent() const; + void set_collision_friction(float p_friction); float get_collision_friction() const; @@ -296,6 +320,12 @@ public: void set_y_sort_mode(bool p_enable); bool is_y_sort_mode_enabled() const; + void set_compatibility_mode(bool p_enable); + bool is_compatibility_mode_enabled() const; + + void set_centered_textures(bool p_enable); + bool is_centered_textures_enabled() const; + Array get_used_cells() const; Array get_used_cells_by_id(int p_id) const; Rect2 get_used_rect(); // Not const because of cache @@ -312,6 +342,8 @@ public: void set_clip_uv(bool p_enable); bool get_clip_uv() const; + String get_configuration_warning() const; + void fix_invalid_tiles(); void clear(); diff --git a/scene/2d/screen_button.cpp b/scene/2d/touch_screen_button.cpp index fb1558a404..9a1a759e72 100644 --- a/scene/2d/screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* screen_button.cpp */ +/* touch_screen_button.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "screen_button.h" +#include "touch_screen_button.h" + #include "core/input_map.h" #include "core/os/input.h" #include "core/os/os.h" diff --git a/scene/2d/screen_button.h b/scene/2d/touch_screen_button.h index fd944ead64..df54e5340b 100644 --- a/scene/2d/screen_button.h +++ b/scene/2d/touch_screen_button.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* screen_button.h */ +/* touch_screen_button.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SCREEN_BUTTON_H -#define SCREEN_BUTTON_H +#ifndef TOUCH_SCREEN_BUTTON_H +#define TOUCH_SCREEN_BUTTON_H #include "scene/2d/node_2d.h" -#include "scene/resources/bit_mask.h" +#include "scene/resources/bit_map.h" #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/texture.h" @@ -112,4 +112,4 @@ public: VARIANT_ENUM_CAST(TouchScreenButton::VisibilityMode); -#endif // SCREEN_BUTTON_H +#endif // TOUCH_SCREEN_BUTTON_H diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp index 1cf037daf2..a1d074e6cd 100644 --- a/scene/2d/visibility_notifier_2d.cpp +++ b/scene/2d/visibility_notifier_2d.cpp @@ -327,7 +327,7 @@ void VisibilityEnabler2D::_node_removed(Node *p_node) { String VisibilityEnabler2D::get_configuration_warning() const { #ifdef TOOLS_ENABLED if (is_inside_tree() && get_parent() && (get_parent()->get_filename() == String() && get_parent() != get_tree()->get_edited_scene_root())) { - return TTR("VisibilityEnable2D works best when used with the edited scene root directly as parent."); + return TTR("VisibilityEnabler2D works best when used with the edited scene root directly as parent."); } #endif return String(); diff --git a/scene/3d/SCsub b/scene/3d/SCsub index 35cc7479d8..31a443bad1 100644 --- a/scene/3d/SCsub +++ b/scene/3d/SCsub @@ -3,10 +3,10 @@ Import('env') if env['disable_3d']: - env.scene_sources.append("3d/spatial.cpp") - env.scene_sources.append("3d/skeleton.cpp") - env.scene_sources.append("3d/particles.cpp") - env.scene_sources.append("3d/visual_instance.cpp") - env.scene_sources.append("3d/scenario_fx.cpp") + env.add_source_files(env.scene_sources, "spatial.cpp") + env.add_source_files(env.scene_sources, "skeleton.cpp") + env.add_source_files(env.scene_sources, "particles.cpp") + env.add_source_files(env.scene_sources, "visual_instance.cpp") + env.add_source_files(env.scene_sources, "world_environment.cpp") else: env.add_source_files(env.scene_sources, "*.cpp") diff --git a/scene/3d/area.cpp b/scene/3d/area.cpp index 99f43b1242..77682abcb3 100644 --- a/scene/3d/area.cpp +++ b/scene/3d/area.cpp @@ -218,10 +218,7 @@ void Area::_body_inout(int p_status, const RID &p_body, int p_instance, int p_bo void Area::_clear_monitoring() { - if (locked) { - ERR_EXPLAIN("This function can't be used during the in/out signal."); - } - ERR_FAIL_COND(locked); + ERR_FAIL_COND_MSG(locked, "This function can't be used during the in/out signal."); { Map<ObjectID, BodyState> bmcopy = body_map; @@ -291,10 +288,7 @@ void Area::_notification(int p_what) { void Area::set_monitoring(bool p_enable) { - if (locked) { - ERR_EXPLAIN("Function blocked during in/out signal. Use set_deferred(\"monitoring\",true/false)"); - } - ERR_FAIL_COND(locked); + ERR_FAIL_COND_MSG(locked, "Function blocked during in/out signal. Use set_deferred(\"monitoring\", true/false)."); if (p_enable == monitoring) return; @@ -356,7 +350,9 @@ void Area::_area_inout(int p_status, const RID &p_area, int p_instance, int p_ar Map<ObjectID, AreaState>::Element *E = area_map.find(objid); - ERR_FAIL_COND(!area_in && !E); + if (!area_in && !E) { + return; //likely removed from the tree + } locked = true; @@ -439,10 +435,7 @@ Array Area::get_overlapping_bodies() const { void Area::set_monitorable(bool p_enable) { - if (locked || PhysicsServer::get_singleton()->is_flushing_queries()) { - ERR_EXPLAIN("Function blocked during in/out signal. Use set_deferred(\"monitorable\",true/false)"); - } - ERR_FAIL_COND(locked || PhysicsServer::get_singleton()->is_flushing_queries()); + ERR_FAIL_COND_MSG(locked || (is_inside_tree() && PhysicsServer::get_singleton()->is_flushing_queries()), "Function blocked during in/out signal. Use set_deferred(\"monitorable\", true/false)."); if (p_enable == monitorable) return; @@ -716,8 +709,8 @@ void Area::_bind_methods() { 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"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "0,1024,0.001"), "set_angular_damp", "get_angular_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,128,1"), "set_priority", "get_priority"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitoring"), "set_monitoring", "is_monitoring"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "monitorable"), "set_monitorable", "is_monitorable"); @@ -750,13 +743,12 @@ Area::Area() : gravity_is_point = false; gravity_distance_scale = 0; linear_damp = 0.1; - angular_damp = 1; + angular_damp = 0.1; priority = 0; monitoring = false; monitorable = false; collision_mask = 1; collision_layer = 1; - set_ray_pickable(false); set_monitoring(true); set_monitorable(true); diff --git a/scene/3d/arvr_nodes.cpp b/scene/3d/arvr_nodes.cpp index 17b698c1b8..4c0449b68e 100644 --- a/scene/3d/arvr_nodes.cpp +++ b/scene/3d/arvr_nodes.cpp @@ -61,7 +61,7 @@ String ARVRCamera::get_configuration_warning() const { // must be child node of ARVROrigin! ARVROrigin *origin = Object::cast_to<ARVROrigin>(get_parent()); if (origin == NULL) { - return TTR("ARVRCamera must have an ARVROrigin node as its parent"); + return TTR("ARVRCamera must have an ARVROrigin node as its parent."); }; return String(); @@ -78,10 +78,7 @@ Vector3 ARVRCamera::project_local_ray_normal(const Point2 &p_pos) const { return Camera::project_local_ray_normal(p_pos); } - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector3()); - }; + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene."); Size2 viewport_size = get_viewport()->get_camera_rect_size(); Vector2 cpos = get_viewport()->get_camera_coords(p_pos); @@ -106,10 +103,7 @@ Point2 ARVRCamera::unproject_position(const Vector3 &p_pos) const { return Camera::unproject_position(p_pos); } - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector2()); - }; + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector2(), "Camera is not inside scene."); Size2 viewport_size = get_viewport()->get_visible_rect().size; @@ -127,7 +121,7 @@ Point2 ARVRCamera::unproject_position(const Vector3 &p_pos) const { return res; }; -Vector3 ARVRCamera::project_position(const Point2 &p_point) const { +Vector3 ARVRCamera::project_position(const Point2 &p_point, float p_z_depth) const { // get our ARVRServer ARVRServer *arvr_server = ARVRServer::get_singleton(); ERR_FAIL_NULL_V(arvr_server, Vector3()); @@ -135,13 +129,10 @@ Vector3 ARVRCamera::project_position(const Point2 &p_point) const { Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); 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); + return Camera::project_position(p_point, p_z_depth); } - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector3()); - }; + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene."); Size2 viewport_size = get_viewport()->get_visible_rect().size; @@ -155,7 +146,7 @@ Vector3 ARVRCamera::project_position(const Point2 &p_point) const { point.y = (1.0 - (p_point.y / viewport_size.y)) * 2.0 - 1.0; point *= vp_size; - Vector3 p(point.x, point.y, -get_znear()); + Vector3 p(point.x, point.y, -p_z_depth); return get_camera_transform().xform(p); }; @@ -233,6 +224,13 @@ void ARVRController::_notification(int p_what) { } else { button_states = 0; }; + + // check for an updated mesh + Ref<Mesh> trackerMesh = tracker->get_mesh(); + if (mesh != trackerMesh) { + mesh = trackerMesh; + emit_signal("mesh_updated", mesh); + } }; }; break; default: @@ -257,9 +255,13 @@ void ARVRController::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rumble"), &ARVRController::get_rumble); ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &ARVRController::set_rumble); ADD_PROPERTY(PropertyInfo(Variant::REAL, "rumble", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_rumble", "get_rumble"); + ADD_PROPERTY_DEFAULT("rumble", 0.0); + + ClassDB::bind_method(D_METHOD("get_mesh"), &ARVRController::get_mesh); ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::INT, "button"))); ADD_SIGNAL(MethodInfo("button_release", PropertyInfo(Variant::INT, "button"))); + ADD_SIGNAL(MethodInfo("mesh_updated", PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"))); }; void ARVRController::set_controller_id(int p_controller_id) { @@ -341,6 +343,10 @@ void ARVRController::set_rumble(real_t p_rumble) { }; }; +Ref<Mesh> ARVRController::get_mesh() const { + return mesh; +} + bool ARVRController::get_is_active() const { return is_active; }; @@ -365,18 +371,18 @@ String ARVRController::get_configuration_warning() const { // must be child node of ARVROrigin! ARVROrigin *origin = Object::cast_to<ARVROrigin>(get_parent()); if (origin == NULL) { - return TTR("ARVRController must have an ARVROrigin node as its parent"); + return TTR("ARVRController must have an ARVROrigin node as its parent."); }; if (controller_id == 0) { - return TTR("The controller id must not be 0 or this controller will not be bound to an actual controller"); + return TTR("The controller ID must not be 0 or this controller won't be bound to an actual controller."); }; return String(); }; ARVRController::ARVRController() { - controller_id = 0; + controller_id = 1; is_active = true; button_states = 0; }; @@ -423,6 +429,13 @@ void ARVRAnchor::_notification(int p_what) { // apply our reference frame and set our transform set_transform(arvr_server->get_reference_frame() * transform); + + // check for an updated mesh + Ref<Mesh> trackerMesh = tracker->get_mesh(); + if (mesh != trackerMesh) { + mesh = trackerMesh; + emit_signal("mesh_updated", mesh); + } }; }; break; default: @@ -441,6 +454,9 @@ void ARVRAnchor::_bind_methods() { ClassDB::bind_method(D_METHOD("get_size"), &ARVRAnchor::get_size); ClassDB::bind_method(D_METHOD("get_plane"), &ARVRAnchor::get_plane); + + ClassDB::bind_method(D_METHOD("get_mesh"), &ARVRAnchor::get_mesh); + ADD_SIGNAL(MethodInfo("mesh_updated", PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"))); }; void ARVRAnchor::set_anchor_id(int p_anchor_id) { @@ -482,11 +498,11 @@ String ARVRAnchor::get_configuration_warning() const { // must be child node of ARVROrigin! ARVROrigin *origin = Object::cast_to<ARVROrigin>(get_parent()); if (origin == NULL) { - return TTR("ARVRAnchor must have an ARVROrigin node as its parent"); + return TTR("ARVRAnchor must have an ARVROrigin node as its parent."); }; if (anchor_id == 0) { - return TTR("The anchor id must not be 0 or this anchor will not be bound to an actual anchor"); + return TTR("The anchor ID must not be 0 or this anchor won't be bound to an actual anchor."); }; return String(); @@ -501,8 +517,12 @@ Plane ARVRAnchor::get_plane() const { return plane; }; +Ref<Mesh> ARVRAnchor::get_mesh() const { + return mesh; +} + ARVRAnchor::ARVRAnchor() { - anchor_id = 0; + anchor_id = 1; is_active = true; }; @@ -517,7 +537,7 @@ String ARVROrigin::get_configuration_warning() const { return String(); if (tracked_camera == NULL) - return TTR("ARVROrigin requires an ARVRCamera child node"); + return TTR("ARVROrigin requires an ARVRCamera child node."); return String(); }; @@ -555,6 +575,10 @@ void ARVROrigin::set_world_scale(float p_world_scale) { }; void ARVROrigin::_notification(int p_what) { + // get our ARVRServer + ARVRServer *arvr_server = ARVRServer::get_singleton(); + ERR_FAIL_NULL(arvr_server); + switch (p_what) { case NOTIFICATION_ENTER_TREE: { set_process_internal(true); @@ -563,10 +587,6 @@ void ARVROrigin::_notification(int p_what) { set_process_internal(false); }; break; case NOTIFICATION_INTERNAL_PROCESS: { - // get our ARVRServer - ARVRServer *arvr_server = ARVRServer::get_singleton(); - ERR_FAIL_NULL(arvr_server); - // set our world origin to our node transform arvr_server->set_world_origin(get_global_transform()); @@ -583,6 +603,14 @@ void ARVROrigin::_notification(int p_what) { default: break; }; + + // send our notification to all active ARVR interfaces, they may need to react to it also + for (int i = 0; i < arvr_server->get_interface_count(); i++) { + Ref<ARVRInterface> interface = arvr_server->get_interface(i); + if (interface.is_valid() && interface->is_initialized()) { + interface->notification(p_what); + } + } }; ARVROrigin::ARVROrigin() { diff --git a/scene/3d/arvr_nodes.h b/scene/3d/arvr_nodes.h index 523bc112c1..8e735f7110 100644 --- a/scene/3d/arvr_nodes.h +++ b/scene/3d/arvr_nodes.h @@ -33,6 +33,7 @@ #include "scene/3d/camera.h" #include "scene/3d/spatial.h" +#include "scene/resources/mesh.h" #include "servers/arvr/arvr_positional_tracker.h" /** @@ -54,7 +55,7 @@ public: virtual Vector3 project_local_ray_normal(const Point2 &p_pos) const; virtual Point2 unproject_position(const Vector3 &p_pos) const; - virtual Vector3 project_position(const Point2 &p_point) const; + virtual Vector3 project_position(const Point2 &p_point, float p_z_depth = 0) const; virtual Vector<Plane> get_frustum() const; ARVRCamera(); @@ -75,6 +76,7 @@ private: int controller_id; bool is_active; int button_states; + Ref<Mesh> mesh; protected: void _notification(int p_what); @@ -95,6 +97,8 @@ public: bool get_is_active() const; ARVRPositionalTracker::TrackerHand get_hand() const; + Ref<Mesh> get_mesh(void) const; + String get_configuration_warning() const; ARVRController(); @@ -113,6 +117,7 @@ private: int anchor_id; bool is_active; Vector3 size; + Ref<Mesh> mesh; protected: void _notification(int p_what); @@ -128,6 +133,8 @@ public: Plane get_plane() const; + Ref<Mesh> get_mesh(void) const; + String get_configuration_warning() const; ARVRAnchor(); diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 0f4d0383a4..27f16f7601 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -32,8 +32,102 @@ #include "core/engine.h" #include "scene/3d/area.h" #include "scene/3d/camera.h" +#include "scene/3d/listener.h" #include "scene/main/viewport.h" +// Based on "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations" by Ramy Sadek and Chris Kyriakakis (2004) +// Speaker-Placement Correction Amplitude Panning (SPCAP) +class Spcap { +private: + struct Speaker { + Vector3 direction; + real_t effective_number_of_speakers; // precalculated + mutable real_t squared_gain; // temporary + }; + + PoolVector<Speaker> speakers; + +public: + Spcap(unsigned int speaker_count, const Vector3 *speaker_directions) { + this->speakers.resize(speaker_count); + PoolVector<Speaker>::Write w = this->speakers.write(); + for (unsigned int speaker_num = 0; speaker_num < speaker_count; speaker_num++) { + w[speaker_num].direction = speaker_directions[speaker_num]; + w[speaker_num].squared_gain = 0.0; + w[speaker_num].effective_number_of_speakers = 0.0; + for (unsigned int other_speaker_num = 0; other_speaker_num < speaker_count; other_speaker_num++) { + w[speaker_num].effective_number_of_speakers += 0.5 * (1.0 + w[speaker_num].direction.dot(w[other_speaker_num].direction)); + } + } + } + + unsigned int get_speaker_count() const { + return (unsigned int)this->speakers.size(); + } + + Vector3 get_speaker_direction(unsigned int index) const { + return this->speakers.read()[index].direction; + } + + void calculate(const Vector3 &source_direction, real_t tightness, unsigned int volume_count, real_t *volumes) const { + PoolVector<Speaker>::Read r = this->speakers.read(); + real_t sum_squared_gains = 0.0; + for (unsigned int speaker_num = 0; speaker_num < (unsigned int)this->speakers.size(); speaker_num++) { + real_t initial_gain = 0.5 * powf(1.0 + r[speaker_num].direction.dot(source_direction), tightness) / r[speaker_num].effective_number_of_speakers; + r[speaker_num].squared_gain = initial_gain * initial_gain; + sum_squared_gains += r[speaker_num].squared_gain; + } + + for (unsigned int speaker_num = 0; speaker_num < MIN(volume_count, (unsigned int)this->speakers.size()); speaker_num++) { + volumes[speaker_num] = sqrtf(r[speaker_num].squared_gain / sum_squared_gains); + } + } +}; + +//TODO: hardcoded main speaker directions for 2, 3.1, 5.1 and 7.1 setups - these are simplified and could also be made configurable +static const Vector3 speaker_directions[7] = { + Vector3(-1.0, 0.0, -1.0).normalized(), // front-left + Vector3(1.0, 0.0, -1.0).normalized(), // front-right + Vector3(0.0, 0.0, -1.0).normalized(), // center + Vector3(-1.0, 0.0, 1.0).normalized(), // rear-left + Vector3(1.0, 0.0, 1.0).normalized(), // rear-right + Vector3(-1.0, 0.0, 0.0).normalized(), // side-left + Vector3(1.0, 0.0, 0.0).normalized(), // side-right +}; + +void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tightness, AudioStreamPlayer3D::Output &output) { + unsigned int speaker_count; // only main speakers (no LFE) + switch (AudioServer::get_singleton()->get_speaker_mode()) { + default: //fallthrough + case AudioServer::SPEAKER_MODE_STEREO: speaker_count = 2; break; + case AudioServer::SPEAKER_SURROUND_31: speaker_count = 3; break; + case AudioServer::SPEAKER_SURROUND_51: speaker_count = 5; break; + case AudioServer::SPEAKER_SURROUND_71: speaker_count = 7; break; + } + + Spcap spcap(speaker_count, speaker_directions); //TODO: should only be created/recreated once the speaker mode / speaker positions changes + real_t volumes[7]; + spcap.calculate(source_dir, tightness, speaker_count, volumes); + + switch (AudioServer::get_singleton()->get_speaker_mode()) { + case AudioServer::SPEAKER_SURROUND_71: + output.vol[3].l = volumes[5]; // side-left + output.vol[3].r = volumes[6]; // side-right + //fallthrough + case AudioServer::SPEAKER_SURROUND_51: + output.vol[2].l = volumes[3]; // rear-left + output.vol[2].r = volumes[4]; // rear-right + //fallthrough + case AudioServer::SPEAKER_SURROUND_31: + output.vol[1].r = 1.0; // LFE - always full power + output.vol[1].l = volumes[2]; // center + //fallthrough + case AudioServer::SPEAKER_MODE_STEREO: + output.vol[0].r = volumes[1]; // front-right + output.vol[0].l = volumes[0]; // front-left + } +} + void AudioStreamPlayer3D::_mix_audio() { if (!stream_playback.is_valid() || !active || @@ -92,7 +186,7 @@ void AudioStreamPlayer3D::_mix_audio() { } bool interpolate_filter = !started; - ; + if (!found) { //create new if was not used before if (prev_output_count < MAX_OUTPUTS) { @@ -217,6 +311,7 @@ float AudioStreamPlayer3D::_get_attenuation_db(float p_distance) const { case ATTENUATION_LOGARITHMIC: { att = -20 * Math::log(p_distance / unit_size + CMP_EPSILON); } break; + case ATTENUATION_DISABLED: break; default: { ERR_PRINT("Unknown attenuation type"); break; @@ -324,16 +419,25 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (!vp->is_audio_listener()) continue; - Vector3 local_pos = camera->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); + bool listener_is_camera = true; + Spatial *listener_node = camera; + + Listener *listener = vp->get_listener(); + if (listener) { + listener_node = listener; + listener_is_camera = false; + } + + Vector3 local_pos = listener_node->get_global_transform().orthonormalized().affine_inverse().xform(global_pos); float dist = local_pos.length(); Vector3 area_sound_pos; - Vector3 cam_area_pos; + Vector3 listener_area_pos; if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { - area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), camera->get_global_transform().origin); - cam_area_pos = camera->get_global_transform().affine_inverse().xform(area_sound_pos); + area_sound_pos = space_state->get_closest_point_to_object_volume(area->get_rid(), listener_node->get_global_transform().origin); + listener_area_pos = listener_node->get_global_transform().affine_inverse().xform(area_sound_pos); } if (max_distance > 0) { @@ -341,10 +445,10 @@ void AudioStreamPlayer3D::_notification(int p_what) { float total_max = max_distance; if (area && area->is_using_reverb_bus() && area->get_reverb_uniformity() > 0) { - total_max = MAX(total_max, cam_area_pos.length()); + total_max = MAX(total_max, listener_area_pos.length()); } if (total_max > max_distance) { - continue; //can't hear this sound in this camera + continue; //can't hear this sound in this listener } } @@ -361,8 +465,8 @@ void AudioStreamPlayer3D::_notification(int p_what) { float db_att = (1.0 - MIN(1.0, multiplier)) * attenuation_filter_db; if (emission_angle_enabled) { - Vector3 camtopos = global_pos - camera->get_global_transform().origin; - float c = camtopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative + Vector3 listenertopos = global_pos - listener_node->get_global_transform().origin; + float c = listenertopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative float angle = Math::rad2deg(Math::acos(c)); if (angle > emission_angle) db_att -= -emission_angle_filter_attenuation_db; @@ -370,59 +474,11 @@ void AudioStreamPlayer3D::_notification(int p_what) { output.filter_gain = Math::db2linear(db_att); - Vector3 flat_pos = local_pos; - flat_pos.y = 0; - flat_pos.normalize(); + //TODO: The lower the second parameter (tightness) the more the sound will "enclose" the listener (more undirected / playing from + // speakers not facing the source) - this could be made distance dependent. + _calc_output_vol(local_pos.normalized(), 4.0, output); unsigned int cc = AudioServer::get_singleton()->get_channel_count(); - if (cc == 1) { - // Stereo pair - float c = flat_pos.x * 0.5 + 0.5; - - output.vol[0].l = 1.0 - c; - output.vol[0].r = c; - } else { - Vector3 camtopos = global_pos - camera->get_global_transform().origin; - float c = camtopos.normalized().dot(get_global_transform().basis.get_axis(2).normalized()); //it's z negative - float angle = Math::rad2deg(Math::acos(c)); - float av = angle * (flat_pos.x < 0 ? -1 : 1) / 180.0; - - if (cc >= 1) { - // Stereo pair - float fl = Math::abs(1.0 - Math::abs(-0.8 - av)); - float fr = Math::abs(1.0 - Math::abs(0.8 - av)); - - output.vol[0].l = fl; - output.vol[0].r = fr; - } - - if (cc >= 2) { - // Center pair - float center = 1.0 - Math::sin(Math::acos(c)); - - output.vol[1].l = center; - output.vol[1].r = center; - } - - if (cc >= 3) { - // Side pair - float sl = Math::abs(1.0 - Math::abs(-0.4 - av)); - float sr = Math::abs(1.0 - Math::abs(0.4 - av)); - - output.vol[2].l = sl; - output.vol[2].r = sr; - } - - if (cc >= 4) { - // Rear pair - float rl = Math::abs(1.0 - Math::abs(-0.2 - av)); - float rr = Math::abs(1.0 - Math::abs(0.2 - av)); - - output.vol[3].l = rl; - output.vol[3].r = rr; - } - } - for (unsigned int k = 0; k < cc; k++) { output.vol[k] *= multiplier; } @@ -449,7 +505,7 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (uniformity > 0.0) { - float distance = cam_area_pos.length(); + float distance = listener_area_pos.length(); float attenuation = Math::db2linear(_get_attenuation_db(distance)); //float dist_att_db = -20 * Math::log(dist + 0.00001); //logarithmic attenuation, like in real life @@ -459,7 +515,7 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (attenuation < 1.0) { //pan the uniform sound - Vector3 rev_pos = cam_area_pos; + Vector3 rev_pos = listener_area_pos; rev_pos.y = 0; rev_pos.normalize(); @@ -518,9 +574,13 @@ void AudioStreamPlayer3D::_notification(int p_what) { if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { - Vector3 camera_velocity = camera->get_doppler_tracked_velocity(); + Vector3 listener_velocity; + + if (listener_is_camera) { + listener_velocity = camera->get_doppler_tracked_velocity(); + } - Vector3 local_velocity = camera->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - camera_velocity); + Vector3 local_velocity = listener_node->get_global_transform().orthonormalized().basis.xform_inv(linear_velocity - listener_velocity); if (local_velocity == Vector3()) { output.pitch_scale = 1.0; @@ -811,7 +871,7 @@ float AudioStreamPlayer3D::get_attenuation_filter_db() const { } void AudioStreamPlayer3D::set_attenuation_model(AttenuationModel p_model) { - ERR_FAIL_INDEX(p_model, 3); + ERR_FAIL_INDEX((int)p_model, 4); attenuation_model = p_model; } @@ -840,7 +900,9 @@ void AudioStreamPlayer3D::set_doppler_tracking(DopplerTracking p_tracking) { if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { set_notify_transform(true); velocity_tracker->set_track_physics_step(doppler_tracking == DOPPLER_TRACKING_PHYSICS_STEP); - velocity_tracker->reset(get_global_transform().origin); + if (is_inside_tree()) { + velocity_tracker->reset(get_global_transform().origin); + } } else { set_notify_transform(false); } @@ -855,8 +917,8 @@ void AudioStreamPlayer3D::set_stream_paused(bool p_pause) { if (p_pause != stream_paused) { stream_paused = p_pause; - stream_paused_fade_in = stream_paused ? false : true; - stream_paused_fade_out = stream_paused ? true : false; + stream_paused_fade_in = !stream_paused; + stream_paused_fade_out = stream_paused; } } @@ -865,6 +927,10 @@ bool AudioStreamPlayer3D::get_stream_paused() const { return stream_paused; } +Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() { + return stream_playback; +} + void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer3D::set_stream); @@ -931,10 +997,12 @@ void AudioStreamPlayer3D::_bind_methods() { 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("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback); + 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"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,InverseSquare,Log"), "set_attenuation_model", "get_attenuation_model"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,InverseSquare,Log,Disabled"), "set_attenuation_model", "get_attenuation_model"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_db", PROPERTY_HINT_RANGE, "-80,80"), "set_unit_db", "get_unit_db"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.1"), "set_unit_size", "get_unit_size"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_db", PROPERTY_HINT_RANGE, "-24,6"), "set_max_db", "get_max_db"); @@ -951,7 +1019,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_angle_degrees", PROPERTY_HINT_RANGE, "0.1,90,0.1"), "set_emission_angle", "get_emission_angle"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_angle_filter_attenuation_db", PROPERTY_HINT_RANGE, "-80,0,0.1"), "set_emission_angle_filter_attenuation_db", "get_emission_angle_filter_attenuation_db"); ADD_GROUP("Attenuation Filter", "attenuation_filter_"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation_filter_cutoff_hz", PROPERTY_HINT_RANGE, "50,50000,1"), "set_attenuation_filter_cutoff_hz", "get_attenuation_filter_cutoff_hz"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation_filter_cutoff_hz", PROPERTY_HINT_RANGE, "1,20500,1"), "set_attenuation_filter_cutoff_hz", "get_attenuation_filter_cutoff_hz"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation_filter_db", PROPERTY_HINT_RANGE, "-80,0,0.1"), "set_attenuation_filter_db", "get_attenuation_filter_db"); ADD_GROUP("Doppler", "doppler_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking"); @@ -959,6 +1027,7 @@ void AudioStreamPlayer3D::_bind_methods() { BIND_ENUM_CONSTANT(ATTENUATION_INVERSE_DISTANCE); BIND_ENUM_CONSTANT(ATTENUATION_INVERSE_SQUARE_DISTANCE); BIND_ENUM_CONSTANT(ATTENUATION_LOGARITHMIC); + BIND_ENUM_CONSTANT(ATTENUATION_DISABLED); BIND_ENUM_CONSTANT(OUT_OF_RANGE_MIX); BIND_ENUM_CONSTANT(OUT_OF_RANGE_PAUSE); diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h index 881652da64..494aa70097 100644 --- a/scene/3d/audio_stream_player_3d.h +++ b/scene/3d/audio_stream_player_3d.h @@ -40,12 +40,14 @@ class Camera; class AudioStreamPlayer3D : public Spatial { - GDCLASS(AudioStreamPlayer3D, Spatial) + GDCLASS(AudioStreamPlayer3D, Spatial); + public: enum AttenuationModel { ATTENUATION_INVERSE_DISTANCE, ATTENUATION_INVERSE_SQUARE_DISTANCE, ATTENUATION_LOGARITHMIC, + ATTENUATION_DISABLED, }; enum OutOfRangeMode { @@ -69,7 +71,7 @@ private: struct Output { AudioFilterSW filter; - AudioFilterSW::Processor filter_process[6]; + AudioFilterSW::Processor filter_process[8]; AudioFrame vol[4]; float filter_gain; float pitch_scale; @@ -113,6 +115,7 @@ private: bool stream_paused_fade_out; StringName bus; + static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Output &output); void _mix_audio(); static void _mix_audios(void *self) { reinterpret_cast<AudioStreamPlayer3D *>(self)->_mix_audio(); } @@ -205,6 +208,8 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + Ref<AudioStreamPlayback> get_stream_playback(); + AudioStreamPlayer3D(); ~AudioStreamPlayer3D(); }; diff --git a/scene/3d/baked_lightmap.cpp b/scene/3d/baked_lightmap.cpp index d66e6cc83d..c5ff4dadbc 100644 --- a/scene/3d/baked_lightmap.cpp +++ b/scene/3d/baked_lightmap.cpp @@ -173,7 +173,7 @@ void BakedLightmapData::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::AABB, "bounds", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_bounds", "get_bounds"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "cell_space_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_space_transform", "get_cell_space_transform"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_subdiv", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_subdiv", "get_cell_subdiv"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_energy", "get_energy"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy"); ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "octree", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_octree", "get_octree"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data"); } @@ -221,6 +221,15 @@ Vector3 BakedLightmap::get_extents() const { return extents; } +void BakedLightmap::set_bake_default_texels_per_unit(const float &p_bake_texels_per_unit) { + bake_default_texels_per_unit = p_bake_texels_per_unit; + update_gizmo(); +} + +float BakedLightmap::get_bake_default_texels_per_unit() const { + return bake_default_texels_per_unit; +} + void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, List<PlotMesh> &plot_meshes, List<PlotLight> &plot_lights) { MeshInstance *mi = Object::cast_to<MeshInstance>(p_at_node); @@ -236,7 +245,7 @@ void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, List<PlotMesh> &plo } } - if (all_have_uv2 && mesh->get_lightmap_size_hint() != Size2()) { + if (all_have_uv2) { //READY TO BAKE! size hint could be computed if not found, actually.. AABB aabb = mesh->get_aabb(); @@ -463,7 +472,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, bool p_create_vi btd.text = RTR("Lighting Meshes: ") + mesh_name + " (" + itos(pmc) + "/" + itos(mesh_list.size()) + ")"; btd.pass = step; btd.last_step = 0; - err = baker.make_lightmap(E->get().local_xform, E->get().mesh, lm, _bake_time, &btd); + err = baker.make_lightmap(E->get().local_xform, E->get().mesh, bake_default_texels_per_unit, lm, _bake_time, &btd); if (err != OK) { bake_end_function(); if (err == ERR_SKIP) @@ -473,7 +482,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, bool p_create_vi step += 100; } else { - err = baker.make_lightmap(E->get().local_xform, E->get().mesh, lm); + err = baker.make_lightmap(E->get().local_xform, E->get().mesh, bake_default_texels_per_unit, lm); } if (err == OK) { @@ -790,6 +799,9 @@ void BakedLightmap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_extents", "extents"), &BakedLightmap::set_extents); ClassDB::bind_method(D_METHOD("get_extents"), &BakedLightmap::get_extents); + ClassDB::bind_method(D_METHOD("set_bake_default_texels_per_unit", "texels"), &BakedLightmap::set_bake_default_texels_per_unit); + ClassDB::bind_method(D_METHOD("get_bake_default_texels_per_unit"), &BakedLightmap::get_bake_default_texels_per_unit); + ClassDB::bind_method(D_METHOD("set_propagation", "propagation"), &BakedLightmap::set_propagation); ClassDB::bind_method(D_METHOD("get_propagation"), &BakedLightmap::get_propagation); @@ -814,6 +826,7 @@ void BakedLightmap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_energy", PROPERTY_HINT_RANGE, "0,32,0.01"), "set_energy", "get_energy"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_hdr"), "set_hdr", "is_hdr"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "bake_extents"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_default_texels_per_unit"), "set_bake_default_texels_per_unit", "get_bake_default_texels_per_unit"); ADD_GROUP("Capture", "capture_"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "capture_cell_size", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_capture_cell_size", "get_capture_cell_size"); ADD_GROUP("Data", ""); @@ -836,6 +849,7 @@ void BakedLightmap::_bind_methods() { BakedLightmap::BakedLightmap() { extents = Vector3(10, 10, 10); + bake_default_texels_per_unit = 20; bake_cell_size = 0.25; capture_cell_size = 0.5; diff --git a/scene/3d/baked_lightmap.h b/scene/3d/baked_lightmap.h index bb3f84719a..3a9f4cf01d 100644 --- a/scene/3d/baked_lightmap.h +++ b/scene/3d/baked_lightmap.h @@ -119,6 +119,7 @@ private: float bake_cell_size; float capture_cell_size; Vector3 extents; + float bake_default_texels_per_unit; float propagation; float energy; BakeQuality bake_quality; @@ -178,6 +179,9 @@ public: void set_extents(const Vector3 &p_extents); Vector3 get_extents() const; + void set_bake_default_texels_per_unit(const float &p_bake_texels_per_unit); + float get_bake_default_texels_per_unit() const; + void set_propagation(float p_propagation); float get_propagation() const; diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp index fd28b876f3..9f8510248c 100644 --- a/scene/3d/camera.cpp +++ b/scene/3d/camera.cpp @@ -55,16 +55,23 @@ void Camera::_update_camera_mode() { case PROJECTION_ORTHOGONAL: { set_orthogonal(size, near, far); } break; + case PROJECTION_FRUSTUM: { + set_frustum(size, frustum_offset, near, far); + } break; } } void Camera::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "fov") { - if (mode == PROJECTION_ORTHOGONAL) { + if (mode != PROJECTION_PERSPECTIVE) { p_property.usage = PROPERTY_USAGE_NOEDITOR; } } else if (p_property.name == "size") { - if (mode == PROJECTION_PERSPECTIVE) { + if (mode != PROJECTION_ORTHOGONAL && mode != PROJECTION_FRUSTUM) { + p_property.usage = PROPERTY_USAGE_NOEDITOR; + } + } else if (p_property.name == "frustum_offset") { + if (mode != PROJECTION_FRUSTUM) { p_property.usage = PROPERTY_USAGE_NOEDITOR; } } @@ -99,9 +106,15 @@ void Camera::_notification(int p_what) { case NOTIFICATION_ENTER_WORLD: { - bool first_camera = get_viewport()->_camera_add(this); - if (!get_tree()->is_node_being_edited(this) && (current || first_camera)) - make_current(); + // Needs to track the Viewport because it's needed on NOTIFICATION_EXIT_WORLD + // and Spatial will handle it first, including clearing its reference to the Viewport, + // therefore making it impossible to subclasses to access it + viewport = get_viewport(); + ERR_FAIL_COND(!viewport); + + bool first_camera = viewport->_camera_add(this); + if (current || first_camera) + viewport->_camera_set(this); } break; case NOTIFICATION_TRANSFORM_CHANGED: { @@ -123,17 +136,20 @@ void Camera::_notification(int p_what) { } } - get_viewport()->_camera_remove(this); + if (viewport) { + viewport->_camera_remove(this); + viewport = NULL; + } } break; case NOTIFICATION_BECAME_CURRENT: { - if (get_world().is_valid()) { - get_world()->_register_camera(this); + if (viewport) { + viewport->find_world()->_register_camera(this); } } break; case NOTIFICATION_LOST_CURRENT: { - if (get_world().is_valid()) { - get_world()->_remove_camera(this); + if (viewport) { + viewport->find_world()->_remove_camera(this); } } break; } @@ -177,8 +193,24 @@ void Camera::set_orthogonal(float p_size, float p_z_near, float p_z_far) { update_gizmo(); } +void Camera::set_frustum(float p_size, Vector2 p_offset, float p_z_near, float p_z_far) { + if (!force_change && size == p_size && frustum_offset == p_offset && p_z_near == near && p_z_far == far && mode == PROJECTION_FRUSTUM) + return; + + size = p_size; + frustum_offset = p_offset; + + near = p_z_near; + far = p_z_far; + mode = PROJECTION_FRUSTUM; + force_change = false; + + VisualServer::get_singleton()->camera_set_frustum(camera, size, frustum_offset, near, far); + update_gizmo(); +} + void Camera::set_projection(Camera::Projection p_mode) { - if (p_mode == PROJECTION_PERSPECTIVE || p_mode == PROJECTION_ORTHOGONAL) { + if (p_mode == PROJECTION_PERSPECTIVE || p_mode == PROJECTION_ORTHOGONAL || p_mode == PROJECTION_FRUSTUM) { mode = p_mode; _update_camera_mode(); _change_notify(); @@ -232,8 +264,6 @@ bool Camera::is_current() const { return get_viewport()->get_camera() == this; } else return current; - - return false; } bool Camera::_can_gizmo_scale() const { @@ -249,10 +279,7 @@ Vector3 Camera::project_ray_normal(const Point2 &p_pos) const { Vector3 Camera::project_local_ray_normal(const Point2 &p_pos) const { - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector3()); - } + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene."); Size2 viewport_size = get_viewport()->get_camera_rect_size(); Vector2 cpos = get_viewport()->get_camera_coords(p_pos); @@ -274,10 +301,7 @@ Vector3 Camera::project_local_ray_normal(const Point2 &p_pos) const { Vector3 Camera::project_ray_origin(const Point2 &p_pos) const { - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector3()); - } + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene."); Size2 viewport_size = get_viewport()->get_camera_rect_size(); Vector2 cpos = get_viewport()->get_camera_coords(p_pos); @@ -315,10 +339,7 @@ bool Camera::is_position_behind(const Vector3 &p_pos) const { } Vector<Vector3> Camera::get_near_plane_points() const { - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector<Vector3>()); - } + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector<Vector3>(), "Camera is not inside scene."); Size2 viewport_size = get_viewport()->get_visible_rect().size; @@ -342,10 +363,7 @@ Vector<Vector3> Camera::get_near_plane_points() const { Point2 Camera::unproject_position(const Vector3 &p_pos) const { - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector2()); - } + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector2(), "Camera is not inside scene."); Size2 viewport_size = get_viewport()->get_visible_rect().size; @@ -368,11 +386,12 @@ Point2 Camera::unproject_position(const Vector3 &p_pos) const { return res; } -Vector3 Camera::project_position(const Point2 &p_point) const { +Vector3 Camera::project_position(const Point2 &p_point, float p_z_depth) const { + + ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene."); - if (!is_inside_tree()) { - ERR_EXPLAIN("Camera is not inside scene."); - ERR_FAIL_COND_V(!is_inside_tree(), Vector3()); + if (p_z_depth == 0) { + return get_global_transform().origin; } Size2 viewport_size = get_viewport()->get_visible_rect().size; @@ -392,7 +411,7 @@ Vector3 Camera::project_position(const Point2 &p_point) const { point.y = (1.0 - (p_point.y / viewport_size.y)) * 2.0 - 1.0; point *= vp_size; - Vector3 p(point.x, point.y, -near); + Vector3 p(point.x, point.y, -p_z_depth); return get_camera_transform().xform(p); } @@ -449,7 +468,9 @@ void Camera::set_doppler_tracking(DopplerTracking p_tracking) { doppler_tracking = p_tracking; if (p_tracking != DOPPLER_TRACKING_DISABLED) { velocity_tracker->set_track_physics_step(doppler_tracking == DOPPLER_TRACKING_PHYSICS_STEP); - velocity_tracker->reset(get_global_transform().origin); + if (is_inside_tree()) { + velocity_tracker->reset(get_global_transform().origin); + } } _update_camera_mode(); } @@ -465,19 +486,22 @@ void Camera::_bind_methods() { ClassDB::bind_method(D_METHOD("project_ray_origin", "screen_point"), &Camera::project_ray_origin); ClassDB::bind_method(D_METHOD("unproject_position", "world_point"), &Camera::unproject_position); ClassDB::bind_method(D_METHOD("is_position_behind", "world_point"), &Camera::is_position_behind); - ClassDB::bind_method(D_METHOD("project_position", "screen_point"), &Camera::project_position); + ClassDB::bind_method(D_METHOD("project_position", "screen_point", "z_depth"), &Camera::project_position, DEFVAL(0)); 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("set_frustum", "size", "offset", "z_near", "z_far"), &Camera::set_frustum); ClassDB::bind_method(D_METHOD("make_current"), &Camera::make_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); ClassDB::bind_method(D_METHOD("get_fov"), &Camera::get_fov); + ClassDB::bind_method(D_METHOD("get_frustum_offset"), &Camera::get_frustum_offset); ClassDB::bind_method(D_METHOD("get_size"), &Camera::get_size); ClassDB::bind_method(D_METHOD("get_zfar"), &Camera::get_zfar); ClassDB::bind_method(D_METHOD("get_znear"), &Camera::get_znear); ClassDB::bind_method(D_METHOD("set_fov"), &Camera::set_fov); + ClassDB::bind_method(D_METHOD("set_frustum_offset"), &Camera::set_frustum_offset); ClassDB::bind_method(D_METHOD("set_size"), &Camera::set_size); ClassDB::bind_method(D_METHOD("set_zfar"), &Camera::set_zfar); ClassDB::bind_method(D_METHOD("set_znear"), &Camera::set_znear); @@ -495,6 +519,8 @@ void Camera::_bind_methods() { ClassDB::bind_method(D_METHOD("get_keep_aspect_mode"), &Camera::get_keep_aspect_mode); ClassDB::bind_method(D_METHOD("set_doppler_tracking", "mode"), &Camera::set_doppler_tracking); ClassDB::bind_method(D_METHOD("get_doppler_tracking"), &Camera::get_doppler_tracking); + ClassDB::bind_method(D_METHOD("get_frustum"), &Camera::get_frustum); + ClassDB::bind_method(D_METHOD("get_camera_rid"), &Camera::get_camera); ClassDB::bind_method(D_METHOD("set_cull_mask_bit", "layer", "enable"), &Camera::set_cull_mask_bit); ClassDB::bind_method(D_METHOD("get_cull_mask_bit", "layer"), &Camera::get_cull_mask_bit); @@ -507,15 +533,17 @@ void Camera::_bind_methods() { 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::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal"), "set_projection", "get_projection"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum"), "set_projection", "get_projection"); 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::VECTOR2, "frustum_offset"), "set_frustum_offset", "get_frustum_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "near", PROPERTY_HINT_EXP_RANGE, "0.01,8192,0.01,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); + BIND_ENUM_CONSTANT(PROJECTION_FRUSTUM); BIND_ENUM_CONSTANT(KEEP_WIDTH); BIND_ENUM_CONSTANT(KEEP_HEIGHT); @@ -540,6 +568,10 @@ float Camera::get_znear() const { return near; } +Vector2 Camera::get_frustum_offset() const { + return frustum_offset; +} + float Camera::get_zfar() const { return far; @@ -567,6 +599,11 @@ void Camera::set_znear(float p_znear) { _update_camera_mode(); } +void Camera::set_frustum_offset(Vector2 p_offset) { + frustum_offset = p_offset; + _update_camera_mode(); +} + void Camera::set_zfar(float p_zfar) { far = p_zfar; _update_camera_mode(); @@ -645,9 +682,11 @@ Camera::Camera() { camera = VisualServer::get_singleton()->camera_create(); size = 1; fov = 0; + frustum_offset = Vector2(); near = 0; far = 0; current = false; + viewport = NULL; force_change = false; mode = PROJECTION_PERSPECTIVE; set_perspective(70.0, 0.05, 100.0); @@ -681,8 +720,9 @@ void ClippedCamera::set_process_mode(ProcessMode p_mode) { if (process_mode == p_mode) { return; } - set_process_internal(p_mode == CLIP_PROCESS_IDLE); - set_physics_process_internal(p_mode == CLIP_PROCESS_PHYSICS); + process_mode = p_mode; + set_process_internal(process_mode == CLIP_PROCESS_IDLE); + set_physics_process_internal(process_mode == CLIP_PROCESS_PHYSICS); } ClippedCamera::ProcessMode ClippedCamera::get_process_mode() const { return process_mode; @@ -745,7 +785,7 @@ void ClippedCamera::_notification(int p_what) { float csafe, cunsafe; if (dspace->cast_motion(pyramid_shape, xf, cam_pos - ray_from, margin, csafe, cunsafe, exclude, collision_mask, clip_to_bodies, clip_to_areas)) { - clip_offset = cam_pos.distance_to(ray_from + (cam_pos - ray_from).normalized() * csafe); + clip_offset = cam_pos.distance_to(ray_from + (cam_pos - ray_from) * csafe); } _update_camera(); @@ -814,6 +854,11 @@ void ClippedCamera::clear_exceptions() { exclude.clear(); } +float ClippedCamera::get_clip_offset() const { + + return clip_offset; +} + void ClippedCamera::set_clip_to_areas(bool p_clip) { clip_to_areas = p_clip; @@ -857,6 +902,8 @@ void ClippedCamera::_bind_methods() { ClassDB::bind_method(D_METHOD("set_clip_to_areas", "enable"), &ClippedCamera::set_clip_to_areas); ClassDB::bind_method(D_METHOD("is_clip_to_areas_enabled"), &ClippedCamera::is_clip_to_areas_enabled); + ClassDB::bind_method(D_METHOD("get_clip_offset"), &ClippedCamera::get_clip_offset); + ClassDB::bind_method(D_METHOD("set_clip_to_bodies", "enable"), &ClippedCamera::set_clip_to_bodies); ClassDB::bind_method(D_METHOD("is_clip_to_bodies_enabled"), &ClippedCamera::is_clip_to_bodies_enabled); diff --git a/scene/3d/camera.h b/scene/3d/camera.h index a531324a85..22223880c1 100644 --- a/scene/3d/camera.h +++ b/scene/3d/camera.h @@ -35,9 +35,7 @@ #include "scene/3d/spatial_velocity_tracker.h" #include "scene/main/viewport.h" #include "scene/resources/environment.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Camera : public Spatial { GDCLASS(Camera, Spatial); @@ -46,7 +44,8 @@ public: enum Projection { PROJECTION_PERSPECTIVE, - PROJECTION_ORTHOGONAL + PROJECTION_ORTHOGONAL, + PROJECTION_FRUSTUM }; enum KeepAspect { @@ -63,11 +62,13 @@ public: private: bool force_change; bool current; + Viewport *viewport; Projection mode; float fov; float size; + Vector2 frustum_offset; float near, far; float v_offset; float h_offset; @@ -110,6 +111,7 @@ public: void set_perspective(float p_fovy_degrees, float p_z_near, float p_z_far); void set_orthogonal(float p_size, float p_z_near, float p_z_far); + void set_frustum(float p_size, Vector2 p_offset, float p_z_near, float p_z_far); void set_projection(Camera::Projection p_mode); void make_current(); @@ -123,12 +125,15 @@ public: float get_size() const; float get_zfar() const; float get_znear() const; + Vector2 get_frustum_offset() const; + Projection get_projection() const; void set_fov(float p_fov); void set_size(float p_size); void set_zfar(float p_zfar); void set_znear(float p_znear); + void set_frustum_offset(Vector2 p_offset); virtual Transform get_camera_transform() const; @@ -137,7 +142,7 @@ public: 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; + virtual Vector3 project_position(const Point2 &p_point, float p_z_depth = 0) const; Vector<Vector3> get_near_plane_points() const; @@ -227,6 +232,8 @@ public: void remove_exception(const Object *p_object); void clear_exceptions(); + float get_clip_offset() const; + ClippedCamera(); ~ClippedCamera(); }; diff --git a/scene/3d/collision_object.cpp b/scene/3d/collision_object.cpp index d8c2042c88..63301fc226 100644 --- a/scene/3d/collision_object.cpp +++ b/scene/3d/collision_object.cpp @@ -52,7 +52,7 @@ void CollisionObject::_notification(int p_what) { _update_pickable(); //get space - }; + } break; case NOTIFICATION_TRANSFORM_CHANGED: { @@ -259,9 +259,9 @@ void CollisionObject::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape> & s.index = total_subshapes; s.shape = p_shape; if (area) { - PhysicsServer::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform); + PhysicsServer::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled); } else { - PhysicsServer::get_singleton()->body_add_shape(rid, p_shape->get_rid(), sd.xform); + PhysicsServer::get_singleton()->body_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled); } sd.shapes.push_back(s); @@ -370,8 +370,8 @@ String CollisionObject::get_configuration_warning() const { String warning = Spatial::get_configuration_warning(); if (shapes.empty()) { - if (warning == String()) { - warning += "\n"; + if (!warning.empty()) { + warning += "\n\n"; } warning += TTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape or CollisionPolygon as a child to define its shape."); } diff --git a/scene/3d/collision_polygon.cpp b/scene/3d/collision_polygon.cpp index db07059b32..37aa95fb43 100644 --- a/scene/3d/collision_polygon.cpp +++ b/scene/3d/collision_polygon.cpp @@ -151,6 +151,8 @@ float CollisionPolygon::get_depth() const { void CollisionPolygon::set_disabled(bool p_disabled) { disabled = p_disabled; + update_gizmo(); + if (parent) { parent->shape_owner_set_disabled(owner_id, p_disabled); } diff --git a/scene/3d/collision_shape.cpp b/scene/3d/collision_shape.cpp index daee291ba3..2b030641eb 100644 --- a/scene/3d/collision_shape.cpp +++ b/scene/3d/collision_shape.cpp @@ -65,7 +65,6 @@ void CollisionShape::make_convex_from_brothers() { } void CollisionShape::_update_in_shape_owner(bool p_xform_only) { - parent->shape_owner_set_transform(owner_id, get_transform()); if (p_xform_only) return; @@ -91,7 +90,7 @@ void CollisionShape::_notification(int p_what) { _update_in_shape_owner(); } if (get_tree()->is_debugging_collisions_hint()) { - _create_debug_shape(); + _update_debug_shape(); } } break; case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { @@ -121,7 +120,11 @@ String CollisionShape::get_configuration_warning() const { } if (!shape.is_valid()) { - return TTR("A shape must be provided for CollisionShape to function. Please create a shape resource for it!"); + return TTR("A shape must be provided for CollisionShape to function. Please create a shape resource for it."); + } + + if (shape->is_class("PlaneShape")) { + return TTR("Plane shapes don't work well and will be removed in future versions. Please don't use them."); } return String(); @@ -138,17 +141,24 @@ void CollisionShape::_bind_methods() { ClassDB::bind_method(D_METHOD("make_convex_from_brothers"), &CollisionShape::make_convex_from_brothers); ClassDB::set_method_flags("CollisionShape", "make_convex_from_brothers", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); + ClassDB::bind_method(D_METHOD("_shape_changed"), &CollisionShape::_shape_changed); + ClassDB::bind_method(D_METHOD("_update_debug_shape"), &CollisionShape::_update_debug_shape); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shape", PROPERTY_HINT_RESOURCE_TYPE, "Shape"), "set_shape", "get_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); } void CollisionShape::set_shape(const Ref<Shape> &p_shape) { - if (!shape.is_null()) + if (!shape.is_null()) { shape->unregister_owner(this); + shape->disconnect("changed", this, "_shape_changed"); + } shape = p_shape; - if (!shape.is_null()) + if (!shape.is_null()) { shape->register_owner(this); + shape->connect("changed", this, "_shape_changed"); + } update_gizmo(); if (parent) { parent->shape_owner_clear_shapes(owner_id); @@ -157,6 +167,8 @@ void CollisionShape::set_shape(const Ref<Shape> &p_shape) { } } + if (is_inside_tree()) + _shape_changed(); update_configuration_warning(); } @@ -195,7 +207,8 @@ CollisionShape::~CollisionShape() { //VisualServer::get_singleton()->free(indicator); } -void CollisionShape::_create_debug_shape() { +void CollisionShape::_update_debug_shape() { + debug_shape_dirty = false; if (debug_shape) { debug_shape->queue_delete(); @@ -203,15 +216,24 @@ void CollisionShape::_create_debug_shape() { } Ref<Shape> s = get_shape(); - if (s.is_null()) return; Ref<Mesh> mesh = s->get_debug_mesh(); - MeshInstance *mi = memnew(MeshInstance); mi->set_mesh(mesh); - add_child(mi); debug_shape = mi; } + +void CollisionShape::_shape_changed() { + // If this is a heightfield shape our center may have changed + if (parent) { + _update_in_shape_owner(true); + } + + if (is_inside_tree() && get_tree()->is_debugging_collisions_hint() && !debug_shape_dirty) { + debug_shape_dirty = true; + call_deferred("_update_debug_shape"); + } +} diff --git a/scene/3d/collision_shape.h b/scene/3d/collision_shape.h index 0c8e383a7f..98427b8590 100644 --- a/scene/3d/collision_shape.h +++ b/scene/3d/collision_shape.h @@ -45,12 +45,14 @@ class CollisionShape : public Spatial { CollisionObject *parent; Node *debug_shape; + bool debug_shape_dirty; void resource_changed(RES res); bool disabled; protected: - void _create_debug_shape(); + void _update_debug_shape(); + void _shape_changed(); void _update_in_shape_owner(bool p_xform_only = false); diff --git a/scene/3d/cpu_particles.cpp b/scene/3d/cpu_particles.cpp index 29610cb0bf..fc16bc36cb 100644 --- a/scene/3d/cpu_particles.cpp +++ b/scene/3d/cpu_particles.cpp @@ -47,20 +47,8 @@ PoolVector<Face3> CPUParticles::get_faces(uint32_t p_usage_flags) const { void CPUParticles::set_emitting(bool p_emitting) { emitting = p_emitting; - if (!is_processing_internal()) { + if (emitting) set_process_internal(true); - if (is_inside_tree()) { -#ifndef NO_THREADS - update_mutex->lock(); -#endif - VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, true); - -#ifndef NO_THREADS - update_mutex->unlock(); -#endif - } - } } void CPUParticles::set_amount(int p_amount) { @@ -104,6 +92,10 @@ void CPUParticles::set_randomness_ratio(float p_ratio) { randomness_ratio = p_ratio; } +void CPUParticles::set_lifetime_randomness(float p_random) { + + lifetime_randomness = p_random; +} void CPUParticles::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; @@ -142,6 +134,10 @@ float CPUParticles::get_randomness_ratio() const { return randomness_ratio; } +float CPUParticles::get_lifetime_randomness() const { + + return lifetime_randomness; +} bool CPUParticles::get_use_local_coordinates() const { @@ -224,7 +220,7 @@ String CPUParticles::get_configuration_warning() const { get_param_curve(PARAM_ANIM_SPEED).is_valid() || get_param_curve(PARAM_ANIM_OFFSET).is_valid())) { if (warnings != String()) warnings += "\n"; - warnings += "- " + TTR("CPUParticles animation requires the usage of a SpatialMaterial with \"Billboard Particles\" enabled."); + warnings += "- " + TTR("CPUParticles animation requires the usage of a SpatialMaterial whose Billboard Mode is set to \"Particle Billboard\"."); } return warnings; @@ -237,6 +233,8 @@ void CPUParticles::restart() { frame_remainder = 0; cycle = 0; + set_emitting(true); + { int pc = particles.size(); PoolVector<Particle>::Write w = particles.write(); @@ -247,6 +245,16 @@ void CPUParticles::restart() { } } +void CPUParticles::set_direction(Vector3 p_direction) { + + direction = p_direction; +} + +Vector3 CPUParticles::get_direction() const { + + return direction; +} + void CPUParticles::set_spread(float p_spread) { spread = p_spread; @@ -314,9 +322,9 @@ void CPUParticles::set_param_curve(Parameter p_param, const Ref<Curve> &p_curve) case PARAM_ANGULAR_VELOCITY: { _adjust_curve_range(p_curve, -360, 360); } break; - /*case PARAM_ORBIT_VELOCITY: { + case PARAM_ORBIT_VELOCITY: { _adjust_curve_range(p_curve, -500, 500); - } break;*/ + } break; case PARAM_LINEAR_ACCEL: { _adjust_curve_range(p_curve, -200, 200); } break; @@ -343,7 +351,8 @@ void CPUParticles::set_param_curve(Parameter p_param, const Ref<Curve> &p_curve) } break; case PARAM_ANIM_OFFSET: { } break; - default: {} + default: { + } } } Ref<Curve> CPUParticles::get_param_curve(Parameter p_param) const { @@ -472,11 +481,10 @@ void CPUParticles::_validate_property(PropertyInfo &property) const { if (property.name == "emission_normals" && emission_shape != EMISSION_SHAPE_DIRECTED_POINTS) { property.usage = 0; } - /* + if (property.name.begins_with("orbit_") && !flags[FLAG_DISABLE_Z]) { property.usage = 0; } - */ } static uint32_t idhash(uint32_t x) { @@ -515,7 +523,8 @@ void CPUParticles::_particles_process(float p_delta) { time = Math::fmod(time, lifetime); cycle++; if (one_shot && cycle > 0) { - emitting = false; + set_emitting(false); + _change_notify(); } } @@ -526,6 +535,8 @@ void CPUParticles::_particles_process(float p_delta) { velocity_xform = emission_xform.basis; } + float system_phase = time / lifetime; + for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -533,21 +544,26 @@ void CPUParticles::_particles_process(float p_delta) { if (!emitting && !p.active) continue; - float restart_time = (float(i) / float(pcount)) * lifetime; float local_delta = p_delta; + // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. + // While we use time in tests later on, for randomness we use the phase as done in the + // original shader code, and we later multiply by lifetime to get the time. + float restart_phase = float(i) / float(pcount); + if (randomness_ratio > 0.0) { uint32_t seed = cycle; - if (restart_time >= time) { + if (restart_phase >= system_phase) { seed -= uint32_t(1); } seed *= uint32_t(pcount); seed += uint32_t(i); float random = float(idhash(seed) % uint32_t(65536)) / 65536.0; - restart_time += randomness_ratio * random * 1.0 / float(pcount); + restart_phase += randomness_ratio * random * 1.0 / float(pcount); } - restart_time *= (1.0 - explosiveness_ratio); + restart_phase *= (1.0 - explosiveness_ratio); + float restart_time = restart_phase * lifetime; bool restart = false; if (time > prev_time) { @@ -556,7 +572,7 @@ void CPUParticles::_particles_process(float p_delta) { if (restart_time >= prev_time && restart_time < time) { restart = true; if (fractional_delta) { - local_delta = (time - restart_time) * lifetime; + local_delta = time - restart_time; } } @@ -564,17 +580,21 @@ void CPUParticles::_particles_process(float p_delta) { if (restart_time >= prev_time) { restart = true; if (fractional_delta) { - local_delta = (1.0 - restart_time + time) * lifetime; + local_delta = lifetime - restart_time + time; } } else if (restart_time < time) { restart = true; if (fractional_delta) { - local_delta = (time - restart_time) * lifetime; + local_delta = time - restart_time; } } } + if (p.time * (1.0 - explosiveness_ratio) > p.lifetime) { + restart = true; + } + if (restart) { if (!emitting) { @@ -606,13 +626,13 @@ void CPUParticles::_particles_process(float p_delta) { p.anim_offset_rand = Math::randf(); if (flags[FLAG_DISABLE_Z]) { - float angle1_rad = (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; + float angle1_rad = Math::atan2(direction.y, direction.x) + (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0); p.velocity = rot * parameters[PARAM_INITIAL_LINEAR_VELOCITY] * Math::lerp(1.0f, float(Math::randf()), randomness[PARAM_INITIAL_LINEAR_VELOCITY]); } else { //initiate velocity spread in 3D - float angle1_rad = (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; - float angle2_rad = (Math::randf() * 2.0 - 1.0) * (1.0 - flatness) * Math_PI * spread / 180.0; + float angle1_rad = Math::atan2(direction.x, direction.z) + (Math::randf() * 2.0 - 1.0) * Math_PI * spread / 180.0; + float angle2_rad = Math::atan2(direction.y, Math::abs(direction.z)) + (Math::randf() * 2.0 - 1.0) * (1.0 - flatness) * Math_PI * spread / 180.0; Vector3 direction_xz = Vector3(Math::sin(angle1_rad), 0, Math::cos(angle1_rad)); Vector3 direction_yz = Vector3(0, Math::sin(angle2_rad), Math::cos(angle2_rad)); @@ -628,6 +648,7 @@ void CPUParticles::_particles_process(float p_delta) { p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]); //animation offset (0-1) p.transform = Transform(); p.time = 0; + p.lifetime = lifetime * (1.0 - Math::randf() * lifetime_randomness); p.base_color = Color(1, 1, 1, 1); switch (emission_shape) { @@ -635,7 +656,9 @@ void CPUParticles::_particles_process(float p_delta) { //do none } break; case EMISSION_SHAPE_SPHERE: { - p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0).normalized() * emission_sphere_radius; + float s = 2.0 * Math::randf() - 1.0, t = 2.0 * Math_PI * Math::randf(); + float radius = emission_sphere_radius * Math::sqrt(1.0 - s * s); + p.transform.origin = Vector3(radius * Math::cos(t), radius * Math::sin(t), emission_sphere_radius * s); } break; case EMISSION_SHAPE_BOX: { p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * emission_box_extents; @@ -691,6 +714,8 @@ void CPUParticles::_particles_process(float p_delta) { } else if (!p.active) { continue; + } else if (p.time > p.lifetime) { + p.active = false; } else { uint32_t alt_seed = p.seed; @@ -702,16 +727,14 @@ void CPUParticles::_particles_process(float p_delta) { if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(p.custom[1]); } - /* - float tex_orbit_velocity = 0.0; + float tex_orbit_velocity = 0.0; if (flags[FLAG_DISABLE_Z]) { - - if (curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY].is_valid()) { - tex_orbit_velocity = curve_parameters[PARAM_INITIAL_ORBIT_VELOCITY]->interpolate(p.custom[1]); + if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { + tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(p.custom[1]); } } -*/ + float tex_angular_velocity = 0.0; if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(p.custom[1]); @@ -765,8 +788,9 @@ void CPUParticles::_particles_process(float p_delta) { //apply tangential acceleration; if (flags[FLAG_DISABLE_Z]) { - Vector3 yx = Vector3(diff.y, 0, diff.x); - force += yx.length() > 0.0 ? (yx * Vector3(-1.0, 0, 1.0)) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + Vector2 yx = Vector2(diff.y, diff.x); + Vector2 yx2 = (yx * Vector2(-1.0, 1.0)).normalized(); + force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); } else { Vector3 crossDiff = diff.normalized().cross(gravity.normalized()); @@ -775,18 +799,18 @@ void CPUParticles::_particles_process(float p_delta) { //apply attractor forces p.velocity += force * local_delta; //orbit velocity -#if 0 if (flags[FLAG_DISABLE_Z]) { - - float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random); + float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); if (orbit_amount != 0.0) { - float ang = orbit_amount * DELTA * pi * 2.0; - mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang))); - TRANSFORM[3].xy -= diff.xy; - TRANSFORM[3].xy += rot * diff.xy; + float ang = orbit_amount * local_delta * Math_PI * 2.0; + // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, + // but we use -ang here to reproduce its behavior. + Transform2D rot = Transform2D(-ang, Vector2()); + Vector2 rotv = rot.basis_xform(Vector2(diff.x, diff.y)); + p.transform.origin -= Vector3(diff.x, diff.y, 0); + p.transform.origin += Vector3(rotv.x, rotv.y, 0); } } -#endif if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { p.velocity = p.velocity.normalized() * tex_linear_velocity; } @@ -921,11 +945,6 @@ void CPUParticles::_update_particle_data_buffer() { PoolVector<Particle>::Read r = particles.read(); float *ptr = w.ptr(); - Transform un_transform; - if (!local_coords) { - un_transform = get_global_transform().affine_inverse(); - } - if (draw_order != DRAW_ORDER_INDEX) { ow = particle_order.write(); order = ow.ptr(); @@ -943,7 +962,12 @@ void CPUParticles::_update_particle_data_buffer() { Vector3 dir = c->get_global_transform().basis.get_axis(2); //far away to close if (local_coords) { - dir = un_transform.basis.xform(dir).normalized(); + + // will look different from Particles in editor as this is based on the camera in the scenetree + // and not the editor camera + dir = inv_emission_transform.xform(dir).normalized(); + } else { + dir = dir.normalized(); } SortArray<int, SortAxis> sorter; @@ -961,7 +985,7 @@ void CPUParticles::_update_particle_data_buffer() { Transform t = r[idx].transform; if (!local_coords) { - t = un_transform * t; + t = inv_emission_transform * t; } if (r[idx].active) { @@ -1004,6 +1028,27 @@ void CPUParticles::_update_particle_data_buffer() { #endif } +void CPUParticles::_set_redraw(bool p_redraw) { + if (redraw == p_redraw) + return; + redraw = p_redraw; +#ifndef NO_THREADS + update_mutex->lock(); +#endif + if (redraw) { + VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, true); + VS::get_singleton()->multimesh_set_visible_instances(multimesh, -1); + } else { + VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); + VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, false); + VS::get_singleton()->multimesh_set_visible_instances(multimesh, 0); + } +#ifndef NO_THREADS + update_mutex->unlock(); +#endif +} + void CPUParticles::_update_render_thread() { #ifndef NO_THREADS @@ -1022,58 +1067,29 @@ void CPUParticles::_update_render_thread() { void CPUParticles::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - if (is_processing_internal()) { - -#ifndef NO_THREADS - update_mutex->lock(); -#endif - VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, true); -#ifndef NO_THREADS - update_mutex->unlock(); -#endif - } + set_process_internal(emitting); } if (p_what == NOTIFICATION_EXIT_TREE) { - if (is_processing_internal()) { - -#ifndef NO_THREADS - update_mutex->lock(); -#endif - VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, false); -#ifndef NO_THREADS - update_mutex->unlock(); -#endif - } - } - - if (p_what == NOTIFICATION_PAUSED || p_what == NOTIFICATION_UNPAUSED) { + _set_redraw(false); } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - if (particles.size() == 0 || !is_visible_in_tree()) + if (particles.size() == 0 || !is_visible_in_tree()) { + _set_redraw(false); return; + } float delta = get_process_delta_time(); if (emitting) { - inactive_time = 0; } else { inactive_time += delta; if (inactive_time > lifetime * 1.2) { set_process_internal(false); -#ifndef NO_THREADS - update_mutex->lock(); -#endif - VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); - VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, false); + _set_redraw(false); -#ifndef NO_THREADS - update_mutex->unlock(); -#endif //reset variables time = 0; inactive_time = 0; @@ -1082,6 +1098,7 @@ void CPUParticles::_notification(int p_what) { return; } } + _set_redraw(true); bool processed = false; @@ -1131,6 +1148,46 @@ void CPUParticles::_notification(int p_what) { _update_particle_data_buffer(); } } + + if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { + + inv_emission_transform = get_global_transform().affine_inverse(); + + if (!local_coords) { + + int pc = particles.size(); + + PoolVector<float>::Write w = particle_data.write(); + PoolVector<Particle>::Read r = particles.read(); + float *ptr = w.ptr(); + + for (int i = 0; i < pc; i++) { + + Transform t = inv_emission_transform * r[i].transform; + + if (r[i].active) { + ptr[0] = t.basis.elements[0][0]; + ptr[1] = t.basis.elements[0][1]; + ptr[2] = t.basis.elements[0][2]; + ptr[3] = t.origin.x; + ptr[4] = t.basis.elements[1][0]; + ptr[5] = t.basis.elements[1][1]; + ptr[6] = t.basis.elements[1][2]; + ptr[7] = t.origin.y; + ptr[8] = t.basis.elements[2][0]; + ptr[9] = t.basis.elements[2][1]; + ptr[10] = t.basis.elements[2][2]; + ptr[11] = t.origin.z; + } else { + zeromem(ptr, sizeof(float) * 12); + } + + ptr += 17; + } + + can_update = true; + } + } } void CPUParticles::convert_from_particles(Node *p_particles) { @@ -1156,6 +1213,7 @@ void CPUParticles::convert_from_particles(Node *p_particles) { if (material.is_null()) return; + set_direction(material->get_direction()); set_spread(material->get_spread()); set_flatness(material->get_flatness()); @@ -1175,6 +1233,7 @@ void CPUParticles::convert_from_particles(Node *p_particles) { set_emission_box_extents(material->get_emission_box_extents()); set_gravity(material->get_gravity()); + set_lifetime_randomness(material->get_lifetime_randomness()); #define CONVERT_PARAM(m_param) \ set_param(m_param, material->get_param(ParticlesMaterial::m_param)); \ @@ -1186,7 +1245,7 @@ void CPUParticles::convert_from_particles(Node *p_particles) { CONVERT_PARAM(PARAM_INITIAL_LINEAR_VELOCITY); CONVERT_PARAM(PARAM_ANGULAR_VELOCITY); - // CONVERT_PARAM(PARAM_ORBIT_VELOCITY); + CONVERT_PARAM(PARAM_ORBIT_VELOCITY); CONVERT_PARAM(PARAM_LINEAR_ACCEL); CONVERT_PARAM(PARAM_RADIAL_ACCEL); CONVERT_PARAM(PARAM_TANGENTIAL_ACCEL); @@ -1209,6 +1268,7 @@ void CPUParticles::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pre_process_time", "secs"), &CPUParticles::set_pre_process_time); ClassDB::bind_method(D_METHOD("set_explosiveness_ratio", "ratio"), &CPUParticles::set_explosiveness_ratio); ClassDB::bind_method(D_METHOD("set_randomness_ratio", "ratio"), &CPUParticles::set_randomness_ratio); + ClassDB::bind_method(D_METHOD("set_lifetime_randomness", "random"), &CPUParticles::set_lifetime_randomness); ClassDB::bind_method(D_METHOD("set_use_local_coordinates", "enable"), &CPUParticles::set_use_local_coordinates); ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &CPUParticles::set_fixed_fps); ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &CPUParticles::set_fractional_delta); @@ -1221,6 +1281,7 @@ void CPUParticles::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pre_process_time"), &CPUParticles::get_pre_process_time); ClassDB::bind_method(D_METHOD("get_explosiveness_ratio"), &CPUParticles::get_explosiveness_ratio); ClassDB::bind_method(D_METHOD("get_randomness_ratio"), &CPUParticles::get_randomness_ratio); + ClassDB::bind_method(D_METHOD("get_lifetime_randomness"), &CPUParticles::get_lifetime_randomness); ClassDB::bind_method(D_METHOD("get_use_local_coordinates"), &CPUParticles::get_use_local_coordinates); ClassDB::bind_method(D_METHOD("get_fixed_fps"), &CPUParticles::get_fixed_fps); ClassDB::bind_method(D_METHOD("get_fractional_delta"), &CPUParticles::get_fractional_delta); @@ -1238,12 +1299,13 @@ void CPUParticles::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); 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_EXP_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,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_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,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"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); ADD_GROUP("Drawing", ""); @@ -1257,6 +1319,9 @@ void CPUParticles::_bind_methods() { //////////////////////////////// + ClassDB::bind_method(D_METHOD("set_direction", "direction"), &CPUParticles::set_direction); + ClassDB::bind_method(D_METHOD("get_direction"), &CPUParticles::get_direction); + ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &CPUParticles::set_spread); ClassDB::bind_method(D_METHOD("get_spread"), &CPUParticles::get_spread); @@ -1317,7 +1382,8 @@ void CPUParticles::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_align_y"), "set_particle_flag", "get_particle_flag", FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_rotate_y"), "set_particle_flag", "get_particle_flag", FLAG_ROTATE_Y); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_disable_z"), "set_particle_flag", "get_particle_flag", FLAG_DISABLE_Z); - ADD_GROUP("Spread", ""); + ADD_GROUP("Direction", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "direction"), "set_direction", "get_direction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "flatness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_flatness", "get_flatness"); ADD_GROUP("Gravity", ""); @@ -1329,12 +1395,10 @@ void CPUParticles::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_velocity", PROPERTY_HINT_RANGE, "-720,720,0.01,or_lesser,or_greater"), "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, "Curve"), "set_param_curve", "get_param_curve", PARAM_ANGULAR_VELOCITY); - /* ADD_GROUP("Orbit Velocity", "orbit_"); 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, "Curve"), "set_param_curve", "get_param_curve", PARAM_ORBIT_VELOCITY); -*/ ADD_GROUP("Linear Accel", "linear_"); 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); @@ -1377,7 +1441,7 @@ void CPUParticles::_bind_methods() { BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY); BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY); - //BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY); + BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY); BIND_ENUM_CONSTANT(PARAM_LINEAR_ACCEL); BIND_ENUM_CONSTANT(PARAM_RADIAL_ACCEL); BIND_ENUM_CONSTANT(PARAM_TANGENTIAL_ACCEL); @@ -1391,6 +1455,7 @@ void CPUParticles::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY); BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); + BIND_ENUM_CONSTANT(FLAG_DISABLE_Z); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT); @@ -1406,8 +1471,12 @@ CPUParticles::CPUParticles() { inactive_time = 0; frame_remainder = 0; cycle = 0; + redraw = false; + + set_notify_transform(true); multimesh = VisualServer::get_singleton()->multimesh_create(); + VisualServer::get_singleton()->multimesh_set_visible_instances(multimesh, 0); set_base(multimesh); set_emitting(true); @@ -1419,15 +1488,18 @@ CPUParticles::CPUParticles() { set_pre_process_time(0); set_explosiveness_ratio(0); set_randomness_ratio(0); + set_lifetime_randomness(0); set_use_local_coordinates(true); set_draw_order(DRAW_ORDER_INDEX); set_speed_scale(1); + set_direction(Vector3(1, 0, 0)); set_spread(45); set_flatness(0); - set_param(PARAM_INITIAL_LINEAR_VELOCITY, 1); - //set_param(PARAM_ORBIT_VELOCITY, 0); + set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param(PARAM_ANGULAR_VELOCITY, 0); + set_param(PARAM_ORBIT_VELOCITY, 0); set_param(PARAM_LINEAR_ACCEL, 0); set_param(PARAM_RADIAL_ACCEL, 0); set_param(PARAM_TANGENTIAL_ACCEL, 0); diff --git a/scene/3d/cpu_particles.h b/scene/3d/cpu_particles.h index b50befe7be..66b37f359a 100644 --- a/scene/3d/cpu_particles.h +++ b/scene/3d/cpu_particles.h @@ -34,10 +34,6 @@ #include "core/rid.h" #include "scene/3d/visual_instance.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class CPUParticles : public GeometryInstance { private: GDCLASS(CPUParticles, GeometryInstance); @@ -53,7 +49,7 @@ public: PARAM_INITIAL_LINEAR_VELOCITY, PARAM_ANGULAR_VELOCITY, - //PARAM_ORBIT_VELOCITY, + PARAM_ORBIT_VELOCITY, PARAM_LINEAR_ACCEL, PARAM_RADIAL_ACCEL, PARAM_TANGENTIAL_ACCEL, @@ -95,6 +91,7 @@ private: float hue_rot_rand; float anim_offset_rand; float time; + float lifetime; Color base_color; uint32_t seed; @@ -104,6 +101,7 @@ private: float inactive_time; float frame_remainder; int cycle; + bool redraw; RID multimesh; @@ -115,7 +113,7 @@ private: const Particle *particles; bool operator()(int p_a, int p_b) const { - return particles[p_a].time < particles[p_b].time; + return particles[p_a].time > particles[p_b].time; } }; @@ -136,11 +134,14 @@ private: float pre_process_time; float explosiveness_ratio; float randomness_ratio; + float lifetime_randomness; float speed_scale; bool local_coords; int fixed_fps; bool fractional_delta; + Transform inv_emission_transform; + volatile bool can_update; DrawOrder draw_order; @@ -149,6 +150,7 @@ private: //////// + Vector3 direction; float spread; float flatness; @@ -178,6 +180,8 @@ private: void _update_render_thread(); + void _set_redraw(bool p_redraw); + protected: static void _bind_methods(); void _notification(int p_what); @@ -194,6 +198,7 @@ public: void set_pre_process_time(float p_time); void set_explosiveness_ratio(float p_ratio); void set_randomness_ratio(float p_ratio); + void set_lifetime_randomness(float p_random); void set_visibility_aabb(const AABB &p_aabb); void set_use_local_coordinates(bool p_enable); void set_speed_scale(float p_scale); @@ -205,6 +210,7 @@ public: float get_pre_process_time() const; float get_explosiveness_ratio() const; float get_randomness_ratio() const; + float get_lifetime_randomness() const; AABB get_visibility_aabb() const; bool get_use_local_coordinates() const; float get_speed_scale() const; @@ -226,6 +232,9 @@ public: /////////////////// + void set_direction(Vector3 p_direction); + Vector3 get_direction() const; + void set_spread(float p_spread); float get_spread() const; @@ -244,7 +253,7 @@ public: void set_color(const Color &p_color); Color get_color() const; - void set_color_ramp(const Ref<Gradient> &p_texture); + void set_color_ramp(const Ref<Gradient> &p_ramp); Ref<Gradient> get_color_ramp() const; void set_particle_flag(Flags p_flag, bool p_enable); diff --git a/scene/3d/gi_probe.cpp b/scene/3d/gi_probe.cpp index 5b08ea7790..a04f156d80 100644 --- a/scene/3d/gi_probe.cpp +++ b/scene/3d/gi_probe.cpp @@ -30,6 +30,8 @@ #include "gi_probe.h" +#include "core/os/os.h" + #include "mesh_instance.h" #include "voxel_light_baker.h" @@ -490,6 +492,14 @@ PoolVector<Face3> GIProbe::get_faces(uint32_t p_usage_flags) const { return PoolVector<Face3>(); } +String GIProbe::get_configuration_warning() const { + + if (OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { + return TTR("GIProbes are not supported by the GLES2 video driver.\nUse a BakedLightmap instead."); + } + return String(); +} + void GIProbe::_bind_methods() { ClassDB::bind_method(D_METHOD("set_probe_data", "data"), &GIProbe::set_probe_data); @@ -529,7 +539,7 @@ void GIProbe::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "subdiv", PROPERTY_HINT_ENUM, "64,128,256,512"), "set_subdiv", "get_subdiv"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); ADD_PROPERTY(PropertyInfo(Variant::INT, "dynamic_range", PROPERTY_HINT_RANGE, "1,16,1"), "set_dynamic_range", "get_dynamic_range"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_energy", "get_energy"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "propagation", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_propagation", "get_propagation"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0,4,0.001"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "normal_bias", PROPERTY_HINT_RANGE, "0,4,0.001"), "set_normal_bias", "get_normal_bias"); @@ -561,4 +571,5 @@ GIProbe::GIProbe() { } GIProbe::~GIProbe() { + VS::get_singleton()->free(gi_probe); } diff --git a/scene/3d/gi_probe.h b/scene/3d/gi_probe.h index 87664e06b8..4badabbadf 100644 --- a/scene/3d/gi_probe.h +++ b/scene/3d/gi_probe.h @@ -168,6 +168,8 @@ public: virtual AABB get_aabb() const; virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + virtual String get_configuration_warning() const; + GIProbe(); ~GIProbe(); }; diff --git a/scene/3d/light.cpp b/scene/3d/light.cpp index 3b514dab8c..85ee925248 100644 --- a/scene/3d/light.cpp +++ b/scene/3d/light.cpp @@ -51,6 +51,7 @@ void Light::set_param(Param p_param, float p_value) { if (p_param == PARAM_SPOT_ANGLE) { _change_notify("spot_angle"); + update_configuration_warning(); } else if (p_param == PARAM_RANGE) { _change_notify("omni_range"); _change_notify("spot_range"); @@ -68,6 +69,10 @@ void Light::set_shadow(bool p_enable) { shadow = p_enable; VS::get_singleton()->light_set_shadow(light, p_enable); + + if (type == VisualServer::LIGHT_SPOT) { + update_configuration_warning(); + } } bool Light::has_shadow() const { @@ -152,6 +157,7 @@ PoolVector<Face3> Light::get_faces(uint32_t p_usage_flags) const { void Light::set_bake_mode(BakeMode p_mode) { bake_mode = p_mode; + VS::get_singleton()->light_set_use_gi(light, p_mode != BAKE_DISABLED); } Light::BakeMode Light::get_bake_mode() const { @@ -194,9 +200,6 @@ void Light::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { _update_visibility(); } - - if (p_what == NOTIFICATION_EXIT_TREE) { - } } void Light::set_editor_only(bool p_editor_only) { @@ -210,6 +213,13 @@ bool Light::is_editor_only() const { return editor_only; } +void Light::_validate_property(PropertyInfo &property) const { + + if (VisualServer::get_singleton()->is_low_end() && property.name == "shadow_contact") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } +} + void Light::_bind_methods() { ClassDB::bind_method(D_METHOD("set_editor_only", "editor_only"), &Light::set_editor_only); @@ -241,8 +251,8 @@ void Light::_bind_methods() { ADD_GROUP("Light", "light_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "light_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_color", "get_color"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_ENERGY); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_INDIRECT_ENERGY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_param", "get_param", PARAM_ENERGY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_param", "get_param", PARAM_INDIRECT_ENERGY); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "light_negative"), "set_negative", "is_negative"); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "light_specular", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SPECULAR); ADD_PROPERTY(PropertyInfo(Variant::INT, "light_bake_mode", PROPERTY_HINT_ENUM, "Disable,Indirect,All"), "set_bake_mode", "get_bake_mode"); @@ -286,7 +296,8 @@ Light::Light(VisualServer::LightType p_type) { case VS::LIGHT_DIRECTIONAL: light = VisualServer::get_singleton()->directional_light_create(); break; case VS::LIGHT_OMNI: light = VisualServer::get_singleton()->omni_light_create(); break; case VS::LIGHT_SPOT: light = VisualServer::get_singleton()->spot_light_create(); break; - default: {}; + default: { + }; } VS::get_singleton()->instance_set_base(get_instance(), light); @@ -399,7 +410,7 @@ DirectionalLight::DirectionalLight() : set_param(PARAM_SHADOW_NORMAL_BIAS, 0.8); set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_MAX_DISTANCE, 200); + set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_BIAS_SPLIT_SCALE, 0.25); set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); set_shadow_depth_range(SHADOW_DEPTH_RANGE_STABLE); @@ -456,6 +467,20 @@ OmniLight::OmniLight() : set_shadow_detail(SHADOW_DETAIL_HORIZONTAL); } +String SpotLight::get_configuration_warning() const { + String warning = Light::get_configuration_warning(); + + if (has_shadow() && get_param(PARAM_SPOT_ANGLE) >= 90.0) { + if (warning != String()) { + warning += "\n\n"; + } + + warning += TTR("A SpotLight with an angle wider than 90 degrees cannot cast shadows."); + } + + return warning; +} + void SpotLight::_bind_methods() { ADD_GROUP("Spot", "spot_"); diff --git a/scene/3d/light.h b/scene/3d/light.h index 85e0ce3c24..cc8675ead1 100644 --- a/scene/3d/light.h +++ b/scene/3d/light.h @@ -35,10 +35,6 @@ #include "scene/resources/texture.h" #include "servers/visual_server.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class Light : public VisualInstance { GDCLASS(Light, VisualInstance); @@ -92,6 +88,7 @@ protected: static void _bind_methods(); void _notification(int p_what); + virtual void _validate_property(PropertyInfo &property) const; Light(VisualServer::LightType p_type); @@ -220,6 +217,8 @@ protected: static void _bind_methods(); public: + virtual String get_configuration_warning() const; + SpotLight() : Light(VisualServer::LIGHT_SPOT) {} }; diff --git a/scene/3d/mesh_instance.cpp b/scene/3d/mesh_instance.cpp index 848889155b..89072519d5 100644 --- a/scene/3d/mesh_instance.cpp +++ b/scene/3d/mesh_instance.cpp @@ -96,7 +96,7 @@ void MeshInstance::_get_property_list(List<PropertyInfo> *p_list) const { ls.sort(); for (List<String>::Element *E = ls.front(); E; E = E->next()) { - p_list->push_back(PropertyInfo(Variant::REAL, E->get(), PROPERTY_HINT_RANGE, "0,1,0.01")); + p_list->push_back(PropertyInfo(Variant::REAL, E->get(), PROPERTY_HINT_RANGE, "0,1,0.00001")); } if (mesh.is_valid()) { diff --git a/scene/3d/mesh_instance.h b/scene/3d/mesh_instance.h index 022ef15aad..8b690b0c21 100644 --- a/scene/3d/mesh_instance.h +++ b/scene/3d/mesh_instance.h @@ -34,9 +34,6 @@ #include "scene/3d/visual_instance.h" #include "scene/resources/mesh.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class MeshInstance : public GeometryInstance { GDCLASS(MeshInstance, GeometryInstance); diff --git a/scene/3d/multimesh_instance.h b/scene/3d/multimesh_instance.h index 8f41aa8fd2..ca69c73251 100644 --- a/scene/3d/multimesh_instance.h +++ b/scene/3d/multimesh_instance.h @@ -34,10 +34,6 @@ #include "scene/3d/visual_instance.h" #include "scene/resources/multimesh.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class MultiMeshInstance : public GeometryInstance { GDCLASS(MultiMeshInstance, GeometryInstance); diff --git a/scene/3d/navigation.cpp b/scene/3d/navigation.cpp index 5a3c8223ff..12d562c0c6 100644 --- a/scene/3d/navigation.cpp +++ b/scene/3d/navigation.cpp @@ -90,7 +90,6 @@ void Navigation::_navmesh_link(int p_id) { if (!valid) { nm.polygons.pop_back(); ERR_CONTINUE(!valid); - continue; } p.center = center; @@ -340,16 +339,12 @@ Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector }; Vector3 entry = Geometry::get_closest_point_to_segment(begin_poly->entry, edge); - begin_poly->edges[i].C->distance = begin_poly->entry.distance_to(entry); + begin_poly->edges[i].C->distance = begin_point.distance_to(entry); begin_poly->edges[i].C->entry = entry; #else begin_poly->edges[i].C->distance = begin_poly->center.distance_to(begin_poly->edges[i].C->center); #endif open_list.push_back(begin_poly->edges[i].C); - - if (begin_poly->edges[i].C == end_poly) { - found_route = true; - } } } @@ -370,28 +365,7 @@ Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector float cost = p->distance; #ifdef USE_ENTRY_POINT - int es = p->edges.size(); - - float shortest_distance = 1e30; - - for (int i = 0; i < es; i++) { - Polygon::Edge &e = p->edges.write[i]; - - if (!e.C) - continue; - - Vector3 edge[2] = { - _get_vertex(p->edges[i].point), - _get_vertex(p->edges[(i + 1) % es].point) - }; - - Vector3 edge_point = Geometry::get_closest_point_to_segment(p->entry, edge); - float dist = p->entry.distance_to(edge_point); - if (dist < shortest_distance) - shortest_distance = dist; - } - - cost += shortest_distance; + cost += p->entry.distance_to(end_point); #else cost += p->center.distance_to(end_point); #endif @@ -404,6 +378,12 @@ Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector Polygon *p = least_cost_poly->get(); //open the neighbours for search + if (p == end_poly) { + //oh my reached end! stop algorithm + found_route = true; + break; + } + for (int i = 0; i < p->edges.size(); i++) { Polygon::Edge &e = p->edges.write[i]; @@ -411,7 +391,17 @@ Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector if (!e.C) continue; +#ifdef USE_ENTRY_POINT + Vector3 edge[2] = { + _get_vertex(p->edges[i].point), + _get_vertex(p->edges[(i + 1) % p->edges.size()].point) + }; + + Vector3 entry = Geometry::get_closest_point_to_segment(p->entry, edge); + float distance = p->entry.distance_to(entry) + p->distance; +#else float distance = p->center.distance_to(e.C->center) + p->distance; +#endif if (e.C->prev_edge != -1) { //oh this was visited already, can we win the cost? @@ -420,25 +410,22 @@ Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector e.C->prev_edge = e.C_edge; e.C->distance = distance; +#ifdef USE_ENTRY_POINT + e.C->entry = entry; +#endif } } else { //add to open neighbours e.C->prev_edge = e.C_edge; e.C->distance = distance; +#ifdef USE_ENTRY_POINT + e.C->entry = entry; +#endif open_list.push_back(e.C); - - if (e.C == end_poly) { - //oh my reached end! stop algorithm - found_route = true; - break; - } } } - if (found_route) - break; - open_list.erase(least_cost_poly); } @@ -539,8 +526,12 @@ Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector path.push_back(end_point); while (true) { int prev = p->prev_edge; +#ifdef USE_ENTRY_POINT + Vector3 point = p->entry; +#else int prev_n = (p->prev_edge + 1) % p->edges.size(); Vector3 point = (_get_vertex(p->edges[prev].point) + _get_vertex(p->edges[prev_n].point)) * 0.5; +#endif path.push_back(point); p = p->edges[prev].C; if (p == begin_poly) diff --git a/scene/3d/navigation_mesh.cpp b/scene/3d/navigation_mesh.cpp index 75301af657..f82543b789 100644 --- a/scene/3d/navigation_mesh.cpp +++ b/scene/3d/navigation_mesh.cpp @@ -73,6 +73,41 @@ int NavigationMesh::get_sample_partition_type() const { return static_cast<int>(partition_type); } +void NavigationMesh::set_parsed_geometry_type(int p_value) { + ERR_FAIL_COND(p_value >= PARSED_GEOMETRY_MAX); + parsed_geometry_type = static_cast<ParsedGeometryType>(p_value); + _change_notify(); +} + +int NavigationMesh::get_parsed_geometry_type() const { + return parsed_geometry_type; +} + +void NavigationMesh::set_collision_mask(uint32_t p_mask) { + + collision_mask = p_mask; +} + +uint32_t NavigationMesh::get_collision_mask() const { + + return collision_mask; +} + +void NavigationMesh::set_collision_mask_bit(int p_bit, bool p_value) { + + uint32_t mask = get_collision_mask(); + if (p_value) + mask |= 1 << p_bit; + else + mask &= ~(1 << p_bit); + set_collision_mask(mask); +} + +bool NavigationMesh::get_collision_mask_bit(int p_bit) const { + + return get_collision_mask() & (1 << p_bit); +} + void NavigationMesh::set_cell_size(float p_value) { cell_size = p_value; } @@ -204,6 +239,7 @@ bool NavigationMesh::get_filter_walkable_low_height_spans() const { void NavigationMesh::set_vertices(const PoolVector<Vector3> &p_vertices) { vertices = p_vertices; + _change_notify(); } PoolVector<Vector3> NavigationMesh::get_vertices() const { @@ -217,6 +253,7 @@ void NavigationMesh::_set_polygons(const Array &p_array) { for (int i = 0; i < p_array.size(); i++) { polygons.write[i].indices = p_array[i]; } + _change_notify(); } Array NavigationMesh::_get_polygons() const { @@ -235,6 +272,7 @@ void NavigationMesh::add_polygon(const Vector<int> &p_polygon) { Polygon polygon; polygon.indices = p_polygon; polygons.push_back(polygon); + _change_notify(); } int NavigationMesh::get_polygon_count() const { @@ -292,11 +330,11 @@ Ref<Mesh> NavigationMesh::get_debug_mesh() { if (ek.from < ek.to) SWAP(ek.from, ek.to); - Map<_EdgeKey, bool>::Element *E = edge_map.find(ek); + Map<_EdgeKey, bool>::Element *F = edge_map.find(ek); - if (E) { + if (F) { - E->get() = false; + F->get() = false; } else { @@ -340,6 +378,15 @@ void NavigationMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_sample_partition_type", "sample_partition_type"), &NavigationMesh::set_sample_partition_type); ClassDB::bind_method(D_METHOD("get_sample_partition_type"), &NavigationMesh::get_sample_partition_type); + ClassDB::bind_method(D_METHOD("set_parsed_geometry_type", "geometry_type"), &NavigationMesh::set_parsed_geometry_type); + ClassDB::bind_method(D_METHOD("get_parsed_geometry_type"), &NavigationMesh::get_parsed_geometry_type); + + ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &NavigationMesh::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &NavigationMesh::get_collision_mask); + + ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &NavigationMesh::set_collision_mask_bit); + ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &NavigationMesh::get_collision_mask_bit); + ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &NavigationMesh::set_cell_size); ClassDB::bind_method(D_METHOD("get_cell_size"), &NavigationMesh::get_cell_size); @@ -405,30 +452,45 @@ void NavigationMesh::_bind_methods() { BIND_CONSTANT(SAMPLE_PARTITION_MONOTONE); BIND_CONSTANT(SAMPLE_PARTITION_LAYERS); + BIND_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES); + BIND_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS); + BIND_CONSTANT(PARSED_GEOMETRY_BOTH); + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type/sample_partition_type", PROPERTY_HINT_ENUM, "Watershed,Monotone,Layers"), "set_sample_partition_type", "get_sample_partition_type"); - - ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell/size", PROPERTY_HINT_RANGE, "0.1,1.0,0.01"), "set_cell_size", "get_cell_size"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell/height", PROPERTY_HINT_RANGE, "0.1,1.0,0.01"), "set_cell_height", "get_cell_height"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/height", PROPERTY_HINT_RANGE, "0.1,5.0,0.01"), "set_agent_height", "get_agent_height"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/radius", PROPERTY_HINT_RANGE, "0.1,5.0,0.01"), "set_agent_radius", "get_agent_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/max_climb", PROPERTY_HINT_RANGE, "0.1,5.0,0.01"), "set_agent_max_climb", "get_agent_max_climb"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry/parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Both"), "set_parsed_geometry_type", "get_parsed_geometry_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "geometry/collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell/size", PROPERTY_HINT_RANGE, "0.1,1.0,0.01,or_greater"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell/height", PROPERTY_HINT_RANGE, "0.1,1.0,0.01,or_greater"), "set_cell_height", "get_cell_height"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/height", PROPERTY_HINT_RANGE, "0.1,5.0,0.01,or_greater"), "set_agent_height", "get_agent_height"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/radius", PROPERTY_HINT_RANGE, "0.1,5.0,0.01,or_greater"), "set_agent_radius", "get_agent_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/max_climb", PROPERTY_HINT_RANGE, "0.1,5.0,0.01,or_greater"), "set_agent_max_climb", "get_agent_max_climb"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/max_slope", PROPERTY_HINT_RANGE, "0.0,90.0,0.1"), "set_agent_max_slope", "get_agent_max_slope"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "region/min_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01"), "set_region_min_size", "get_region_min_size"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "region/merge_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01"), "set_region_merge_size", "get_region_merge_size"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge/max_length", PROPERTY_HINT_RANGE, "0.0,50.0,0.01"), "set_edge_max_length", "get_edge_max_length"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge/max_error", PROPERTY_HINT_RANGE, "0.1,3.0,0.01"), "set_edge_max_error", "get_edge_max_error"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "polygon/verts_per_poly", PROPERTY_HINT_RANGE, "3.0,12.0,1.0"), "set_verts_per_poly", "get_verts_per_poly"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "detail/sample_distance", PROPERTY_HINT_RANGE, "0.0,16.0,0.01"), "set_detail_sample_distance", "get_detail_sample_distance"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "detail/sample_max_error", PROPERTY_HINT_RANGE, "0.0,16.0,0.01"), "set_detail_sample_max_error", "get_detail_sample_max_error"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "region/min_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01,or_greater"), "set_region_min_size", "get_region_min_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "region/merge_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01,or_greater"), "set_region_merge_size", "get_region_merge_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge/max_length", PROPERTY_HINT_RANGE, "0.0,50.0,0.01,or_greater"), "set_edge_max_length", "get_edge_max_length"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge/max_error", PROPERTY_HINT_RANGE, "0.1,3.0,0.01,or_greater"), "set_edge_max_error", "get_edge_max_error"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "polygon/verts_per_poly", PROPERTY_HINT_RANGE, "3.0,12.0,1.0,or_greater"), "set_verts_per_poly", "get_verts_per_poly"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "detail/sample_distance", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater"), "set_detail_sample_distance", "get_detail_sample_distance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "detail/sample_max_error", PROPERTY_HINT_RANGE, "0.0,16.0,0.01,or_greater"), "set_detail_sample_max_error", "get_detail_sample_max_error"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/low_hanging_obstacles"), "set_filter_low_hanging_obstacles", "get_filter_low_hanging_obstacles"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/ledge_spans"), "set_filter_ledge_spans", "get_filter_ledge_spans"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/filter_walkable_low_height_spans"), "set_filter_walkable_low_height_spans", "get_filter_walkable_low_height_spans"); } +void NavigationMesh::_validate_property(PropertyInfo &property) const { + if (property.name == "geometry/collision_mask") { + if (parsed_geometry_type == PARSED_GEOMETRY_MESH_INSTANCES) { + property.usage = 0; + return; + } + } +} + NavigationMesh::NavigationMesh() { cell_size = 0.3f; cell_height = 0.2f; @@ -445,7 +507,8 @@ NavigationMesh::NavigationMesh() { detail_sample_max_error = 1.0f; partition_type = SAMPLE_PARTITION_WATERSHED; - + parsed_geometry_type = PARSED_GEOMETRY_MESH_INSTANCES; + collision_mask = 0xFFFFFFFF; filter_low_hanging_obstacles = false; filter_ledge_spans = false; filter_walkable_low_height_spans = false; @@ -566,8 +629,17 @@ void NavigationMeshInstance::set_navigation_mesh(const Ref<NavigationMesh> &p_na navigation->navmesh_remove(nav_id); nav_id = -1; } + + if (navmesh.is_valid()) { + navmesh->remove_change_receptor(this); + } + navmesh = p_navmesh; + if (navmesh.is_valid()) { + navmesh->add_change_receptor(this); + } + if (navigation && navmesh.is_valid() && enabled) { nav_id = navigation->navmesh_add(navmesh, get_relative_transform(navigation), this); } @@ -617,6 +689,11 @@ void NavigationMeshInstance::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); } +void NavigationMeshInstance::_changed_callback(Object *p_changed, const char *p_prop) { + update_gizmo(); + update_configuration_warning(); +} + NavigationMeshInstance::NavigationMeshInstance() { debug_view = NULL; @@ -625,3 +702,8 @@ NavigationMeshInstance::NavigationMeshInstance() { enabled = true; set_notify_transform(true); } + +NavigationMeshInstance::~NavigationMeshInstance() { + if (navmesh.is_valid()) + navmesh->remove_change_receptor(this); +} diff --git a/scene/3d/navigation_mesh.h b/scene/3d/navigation_mesh.h index 74531e2423..5fbf3998ff 100644 --- a/scene/3d/navigation_mesh.h +++ b/scene/3d/navigation_mesh.h @@ -57,6 +57,7 @@ class NavigationMesh : public Resource { protected: static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const; void _set_polygons(const Array &p_array); Array _get_polygons() const; @@ -69,6 +70,13 @@ public: SAMPLE_PARTITION_MAX }; + enum ParsedGeometryType { + PARSED_GEOMETRY_MESH_INSTANCES = 0, + PARSED_GEOMETRY_STATIC_COLLIDERS, + PARSED_GEOMETRY_BOTH, + PARSED_GEOMETRY_MAX + }; + protected: float cell_size; float cell_height; @@ -85,6 +93,8 @@ protected: float detail_sample_max_error; SamplePartitionType partition_type; + ParsedGeometryType parsed_geometry_type; + uint32_t collision_mask; bool filter_low_hanging_obstacles; bool filter_ledge_spans; @@ -95,6 +105,15 @@ public: void set_sample_partition_type(int p_value); int get_sample_partition_type() const; + void set_parsed_geometry_type(int p_value); + int get_parsed_geometry_type() const; + + void set_collision_mask(uint32_t p_mask); + uint32_t get_collision_mask() const; + + void set_collision_mask_bit(int p_bit, bool p_value); + bool get_collision_mask_bit(int p_bit) const; + void set_cell_size(float p_value); float get_cell_size() const; @@ -174,6 +193,7 @@ class NavigationMeshInstance : public Spatial { protected: void _notification(int p_what); static void _bind_methods(); + void _changed_callback(Object *p_changed, const char *p_prop); public: void set_enabled(bool p_enabled); @@ -185,6 +205,7 @@ public: String get_configuration_warning() const; NavigationMeshInstance(); + ~NavigationMeshInstance(); }; #endif // NAVIGATION_MESH_H diff --git a/scene/3d/particles.cpp b/scene/3d/particles.cpp index 2add50dd5d..a6ccdb5791 100644 --- a/scene/3d/particles.cpp +++ b/scene/3d/particles.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "particles.h" + +#include "core/os/os.h" #include "scene/resources/particles_material.h" #include "servers/visual_server.h" @@ -45,6 +47,12 @@ PoolVector<Face3> Particles::get_faces(uint32_t p_usage_flags) const { void Particles::set_emitting(bool p_emitting) { VS::get_singleton()->particles_set_emitting(particles, p_emitting); + + if (p_emitting && one_shot) { + set_process_internal(true); + } else if (!p_emitting) { + set_process_internal(false); + } } void Particles::set_amount(int p_amount) { @@ -64,8 +72,16 @@ void Particles::set_one_shot(bool p_one_shot) { one_shot = p_one_shot; VS::get_singleton()->particles_set_one_shot(particles, one_shot); - if (!one_shot && is_emitting()) - VisualServer::get_singleton()->particles_restart(particles); + + if (is_emitting()) { + + set_process_internal(true); + if (!one_shot) + VisualServer::get_singleton()->particles_restart(particles); + } + + if (!one_shot) + set_process_internal(false); } void Particles::set_pre_process_time(float p_time) { @@ -224,6 +240,10 @@ bool Particles::get_fractional_delta() const { String Particles::get_configuration_warning() const { + if (OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { + return TTR("GPU-based particles are not supported by the GLES2 video driver.\nUse the CPUParticles node instead. You can use the \"Convert to CPUParticles\" option for this purpose."); + } + String warnings; bool meshes_found = false; @@ -237,7 +257,7 @@ String Particles::get_configuration_warning() const { SpatialMaterial *spat = Object::cast_to<SpatialMaterial>(draw_passes[i]->surface_get_material(j).ptr()); anim_material_found = anim_material_found || (spat && spat->get_billboard_mode() == SpatialMaterial::BILLBOARD_PARTICLES); } - if (meshes_found && anim_material_found) break; + if (anim_material_found) break; } } @@ -262,7 +282,7 @@ String Particles::get_configuration_warning() const { process->get_param_texture(ParticlesMaterial::PARAM_ANIM_SPEED).is_valid() || process->get_param_texture(ParticlesMaterial::PARAM_ANIM_OFFSET).is_valid())) { if (warnings != String()) warnings += "\n"; - warnings += "- " + TTR("Particles animation requires the usage of a SpatialMaterial with \"Billboard Particles\" enabled."); + warnings += "- " + TTR("Particles animation requires the usage of a SpatialMaterial whose Billboard Mode is set to \"Particle Billboard\"."); } } @@ -272,6 +292,7 @@ String Particles::get_configuration_warning() const { void Particles::restart() { VisualServer::get_singleton()->particles_restart(particles); + VisualServer::get_singleton()->particles_set_emitting(particles, true); } AABB Particles::capture_aabb() const { @@ -300,6 +321,16 @@ void Particles::_notification(int p_what) { VS::get_singleton()->particles_set_speed_scale(particles, 0); } } + + // Use internal process when emitting and one_shot are on so that when + // the shot ends the editor can properly update + if (p_what == NOTIFICATION_INTERNAL_PROCESS) { + + if (one_shot && !is_emitting()) { + _change_notify(); + set_process_internal(false); + } + } } void Particles::_bind_methods() { @@ -348,7 +379,7 @@ void Particles::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting"); 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_EXP_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,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_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,64,0.01"), "set_speed_scale", "get_speed_scale"); @@ -380,6 +411,7 @@ Particles::Particles() { particles = VS::get_singleton()->particles_create(); set_base(particles); + one_shot = false; // Needed so that set_emitting doesn't access uninitialized values set_emitting(true); set_one_shot(false); set_amount(8); diff --git a/scene/3d/particles.h b/scene/3d/particles.h index 42c68010db..391491e8b8 100644 --- a/scene/3d/particles.h +++ b/scene/3d/particles.h @@ -35,10 +35,6 @@ #include "scene/3d/visual_instance.h" #include "scene/resources/material.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class Particles : public GeometryInstance { private: GDCLASS(Particles, GeometryInstance); diff --git a/scene/3d/path.cpp b/scene/3d/path.cpp index 9fae5a9a54..d55c795d38 100644 --- a/scene/3d/path.cpp +++ b/scene/3d/path.cpp @@ -107,6 +107,9 @@ void PathFollow::_update_transform() { } float bl = c->get_baked_length(); + if (bl == 0.0) { + return; + } float bi = c->get_bake_interval(); float o = offset; float o_next = offset + bi; @@ -122,11 +125,10 @@ void PathFollow::_update_transform() { Vector3 pos = c->interpolate_baked(o, cubic); Transform t = get_transform(); // Vector3 pos_offset = Vector3(h_offset, v_offset, 0); not used in all cases - // will be replaced by "Vector3(h_offset, v_offset, 0)" where it was formely used + // will be replaced by "Vector3(h_offset, v_offset, 0)" where it was formerly used if (rotation_mode == ROTATION_ORIENTED) { - Vector3 pos = c->interpolate_baked(o, cubic); Vector3 forward = c->interpolate_baked(o_next, cubic) - pos; if (forward.length_squared() < CMP_EPSILON2) @@ -171,7 +173,7 @@ void PathFollow::_update_transform() { float dot = t_prev.dot(t_cur); float angle = Math::acos(CLAMP(dot, -1, 1)); - if (likely(Math::abs(angle) > CMP_EPSILON)) { + if (likely(!Math::is_zero_approx(angle))) { if (rotation_mode == ROTATION_Y) { // assuming we're referring to global Y-axis. is this correct? axis.x = 0; @@ -182,7 +184,7 @@ void PathFollow::_update_transform() { // all components are allowed } - if (likely(axis.length() > CMP_EPSILON)) { + if (likely(!Math::is_zero_approx(axis.length()))) { t.rotate_basis(axis.normalized(), angle); } } @@ -191,7 +193,7 @@ void PathFollow::_update_transform() { float tilt_angle = c->interpolate_baked_tilt(o); Vector3 tilt_axis = t_cur; // not sure what tilt is supposed to do, is this correct?? - if (likely(Math::abs(tilt_angle) > CMP_EPSILON)) { + if (likely(!Math::is_zero_approx(Math::abs(tilt_angle)))) { if (rotation_mode == ROTATION_Y) { tilt_axis.x = 0; tilt_axis.z = 0; @@ -201,7 +203,7 @@ void PathFollow::_update_transform() { // all components are allowed } - if (likely(tilt_axis.length() > CMP_EPSILON)) { + if (likely(!Math::is_zero_approx(tilt_axis.length()))) { t.rotate_basis(tilt_axis.normalized(), tilt_angle); } } @@ -268,7 +270,7 @@ String PathFollow::get_configuration_warning() const { } else { Path *path = Object::cast_to<Path>(get_parent()); if (path->get_curve().is_valid() && !path->get_curve()->is_up_vector_enabled() && rotation_mode == ROTATION_ORIENTED) { - return TTR("PathFollow ROTATION_ORIENTED requires \"Up Vector\" enabled in its parent Path's Curve resource."); + return TTR("PathFollow's ROTATION_ORIENTED requires \"Up Vector\" to be enabled in its parent Path's Curve resource."); } } diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index 50abbd483a..ebac968cb4 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -129,10 +129,7 @@ void PhysicsBody::add_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); - if (!collision_object) { - ERR_EXPLAIN("Collision exception only works between two CollisionObject"); - } - ERR_FAIL_COND(!collision_object); + ERR_FAIL_COND_MSG(!collision_object, "Collision exception only works between two CollisionObject."); PhysicsServer::get_singleton()->body_add_collision_exception(get_rid(), collision_object->get_rid()); } @@ -140,10 +137,7 @@ void PhysicsBody::remove_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); - if (!collision_object) { - ERR_EXPLAIN("Collision exception only works between two CollisionObject"); - } - ERR_FAIL_COND(!collision_object); + ERR_FAIL_COND_MSG(!collision_object, "Collision exception only works between two CollisionObject."); PhysicsServer::get_singleton()->body_remove_collision_exception(get_rid(), collision_object->get_rid()); } @@ -192,8 +186,7 @@ void StaticBody::set_friction(real_t p_friction) { return; } - ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_friction has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_friction < 0 || p_friction > 1); @@ -206,8 +199,7 @@ void StaticBody::set_friction(real_t p_friction) { real_t StaticBody::get_friction() const { - ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_friction has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 1; @@ -222,8 +214,7 @@ void StaticBody::set_bounce(real_t p_bounce) { return; } - ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_bounce has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); @@ -236,8 +227,7 @@ void StaticBody::set_bounce(real_t p_bounce) { real_t StaticBody::get_bounce() const { - ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_bounce has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 0; @@ -636,8 +626,7 @@ void RigidBody::set_friction(real_t p_friction) { return; } - ERR_EXPLAIN("The method set_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_friction has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_friction < 0 || p_friction > 1); @@ -649,8 +638,7 @@ void RigidBody::set_friction(real_t p_friction) { } real_t RigidBody::get_friction() const { - ERR_EXPLAIN("The method get_friction has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_friction has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 1; @@ -665,8 +653,7 @@ void RigidBody::set_bounce(real_t p_bounce) { return; } - ERR_EXPLAIN("The method set_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method set_bounce has been deprecated and will be removed in the future, use physics material instead."); ERR_FAIL_COND(p_bounce < 0 || p_bounce > 1); @@ -677,8 +664,7 @@ void RigidBody::set_bounce(real_t p_bounce) { physics_material_override->set_bounce(p_bounce); } real_t RigidBody::get_bounce() const { - ERR_EXPLAIN("The method get_bounce has been deprecated and will be removed in the future, use physics material instead.") - WARN_DEPRECATED + WARN_DEPRECATED_MSG("The method get_bounce has been deprecated and will be removed in the future, use physics material instead."); if (physics_material_override.is_null()) { return 0; } @@ -867,10 +853,7 @@ void RigidBody::set_contact_monitor(bool p_enabled) { if (!p_enabled) { - if (contact_monitor->locked) { - ERR_EXPLAIN("Can't disable contact monitoring during in/out callback. Use call_deferred(\"set_contact_monitor\",false) instead"); - } - ERR_FAIL_COND(contact_monitor->locked); + ERR_FAIL_COND_MSG(contact_monitor->locked, "Can't disable contact monitoring during in/out callback. Use call_deferred(\"set_contact_monitor\", false) instead."); for (Map<ObjectID, BodyState>::Element *E = contact_monitor->body_map.front(); E; E = E->next()) { @@ -934,7 +917,7 @@ String RigidBody::get_configuration_warning() const { 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"; + warning += "\n\n"; } warning += TTR("Size changes to RigidBody (in character or rigid modes) will be overridden by the physics engine when running.\nChange the size in children collision shapes instead."); } @@ -1044,10 +1027,10 @@ void RigidBody::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "axis_lock_angular_z"), "set_axis_lock", "get_axis_lock", PhysicsServer::BODY_AXIS_ANGULAR_Z); ADD_GROUP("Linear", "linear_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "-1,128,0.01"), "set_linear_damp", "get_linear_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_linear_damp", "get_linear_damp"); ADD_GROUP("Angular", "angular_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "-1,128,0.01"), "set_angular_damp", "get_angular_damp"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), "set_angular_damp", "get_angular_damp"); ADD_SIGNAL(MethodInfo("body_shape_entered", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); ADD_SIGNAL(MethodInfo("body_shape_exited", PropertyInfo(Variant::INT, "body_id"), PropertyInfo(Variant::OBJECT, "body", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::INT, "body_shape"), PropertyInfo(Variant::INT, "local_shape"))); @@ -1104,10 +1087,10 @@ void RigidBody::_reload_physics_characteristics() { ////////////////////////////////////////////////////// ////////////////////////// -Ref<KinematicCollision> KinematicBody::_move(const Vector3 &p_motion, bool p_infinite_inertia, bool p_test_only) { +Ref<KinematicCollision> KinematicBody::_move(const Vector3 &p_motion, bool p_infinite_inertia, bool p_exclude_raycast_shapes, bool p_test_only) { Collision col; - if (move_and_collide(p_motion, p_infinite_inertia, col, p_test_only)) { + if (move_and_collide(p_motion, p_infinite_inertia, col, p_exclude_raycast_shapes, p_test_only)) { if (motion_cache.is_null()) { motion_cache.instance(); motion_cache->owner = this; @@ -1121,11 +1104,11 @@ Ref<KinematicCollision> KinematicBody::_move(const Vector3 &p_motion, bool p_inf return Ref<KinematicCollision>(); } -bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_test_only) { +bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes, bool p_test_only) { Transform gt = get_global_transform(); PhysicsServer::MotionResult result; - bool colliding = PhysicsServer::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, &result); + bool colliding = PhysicsServer::get_singleton()->body_test_motion(get_rid(), gt, p_motion, p_infinite_inertia, &result, p_exclude_raycast_shapes); if (colliding) { r_collision.collider_metadata = result.collider_metadata; @@ -1181,19 +1164,16 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve while (p_max_slides) { Collision collision; - bool found_collision = false; - int test_type = 0; - - do { + for (int i = 0; i < 2; ++i) { bool collided; - if (test_type == 0) { //collide + if (i == 0) { //collide collided = move_and_collide(motion, p_infinite_inertia, collision); if (!collided) { motion = Vector3(); //clear because no collision happened and motion completed } - } else { + } else { //separate raycasts (if any) collided = separate_raycast_shapes(p_infinite_inertia, collision); if (collided) { collision.remainder = motion; //keep @@ -1203,9 +1183,6 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve if (collided) { found_collision = true; - } - - if (collided) { colliders.push_back(collision); motion = collision.remainder; @@ -1215,14 +1192,14 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve //all is a wall on_wall = true; } else { - if (collision.normal.dot(p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //floor + if (Math::acos(collision.normal.dot(p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; on_floor_body = collision.collider_rid; floor_velocity = collision.collider_vel; if (p_stop_on_slope) { - if (Vector3() == lv_n + p_floor_direction) { + if ((lv_n + p_floor_direction).length() < 0.01 && collision.travel.length() < 1) { Transform gt = get_global_transform(); gt.origin -= collision.travel; set_global_transform(gt); @@ -1232,7 +1209,7 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve is_on_slope = true; - } else if (collision.normal.dot(-p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //ceiling + } else if (Math::acos(collision.normal.dot(-p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; @@ -1248,15 +1225,13 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve lv = lv.slide(n); } - for (int i = 0; i < 3; i++) { - if (locked_axis & (1 << i)) { - lv[i] = 0; + for (int j = 0; j < 3; j++) { + if (locked_axis & (1 << j)) { + lv[j] = 0; } } } - - ++test_type; - } while (!p_stop_on_slope && test_type < 2); + } if (!found_collision || motion == Vector3()) break; @@ -1279,14 +1254,27 @@ Vector3 KinematicBody::move_and_slide_with_snap(const Vector3 &p_linear_velocity Collision col; Transform gt = get_global_transform(); - if (move_and_collide(p_snap, p_infinite_inertia, col, true)) { - gt.origin += col.travel; - if (p_floor_direction != Vector3() && Math::acos(p_floor_direction.normalized().dot(col.normal)) < p_floor_max_angle) { - on_floor = true; - on_floor_body = col.collider_rid; - floor_velocity = col.collider_vel; + if (move_and_collide(p_snap, p_infinite_inertia, col, false, true)) { + + bool apply = true; + if (p_floor_direction != Vector3()) { + if (Math::acos(p_floor_direction.normalized().dot(col.normal)) < p_floor_max_angle) { + on_floor = true; + on_floor_body = col.collider_rid; + floor_velocity = col.collider_vel; + if (p_stop_on_slope) { + // move and collide may stray the object a bit because of pre un-stucking, + // so only ensure that motion happens on floor direction in this case. + col.travel = p_floor_direction * p_floor_direction.dot(col.travel); + } + } else { + apply = false; //snapped with floor direction, but did not snap to a floor, do not snap. + } + } + if (apply) { + gt.origin += col.travel; + set_global_transform(gt); } - set_global_transform(gt); } return ret; @@ -1401,7 +1389,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", "test_only"), &KinematicBody::_move, DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia", "exclude_raycast_shapes", "test_only"), &KinematicBody::_move, DEFVAL(true), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "stop_on_slope", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody::move_and_slide, DEFVAL(Vector3(0, 0, 0)), DEFVAL(false), DEFVAL(4), DEFVAL(Math::deg2rad((float)45)), DEFVAL(true)); ClassDB::bind_method(D_METHOD("move_and_slide_with_snap", "linear_velocity", "snap", "floor_normal", "stop_on_slope", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody::move_and_slide_with_snap, DEFVAL(Vector3(0, 0, 0)), DEFVAL(false), DEFVAL(4), DEFVAL(Math::deg2rad((float)45)), DEFVAL(true)); @@ -1465,7 +1453,7 @@ Vector3 KinematicCollision::get_remainder() const { return collision.remainder; } Object *KinematicCollision::get_local_shape() const { - ERR_FAIL_COND_V(!owner, NULL); + if (!owner) return NULL; uint32_t ownerid = owner->shape_find_owner(collision.local_shape); return owner->shape_owner_get_owner(ownerid); } diff --git a/scene/3d/physics_body.h b/scene/3d/physics_body.h index 5570d0c86b..0e2e614717 100644 --- a/scene/3d/physics_body.h +++ b/scene/3d/physics_body.h @@ -162,6 +162,7 @@ protected: ShapePair(int p_bs, int p_ls) { body_shape = p_bs; local_shape = p_ls; + tagged = false; } }; struct RigidBody_RemoveAction { @@ -310,14 +311,14 @@ private: _FORCE_INLINE_ bool _ignores_mode(PhysicsServer::BodyMode) const; - Ref<KinematicCollision> _move(const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_test_only = false); + Ref<KinematicCollision> _move(const Vector3 &p_motion, bool p_infinite_inertia = true, bool p_exclude_raycast_shapes = true, bool p_test_only = false); Ref<KinematicCollision> _get_slide_collision(int p_bounce); protected: static void _bind_methods(); public: - bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collisionz, bool p_test_only = false); + bool move_and_collide(const Vector3 &p_motion, bool p_infinite_inertia, Collision &r_collision, bool p_exclude_raycast_shapes = true, bool p_test_only = false); bool test_move(const Transform &p_from, const Vector3 &p_motion, bool p_infinite_inertia); bool separate_raycast_shapes(bool p_infinite_inertia, Collision &r_collision); diff --git a/scene/3d/portal.h b/scene/3d/portal.h index f053867917..04af3a750c 100644 --- a/scene/3d/portal.h +++ b/scene/3d/portal.h @@ -32,9 +32,6 @@ #define PORTAL_H #include "scene/3d/visual_instance.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ /* Portal Logic: If a portal is placed next (very close to) a similar, opposing portal, they automatically connect, diff --git a/scene/3d/proximity_group.cpp b/scene/3d/proximity_group.cpp index 12eab2e4e8..96dc3304f2 100644 --- a/scene/3d/proximity_group.cpp +++ b/scene/3d/proximity_group.cpp @@ -204,6 +204,7 @@ ProximityGroup::ProximityGroup() { group_version = 0; dispatch_mode = MODE_PROXY; + cell_size = 1.0; grid_radius = Vector3(1, 1, 1); set_notify_transform(true); }; diff --git a/scene/3d/ray_cast.cpp b/scene/3d/ray_cast.cpp index 10f92058e0..30eed8f1a7 100644 --- a/scene/3d/ray_cast.cpp +++ b/scene/3d/ray_cast.cpp @@ -102,6 +102,8 @@ Vector3 RayCast::get_collision_normal() const { void RayCast::set_enabled(bool p_enabled) { enabled = p_enabled; + update_gizmo(); + if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) set_physics_process_internal(p_enabled); if (!p_enabled) diff --git a/scene/3d/reflection_probe.h b/scene/3d/reflection_probe.h index 2921a3a881..48d65b79f7 100644 --- a/scene/3d/reflection_probe.h +++ b/scene/3d/reflection_probe.h @@ -32,7 +32,7 @@ #define REFLECTIONPROBE_H #include "scene/3d/visual_instance.h" -#include "scene/resources/sky_box.h" +#include "scene/resources/sky.h" #include "scene/resources/texture.h" #include "servers/visual_server.h" diff --git a/scene/3d/remote_transform.cpp b/scene/3d/remote_transform.cpp index add77e0272..76b0b0c92f 100644 --- a/scene/3d/remote_transform.cpp +++ b/scene/3d/remote_transform.cpp @@ -105,7 +105,7 @@ void RemoteTransform::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { + case NOTIFICATION_ENTER_TREE: { _update_cache(); @@ -174,10 +174,14 @@ bool RemoteTransform::get_update_scale() const { return update_remote_scale; } +void RemoteTransform::force_update_cache() { + _update_cache(); +} + String RemoteTransform::get_configuration_warning() const { if (!has_node(remote_node) || !Object::cast_to<Spatial>(get_node(remote_node))) { - return TTR("Path property must point to a valid Spatial node to work."); + return TTR("The \"Remote Path\" property must point to a valid Spatial or Spatial-derived node to work."); } return String(); @@ -187,6 +191,7 @@ void RemoteTransform::_bind_methods() { ClassDB::bind_method(D_METHOD("set_remote_node", "path"), &RemoteTransform::set_remote_node); ClassDB::bind_method(D_METHOD("get_remote_node"), &RemoteTransform::get_remote_node); + ClassDB::bind_method(D_METHOD("force_update_cache"), &RemoteTransform::force_update_cache); ClassDB::bind_method(D_METHOD("set_use_global_coordinates", "use_global_coordinates"), &RemoteTransform::set_use_global_coordinates); ClassDB::bind_method(D_METHOD("get_use_global_coordinates"), &RemoteTransform::get_use_global_coordinates); diff --git a/scene/3d/remote_transform.h b/scene/3d/remote_transform.h index b737a4f858..07e01284e5 100644 --- a/scene/3d/remote_transform.h +++ b/scene/3d/remote_transform.h @@ -68,6 +68,8 @@ public: void set_update_scale(const bool p_update); bool get_update_scale() const; + void force_update_cache(); + virtual String get_configuration_warning() const; RemoteTransform(); diff --git a/scene/3d/room_instance.h b/scene/3d/room_instance.h index 9ee140d522..01efde53c9 100644 --- a/scene/3d/room_instance.h +++ b/scene/3d/room_instance.h @@ -34,10 +34,6 @@ #include "scene/3d/visual_instance.h" #include "scene/resources/room.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - /* RoomInstance Logic: a) Instances that belong to the room are drawn only if the room is visible (seen through portal, or player inside) b) Instances that don't belong to any room are considered to belong to the root room (RID empty) diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index 8caf4e8e39..e192e040f2 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -70,9 +70,9 @@ bool Skeleton::_set(const StringName &p_path, const Variant &p_value) { for (int i = 0; i < children.size(); i++) { - NodePath path = children[i]; - ERR_CONTINUE(path.operator String() == ""); - Node *node = get_node(path); + NodePath npath = children[i]; + ERR_CONTINUE(npath.operator String() == ""); + Node *node = get_node(npath); ERR_CONTINUE(!node); bind_child_node_to_bone(which, node); } @@ -115,8 +115,8 @@ bool Skeleton::_get(const StringName &p_path, Variant &r_ret) const { ERR_CONTINUE(!obj); Node *node = Object::cast_to<Node>(obj); ERR_CONTINUE(!node); - NodePath path = get_path_to(node); - children.push_back(path); + NodePath npath = get_path_to(node); + children.push_back(npath); } r_ret = children; @@ -162,7 +162,7 @@ void Skeleton::_update_process_order() { //now check process order int pass_count = 0; while (pass_count < len * len) { - //using bubblesort because of simplicity, it wont run every frame though. + //using bubblesort because of simplicity, it won't run every frame though. //bublesort worst case is O(n^2), and this may be an infinite loop if cyclic bool swapped = false; for (int i = 0; i < len; i++) { @@ -198,11 +198,7 @@ void Skeleton::_notification(int p_what) { case NOTIFICATION_ENTER_WORLD: { - if (dirty) { - - dirty = false; - _make_dirty(); // property make it dirty - } + VS::get_singleton()->skeleton_set_world_transform(skeleton, use_bones_in_world_transform, get_global_transform()); } break; case NOTIFICATION_EXIT_WORLD: { @@ -210,21 +206,7 @@ void Skeleton::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { - if (dirty) - break; //will be eventually updated - - //if moved, just update transforms - VisualServer *vs = VisualServer::get_singleton(); - const Bone *bonesptr = bones.ptr(); - int len = bones.size(); - Transform global_transform = get_global_transform(); - Transform global_transform_inverse = global_transform.affine_inverse(); - - for (int i = 0; i < len; i++) { - - const Bone &b = bonesptr[i]; - vs->skeleton_bone_set_transform(skeleton, i, global_transform * (b.transform_final * global_transform_inverse)); - } + VS::get_singleton()->skeleton_set_world_transform(skeleton, use_bones_in_world_transform, get_global_transform()); } break; case NOTIFICATION_UPDATE_SKELETON: { @@ -232,7 +214,7 @@ void Skeleton::_notification(int p_what) { Bone *bonesptr = bones.ptrw(); int len = bones.size(); - vs->skeleton_allocate(skeleton, len); // if same size, nothin really happens + vs->skeleton_allocate(skeleton, len); // if same size, nothing really happens _update_process_order(); @@ -257,9 +239,6 @@ void Skeleton::_notification(int p_what) { rest_global_inverse_dirty = false; } - Transform global_transform = get_global_transform(); - Transform global_transform_inverse = global_transform.affine_inverse(); - for (int i = 0; i < len; i++) { Bone &b = bonesptr[order[i]]; @@ -320,7 +299,7 @@ void Skeleton::_notification(int p_what) { } b.transform_final = b.pose_global * b.rest_global_inverse; - vs->skeleton_bone_set_transform(skeleton, order[i], global_transform * (b.transform_final * global_transform_inverse)); + vs->skeleton_bone_set_transform(skeleton, order[i], b.transform_final); for (List<uint32_t>::Element *E = b.nodes_bound.front(); E; E = E->next()) { @@ -561,10 +540,11 @@ void Skeleton::clear_bones() { void Skeleton::set_bone_pose(int p_bone, const Transform &p_pose) { ERR_FAIL_INDEX(p_bone, bones.size()); - ERR_FAIL_COND(!is_inside_tree()); bones.write[p_bone].pose = p_pose; - _make_dirty(); + if (is_inside_tree()) { + _make_dirty(); + } } Transform Skeleton::get_bone_pose(int p_bone) const { @@ -594,10 +574,6 @@ void Skeleton::_make_dirty() { if (dirty) return; - if (!is_inside_tree()) { - dirty = true; - return; - } MessageQueue::get_singleton()->push_notification(this, NOTIFICATION_UPDATE_SKELETON); dirty = true; } @@ -771,6 +747,16 @@ void Skeleton::physical_bones_remove_collision_exception(RID p_exception) { #endif // _3D_DISABLED +void Skeleton::set_use_bones_in_world_transform(bool p_enable) { + use_bones_in_world_transform = p_enable; + if (is_inside_tree()) { + VS::get_singleton()->skeleton_set_world_transform(skeleton, use_bones_in_world_transform, get_global_transform()); + } +} +bool Skeleton::is_using_bones_in_world_transform() const { + return use_bones_in_world_transform; +} + void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton::add_bone); @@ -787,6 +773,8 @@ void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_rest", "bone_idx"), &Skeleton::get_bone_rest); ClassDB::bind_method(D_METHOD("set_bone_rest", "bone_idx", "rest"), &Skeleton::set_bone_rest); + ClassDB::bind_method(D_METHOD("localize_rests"), &Skeleton::localize_rests); + ClassDB::bind_method(D_METHOD("set_bone_disable_rest", "bone_idx", "disable"), &Skeleton::set_bone_disable_rest); ClassDB::bind_method(D_METHOD("is_bone_rest_disabled", "bone_idx"), &Skeleton::is_bone_rest_disabled); @@ -807,6 +795,9 @@ void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_transform", "bone_idx"), &Skeleton::get_bone_transform); + ClassDB::bind_method(D_METHOD("set_use_bones_in_world_transform", "enable"), &Skeleton::set_use_bones_in_world_transform); + ClassDB::bind_method(D_METHOD("is_using_bones_in_world_transform"), &Skeleton::is_using_bones_in_world_transform); + #ifndef _3D_DISABLED ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &Skeleton::physical_bones_stop_simulation); @@ -818,6 +809,7 @@ void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bone_ignore_animation", "bone", "ignore"), &Skeleton::set_bone_ignore_animation); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bones_in_world_transform"), "set_use_bones_in_world_transform", "is_using_bones_in_world_transform"); BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } @@ -828,6 +820,7 @@ Skeleton::Skeleton() { process_order_dirty = true; skeleton = VisualServer::get_singleton()->skeleton_create(); set_notify_transform(true); + use_bones_in_world_transform = false; } Skeleton::~Skeleton() { diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index 0f463c9ea7..5b55dffbc8 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -34,10 +34,6 @@ #include "core/rid.h" #include "scene/3d/spatial.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - #ifndef _3D_DISABLED typedef int BoneId; @@ -100,6 +96,7 @@ class Skeleton : public Spatial { void _make_dirty(); bool dirty; + bool use_bones_in_world_transform; // bind helpers Array _get_bound_child_nodes_to_bone(int p_bone) const { @@ -179,6 +176,9 @@ public: void localize_rests(); // used for loaders and tools int get_process_order(int p_idx); + void set_use_bones_in_world_transform(bool p_enable); + bool is_using_bones_in_world_transform() const; + #ifndef _3D_DISABLED // Physical bone API diff --git a/scene/3d/soft_body.cpp b/scene/3d/soft_body.cpp index ac20609c21..6c3949a0a8 100644 --- a/scene/3d/soft_body.cpp +++ b/scene/3d/soft_body.cpp @@ -73,7 +73,7 @@ void SoftBodyVisualServerHandler::open() { } void SoftBodyVisualServerHandler::close() { - write_buffer = PoolVector<uint8_t>::Write(); + write_buffer.release(); } void SoftBodyVisualServerHandler::commit_changes() { @@ -104,6 +104,14 @@ SoftBody::PinnedPoint::PinnedPoint(const PinnedPoint &obj_tocopy) { offset = obj_tocopy.offset; } +SoftBody::PinnedPoint SoftBody::PinnedPoint::operator=(const PinnedPoint &obj) { + point_index = obj.point_index; + spatial_attachment_path = obj.spatial_attachment_path; + spatial_attachment = obj.spatial_attachment; + offset = obj.offset; + return *this; +} + void SoftBody::_update_pickable() { if (!is_inside_tree()) return; @@ -397,7 +405,7 @@ String SoftBody::get_configuration_warning() const { if (!warning.empty()) warning += "\n\n"; - warning += TTR("This body will be ignored until you set a mesh"); + warning += TTR("This body will be ignored until you set a mesh."); } Transform t = get_transform(); @@ -569,20 +577,14 @@ Array SoftBody::get_collision_exceptions() { void SoftBody::add_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); - if (!collision_object) { - ERR_EXPLAIN("Collision exception only works between two CollisionObject"); - } - ERR_FAIL_COND(!collision_object); + ERR_FAIL_COND_MSG(!collision_object, "Collision exception only works between two CollisionObject."); PhysicsServer::get_singleton()->soft_body_add_collision_exception(physics_rid, collision_object->get_rid()); } void SoftBody::remove_collision_exception_with(Node *p_node) { ERR_FAIL_NULL(p_node); CollisionObject *collision_object = Object::cast_to<CollisionObject>(p_node); - if (!collision_object) { - ERR_EXPLAIN("Collision exception only works between two CollisionObject"); - } - ERR_FAIL_COND(!collision_object); + ERR_FAIL_COND_MSG(!collision_object, "Collision exception only works between two CollisionObject."); PhysicsServer::get_singleton()->soft_body_remove_collision_exception(physics_rid, collision_object->get_rid()); } @@ -691,7 +693,6 @@ bool SoftBody::is_ray_pickable() const { } SoftBody::SoftBody() : - MeshInstance(), physics_rid(PhysicsServer::get_singleton()->soft_body_create()), mesh_owner(false), collision_mask(1), @@ -705,6 +706,7 @@ SoftBody::SoftBody() : } SoftBody::~SoftBody() { + PhysicsServer::get_singleton()->free(physics_rid); } void SoftBody::reset_softbody_pin() { diff --git a/scene/3d/soft_body.h b/scene/3d/soft_body.h index 2516d39552..ee455f8dab 100644 --- a/scene/3d/soft_body.h +++ b/scene/3d/soft_body.h @@ -75,6 +75,7 @@ public: PinnedPoint(); PinnedPoint(const PinnedPoint &obj_tocopy); + PinnedPoint operator=(const PinnedPoint &obj); }; private: diff --git a/scene/3d/spatial.cpp b/scene/3d/spatial.cpp index d6544c39da..df831f92ef 100644 --- a/scene/3d/spatial.cpp +++ b/scene/3d/spatial.cpp @@ -178,7 +178,7 @@ void Spatial::_notification(int p_what) { get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_enter_world, NULL, 0); } #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { + if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) { //get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,SceneStringNames::get_singleton()->_spatial_editor_group,SceneStringNames::get_singleton()->_request_gizmo,this); get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_request_gizmo, this); @@ -224,7 +224,8 @@ void Spatial::_notification(int p_what) { #endif } break; - default: {} + default: { + } } } @@ -505,6 +506,8 @@ bool Spatial::is_set_as_toplevel() const { Ref<World> Spatial::get_world() const { ERR_FAIL_COND_V(!is_inside_world(), Ref<World>()); + ERR_FAIL_COND_V(!data.viewport, Ref<World>()); + return data.viewport->find_world(); } @@ -675,26 +678,22 @@ void Spatial::set_identity() { void Spatial::look_at(const Vector3 &p_target, const Vector3 &p_up) { - Transform lookat; - lookat.origin = get_global_transform().origin; - if (lookat.origin == p_target) { - ERR_EXPLAIN("Node origin and target are in the same position, look_at() failed"); - ERR_FAIL(); - } - - if (p_up.cross(p_target - lookat.origin) == Vector3()) { - ERR_EXPLAIN("Up vector and direction between node origin and target are aligned, look_at() failed"); - ERR_FAIL(); - } - lookat = lookat.looking_at(p_target, p_up); - set_global_transform(lookat); + Vector3 origin(get_global_transform().origin); + look_at_from_position(origin, p_target, p_up); } void Spatial::look_at_from_position(const Vector3 &p_pos, const Vector3 &p_target, const Vector3 &p_up) { + ERR_FAIL_COND_MSG(p_pos == p_target, "Node origin and target are in the same position, look_at() failed."); + ERR_FAIL_COND_MSG(p_up.cross(p_target - p_pos) == Vector3(), "Up vector and direction between node origin and target are aligned, look_at() failed."); + Transform lookat; lookat.origin = p_pos; + + Vector3 original_scale(get_global_transform().basis.get_scale()); lookat = lookat.looking_at(p_target, p_up); + // as basis was normalized, we just need to apply original scale back + lookat.basis.scale(original_scale); set_global_transform(lookat); } diff --git a/scene/3d/spatial.h b/scene/3d/spatial.h index 18a4a5b54d..eea2696767 100644 --- a/scene/3d/spatial.h +++ b/scene/3d/spatial.h @@ -34,10 +34,6 @@ #include "scene/main/node.h" #include "scene/main/scene_tree.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class SpatialGizmo : public Reference { GDCLASS(SpatialGizmo, Reference); diff --git a/scene/3d/spatial_velocity_tracker.h b/scene/3d/spatial_velocity_tracker.h index 795f56091f..8260cf3f06 100644 --- a/scene/3d/spatial_velocity_tracker.h +++ b/scene/3d/spatial_velocity_tracker.h @@ -34,7 +34,7 @@ #include "scene/3d/spatial.h" class SpatialVelocityTracker : public Reference { - GDCLASS(SpatialVelocityTracker, Reference) + GDCLASS(SpatialVelocityTracker, Reference); struct PositionHistory { uint64_t frame; diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index a3fa2ac98d..a9dacc442c 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -163,6 +163,7 @@ float SpriteBase3D::get_opacity() const { void SpriteBase3D::set_axis(Vector3::Axis p_axis) { + ERR_FAIL_INDEX(p_axis, 3); axis = p_axis; _queue_update(); } @@ -253,7 +254,7 @@ Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const { facesw[j] = vtx; } - facesw = PoolVector<Vector3>::Write(); + facesw.release(); triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); triangle_mesh->create(faces); @@ -285,6 +286,18 @@ SpriteBase3D::AlphaCutMode SpriteBase3D::get_alpha_cut_mode() const { return alpha_cut; } +void SpriteBase3D::set_billboard_mode(SpatialMaterial::BillboardMode p_mode) { + + ERR_FAIL_INDEX(p_mode, 3); + billboard_mode = p_mode; + _queue_update(); +} + +SpatialMaterial::BillboardMode SpriteBase3D::get_billboard_mode() const { + + return billboard_mode; +} + void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_centered", "centered"), &SpriteBase3D::set_centered); @@ -317,6 +330,9 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_alpha_cut_mode", "mode"), &SpriteBase3D::set_alpha_cut_mode); ClassDB::bind_method(D_METHOD("get_alpha_cut_mode"), &SpriteBase3D::get_alpha_cut_mode); + ClassDB::bind_method(D_METHOD("set_billboard_mode", "mode"), &SpriteBase3D::set_billboard_mode); + ClassDB::bind_method(D_METHOD("get_billboard_mode"), &SpriteBase3D::get_billboard_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); @@ -332,6 +348,7 @@ void SpriteBase3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001"), "set_pixel_size", "get_pixel_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "axis", PROPERTY_HINT_ENUM, "X-Axis,Y-Axis,Z-Axis"), "set_axis", "get_axis"); ADD_GROUP("Flags", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "billboard", PROPERTY_HINT_ENUM, "Disabled,Enabled,Y-Billboard"), "set_billboard_mode", "get_billboard_mode"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "transparent"), "set_draw_flag", "get_draw_flag", FLAG_TRANSPARENT); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "shaded"), "set_draw_flag", "get_draw_flag", FLAG_SHADED); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "double_sided"), "set_draw_flag", "get_draw_flag", FLAG_DOUBLE_SIDED); @@ -360,6 +377,7 @@ SpriteBase3D::SpriteBase3D() { flags[i] = i == FLAG_TRANSPARENT || i == FLAG_DOUBLE_SIDED; alpha_cut = ALPHA_CUT_DISABLED; + billboard_mode = SpatialMaterial::BILLBOARD_DISABLED; axis = Vector3::AXIS_Z; pixel_size = 0.01; modulate = Color(1, 1, 1, 1); @@ -382,36 +400,30 @@ void Sprite3D::_draw() { VS::get_singleton()->immediate_clear(immediate); if (!texture.is_valid()) - return; //no texuture no life + return; Vector2 tsize = texture->get_size(); if (tsize.x == 0 || tsize.y == 0) return; - Size2i s; - Rect2 src_rect; - - if (region) { - - s = region_rect.size; - src_rect = region_rect; - } else { - s = texture->get_size(); - s = s / Size2(hframes, vframes); + Rect2 base_rect; + if (region) + base_rect = region_rect; + else + base_rect = Rect2(0, 0, texture->get_width(), texture->get_height()); - src_rect.size = s; - src_rect.position.x += (frame % hframes) * s.x; - src_rect.position.y += (frame / hframes) * s.y; - } + Size2 frame_size = base_rect.size / Size2(hframes, vframes); + Point2 frame_offset = Point2(frame % hframes, frame / hframes); + frame_offset *= frame_size; - Point2 ofs = get_offset(); + Point2 dest_offset = get_offset(); if (is_centered()) - ofs -= s / 2; - - Rect2 dst_rect(ofs, s); + dest_offset -= frame_size / 2; + Rect2 src_rect(base_rect.position + frame_offset, frame_size); + Rect2 final_dst_rect(dest_offset, frame_size); Rect2 final_rect; Rect2 final_src_rect; - if (!texture->get_rect_region(dst_rect, src_rect, final_rect, final_src_rect)) + if (!texture->get_rect_region(final_dst_rect, src_rect, final_rect, final_src_rect)) return; if (final_rect.size.x == 0 || final_rect.size.y == 0) @@ -468,7 +480,7 @@ void Sprite3D::_draw() { tangent = Plane(1, 0, 0, 1); } - RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS); + RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == SpatialMaterial::BILLBOARD_ENABLED, get_billboard_mode() == SpatialMaterial::BILLBOARD_FIXED_Y); VS::get_singleton()->immediate_set_material(immediate, mat); VS::get_singleton()->immediate_begin(immediate, VS::PRIMITIVE_TRIANGLE_FAN, texture->get_rid()); @@ -563,12 +575,15 @@ Rect2 Sprite3D::get_region_rect() const { void Sprite3D::set_frame(int p_frame) { - ERR_FAIL_INDEX(p_frame, vframes * hframes); + ERR_FAIL_INDEX(p_frame, int64_t(vframes) * hframes); if (frame != p_frame) frame = p_frame; _queue_update(); + + _change_notify("frame"); + _change_notify("frame_coords"); emit_signal(SceneStringNames::get_singleton()->frame_changed); } @@ -577,6 +592,17 @@ int Sprite3D::get_frame() const { return frame; } +void Sprite3D::set_frame_coords(const Vector2 &p_coord) { + ERR_FAIL_INDEX(int(p_coord.x), vframes); + ERR_FAIL_INDEX(int(p_coord.y), hframes); + + set_frame(int(p_coord.y) * hframes + int(p_coord.x)); +} + +Vector2 Sprite3D::get_frame_coords() const { + return Vector2(frame % hframes, frame / hframes); +} + void Sprite3D::set_vframes(int p_amount) { ERR_FAIL_COND(p_amount < 1); @@ -633,10 +659,9 @@ Rect2 Sprite3D::get_item_rect() const { void Sprite3D::_validate_property(PropertyInfo &property) const { if (property.name == "frame") { - - property.hint = PROPERTY_HINT_SPRITE_FRAME; - + property.hint = PROPERTY_HINT_RANGE; property.hint_string = "0," + itos(vframes * hframes - 1) + ",1"; + property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } } @@ -654,6 +679,9 @@ void Sprite3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_frame", "frame"), &Sprite3D::set_frame); ClassDB::bind_method(D_METHOD("get_frame"), &Sprite3D::get_frame); + ClassDB::bind_method(D_METHOD("set_frame_coords", "coords"), &Sprite3D::set_frame_coords); + ClassDB::bind_method(D_METHOD("get_frame_coords"), &Sprite3D::get_frame_coords); + ClassDB::bind_method(D_METHOD("set_vframes", "vframes"), &Sprite3D::set_vframes); ClassDB::bind_method(D_METHOD("get_vframes"), &Sprite3D::get_vframes); @@ -664,7 +692,8 @@ void Sprite3D::_bind_methods() { ADD_GROUP("Animation", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "frame", PROPERTY_HINT_SPRITE_FRAME), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords"); ADD_GROUP("Region", "region_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region", "is_region"); ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect"), "set_region_rect", "get_region_rect"); @@ -776,7 +805,7 @@ void AnimatedSprite3D::_draw() { tangent = Plane(1, 0, 0, -1); } - RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS); + RID mat = SpatialMaterial::get_material_rid_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == SpatialMaterial::BILLBOARD_ENABLED, get_billboard_mode() == SpatialMaterial::BILLBOARD_FIXED_Y); VS::get_singleton()->immediate_set_material(immediate, mat); @@ -856,14 +885,11 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &property) const { } if (property.name == "frame") { - property.hint = PROPERTY_HINT_RANGE; - - if (frames->has_animation(animation)) { + if (frames->has_animation(animation) && frames->get_frame_count(animation) > 1) { property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; - } else { - property.hint_string = "0,0,0"; } + property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } } @@ -1066,7 +1092,7 @@ StringName AnimatedSprite3D::get_animation() const { String AnimatedSprite3D::get_configuration_warning() const { if (frames.is_null()) { - return TTR("A SpriteFrames resource must be created or set in the 'Frames' property in order for AnimatedSprite3D to display frames."); + return TTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames."); } return String(); @@ -1096,7 +1122,7 @@ void AnimatedSprite3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "frames", PROPERTY_HINT_RESOURCE_TYPE, "SpriteFrames"), "set_sprite_frames", "get_sprite_frames"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "frame", PROPERTY_HINT_SPRITE_FRAME), "set_frame", "get_frame"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing"), "_set_playing", "_is_playing"); } diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index 0e086ef31e..065931de84 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -80,6 +80,7 @@ private: bool flags[FLAG_MAX]; AlphaCutMode alpha_cut; + SpatialMaterial::BillboardMode billboard_mode; bool pending_update; void _im_update(); @@ -130,6 +131,8 @@ public: void set_alpha_cut_mode(AlphaCutMode p_mode); AlphaCutMode get_alpha_cut_mode() const; + void set_billboard_mode(SpatialMaterial::BillboardMode p_mode); + SpatialMaterial::BillboardMode get_billboard_mode() const; virtual Rect2 get_item_rect() const = 0; @@ -173,6 +176,9 @@ public: void set_frame(int p_frame); int get_frame() const; + void set_frame_coords(const Vector2 &p_coord); + Vector2 get_frame_coords() const; + void set_vframes(int p_amount); int get_vframes() const; diff --git a/scene/3d/vehicle_body.cpp b/scene/3d/vehicle_body.cpp index c7f7b14a8f..98e68cfbda 100644 --- a/scene/3d/vehicle_body.cpp +++ b/scene/3d/vehicle_body.cpp @@ -270,6 +270,22 @@ void VehicleWheel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skidinfo"), &VehicleWheel::get_skidinfo); + ClassDB::bind_method(D_METHOD("get_rpm"), &VehicleWheel::get_rpm); + + ClassDB::bind_method(D_METHOD("set_engine_force", "engine_force"), &VehicleWheel::set_engine_force); + ClassDB::bind_method(D_METHOD("get_engine_force"), &VehicleWheel::get_engine_force); + + ClassDB::bind_method(D_METHOD("set_brake", "brake"), &VehicleWheel::set_brake); + ClassDB::bind_method(D_METHOD("get_brake"), &VehicleWheel::get_brake); + + ClassDB::bind_method(D_METHOD("set_steering", "steering"), &VehicleWheel::set_steering); + ClassDB::bind_method(D_METHOD("get_steering"), &VehicleWheel::get_steering); + + ADD_GROUP("Per-Wheel Motion", ""); + 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"); + ADD_GROUP("VehicleBody Motion", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_as_traction"), "set_use_as_traction", "is_used_as_traction"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_as_steering"), "set_use_as_steering", "is_used_as_steering"); ADD_GROUP("Wheel", "wheel_"); @@ -286,6 +302,34 @@ void VehicleWheel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "damping_relaxation"), "set_damping_relaxation", "get_damping_relaxation"); } +void VehicleWheel::set_engine_force(float p_engine_force) { + + m_engineForce = p_engine_force; +} + +float VehicleWheel::get_engine_force() const { + + return m_engineForce; +} + +void VehicleWheel::set_brake(float p_brake) { + + m_brake = p_brake; +} +float VehicleWheel::get_brake() const { + + return m_brake; +} + +void VehicleWheel::set_steering(float p_steering) { + + m_steering = p_steering; +} +float VehicleWheel::get_steering() const { + + return m_steering; +} + void VehicleWheel::set_use_as_traction(bool p_enable) { engine_traction = p_enable; @@ -311,6 +355,11 @@ float VehicleWheel::get_skidinfo() const { return m_skidInfo; } +float VehicleWheel::get_rpm() const { + + return m_rpm; +} + VehicleWheel::VehicleWheel() { steers = false; @@ -367,10 +416,7 @@ void VehicleBody::_update_wheel(int p_idx, PhysicsDirectBodyState *s) { Vector3 fwd = up.cross(right); fwd = fwd.normalized(); - //rotate around steering over de wheelAxleWS - real_t steering = wheel.steers ? m_steeringValue : 0.0; - - Basis steeringMat(up, steering); + Basis steeringMat(up, wheel.m_steering); Basis rotatingMat(right, wheel.m_rotation); @@ -578,7 +624,7 @@ void VehicleBody::_resolve_single_bilateral(PhysicsDirectBodyState *s, const Vec 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; + contactDamping = MIN(contactDamping, s->get_step() / p_rollInfluence); } #define ONLY_USE_LINEAR_MASS @@ -716,12 +762,11 @@ void VehicleBody::_update_friction(PhysicsDirectBodyState *s) { real_t rollingFriction = 0.f; if (wheelInfo.m_raycastInfo.m_isInContact) { - if (engine_force != 0.f) { - rollingFriction = -engine_force * s->get_step(); + if (wheelInfo.m_engineForce != 0.f) { + rollingFriction = -wheelInfo.m_engineForce * s->get_step(); } else { real_t defaultRollingFrictionImpulse = 0.f; - float cbrake = MAX(wheelInfo.m_brake, brake); - real_t maxImpulse = cbrake ? cbrake : defaultRollingFrictionImpulse; + real_t maxImpulse = wheelInfo.m_brake ? wheelInfo.m_brake : defaultRollingFrictionImpulse; btVehicleWheelContactPoint contactPt(s, wheelInfo.m_raycastInfo.m_groundObject, wheelInfo.m_raycastInfo.m_contactPointWS, m_forwardWS[wheel], maxImpulse); rollingFriction = _calc_rolling_friction(contactPt); } @@ -865,12 +910,11 @@ void VehicleBody::_direct_state_changed(Object *p_state) { real_t proj2 = fwd.dot(vel); wheel.m_deltaRotation = (proj2 * step) / (wheel.m_wheelRadius); - wheel.m_rotation += wheel.m_deltaRotation; - - } else { - wheel.m_rotation += wheel.m_deltaRotation; } + wheel.m_rotation += wheel.m_deltaRotation; + wheel.m_rpm = ((wheel.m_deltaRotation / step) * 60) / Math_TAU; + wheel.m_deltaRotation *= real_t(0.99); //damping of rotation when not in contact } @@ -880,6 +924,11 @@ void VehicleBody::_direct_state_changed(Object *p_state) { void VehicleBody::set_engine_force(float p_engine_force) { engine_force = p_engine_force; + for (int i = 0; i < wheels.size(); i++) { + VehicleWheel &wheelInfo = *wheels[i]; + if (wheelInfo.engine_traction) + wheelInfo.m_engineForce = p_engine_force; + } } float VehicleBody::get_engine_force() const { @@ -890,6 +939,10 @@ float VehicleBody::get_engine_force() const { void VehicleBody::set_brake(float p_brake) { brake = p_brake; + for (int i = 0; i < wheels.size(); i++) { + VehicleWheel &wheelInfo = *wheels[i]; + wheelInfo.m_brake = p_brake; + } } float VehicleBody::get_brake() const { @@ -899,6 +952,11 @@ float VehicleBody::get_brake() const { void VehicleBody::set_steering(float p_steering) { m_steeringValue = p_steering; + for (int i = 0; i < wheels.size(); i++) { + VehicleWheel &wheelInfo = *wheels[i]; + if (wheelInfo.steers) + wheelInfo.m_steering = p_steering; + } } float VehicleBody::get_steering() const { @@ -922,8 +980,7 @@ void VehicleBody::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "steering", PROPERTY_HINT_RANGE, "-180,180.0,0.01"), "set_steering", "get_steering"); } -VehicleBody::VehicleBody() : - RigidBody() { +VehicleBody::VehicleBody() { m_pitchControl = 0; m_currentVehicleSpeedKmHour = real_t(0.); diff --git a/scene/3d/vehicle_body.h b/scene/3d/vehicle_body.h index 7e7571df4d..914bfd54bd 100644 --- a/scene/3d/vehicle_body.h +++ b/scene/3d/vehicle_body.h @@ -68,8 +68,9 @@ class VehicleWheel : public Spatial { real_t m_steering; real_t m_rotation; real_t m_deltaRotation; + real_t m_rpm; real_t m_rollInfluence; - //real_t m_engineForce; + real_t m_engineForce; real_t m_brake; real_t m_clippedInvContactDotSuspension; @@ -134,6 +135,17 @@ public: float get_skidinfo() const; + float get_rpm() const; + + void set_engine_force(float p_engine_force); + float get_engine_force() const; + + void set_brake(float p_brake); + float get_brake() const; + + void set_steering(float p_steering); + float get_steering() const; + String get_configuration_warning() const; VehicleWheel(); diff --git a/scene/3d/visual_instance.cpp b/scene/3d/visual_instance.cpp index 1bbf1b7bc7..d1de0c56a7 100644 --- a/scene/3d/visual_instance.cpp +++ b/scene/3d/visual_instance.cpp @@ -60,7 +60,7 @@ void VisualInstance::_notification(int p_what) { if (skeleton) VisualServer::get_singleton()->instance_attach_skeleton( instance, skeleton->get_skeleton() ); */ - + ERR_FAIL_COND(get_world().is_null()); VisualServer::get_singleton()->instance_set_scenario(instance, get_world()->get_scenario()); _update_visibility(); @@ -123,6 +123,8 @@ void VisualInstance::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_visual_instance_rid"), &VisualInstance::_get_visual_instance_rid); ClassDB::bind_method(D_METHOD("set_base", "base"), &VisualInstance::set_base); + ClassDB::bind_method(D_METHOD("get_base"), &VisualInstance::get_base); + ClassDB::bind_method(D_METHOD("get_instance"), &VisualInstance::get_instance); ClassDB::bind_method(D_METHOD("set_layer_mask", "mask"), &VisualInstance::set_layer_mask); ClassDB::bind_method(D_METHOD("get_layer_mask"), &VisualInstance::get_layer_mask); ClassDB::bind_method(D_METHOD("set_layer_mask_bit", "layer", "enabled"), &VisualInstance::set_layer_mask_bit); @@ -136,6 +138,12 @@ void VisualInstance::_bind_methods() { void VisualInstance::set_base(const RID &p_base) { VisualServer::get_singleton()->instance_set_base(instance, p_base); + base = p_base; +} + +RID VisualInstance::get_base() const { + + return base; } VisualInstance::VisualInstance() { @@ -207,17 +215,6 @@ float GeometryInstance::get_lod_max_hysteresis() const { } void GeometryInstance::_notification(int p_what) { - - if (p_what == NOTIFICATION_ENTER_WORLD) { - - if (flags[FLAG_USE_BAKED_LIGHT]) { - } - - } else if (p_what == NOTIFICATION_EXIT_WORLD) { - - if (flags[FLAG_USE_BAKED_LIGHT]) { - } - } } void GeometryInstance::set_flag(Flags p_flag, bool p_value) { @@ -228,8 +225,6 @@ void GeometryInstance::set_flag(Flags p_flag, bool p_value) { flags[p_flag] = p_value; VS::get_singleton()->instance_geometry_set_flag(get_instance(), (VS::InstanceFlags)p_flag, p_value); - if (p_flag == FLAG_USE_BAKED_LIGHT) { - } } bool GeometryInstance::get_flag(Flags p_flag) const { @@ -263,6 +258,11 @@ float GeometryInstance::get_extra_cull_margin() const { return extra_cull_margin; } +void GeometryInstance::set_custom_aabb(AABB aabb) { + + VS::get_singleton()->instance_set_custom_aabb(get_instance(), aabb); +} + void GeometryInstance::_bind_methods() { ClassDB::bind_method(D_METHOD("set_material_override", "material"), &GeometryInstance::set_material_override); @@ -289,6 +289,8 @@ void GeometryInstance::_bind_methods() { ClassDB::bind_method(D_METHOD("set_extra_cull_margin", "margin"), &GeometryInstance::set_extra_cull_margin); ClassDB::bind_method(D_METHOD("get_extra_cull_margin"), &GeometryInstance::get_extra_cull_margin); + ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &GeometryInstance::set_custom_aabb); + ClassDB::bind_method(D_METHOD("get_aabb"), &GeometryInstance::get_aabb); ADD_GROUP("Geometry", ""); @@ -311,6 +313,7 @@ void GeometryInstance::_bind_methods() { BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_SHADOWS_ONLY); BIND_ENUM_CONSTANT(FLAG_USE_BAKED_LIGHT); + BIND_ENUM_CONSTANT(FLAG_DRAW_NEXT_FRAME_IF_VISIBLE); BIND_ENUM_CONSTANT(FLAG_MAX); } diff --git a/scene/3d/visual_instance.h b/scene/3d/visual_instance.h index 3b6fccf65f..63d15680aa 100644 --- a/scene/3d/visual_instance.h +++ b/scene/3d/visual_instance.h @@ -35,14 +35,13 @@ #include "core/rid.h" #include "scene/3d/spatial.h" #include "scene/resources/material.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class VisualInstance : public Spatial { GDCLASS(VisualInstance, Spatial); OBJ_CATEGORY("3D Visual Nodes"); + RID base; RID instance; uint32_t layers; @@ -69,6 +68,7 @@ public: virtual AABB get_transformed_aabb() const; // helper void set_base(const RID &p_base); + RID get_base() const; void set_layer_mask(uint32_t p_mask); uint32_t get_layer_mask() const; @@ -87,6 +87,7 @@ class GeometryInstance : public VisualInstance { public: enum Flags { FLAG_USE_BAKED_LIGHT = VS::INSTANCE_FLAG_USE_BAKED_LIGHT, + FLAG_DRAW_NEXT_FRAME_IF_VISIBLE = VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, FLAG_MAX = VS::INSTANCE_FLAG_MAX, }; @@ -137,6 +138,8 @@ public: void set_extra_cull_margin(float p_margin); float get_extra_cull_margin() const; + void set_custom_aabb(AABB aabb); + GeometryInstance(); }; diff --git a/scene/3d/voxel_light_baker.cpp b/scene/3d/voxel_light_baker.cpp index 30e38c8ee2..8e09930aed 100644 --- a/scene/3d/voxel_light_baker.cpp +++ b/scene/3d/voxel_light_baker.cpp @@ -212,9 +212,7 @@ static bool fast_tri_box_overlap(const Vector3 &boxcenter, const Vector3 boxhalf /* compute plane equation of triangle: normal*x+d=0 */ normal = e0.cross(e1); d = -normal.dot(v0); /* plane eq: normal.x+d=0 */ - if (!planeBoxOverlap(normal, d, boxhalfsize)) return false; - - return true; /* box and triangle overlaps */ + return planeBoxOverlap(normal, d, boxhalfsize); /* if true, box and triangle overlaps */ } static _FORCE_INLINE_ void get_uv_and_normal(const Vector3 &p_pos, const Vector3 *p_vtx, const Vector2 *p_uv, const Vector3 *p_normal, Vector2 &r_uv, Vector3 &r_normal) { @@ -835,7 +833,7 @@ void VoxelLightBaker::plot_light_directional(const Vector3 &p_direction, const C for (int i = 0; i < 3; i++) { - if (ABS(light_axis[i]) < CMP_EPSILON) + if (Math::is_zero_approx(light_axis[i])) continue; clip[clip_planes].normal[i] = 1.0; @@ -887,7 +885,7 @@ void VoxelLightBaker::plot_light_directional(const Vector3 &p_direction, const C distance -= distance_adv; } - if (result == idx) { + if (result == (uint32_t)idx) { //cell hit itself! hooray! Vector3 normal(cells[idx].normal[0], cells[idx].normal[1], cells[idx].normal[2]); @@ -978,7 +976,7 @@ void VoxelLightBaker::plot_light_omni(const Vector3 &p_pos, const Color &p_color for (int c = 0; c < 3; c++) { - if (ABS(light_axis[c]) < CMP_EPSILON) + if (Math::is_zero_approx(light_axis[c])) continue; clip[clip_planes].normal[c] = 1.0; @@ -1018,7 +1016,7 @@ void VoxelLightBaker::plot_light_omni(const Vector3 &p_pos, const Color &p_color distance -= distance_adv; } - if (result == idx) { + if (result == (uint32_t)idx) { //cell hit itself! hooray! if (normal == Vector3()) { @@ -1113,7 +1111,7 @@ void VoxelLightBaker::plot_light_spot(const Vector3 &p_pos, const Vector3 &p_axi for (int c = 0; c < 3; c++) { - if (ABS(light_axis[c]) < CMP_EPSILON) + if (Math::is_zero_approx(light_axis[c])) continue; clip[clip_planes].normal[c] = 1.0; @@ -1152,7 +1150,7 @@ void VoxelLightBaker::plot_light_spot(const Vector3 &p_pos, const Vector3 &p_axi distance -= distance_adv; } - if (result == idx) { + if (result == (uint32_t)idx) { //cell hit itself! hooray! if (normal == Vector3()) { @@ -1730,7 +1728,7 @@ Vector3 VoxelLightBaker::_compute_ray_trace_at_pos(const Vector3 &p_pos, const V //int level_limit = max_level; cell = 0; //start from root - for (int i = 0; i < max_level; i++) { + for (int j = 0; j < max_level; j++) { const Cell *bc = &cells[cell]; @@ -1759,14 +1757,14 @@ Vector3 VoxelLightBaker::_compute_ray_trace_at_pos(const Vector3 &p_pos, const V } if (unlikely(cell != CHILD_EMPTY)) { - for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { //anisotropic read light - float amount = direction.dot(aniso_normal[i]); + float amount = direction.dot(aniso_normal[j]); if (amount <= 0) continue; - accum.x += light[cell].accum[i][0] * amount; - accum.y += light[cell].accum[i][1] * amount; - accum.z += light[cell].accum[i][2] * amount; + accum.x += light[cell].accum[j][0] * amount; + accum.y += light[cell].accum[j][1] * amount; + accum.z += light[cell].accum[j][2] * amount; } accum.x += cells[cell].emission[0]; accum.y += cells[cell].emission[1]; @@ -1794,19 +1792,82 @@ void VoxelLightBaker::_lightmap_bake_point(uint32_t p_x, LightMap *p_line) { } } -Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float), void *p_bake_time_ud) { +Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh, float default_texels_per_unit, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float), void *p_bake_time_ud) { //transfer light information to a lightmap Ref<Mesh> mesh = p_mesh; - int width = mesh->get_lightmap_size_hint().x; - int height = mesh->get_lightmap_size_hint().y; - //step 1 - create lightmap + int width; + int height; Vector<LightMap> lightmap; - lightmap.resize(width * height); - Transform xform = to_cell_space * p_xform; + if (mesh->get_lightmap_size_hint() == Size2()) { + double area = 0; + double uv_area = 0; + for (int i = 0; i < mesh->get_surface_count(); i++) { + Array arrays = mesh->surface_get_arrays(i); + PoolVector<Vector3> vertices = arrays[Mesh::ARRAY_VERTEX]; + PoolVector<Vector2> uv2 = arrays[Mesh::ARRAY_TEX_UV2]; + PoolVector<int> indices = arrays[Mesh::ARRAY_INDEX]; + + ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER); + + int vc = vertices.size(); + PoolVector<Vector3>::Read vr = vertices.read(); + PoolVector<Vector2>::Read u2r = uv2.read(); + PoolVector<int>::Read ir; + int ic = 0; + + if (indices.size()) { + ic = indices.size(); + ir = indices.read(); + } + + int faces = ic ? ic / 3 : vc / 3; + for (int j = 0; j < faces; j++) { + Vector3 vertex[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + int idx = ic ? ir[j * 3 + k] : j * 3 + k; + vertex[k] = xform.xform(vr[idx]); + uv[k] = u2r[idx]; + } + + Vector3 p1 = vertex[0]; + Vector3 p2 = vertex[1]; + Vector3 p3 = vertex[2]; + double a = p1.distance_to(p2); + double b = p2.distance_to(p3); + double c = p3.distance_to(p1); + double halfPerimeter = (a + b + c) / 2.0; + area += sqrt(halfPerimeter * (halfPerimeter - a) * (halfPerimeter - b) * (halfPerimeter - c)); + + Vector2 uv_p1 = uv[0]; + Vector2 uv_p2 = uv[1]; + Vector2 uv_p3 = uv[2]; + double uv_a = uv_p1.distance_to(uv_p2); + double uv_b = uv_p2.distance_to(uv_p3); + double uv_c = uv_p3.distance_to(uv_p1); + double uv_halfPerimeter = (uv_a + uv_b + uv_c) / 2.0; + uv_area += sqrt(uv_halfPerimeter * (uv_halfPerimeter - uv_a) * (uv_halfPerimeter - uv_b) * (uv_halfPerimeter - uv_c)); + } + } + + if (uv_area < 0.0001f) { + uv_area = 1.0; + } + + int pixels = (ceil((1.0 / sqrt(uv_area)) * sqrt(area * default_texels_per_unit))); + width = height = CLAMP(pixels, 2, 4096); + } else { + width = mesh->get_lightmap_size_hint().x; + height = mesh->get_lightmap_size_hint().y; + } + + lightmap.resize(width * height); //step 2 plot faces to lightmap for (int i = 0; i < mesh->get_surface_count(); i++) { @@ -1833,16 +1894,16 @@ Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh } int faces = ic ? ic / 3 : vc / 3; - for (int i = 0; i < faces; i++) { + for (int j = 0; j < faces; j++) { Vector3 vertex[3]; Vector3 normal[3]; Vector2 uv[3]; - for (int j = 0; j < 3; j++) { - int idx = ic ? ir[i * 3 + j] : i * 3 + j; - vertex[j] = xform.xform(vr[idx]); - normal[j] = xform.basis.xform(nr[idx]).normalized(); - uv[j] = u2r[idx]; + for (int k = 0; k < 3; k++) { + int idx = ic ? ir[j * 3 + k] : j * 3 + k; + vertex[k] = xform.xform(vr[idx]); + normal[k] = xform.basis.xform(nr[idx]).normalized(); + uv[k] = u2r[idx]; } _plot_triangle(uv, vertex, normal, lightmap.ptrw(), width, height); @@ -2208,7 +2269,7 @@ PoolVector<int> VoxelLightBaker::create_gi_probe_data() { } { - uint16_t alpha = CLAMP(uint32_t(bake_cells[i].alpha * 65535.0), 0, 65535); + uint16_t alpha = MIN(uint32_t(bake_cells[i].alpha * 65535.0), 65535); uint16_t level = bake_cells[i].level; w32[ofs++] = (uint32_t(level) << 16) | uint32_t(alpha); @@ -2252,7 +2313,7 @@ void VoxelLightBaker::_debug_mesh(int p_idx, int p_level, const AABB &p_aabb, Re uint32_t child = bake_cells[p_idx].children[i]; - if (child == CHILD_EMPTY || child >= max_original_cells) + if (child == CHILD_EMPTY || child >= (uint32_t)max_original_cells) continue; AABB aabb = p_aabb; diff --git a/scene/3d/voxel_light_baker.h b/scene/3d/voxel_light_baker.h index 295e099d47..2e2efc62ff 100644 --- a/scene/3d/voxel_light_baker.h +++ b/scene/3d/voxel_light_baker.h @@ -177,7 +177,7 @@ public: PoolVector<float> light; }; - Error make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float) = NULL, void *p_bake_time_ud = NULL); + Error make_lightmap(const Transform &p_xform, Ref<Mesh> &p_mesh, float default_texels_per_unit, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float) = NULL, void *p_bake_time_ud = NULL); PoolVector<int> create_gi_probe_data(); Ref<MultiMesh> create_debug_multimesh(DebugMode p_mode = DEBUG_ALBEDO); diff --git a/scene/3d/scenario_fx.cpp b/scene/3d/world_environment.cpp index ad9b61e1ff..8d46b4161d 100644 --- a/scene/3d/scenario_fx.cpp +++ b/scene/3d/world_environment.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* scenario_fx.cpp */ +/* world_environment.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "scenario_fx.h" +#include "world_environment.h" #include "scene/main/viewport.h" void WorldEnvironment::_notification(int p_what) { @@ -80,7 +80,7 @@ Ref<Environment> WorldEnvironment::get_environment() const { String WorldEnvironment::get_configuration_warning() const { if (!environment.is_valid()) { - return TTR("WorldEnvironment needs an Environment resource."); + return TTR("WorldEnvironment requires its \"Environment\" property to contain an Environment to have a visible effect."); } if (/*!is_visible_in_tree() ||*/ !is_inside_tree()) diff --git a/scene/3d/scenario_fx.h b/scene/3d/world_environment.h index 6317dae75d..656e16371f 100644 --- a/scene/3d/scenario_fx.h +++ b/scene/3d/world_environment.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* scenario_fx.h */ +/* world_environment.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -33,10 +33,6 @@ #include "scene/3d/spatial.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class WorldEnvironment : public Node { GDCLASS(WorldEnvironment, Node); diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index dded44b990..416a291da1 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -266,7 +266,7 @@ float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) { // fill in weights - if (point_lower == -1) { + if (point_lower == -1 && point_higher != -1) { // we are on the left side, no other point to the left // we just play the next point. diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index dfac88b712..2e2d848450 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -34,7 +34,7 @@ #include "scene/animation/animation_tree.h" class AnimationNodeBlendSpace1D : public AnimationRootNode { - GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode) + GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode); enum { MAX_BLEND_POINTS = 64 diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index 1fe14e633b..75031f0149 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -461,9 +461,9 @@ float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) { 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; + Vector2 closest2 = Geometry::get_closest_point_to_segment_2d(blend_pos, s); + if (first || closest2.distance_to(blend_pos) < best_point.distance_to(blend_pos)) { + best_point = closest2; blend_triangle = i; first = false; float d = s[0].distance_to(s[1]); @@ -472,7 +472,7 @@ float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) { blend_weights[(j + 1) % 3] = 0.0; blend_weights[(j + 2) % 3] = 0.0; } else { - float c = s[0].distance_to(closest) / d; + float c = s[0].distance_to(closest2) / d; blend_weights[j] = 1.0 - c; blend_weights[(j + 1) % 3] = c; @@ -527,7 +527,7 @@ float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) { } } - if (new_closest != closest) { + if (new_closest != closest && new_closest != -1) { float from = 0; if (blend_mode == BLEND_MODE_DISCRETE_CARRY && closest != -1) { diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index c21360beb9..66ba9b0db7 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -34,7 +34,8 @@ #include "scene/animation/animation_tree.h" class AnimationNodeBlendSpace2D : public AnimationRootNode { - GDCLASS(AnimationNodeBlendSpace2D, AnimationRootNode) + GDCLASS(AnimationNodeBlendSpace2D, AnimationRootNode); + public: enum BlendMode { BLEND_MODE_INTERPOLATED, diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 0daa574f72..20a09696e1 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -363,6 +363,7 @@ AnimationNodeOneShot::AnimationNodeOneShot() { fade_out = 0.1; autorestart = false; autorestart_delay = 1; + autorestart_random_delay = 0; mix = MIX_MODE_BLEND; sync = false; @@ -857,6 +858,7 @@ AnimationNodeTransition::AnimationNodeTransition() { time = "time"; current = "current"; prev_current = "prev_current"; + xfade = 0.0; enabled_inputs = 0; for (int i = 0; i < MAX_INPUTS; i++) { @@ -1047,7 +1049,7 @@ AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node return CONNECTION_ERROR_NO_INPUT; } - if (!nodes.has(p_input_node)) { + if (p_input_node == p_output_node) { return CONNECTION_ERROR_SAME_NODE; } diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index c16dcb1b8c..a6ef78d82e 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -322,7 +322,8 @@ public: }; class AnimationNodeOutput : public AnimationNode { - GDCLASS(AnimationNodeOutput, AnimationNode) + GDCLASS(AnimationNodeOutput, AnimationNode); + public: virtual String get_caption() const; virtual float process(float p_time, bool p_seek); @@ -332,7 +333,7 @@ public: ///// class AnimationNodeBlendTree : public AnimationRootNode { - GDCLASS(AnimationNodeBlendTree, AnimationRootNode) + GDCLASS(AnimationNodeBlendTree, AnimationRootNode); struct Node { Ref<AnimationNode> node; diff --git a/scene/animation/animation_cache.cpp b/scene/animation/animation_cache.cpp index 6c67af5777..e26bd5b5a1 100644 --- a/scene/animation/animation_cache.cpp +++ b/scene/animation/animation_cache.cpp @@ -80,8 +80,7 @@ void AnimationCache::_update_cache() { if (!node) { path_cache.push_back(Path()); - ERR_EXPLAIN("Invalid Track Path in Animation: " + np); - ERR_CONTINUE(!node); + ERR_CONTINUE_MSG(!node, "Invalid track path in animation: " + np + "."); } Path path; @@ -92,8 +91,7 @@ void AnimationCache::_update_cache() { if (np.get_subname_count() > 1) { path_cache.push_back(Path()); - ERR_EXPLAIN("Transform tracks can't have a subpath: " + np); - ERR_CONTINUE(animation->track_get_type(i) == Animation::TYPE_TRANSFORM); + ERR_CONTINUE_MSG(animation->track_get_type(i) == Animation::TYPE_TRANSFORM, "Transform tracks can't have a subpath: " + np + "."); } Spatial *sp = Object::cast_to<Spatial>(node); @@ -101,8 +99,7 @@ void AnimationCache::_update_cache() { if (!sp) { path_cache.push_back(Path()); - ERR_EXPLAIN("Transform track not of type Spatial: " + np); - ERR_CONTINUE(!sp); + ERR_CONTINUE_MSG(!sp, "Transform track not of type Spatial: " + np + "."); } if (np.get_subname_count() == 1) { @@ -113,15 +110,13 @@ void AnimationCache::_update_cache() { if (!sk) { path_cache.push_back(Path()); - ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton!: " + np); - ERR_CONTINUE(!sk); + ERR_CONTINUE_MSG(!sk, "Property defined in Transform track, but not a Skeleton!: " + np + "."); } int idx = sk->find_bone(ps); if (idx == -1) { path_cache.push_back(Path()); - ERR_EXPLAIN("Property defined in Transform track, but not a Skeleton Bone!: " + np); - ERR_CONTINUE(idx == -1); + ERR_CONTINUE_MSG(idx == -1, "Property defined in Transform track, but not a Skeleton Bone!: " + np + "."); } path.bone_idx = idx; @@ -133,19 +128,19 @@ void AnimationCache::_update_cache() { } else { if (np.get_subname_count() > 0) { - RES res; + RES res2; Vector<StringName> leftover_subpath; // We don't want to cache the last resource unless it is a method call bool is_method = animation->track_get_type(i) == Animation::TYPE_METHOD; - root->get_node_and_resource(np, res, leftover_subpath, is_method); + root->get_node_and_resource(np, res2, leftover_subpath, is_method); - if (res.is_valid()) { - path.resource = res; + if (res2.is_valid()) { + path.resource = res2; } else { path.node = node; } - path.object = res.is_valid() ? res.ptr() : (Object *)node; + path.object = res2.is_valid() ? res2.ptr() : (Object *)node; path.subpath = leftover_subpath; } else { @@ -161,8 +156,7 @@ void AnimationCache::_update_cache() { if (np.get_subname_count() == 0) { path_cache.push_back(Path()); - ERR_EXPLAIN("Value Track lacks property: " + np); - ERR_CONTINUE(np.get_subname_count() == 0); + ERR_CONTINUE_MSG(np.get_subname_count() == 0, "Value Track lacks property: " + np + "."); } } else if (animation->track_get_type(i) == Animation::TYPE_METHOD) { @@ -170,8 +164,7 @@ void AnimationCache::_update_cache() { if (path.subpath.size() != 0) { // Trying to call a method of a non-resource path_cache.push_back(Path()); - ERR_EXPLAIN("Method Track has property: " + np); - ERR_CONTINUE(path.subpath.size() != 0); + ERR_CONTINUE_MSG(path.subpath.size() != 0, "Method Track has property: " + np + "."); } } @@ -309,7 +302,8 @@ void AnimationCache::set_all(float p_time, float p_delta) { } } break; - default: {} + default: { + } } } } diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 56faba27cf..65bf1e0134 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -178,11 +178,11 @@ float AnimationNodeStateMachinePlayback::get_current_length() const { return len_current; } -bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *sm, const StringName &p_travel) { +bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel) { ERR_FAIL_COND_V(!playing, false); - ERR_FAIL_COND_V(!sm->states.has(p_travel), false); - ERR_FAIL_COND_V(!sm->states.has(current), false); + ERR_FAIL_COND_V(!p_state_machine->states.has(p_travel), false); + ERR_FAIL_COND_V(!p_state_machine->states.has(current), false); path.clear(); //a new one will be needed @@ -191,25 +191,25 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *sm, c loops_current = 0; // reset loops, so fade does not happen immediately - Vector2 current_pos = sm->states[current].position; - Vector2 target_pos = sm->states[p_travel].position; + Vector2 current_pos = p_state_machine->states[current].position; + Vector2 target_pos = p_state_machine->states[p_travel].position; Map<StringName, AStarCost> cost_map; List<int> open_list; //build open list - for (int i = 0; i < sm->transitions.size(); i++) { - if (sm->transitions[i].from == current) { + for (int i = 0; i < p_state_machine->transitions.size(); i++) { + if (p_state_machine->transitions[i].from == current) { open_list.push_back(i); - float cost = sm->states[sm->transitions[i].to].position.distance_to(current_pos); - cost *= sm->transitions[i].transition->get_priority(); + float cost = p_state_machine->states[p_state_machine->transitions[i].to].position.distance_to(current_pos); + cost *= p_state_machine->transitions[i].transition->get_priority(); AStarCost ap; ap.prev = current; ap.distance = cost; - cost_map[sm->transitions[i].to] = ap; + cost_map[p_state_machine->transitions[i].to] = ap; - if (sm->transitions[i].to == p_travel) { //prematurely found it! :D + if (p_state_machine->transitions[i].to == p_travel) { //prematurely found it! :D path.push_back(p_travel); return true; } @@ -230,42 +230,43 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *sm, c for (List<int>::Element *E = open_list.front(); E; E = E->next()) { - float cost = cost_map[sm->transitions[E->get()].to].distance; - cost += sm->states[sm->transitions[E->get()].to].position.distance_to(target_pos); + float cost = cost_map[p_state_machine->transitions[E->get()].to].distance; + cost += p_state_machine->states[p_state_machine->transitions[E->get()].to].position.distance_to(target_pos); if (cost < least_cost) { least_cost_transition = E; + least_cost = cost; } } - StringName transition_prev = sm->transitions[least_cost_transition->get()].from; - StringName transition = sm->transitions[least_cost_transition->get()].to; + StringName transition_prev = p_state_machine->transitions[least_cost_transition->get()].from; + StringName transition = p_state_machine->transitions[least_cost_transition->get()].to; - for (int i = 0; i < sm->transitions.size(); i++) { - if (sm->transitions[i].from != transition || sm->transitions[i].to == transition_prev) { + for (int i = 0; i < p_state_machine->transitions.size(); i++) { + if (p_state_machine->transitions[i].from != transition || p_state_machine->transitions[i].to == transition_prev) { continue; //not interested on those } - float distance = sm->states[sm->transitions[i].from].position.distance_to(sm->states[sm->transitions[i].to].position); - distance *= sm->transitions[i].transition->get_priority(); - distance += cost_map[sm->transitions[i].from].distance; + float distance = p_state_machine->states[p_state_machine->transitions[i].from].position.distance_to(p_state_machine->states[p_state_machine->transitions[i].to].position); + distance *= p_state_machine->transitions[i].transition->get_priority(); + distance += cost_map[p_state_machine->transitions[i].from].distance; - if (cost_map.has(sm->transitions[i].to)) { + if (cost_map.has(p_state_machine->transitions[i].to)) { //oh this was visited already, can we win the cost? - if (distance < cost_map[sm->transitions[i].to].distance) { - cost_map[sm->transitions[i].to].distance = distance; - cost_map[sm->transitions[i].to].prev = sm->transitions[i].from; + if (distance < cost_map[p_state_machine->transitions[i].to].distance) { + cost_map[p_state_machine->transitions[i].to].distance = distance; + cost_map[p_state_machine->transitions[i].to].prev = p_state_machine->transitions[i].from; } } else { //add to open list AStarCost ac; - ac.prev = sm->transitions[i].from; + ac.prev = p_state_machine->transitions[i].from; ac.distance = distance; - cost_map[sm->transitions[i].to] = ac; + cost_map[p_state_machine->transitions[i].to] = ac; open_list.push_back(i); - if (sm->transitions[i].to == p_travel) { + if (p_state_machine->transitions[i].to == p_travel) { found_route = true; break; } @@ -291,12 +292,12 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *sm, c return true; } -float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, float p_time, bool p_seek) { +float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_state_machine, float p_time, bool p_seek) { //if not playing and it can restart, then restart if (!playing && start_request == StringName()) { - if (!stop_request && sm->start_node) { - start(sm->start_node); + if (!stop_request && p_state_machine->start_node) { + start(p_state_machine->start_node); } else { return 0; } @@ -314,12 +315,12 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, if (start_request_travel) { if (!playing) { + String node_name = start_request; start_request = StringName(); - ERR_EXPLAIN("Can't travel to '" + String(start_request) + "' if state machine is not active."); - ERR_FAIL_V(0); + ERR_FAIL_V_MSG(0, "Can't travel to '" + node_name + "' if state machine is not playing."); } - if (!_travel(sm, start_request)) { + if (!_travel(p_state_machine, start_request)) { //can't travel, then teleport path.clear(); current = start_request; @@ -338,16 +339,16 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, if (do_start) { - if (sm->start_node != StringName() && p_seek && p_time == 0) { - current = sm->start_node; + if (p_state_machine->start_node != StringName() && p_seek && p_time == 0) { + current = p_state_machine->start_node; } - len_current = sm->blend_node(current, sm->states[current].node, 0, true, 1.0, AnimationNode::FILTER_IGNORE, false); + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, 1.0, AnimationNode::FILTER_IGNORE, false); pos_current = 0; loops_current = 0; } - if (!sm->states.has(current)) { + if (!p_state_machine->states.has(current)) { playing = false; //current does not exist current = StringName(); return 0; @@ -356,7 +357,7 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, if (fading_from != StringName()) { - if (!sm->states.has(fading_from)) { + if (!p_state_machine->states.has(fading_from)) { fading_from = StringName(); } else { if (!p_seek) { @@ -369,11 +370,11 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, } } - float rem = sm->blend_node(current, sm->states[current].node, p_time, p_seek, fade_blend, AnimationNode::FILTER_IGNORE, false); + float rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, fade_blend, AnimationNode::FILTER_IGNORE, false); if (fading_from != StringName()) { - sm->blend_node(current, sm->states[fading_from].node, p_time, p_seek, 1.0 - fade_blend, AnimationNode::FILTER_IGNORE, false); + p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, 1.0 - fade_blend, AnimationNode::FILTER_IGNORE, false); } //guess playback position @@ -398,39 +399,40 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, if (path.size()) { - for (int i = 0; i < sm->transitions.size(); i++) { - if (sm->transitions[i].from == current && sm->transitions[i].to == path[0]) { - next_xfade = sm->transitions[i].transition->get_xfade_time(); - switch_mode = sm->transitions[i].transition->get_switch_mode(); + for (int i = 0; i < p_state_machine->transitions.size(); i++) { + if (p_state_machine->transitions[i].from == current && p_state_machine->transitions[i].to == path[0]) { + next_xfade = p_state_machine->transitions[i].transition->get_xfade_time(); + switch_mode = p_state_machine->transitions[i].transition->get_switch_mode(); next = path[0]; } } } else { float priority_best = 1e20; int auto_advance_to = -1; - for (int i = 0; i < sm->transitions.size(); i++) { + for (int i = 0; i < p_state_machine->transitions.size(); i++) { bool auto_advance = false; - if (sm->transitions[i].transition->has_auto_advance()) { + if (p_state_machine->transitions[i].transition->has_auto_advance()) { auto_advance = true; } - StringName advance_condition_name = sm->transitions[i].transition->get_advance_condition_name(); - if (advance_condition_name != StringName() && bool(sm->get_parameter(advance_condition_name))) { + StringName advance_condition_name = p_state_machine->transitions[i].transition->get_advance_condition_name(); + if (advance_condition_name != StringName() && bool(p_state_machine->get_parameter(advance_condition_name))) { auto_advance = true; } - if (sm->transitions[i].from == current && auto_advance) { + if (p_state_machine->transitions[i].from == current && auto_advance) { - if (sm->transitions[i].transition->get_priority() < priority_best) { + if (p_state_machine->transitions[i].transition->get_priority() <= priority_best) { + priority_best = p_state_machine->transitions[i].transition->get_priority(); auto_advance_to = i; } } } if (auto_advance_to != -1) { - next = sm->transitions[auto_advance_to].to; - next_xfade = sm->transitions[auto_advance_to].transition->get_xfade_time(); - switch_mode = sm->transitions[auto_advance_to].transition->get_switch_mode(); + next = p_state_machine->transitions[auto_advance_to].to; + next_xfade = p_state_machine->transitions[auto_advance_to].transition->get_xfade_time(); + switch_mode = p_state_machine->transitions[auto_advance_to].transition->get_switch_mode(); } } @@ -439,13 +441,13 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, bool goto_next = false; - if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE) { - goto_next = fading_from == StringName(); - } else { + if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) { goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0; if (loops_current > 0) { next_xfade = 0; } + } else { + goto_next = fading_from == StringName(); } if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped @@ -465,12 +467,12 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, } current = next; if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { - len_current = sm->blend_node(current, sm->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); pos_current = MIN(pos_current, len_current); - sm->blend_node(current, sm->states[current].node, pos_current, true, 0, AnimationNode::FILTER_IGNORE, false); + p_state_machine->blend_node(current, p_state_machine->states[current].node, pos_current, true, 0, AnimationNode::FILTER_IGNORE, false); } else { - len_current = sm->blend_node(current, sm->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); + len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); pos_current = 0; } @@ -480,9 +482,9 @@ float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, } //compute time left for transitions by using the end node - if (sm->end_node != StringName() && sm->end_node != current) { + if (p_state_machine->end_node != StringName() && p_state_machine->end_node != current) { - rem = sm->blend_node(sm->end_node, sm->states[sm->end_node].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); + rem = p_state_machine->blend_node(p_state_machine->end_node, p_state_machine->states[p_state_machine->end_node].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); } return rem; @@ -597,6 +599,9 @@ void AnimationNodeStateMachine::remove_node(const StringName &p_name) { { Ref<AnimationNode> node = states[p_name].node; + + ERR_FAIL_COND(node.is_null()); + node->disconnect("tree_changed", this, "_tree_changed"); } @@ -959,7 +964,7 @@ void AnimationNodeStateMachine::_bind_methods() { 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("set_graph_offset", "offset"), &AnimationNodeStateMachine::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); ClassDB::bind_method(D_METHOD("_tree_changed"), &AnimationNodeStateMachine::_tree_changed); diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index e47b940c35..26909a326e 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -34,7 +34,8 @@ #include "scene/animation/animation_tree.h" class AnimationNodeStateMachineTransition : public Resource { - GDCLASS(AnimationNodeStateMachineTransition, Resource) + GDCLASS(AnimationNodeStateMachineTransition, Resource); + public: enum SwitchMode { SWITCH_MODE_IMMEDIATE, diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 1d2001c30d..051f832882 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -34,12 +34,23 @@ #include "core/message_queue.h" #include "scene/scene_string_names.h" #include "servers/audio/audio_stream.h" + #ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#include "scene/2d/skeleton_2d.h" + void AnimatedValuesBackup::update_skeletons() { for (int i = 0; i < entries.size(); i++) { if (entries[i].bone_idx != -1) { + // 3D bone Object::cast_to<Skeleton>(entries[i].object)->notification(Skeleton::NOTIFICATION_UPDATE_SKELETON); + } else { + Bone2D *bone = Object::cast_to<Bone2D>(entries[i].object); + if (bone && bone->skeleton) { + // 2D bone + bone->skeleton->_update_transform(); + } } } } @@ -237,10 +248,7 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) { RES resource; Vector<StringName> leftover_path; Node *child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path); - if (!child) { - ERR_EXPLAIN("On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'"); - } - ERR_CONTINUE(!child); // couldn't find the child node + ERR_CONTINUE_MSG(!child, "On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'."); // couldn't find the child node uint32_t id = resource.is_valid() ? resource->get_instance_id() : child->get_instance_id(); int bone_idx = -1; @@ -287,7 +295,6 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) { // broken track (nonexistent bone) p_anim->node_cache[i]->skeleton = NULL; p_anim->node_cache[i]->spatial = NULL; - printf("bone is %ls\n", String(bone_name).c_str()); ERR_CONTINUE(p_anim->node_cache[i]->bone_idx < 0); } } else { @@ -344,10 +351,16 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float for (int i = 0; i < a->get_track_count(); i++) { + // If an animation changes this animation (or it animates itself) + // we need to recreate our animation cache + if (p_anim->node_cache.size() != a->get_track_count()) { + _ensure_node_caches(p_anim); + } + TrackNodeCache *nc = p_anim->node_cache[i]; - if (!nc) // no node cache for this track, skip it - continue; + if (!nc) + continue; // no node cache for this track, skip it if (!a->track_is_enabled(i)) continue; // do nothing if the track is disabled @@ -545,14 +558,24 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float #endif if (can_call) { - MessageQueue::get_singleton()->push_call( - nc->node, - 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()); + if (method_call_mode == ANIMATION_METHOD_CALL_DEFERRED) { + MessageQueue::get_singleton()->push_call( + nc->node, + 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()); + } else { + nc->node->call( + 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()); + } } } @@ -947,8 +970,7 @@ void AnimationPlayer::_animation_process(float p_delta) { Error AnimationPlayer::add_animation(const StringName &p_name, const Ref<Animation> &p_animation) { #ifdef DEBUG_ENABLED - ERR_EXPLAIN("Invalid animation name: " + String(p_name)); - ERR_FAIL_COND_V(String(p_name).find("/") != -1 || String(p_name).find(":") != -1 || String(p_name).find(",") != -1 || String(p_name).find("[") != -1, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V_MSG(String(p_name).find("/") != -1 || String(p_name).find(":") != -1 || String(p_name).find(",") != -1 || String(p_name).find("[") != -1, ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); #endif ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER); @@ -985,25 +1007,12 @@ void AnimationPlayer::remove_animation(const StringName &p_name) { void AnimationPlayer::_ref_anim(const Ref<Animation> &p_anim) { - if (used_anims.has(p_anim)) - used_anims[p_anim]++; - else { - used_anims[p_anim] = 1; - Ref<Animation>(p_anim)->connect("changed", this, "_animation_changed"); - } + Ref<Animation>(p_anim)->connect(SceneStringNames::get_singleton()->tracks_changed, this, "_animation_changed", varray(), CONNECT_REFERENCE_COUNTED); } void AnimationPlayer::_unref_anim(const Ref<Animation> &p_anim) { - ERR_FAIL_COND(!used_anims.has(p_anim)); - - int &n = used_anims[p_anim]; - n--; - if (n == 0) { - - Ref<Animation>(p_anim)->disconnect("changed", this, "_animation_changed"); - used_anims.erase(p_anim); - } + Ref<Animation>(p_anim)->disconnect(SceneStringNames::get_singleton()->tracks_changed, this, "_animation_changed"); } void AnimationPlayer::rename_animation(const StringName &p_name, const StringName &p_new_name) { @@ -1140,17 +1149,12 @@ void AnimationPlayer::play_backwards(const StringName &p_name, float p_custom_bl void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float p_custom_scale, bool p_from_end) { - //printf("animation is %ls\n", String(p_name).c_str()); - //ERR_FAIL_COND(!is_inside_scene()); StringName name = p_name; if (String(name) == "") name = playback.assigned; - if (!animation_set.has(name)) { - ERR_EXPLAIN("Animation not found: " + name); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(!animation_set.has(name), "Animation not found: " + name + "."); Playback &c = playback; @@ -1196,12 +1200,26 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float } } - _stop_playing_caches(); + if (get_current_animation() != p_name) { + _stop_playing_caches(); + } c.current.from = &animation_set[name]; - c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0; + + if (c.assigned != name) { // reset + c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0; + } else { + if (p_from_end && c.current.pos == 0) { + // Animation reset BUT played backwards, set position to the end + c.current.pos = c.current.from->animation->get_length(); + } else if (!p_from_end && c.current.pos == c.current.from->animation->get_length()) { + // Animation resumed but already ended, set position to the beginning + c.current.pos = 0; + } + } + c.current.speed_scale = p_custom_scale; - c.assigned = p_name; + c.assigned = name; c.seeked = false; c.started = true; @@ -1280,6 +1298,7 @@ void AnimationPlayer::stop(bool p_reset) { if (p_reset) { c.current.from = NULL; c.current.speed_scale = 1; + c.current.pos = 0; } _set_process(false); queued.clear(); @@ -1456,6 +1475,16 @@ AnimationPlayer::AnimationProcessMode AnimationPlayer::get_animation_process_mod return animation_process_mode; } +void AnimationPlayer::set_method_call_mode(AnimationMethodCallMode p_mode) { + + method_call_mode = p_mode; +} + +AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode() const { + + return method_call_mode; +} + void AnimationPlayer::_set_process(bool p_process, bool p_force) { if (processing == p_process && !p_force) @@ -1507,13 +1536,19 @@ NodePath AnimationPlayer::get_root() const { void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { +#ifdef TOOLS_ENABLED + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; +#else + const String quote_style = "\""; +#endif + String pf = p_function; 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()) { - r_options->push_back("\"" + String(E->get()) + "\""); + r_options->push_back(quote_style + String(E->get()) + quote_style); } } Node::get_argument_options(p_function, p_idx, r_options); @@ -1637,6 +1672,9 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_animation_process_mode", "mode"), &AnimationPlayer::set_animation_process_mode); ClassDB::bind_method(D_METHOD("get_animation_process_mode"), &AnimationPlayer::get_animation_process_mode); + ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode); + ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode); + ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position); ClassDB::bind_method(D_METHOD("get_current_animation_length"), &AnimationPlayer::get_current_animation_length); @@ -1655,6 +1693,7 @@ void AnimationPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_default_blend_time", "get_default_blend_time"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", 0), "set_active", "is_active"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode"); 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"))); @@ -1664,6 +1703,9 @@ void AnimationPlayer::_bind_methods() { BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); + + BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_DEFERRED); + BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_IMMEDIATE); } AnimationPlayer::AnimationPlayer() { @@ -1676,6 +1718,7 @@ AnimationPlayer::AnimationPlayer() { end_reached = false; end_notify = false; animation_process_mode = ANIMATION_PROCESS_IDLE; + method_call_mode = ANIMATION_METHOD_CALL_DEFERRED; processing = false; default_blend_time = 0; root = SceneStringNames::get_singleton()->path_pp; diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 175072edcf..320fd2084c 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -35,9 +35,6 @@ #include "scene/3d/skeleton.h" #include "scene/3d/spatial.h" #include "scene/resources/animation.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ #ifdef TOOLS_ENABLED // To save/restore animated values @@ -68,6 +65,11 @@ public: ANIMATION_PROCESS_MANUAL, }; + enum AnimationMethodCallMode { + ANIMATION_METHOD_CALL_DEFERRED, + ANIMATION_METHOD_CALL_IMMEDIATE, + }; + private: enum { @@ -114,10 +116,12 @@ private: Variant value_accum; uint64_t accum_pass; Variant capture; - PropertyAnim() { - accum_pass = 0; - object = NULL; - } + + PropertyAnim() : + owner(NULL), + special(SP_NONE), + object(NULL), + accum_pass(0) {} }; Map<StringName, PropertyAnim> property_anim; @@ -129,25 +133,28 @@ private: float bezier_accum; Object *object; uint64_t accum_pass; - BezierAnim() { - accum_pass = 0; - bezier_accum = 0; - object = NULL; - } + + BezierAnim() : + owner(NULL), + bezier_accum(0.0), + object(NULL), + accum_pass(0) {} }; Map<StringName, BezierAnim> bezier_anim; - TrackNodeCache() { - skeleton = NULL; - spatial = NULL; - node = NULL; - accum_pass = 0; - bone_idx = -1; - node_2d = NULL; - audio_playing = false; - animation_playing = false; - } + TrackNodeCache() : + id(0), + node(NULL), + spatial(NULL), + node_2d(NULL), + skeleton(NULL), + bone_idx(-1), + accum_pass(0), + audio_playing(false), + audio_start(0.0), + audio_len(0.0), + animation_playing(false) {} }; struct TrackNodeCacheKey { @@ -176,8 +183,6 @@ private: int cache_update_bezier_size; Set<TrackNodeCache *> playing_caches; - Map<Ref<Animation>, int> used_anims; - uint64_t accum_pass; float speed_scale; float default_blend_time; @@ -243,6 +248,7 @@ private: String autoplay; AnimationProcessMode animation_process_mode; + AnimationMethodCallMode method_call_mode; bool processing; bool active; @@ -335,6 +341,9 @@ public: void set_animation_process_mode(AnimationProcessMode p_mode); AnimationProcessMode get_animation_process_mode() const; + void set_method_call_mode(AnimationMethodCallMode p_mode); + AnimationMethodCallMode get_method_call_mode() const; + void seek(float p_time, bool p_update = false); void seek_delta(float p_time, float p_delta); float get_current_animation_position() const; @@ -360,5 +369,6 @@ public: }; VARIANT_ENUM_CAST(AnimationPlayer::AnimationProcessMode); +VARIANT_ENUM_CAST(AnimationPlayer::AnimationMethodCallMode); #endif diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index dc022bcd70..bb7c400cfe 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -316,7 +316,7 @@ String AnimationNode::get_caption() const { void AnimationNode::add_input(const String &p_name) { //root nodes can't add inputs - ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != NULL) + 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; @@ -686,7 +686,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } break; default: { - ERR_PRINT("Animation corrupted (invalid track type"); + ERR_PRINT("Animation corrupted (invalid track type)"); continue; } } @@ -883,16 +883,16 @@ void AnimationTree::_process_graph(float p_delta) { TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - if (t->process_pass != process_pass) { + if (track->root_motion) { - t->process_pass = process_pass; - t->loc = Vector3(); - t->rot = Quat(); - t->rot_blend_accum = 0; - t->scale = Vector3(); - } + if (t->process_pass != process_pass) { - if (track->root_motion) { + t->process_pass = process_pass; + t->loc = Vector3(); + t->rot = Quat(); + t->rot_blend_accum = 0; + t->scale = Vector3(1, 1, 1); + } float prev_time = time - delta; if (prev_time < 0) { @@ -946,7 +946,14 @@ void AnimationTree::_process_graph(float p_delta) { 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 (t->process_pass != process_pass) { + + t->process_pass = process_pass; + t->loc = loc; + t->rot = rot; + t->rot_blend_accum = 0; + t->scale = scale; + } if (err != OK) continue; @@ -978,8 +985,7 @@ void AnimationTree::_process_graph(float p_delta) { continue; if (t->process_pass != process_pass) { - Variant::CallError ce; - t->value = Variant::construct(value.get_type(), NULL, 0, ce); //reset + t->value = value; t->process_pass = process_pass; } @@ -1009,10 +1015,10 @@ void AnimationTree::_process_graph(float p_delta) { a->method_track_get_key_indices(i, time, delta, &indices); - for (List<int>::Element *E = indices.front(); E; E = E->next()) { + for (List<int>::Element *F = indices.front(); F; F = F->next()) { - StringName method = a->method_track_get_name(i, E->get()); - Vector<Variant> params = a->method_track_get_params(i, E->get()); + StringName method = a->method_track_get_name(i, F->get()); + Vector<Variant> params = a->method_track_get_params(i, F->get()); int s = params.size(); @@ -1036,7 +1042,7 @@ void AnimationTree::_process_graph(float p_delta) { float bezier = a->bezier_track_interpolate(i, time); if (t->process_pass != process_pass) { - t->value = 0; + t->value = bezier; t->process_pass = process_pass; } @@ -1151,9 +1157,9 @@ void AnimationTree::_process_graph(float p_delta) { TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track); - AnimationPlayer *player = Object::cast_to<AnimationPlayer>(t->object); + AnimationPlayer *player2 = Object::cast_to<AnimationPlayer>(t->object); - if (!player) + if (!player2) continue; if (delta == 0 || seeked) { @@ -1165,10 +1171,10 @@ void AnimationTree::_process_graph(float p_delta) { 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)) + if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) continue; - Ref<Animation> anim = player->get_animation(anim_name); + Ref<Animation> anim = player2->get_animation(anim_name); float at_anim_pos; @@ -1178,14 +1184,14 @@ void AnimationTree::_process_graph(float p_delta) { 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); + if (player2->is_playing() || seeked) { + player2->play(anim_name); + player2->seek(at_anim_pos); t->playing = true; playing_caches.insert(t); } else { - player->set_assigned_animation(anim_name); - player->seek(at_anim_pos, true); + player2->set_assigned_animation(anim_name); + player2->seek(at_anim_pos, true); } } else { //find stuff to play @@ -1195,15 +1201,15 @@ void AnimationTree::_process_graph(float p_delta) { 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 (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { if (playing_caches.has(t)) { playing_caches.erase(t); - player->stop(); + player2->stop(); t->playing = false; } } else { - player->play(anim_name); + player2->play(anim_name); t->playing = true; playing_caches.insert(t); } @@ -1233,8 +1239,6 @@ void AnimationTree::_process_graph(float p_delta) { 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) { @@ -1268,7 +1272,8 @@ void AnimationTree::_process_graph(float p_delta) { t->object->set_indexed(t->subpath, t->value); } break; - default: {} //the rest don't matter + default: { + } //the rest don't matter } } } @@ -1293,9 +1298,17 @@ void AnimationTree::_notification(int p_what) { _clear_caches(); if (last_animation_player) { - Object *old_player = ObjectDB::get_instance(last_animation_player); - if (old_player) { - old_player->disconnect("caches_cleared", this, "_clear_caches"); + Object *player = ObjectDB::get_instance(last_animation_player); + if (player) { + player->disconnect("caches_cleared", this, "_clear_caches"); + } + } + } else if (p_what == NOTIFICATION_ENTER_TREE) { + if (last_animation_player) { + + Object *player = ObjectDB::get_instance(last_animation_player); + if (player) { + player->connect("caches_cleared", this, "_clear_caches"); } } } @@ -1329,15 +1342,15 @@ String AnimationTree::get_configuration_warning() const { if (!root.is_valid()) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } - warning += TTR("A root AnimationNode for the graph is not set."); + warning += TTR("No root AnimationNode for the graph is set."); } if (!has_node(animation_player)) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } warning += TTR("Path to an AnimationPlayer node containing animations is not set."); @@ -1348,7 +1361,7 @@ String AnimationTree::get_configuration_warning() const { if (!player) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } warning += TTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node."); @@ -1357,10 +1370,10 @@ String AnimationTree::get_configuration_warning() const { if (!player->has_node(player->get_root())) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } - warning += TTR("AnimationPlayer root is not a valid node."); + warning += TTR("The AnimationPlayer root node is not a valid node."); return warning; } @@ -1399,6 +1412,7 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A Vector<Activity> activity; for (int i = 0; i < node->get_input_count(); i++) { Activity a; + a.activity = 0; a.last_pass = 0; activity.push_back(a); } @@ -1567,6 +1581,7 @@ AnimationTree::AnimationTree() { active = false; cache_valid = false; setup_pass = 1; + process_pass = 1; started = true; properties_dirty = true; last_animation_player = 0; diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 4c65b2a92c..e22d6e4c2d 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -41,7 +41,8 @@ class AnimationPlayer; class AnimationTree; class AnimationNode : public Resource { - GDCLASS(AnimationNode, Resource) + GDCLASS(AnimationNode, Resource); + public: enum FilterAction { FILTER_IGNORE, @@ -155,13 +156,15 @@ VARIANT_ENUM_CAST(AnimationNode::FilterAction) //root node does not allow inputs class AnimationRootNode : public AnimationNode { - GDCLASS(AnimationRootNode, AnimationNode) + GDCLASS(AnimationRootNode, AnimationNode); + public: AnimationRootNode() {} }; class AnimationTree : public Node { - GDCLASS(AnimationTree, Node) + GDCLASS(AnimationTree, Node); + public: enum AnimationProcessMode { ANIMATION_PROCESS_PHYSICS, diff --git a/scene/animation/animation_tree_player.cpp b/scene/animation/animation_tree_player.cpp index e3a21d8b46..8f6d53c21c 100644 --- a/scene/animation/animation_tree_player.cpp +++ b/scene/animation/animation_tree_player.cpp @@ -136,9 +136,9 @@ bool AnimationTreePlayer::_set(const StringName &p_name, const Variant &p_value) else animation_node_set_animation(id, node.get_valid("animation")); Array filters = node.get_valid("filter"); - for (int i = 0; i < filters.size(); i++) { + for (int j = 0; j < filters.size(); j++) { - animation_node_set_filter_path(id, filters[i], true); + animation_node_set_filter_path(id, filters[j], true); } } break; case NODE_ONESHOT: { @@ -150,9 +150,9 @@ bool AnimationTreePlayer::_set(const StringName &p_name, const Variant &p_value) oneshot_node_set_autorestart_delay(id, node.get_valid("autorestart_delay")); oneshot_node_set_autorestart_random_delay(id, node.get_valid("autorestart_random_delay")); Array filters = node.get_valid("filter"); - for (int i = 0; i < filters.size(); i++) { + for (int j = 0; j < filters.size(); j++) { - oneshot_node_set_filter_path(id, filters[i], true); + oneshot_node_set_filter_path(id, filters[j], true); } } break; @@ -162,9 +162,9 @@ bool AnimationTreePlayer::_set(const StringName &p_name, const Variant &p_value) case NODE_BLEND2: { blend2_node_set_amount(id, node.get_valid("blend")); Array filters = node.get_valid("filter"); - for (int i = 0; i < filters.size(); i++) { + for (int j = 0; j < filters.size(); j++) { - blend2_node_set_filter_path(id, filters[i], true); + blend2_node_set_filter_path(id, filters[j], true); } } break; case NODE_BLEND3: { @@ -193,7 +193,8 @@ bool AnimationTreePlayer::_set(const StringName &p_name, const Variant &p_value) } } break; - default: {}; + default: { + }; } } @@ -278,8 +279,8 @@ bool AnimationTreePlayer::_get(const StringName &p_name, Variant &r_ret) const { an->filter.get_key_list(&keys); k.resize(keys.size()); int i = 0; - for (List<NodePath>::Element *E = keys.front(); E; E = E->next()) { - k[i++] = E->get(); + for (List<NodePath>::Element *F = keys.front(); F; F = F->next()) { + k[i++] = F->get(); } node["filter"] = k; } break; @@ -297,8 +298,8 @@ bool AnimationTreePlayer::_get(const StringName &p_name, Variant &r_ret) const { osn->filter.get_key_list(&keys); k.resize(keys.size()); int i = 0; - for (List<NodePath>::Element *E = keys.front(); E; E = E->next()) { - k[i++] = E->get(); + for (List<NodePath>::Element *F = keys.front(); F; F = F->next()) { + k[i++] = F->get(); } node["filter"] = k; @@ -315,8 +316,8 @@ bool AnimationTreePlayer::_get(const StringName &p_name, Variant &r_ret) const { bn->filter.get_key_list(&keys); k.resize(keys.size()); int i = 0; - for (List<NodePath>::Element *E = keys.front(); E; E = E->next()) { - k[i++] = E->get(); + for (List<NodePath>::Element *F = keys.front(); F; F = F->next()) { + k[i++] = F->get(); } node["filter"] = k; @@ -352,7 +353,8 @@ bool AnimationTreePlayer::_get(const StringName &p_name, Variant &r_ret) const { node["transitions"] = transitions; } break; - default: {}; + default: { + }; } nodes.push_back(node); @@ -401,8 +403,7 @@ void AnimationTreePlayer::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { - ERR_EXPLAIN("AnimationTreePlayer has been deprecated. Use AnimationTree instead."); - WARN_DEPRECATED + WARN_DEPRECATED_MSG("AnimationTreePlayer has been deprecated. Use AnimationTree instead."); if (!processing) { //make sure that a previous process state was not saved @@ -772,7 +773,8 @@ float AnimationTreePlayer::_process_node(const StringName &p_node, AnimationNode } } break; - default: {} + default: { + } } return 0; @@ -874,15 +876,16 @@ void AnimationTreePlayer::_process_animation(float p_delta) { List<int> indices; a->method_track_get_key_indices(tr.local_track, anim_list->time, anim_list->step, &indices); - for (List<int>::Element *E = indices.front(); E; E = E->next()) { + for (List<int>::Element *F = indices.front(); F; F = F->next()) { - StringName method = a->method_track_get_name(tr.local_track, E->get()); - Vector<Variant> args = a->method_track_get_params(tr.local_track, E->get()); + StringName method = a->method_track_get_name(tr.local_track, F->get()); + Vector<Variant> args = a->method_track_get_params(tr.local_track, F->get()); args.resize(VARIANT_ARG_MAX); tr.track->object->call(method, args[0], args[1], args[2], args[3], args[4]); } } break; - default: {} + default: { + } } } } @@ -969,7 +972,8 @@ void AnimationTreePlayer::add_node(NodeType p_type, const StringName &p_node) { n = memnew(TransitionNode); } break; - default: {} + default: { + } } //n->name+=" "+itos(p_node); @@ -988,10 +992,9 @@ int AnimationTreePlayer::node_get_input_count(const StringName &p_node) const { ERR_FAIL_COND_V(!node_map.has(p_node), -1); return node_map[p_node]->inputs.size(); } -#define GET_NODE(m_type, m_cast) \ - ERR_FAIL_COND(!node_map.has(p_node)); \ - ERR_EXPLAIN("Invalid parameter for node type."); \ - ERR_FAIL_COND(node_map[p_node]->type != m_type); \ +#define GET_NODE(m_type, m_cast) \ + ERR_FAIL_COND(!node_map.has(p_node)); \ + ERR_FAIL_COND_MSG(node_map[p_node]->type != m_type, "Invalid parameter for node type."); \ m_cast *n = static_cast<m_cast *>(node_map[p_node]); void AnimationTreePlayer::animation_node_set_animation(const StringName &p_node, const Ref<Animation> &p_animation) { @@ -1204,10 +1207,9 @@ Point2 AnimationTreePlayer::node_get_position(const StringName &p_node) const { return node_map[p_node]->pos; } -#define GET_NODE_V(m_type, m_cast, m_ret) \ - ERR_FAIL_COND_V(!node_map.has(p_node), m_ret); \ - ERR_EXPLAIN("Invalid parameter for node type."); \ - ERR_FAIL_COND_V(node_map[p_node]->type != m_type, m_ret); \ +#define GET_NODE_V(m_type, m_cast, m_ret) \ + ERR_FAIL_COND_V(!node_map.has(p_node), m_ret); \ + ERR_FAIL_COND_V_MSG(node_map[p_node]->type != m_type, m_ret, "Invalid parameter for node type."); \ m_cast *n = static_cast<m_cast *>(node_map[p_node]); Ref<Animation> AnimationTreePlayer::animation_node_get_animation(const StringName &p_node) const { @@ -1362,8 +1364,7 @@ void AnimationTreePlayer::get_node_list(List<StringName> *p_node_list) const { void AnimationTreePlayer::remove_node(const StringName &p_node) { ERR_FAIL_COND(!node_map.has(p_node)); - ERR_EXPLAIN("Node 0 (output) can't be removed."); - ERR_FAIL_COND(p_node == out_name); + ERR_FAIL_COND_MSG(p_node == out_name, "Node 0 (output) can't be removed."); for (Map<StringName, NodeBase *>::Element *E = node_map.front(); E; E = E->next()) { diff --git a/scene/animation/animation_tree_player.h b/scene/animation/animation_tree_player.h index 12e514f7d5..9ec6325969 100644 --- a/scene/animation/animation_tree_player.h +++ b/scene/animation/animation_tree_player.h @@ -109,6 +109,14 @@ private: Variant value; bool skip; + + Track() : + id(0), + object(NULL), + spatial(NULL), + skeleton(NULL), + bone_idx(-1), + skip(false) {} }; typedef Map<TrackKey, Track> TrackMap; diff --git a/scene/animation/root_motion_view.h b/scene/animation/root_motion_view.h index bca265b1f0..b30b06229e 100644 --- a/scene/animation/root_motion_view.h +++ b/scene/animation/root_motion_view.h @@ -34,7 +34,8 @@ #include "scene/3d/visual_instance.h" class RootMotionView : public VisualInstance { - GDCLASS(RootMotionView, VisualInstance) + GDCLASS(RootMotionView, VisualInstance); + public: RID immediate; NodePath path; @@ -55,7 +56,7 @@ public: void set_animation_path(const NodePath &p_path); NodePath get_animation_path() const; - void set_color(const Color &p_path); + void set_color(const Color &p_color); Color get_color() const; void set_cell_size(float p_size); diff --git a/scene/animation/skeleton_ik.cpp b/scene/animation/skeleton_ik.cpp index 88fb2d5bfc..7a1b10792b 100644 --- a/scene/animation/skeleton_ik.cpp +++ b/scene/animation/skeleton_ik.cpp @@ -37,20 +37,20 @@ #ifndef _3D_DISABLED FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::find_child(const BoneId p_bone_id) { - for (int i = childs.size() - 1; 0 <= i; --i) { - if (p_bone_id == childs[i].bone) { - return &childs.write[i]; + for (int i = children.size() - 1; 0 <= i; --i) { + if (p_bone_id == children[i].bone) { + return &children.write[i]; } } return NULL; } FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::add_child(const BoneId p_bone_id) { - const int infant_child_id = childs.size(); - childs.resize(infant_child_id + 1); - childs.write[infant_child_id].bone = p_bone_id; - childs.write[infant_child_id].parent_item = this; - return &childs.write[infant_child_id]; + const int infant_child_id = children.size(); + children.resize(infant_child_id + 1); + children.write[infant_child_id].bone = p_bone_id; + children.write[infant_child_id].parent_item = this; + return &children.write[infant_child_id]; } /// Build a chain that starts from the root to tip @@ -144,8 +144,8 @@ void FabrikInverseKinematic::update_chain(const Skeleton *p_sk, ChainItem *p_cha p_chain_item->initial_transform = p_sk->get_bone_global_pose(p_chain_item->bone); p_chain_item->current_pos = p_chain_item->initial_transform.origin; - for (int i = p_chain_item->childs.size() - 1; 0 <= i; --i) { - update_chain(p_sk, &p_chain_item->childs.write[i]); + for (int i = p_chain_item->children.size() - 1; 0 <= i; --i) { + update_chain(p_sk, &p_chain_item->children.write[i]); } } @@ -210,9 +210,9 @@ void FabrikInverseKinematic::solve_simple_forwards(Chain &r_chain, bool p_solve_ while (sub_chain_root) { // Reach the tip sub_chain_root->current_pos = origin; - if (!sub_chain_root->childs.empty()) { + if (!sub_chain_root->children.empty()) { - ChainItem &child(sub_chain_root->childs.write[0]); + ChainItem &child(sub_chain_root->children.write[0]); // Is not tip // So calculate next origin location @@ -302,10 +302,10 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove Transform new_bone_pose(ci->initial_transform); new_bone_pose.origin = ci->current_pos; - if (!ci->childs.empty()) { + if (!ci->children.empty()) { /// Rotate basis - const Vector3 initial_ori((ci->childs[0].initial_transform.origin - ci->initial_transform.origin).normalized()); + const Vector3 initial_ori((ci->children[0].initial_transform.origin - ci->initial_transform.origin).normalized()); const Vector3 rot_axis(initial_ori.cross(ci->current_ori).normalized()); if (rot_axis[0] != 0 && rot_axis[1] != 0 && rot_axis[2] != 0) { @@ -322,8 +322,8 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove p_task->skeleton->set_bone_global_pose(ci->bone, new_bone_pose); - if (!ci->childs.empty()) - ci = &ci->childs.write[0]; + if (!ci->children.empty()) + ci = &ci->children.write[0]; else ci = NULL; } @@ -335,7 +335,7 @@ void SkeletonIK::_validate_property(PropertyInfo &property) const { if (skeleton) { - String names; + String names("--,"); for (int i = 0; i < skeleton->get_bone_count(); i++) { if (i > 0) names += ","; @@ -406,6 +406,7 @@ void SkeletonIK::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { skeleton = Object::cast_to<Skeleton>(get_parent()); + set_process_priority(1); reload_chain(); } break; case NOTIFICATION_INTERNAL_PROCESS: { @@ -423,7 +424,6 @@ void SkeletonIK::_notification(int p_what) { } SkeletonIK::SkeletonIK() : - Node(), interpolation(1), override_tip_basis(true), use_magnet(false), @@ -432,8 +432,6 @@ SkeletonIK::SkeletonIK() : skeleton(NULL), target_node_override(NULL), task(NULL) { - - set_process_priority(1); } SkeletonIK::~SkeletonIK() { diff --git a/scene/animation/skeleton_ik.h b/scene/animation/skeleton_ik.h index 228184a2df..d2c5f56ace 100644 --- a/scene/animation/skeleton_ik.h +++ b/scene/animation/skeleton_ik.h @@ -49,7 +49,7 @@ class FabrikInverseKinematic { struct ChainItem { - Vector<ChainItem> childs; + Vector<ChainItem> children; ChainItem *parent_item; // Bone info @@ -165,7 +165,7 @@ protected: _validate_property(PropertyInfo &property) const; static void _bind_methods(); - virtual void _notification(int p_notification); + virtual void _notification(int p_what); public: SkeletonIK(); @@ -192,7 +192,7 @@ public: void set_use_magnet(bool p_use); bool is_using_magnet() const; - void set_magnet_position(const Vector3 &p_constraint); + void set_magnet_position(const Vector3 &p_local_position); const Vector3 &get_magnet_position() const; void set_min_distance(real_t p_min_distance); diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 6a5d7839f4..2609924f33 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -34,10 +34,14 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8, const Variant &p_arg9, const Variant &p_arg10) { + // Add a new pending command and reference it pending_commands.push_back(PendingCommand()); PendingCommand &cmd = pending_commands.back()->get(); + // Update the command with the target key cmd.key = p_key; + + // Determine command argument count int &count = cmd.args; if (p_arg10.get_type() != Variant::NIL) count = 10; @@ -59,6 +63,9 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const count = 2; else if (p_arg1.get_type() != Variant::NIL) count = 1; + + // Add the specified arguments to the command + // TODO: Make this a switch statement? if (count > 0) cmd.arg[0] = p_arg1; if (count > 1) @@ -83,10 +90,14 @@ void Tween::_add_pending_command(StringName p_key, const Variant &p_arg1, const void Tween::_process_pending_commands() { + // For each pending command... for (List<PendingCommand>::Element *E = pending_commands.front(); E; E = E->next()) { + // Get the command PendingCommand &cmd = E->get(); Variant::CallError err; + + // Grab all of the arguments for the command Variant *arg[10] = { &cmd.arg[0], &cmd.arg[1], @@ -99,16 +110,20 @@ void Tween::_process_pending_commands() { &cmd.arg[8], &cmd.arg[9], }; + + // Execute the command (and retrieve any errors) this->call(cmd.key, (const Variant **)arg, cmd.args, err); } + + // Clear the pending commands pending_commands.clear(); } bool Tween::_set(const StringName &p_name, const Variant &p_value) { + // Set the correct attribute based on the given name String name = p_name; - - if (name == "playback/speed" || name == "speed") { //bw compatibility + if (name == "playback/speed" || name == "speed") { // Backwards compatibility set_speed_scale(p_value); } else if (name == "playback/active") { @@ -122,69 +137,78 @@ bool Tween::_set(const StringName &p_name, const Variant &p_value) { bool Tween::_get(const StringName &p_name, Variant &r_ret) const { + // Get the correct attribute based on the given name String name = p_name; - - if (name == "playback/speed") { //bw compatibility - + if (name == "playback/speed") { // Backwards compatibility r_ret = speed_scale; - } else if (name == "playback/active") { + } else if (name == "playback/active") { r_ret = is_active(); - } else if (name == "playback/repeat") { + } else if (name == "playback/repeat") { r_ret = is_repeat(); } - return true; } void Tween::_get_property_list(List<PropertyInfo> *p_list) const { - + // Add the property info for the Tween object p_list->push_back(PropertyInfo(Variant::BOOL, "playback/active", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::BOOL, "playback/repeat", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::REAL, "playback/speed", PROPERTY_HINT_RANGE, "-64,64,0.01")); } void Tween::_notification(int p_what) { - + // What notification did we receive? switch (p_what) { case NOTIFICATION_ENTER_TREE: { - + // Are we not already active? if (!is_active()) { - //make sure that a previous process state was not saved - //only process if "processing" is set + // Make sure that a previous process state was not saved + // Only process if "processing" is set set_physics_process_internal(false); set_process_internal(false); } } break; - case NOTIFICATION_READY: { + case NOTIFICATION_READY: { + // Do nothing } break; + case NOTIFICATION_INTERNAL_PROCESS: { + // Are we processing during physics time? if (tween_process_mode == TWEEN_PROCESS_PHYSICS) + // Do nothing since we aren't aligned with physics when we should be break; + // Should we update? if (is_active()) + // Update the tweens _tween_process(get_process_delta_time()); } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Are we processing during 'regular' time? if (tween_process_mode == TWEEN_PROCESS_IDLE) + // Do nothing since we whould only process during idle time break; + // Should we update? if (is_active()) + // Update the tweens _tween_process(get_physics_process_delta_time()); } break; - case NOTIFICATION_EXIT_TREE: { + case NOTIFICATION_EXIT_TREE: { + // We've left the tree. Stop all tweens stop_all(); } break; } } void Tween::_bind_methods() { - + // Bind getters and setters ClassDB::bind_method(D_METHOD("is_active"), &Tween::is_active); ClassDB::bind_method(D_METHOD("set_active", "active"), &Tween::set_active); @@ -197,6 +221,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tween_process_mode", "mode"), &Tween::set_tween_process_mode); ClassDB::bind_method(D_METHOD("get_tween_process_mode"), &Tween::get_tween_process_mode); + // Bind the various Tween control methods ClassDB::bind_method(D_METHOD("start"), &Tween::start); ClassDB::bind_method(D_METHOD("reset", "object", "key"), &Tween::reset, DEFVAL("")); ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all); @@ -211,6 +236,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("tell"), &Tween::tell); ClassDB::bind_method(D_METHOD("get_runtime"), &Tween::get_runtime); + // Bind interpolation and follow methods ClassDB::bind_method(D_METHOD("interpolate_property", "object", "property", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_property, DEFVAL(0)); ClassDB::bind_method(D_METHOD("interpolate_method", "object", "method", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_method, DEFVAL(0)); ClassDB::bind_method(D_METHOD("interpolate_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5"), &Tween::interpolate_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant())); @@ -220,17 +246,22 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("targeting_property", "object", "property", "initial", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_property, DEFVAL(0)); ClassDB::bind_method(D_METHOD("targeting_method", "object", "method", "initial", "initial_method", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_method, DEFVAL(0)); + // Add the Tween signals ADD_SIGNAL(MethodInfo("tween_started", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"))); ADD_SIGNAL(MethodInfo("tween_step", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"), PropertyInfo(Variant::REAL, "elapsed"), PropertyInfo(Variant::OBJECT, "value"))); ADD_SIGNAL(MethodInfo("tween_completed", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"))); + ADD_SIGNAL(MethodInfo("tween_all_completed")); + // Add the properties and tie them to the getters and setters ADD_PROPERTY(PropertyInfo(Variant::BOOL, "repeat"), "set_repeat", "is_repeat"); ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_tween_process_mode", "get_tween_process_mode"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); + // Bind Idle vs Physics process BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE); + // Bind the Transition type constants BIND_ENUM_CONSTANT(TRANS_LINEAR); BIND_ENUM_CONSTANT(TRANS_SINE); BIND_ENUM_CONSTANT(TRANS_QUINT); @@ -243,6 +274,7 @@ void Tween::_bind_methods() { BIND_ENUM_CONSTANT(TRANS_BOUNCE); BIND_ENUM_CONSTANT(TRANS_BACK); + // Bind the easing constants BIND_ENUM_CONSTANT(EASE_IN); BIND_ENUM_CONSTANT(EASE_OUT); BIND_ENUM_CONSTANT(EASE_IN_OUT); @@ -251,27 +283,30 @@ void Tween::_bind_methods() { Variant &Tween::_get_initial_val(InterpolateData &p_data) { + // What type of data are we interpolating? switch (p_data.type) { case INTER_PROPERTY: case INTER_METHOD: case FOLLOW_PROPERTY: case FOLLOW_METHOD: + // Simply use the given initial value return p_data.initial_val; case TARGETING_PROPERTY: case TARGETING_METHOD: { - + // Get the object that is being targeted Object *object = ObjectDB::get_instance(p_data.target_id); ERR_FAIL_COND_V(object == NULL, p_data.initial_val); + // Are we targeting a property or a method? static Variant initial_val; if (p_data.type == TARGETING_PROPERTY) { - + // Get the property from the target object bool valid = false; initial_val = object->get_indexed(p_data.target_key, &valid); ERR_FAIL_COND_V(!valid, p_data.initial_val); } else { - + // Call the method and get the initial value from it Variant::CallError error; initial_val = object->call(p_data.target_key[0], NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val); @@ -280,64 +315,75 @@ Variant &Tween::_get_initial_val(InterpolateData &p_data) { } case INTER_CALLBACK: + // Callback does not have a special initial value break; } + // If we've made it here, just return the delta value as the initial value return p_data.delta_val; } Variant &Tween::_get_delta_val(InterpolateData &p_data) { + // What kind of data are we interpolating? switch (p_data.type) { case INTER_PROPERTY: case INTER_METHOD: + // Simply return the given delta value return p_data.delta_val; case FOLLOW_PROPERTY: case FOLLOW_METHOD: { - + // We're following an object, so grab that instance Object *target = ObjectDB::get_instance(p_data.target_id); ERR_FAIL_COND_V(target == NULL, p_data.initial_val); + // We want to figure out the final value Variant final_val; - if (p_data.type == FOLLOW_PROPERTY) { - + // Read the property as-is bool valid = false; final_val = target->get_indexed(p_data.target_key, &valid); ERR_FAIL_COND_V(!valid, p_data.initial_val); } else { - + // We're looking at a method. Call the method on the target object Variant::CallError error; final_val = target->call(p_data.target_key[0], NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val); } - // convert INT to REAL is better for interpolaters + // If we're looking at an INT value, instead convert it to a REAL + // This is better for interpolation if (final_val.get_type() == Variant::INT) final_val = final_val.operator real_t(); + + // Calculate the delta based on the initial value and the final value _calc_delta_val(p_data.initial_val, final_val, p_data.delta_val); return p_data.delta_val; } case TARGETING_PROPERTY: case TARGETING_METHOD: { - + // Grab the initial value from the data to calculate delta Variant initial_val = _get_initial_val(p_data); - // convert INT to REAL is better for interpolaters + + // If we're looking at an INT value, instead convert it to a REAL + // This is better for interpolation if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t(); - //_calc_delta_val(p_data.initial_val, p_data.final_val, p_data.delta_val); + // Calculate the delta based on the initial value and the final value _calc_delta_val(initial_val, p_data.final_val, p_data.delta_val); return p_data.delta_val; } case INTER_CALLBACK: + // Callbacks have no special delta break; } + // If we've made it here, use the initial value as the delta return p_data.initial_val; } Variant Tween::_run_equation(InterpolateData &p_data) { - + // Get the initial and delta values from the data Variant &initial_val = _get_initial_val(p_data); Variant &delta_val = _get_delta_val(p_data); Variant result; @@ -345,48 +391,59 @@ Variant Tween::_run_equation(InterpolateData &p_data) { #define APPLY_EQUATION(element) \ r.element = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, i.element, d.element, p_data.duration); + // What type of data are we interpolating? switch (initial_val.get_type()) { case Variant::BOOL: + // Run the boolean specific equation (checking if it is at least 0.5) result = (_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, initial_val, delta_val, p_data.duration)) >= 0.5; break; case Variant::INT: + // Run the integer specific equation result = (int)_run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (int)initial_val, (int)delta_val, p_data.duration); break; case Variant::REAL: + // Run the REAL specific equation result = _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (real_t)initial_val, (real_t)delta_val, p_data.duration); break; case Variant::VECTOR2: { + // Get vectors for initial and delta values Vector2 i = initial_val; Vector2 d = delta_val; Vector2 r; + // Execute the equation and mutate the r vector + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(x); APPLY_EQUATION(y); - result = r; } break; case Variant::VECTOR3: { + // Get vectors for initial and delta values Vector3 i = initial_val; Vector3 d = delta_val; Vector3 r; + // Execute the equation and mutate the r vector + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(x); APPLY_EQUATION(y); APPLY_EQUATION(z); - result = r; } break; case Variant::BASIS: { + // Get the basis for initial and delta values Basis i = initial_val; Basis d = delta_val; Basis r; + // Execute the equation on all the basis and mutate the r basis + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(elements[0][0]); APPLY_EQUATION(elements[0][1]); APPLY_EQUATION(elements[0][2]); @@ -396,55 +453,63 @@ Variant Tween::_run_equation(InterpolateData &p_data) { APPLY_EQUATION(elements[2][0]); APPLY_EQUATION(elements[2][1]); APPLY_EQUATION(elements[2][2]); - result = r; } break; case Variant::TRANSFORM2D: { + // Get the transforms for initial and delta values Transform2D i = initial_val; Transform2D d = delta_val; Transform2D r; + // Execute the equation on the transforms and mutate the r transform + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(elements[0][0]); APPLY_EQUATION(elements[0][1]); APPLY_EQUATION(elements[1][0]); APPLY_EQUATION(elements[1][1]); APPLY_EQUATION(elements[2][0]); APPLY_EQUATION(elements[2][1]); - result = r; } break; case Variant::QUAT: { + // Get the quaternian for the initial and delta values Quat i = initial_val; Quat d = delta_val; Quat r; + // Execute the equation on the quaternian values and mutate the r quaternian + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(x); APPLY_EQUATION(y); APPLY_EQUATION(z); APPLY_EQUATION(w); - result = r; } break; case Variant::AABB: { + // Get the AABB's for the initial and delta values AABB i = initial_val; AABB d = delta_val; AABB r; + // Execute the equation for the position and size of the AABB's and mutate the r AABB + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(position.x); APPLY_EQUATION(position.y); APPLY_EQUATION(position.z); APPLY_EQUATION(size.x); APPLY_EQUATION(size.y); APPLY_EQUATION(size.z); - result = r; } break; case Variant::TRANSFORM: { + // Get the transforms for the initial and delta values Transform i = initial_val; Transform d = delta_val; Transform r; + // Execute the equation for each of the transforms and their origin and mutate the r transform + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(basis.elements[0][0]); APPLY_EQUATION(basis.elements[0][1]); APPLY_EQUATION(basis.elements[0][2]); @@ -457,40 +522,45 @@ Variant Tween::_run_equation(InterpolateData &p_data) { APPLY_EQUATION(origin.x); APPLY_EQUATION(origin.y); APPLY_EQUATION(origin.z); - result = r; } break; case Variant::COLOR: { + // Get the Color for initial and delta value Color i = initial_val; Color d = delta_val; Color r; + // Apply the equation on the Color RGBA, and mutate the r color + // This uses the custom APPLY_EQUATION macro defined above APPLY_EQUATION(r); APPLY_EQUATION(g); APPLY_EQUATION(b); APPLY_EQUATION(a); - result = r; } break; default: { + // If unknown, just return the initial value result = initial_val; } break; }; #undef APPLY_EQUATION - + // Return the result that was computed return result; } bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) { + // Get the object we want to apply the new value to Object *object = ObjectDB::get_instance(p_data.id); ERR_FAIL_COND_V(object == NULL, false); + // What kind of data are we mutating? switch (p_data.type) { case INTER_PROPERTY: case FOLLOW_PROPERTY: case TARGETING_PROPERTY: { + // Simply set the property on the object bool valid = false; object->set_indexed(p_data.key, value, &valid); return valid; @@ -499,85 +569,110 @@ bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) { case INTER_METHOD: case FOLLOW_METHOD: case TARGETING_METHOD: { + // We want to call the method on the target object Variant::CallError error; + + // Do we have a non-nil value passed in? if (value.get_type() != Variant::NIL) { + // Pass it as an argument to the function call Variant *arg[1] = { &value }; object->call(p_data.key[0], (const Variant **)arg, 1, error); } else { + // Don't pass any argument object->call(p_data.key[0], NULL, 0, error); } - if (error.error == Variant::CallError::CALL_OK) - return true; - return false; + // Did we get an error from the function call? + return error.error == Variant::CallError::CALL_OK; } case INTER_CALLBACK: + // Nothing to apply for a callback break; }; + // No issues found! return true; } void Tween::_tween_process(float p_delta) { - + // Process all of the pending commands _process_pending_commands(); + // If the scale is 0, make no progress on the tweens if (speed_scale == 0) return; - p_delta *= speed_scale; + // Update the delta and whether we are pending an update + p_delta *= speed_scale; pending_update++; - // if repeat and all interpolates was finished then reset all interpolates - bool all_finished = true; - if (repeat) { + // Are we repeating the interpolations? + if (repeat) { + // For each interpolation... + bool repeats_finished = true; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the data from it InterpolateData &data = E->get(); + // Is not finished? if (!data.finish) { - all_finished = false; + // We aren't finished yet, no need to check the rest + repeats_finished = false; break; } } - if (all_finished) + // If we are all finished, we can reset all of the tweens + if (repeats_finished) reset_all(); } - all_finished = true; + // Are all of the tweens complete? + bool all_finished = true; + + // For each tween we wish to interpolate... for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // Get the data from it InterpolateData &data = E->get(); + + // Track if we hit one that isn't finished yet all_finished = all_finished && data.finish; + // Is the data not active or already finished? No need to go any further if (!data.active || data.finish) continue; + // Get the target object for this interpolation Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + // Are we still delaying this tween? bool prev_delaying = data.elapsed <= data.delay; data.elapsed += p_delta; if (data.elapsed < data.delay) continue; else if (prev_delaying) { - + // We can apply the tween's value to the data and emit that the tween has started _apply_tween_value(data, data.initial_val); emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false)); } + // Are we at the end of the tween? if (data.elapsed > (data.delay + data.duration)) { - + // Set the elapsed time to the end and mark this one as finished data.elapsed = data.delay + data.duration; data.finish = true; } + // Are we interpolating a callback? if (data.type == INTER_CALLBACK) { + // Is the tween completed? if (data.finish) { + // Are we calling this callback deferred or immediately? if (data.call_deferred) { - + // Run the deferred function callback, applying the correct number of arguments switch (data.args) { case 0: object->call_deferred(data.key[0]); @@ -599,6 +694,7 @@ void Tween::_tween_process(float p_delta) { break; } } else { + // Call the function directly with the arguments Variant::CallError error; Variant *arg[5] = { &data.arg[0], @@ -611,98 +707,111 @@ void Tween::_tween_process(float p_delta) { } } } else { + // We can apply the value directly Variant result = _run_equation(data); _apply_tween_value(data, result); + + // Emit that the tween has taken a step emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result); } + // Is the tween now finished? if (data.finish) { + // Set it to the final value directly _apply_tween_value(data, data.final_val); + + // Mark the tween as completed and emit the signal data.elapsed = 0; emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false)); - // not repeat mode, remove completed action + + // If we are not repeating the tween, remove it if (!repeat) call_deferred("_remove_by_uid", data.uid); - } else if (!repeat) + } else if (!repeat) { + // Check whether all tweens are finished all_finished = all_finished && data.finish; + } } + // One less update left to go pending_update--; - if (all_finished) + // If all tweens are completed, we no longer need to be active + if (all_finished) { set_active(false); + emit_signal("tween_all_completed"); + } } void Tween::set_tween_process_mode(TweenProcessMode p_mode) { - tween_process_mode = p_mode; } Tween::TweenProcessMode Tween::get_tween_process_mode() const { - return tween_process_mode; } bool Tween::is_active() const { - return is_processing_internal() || is_physics_processing_internal(); } void Tween::set_active(bool p_active) { - + // Do nothing if it's the same active mode that we currently are if (is_active() == p_active) return; + // Depending on physics or idle, set processing switch (tween_process_mode) { - case TWEEN_PROCESS_IDLE: set_process_internal(p_active); break; case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_active); break; } } bool Tween::is_repeat() const { - return repeat; } void Tween::set_repeat(bool p_repeat) { - repeat = p_repeat; } void Tween::set_speed_scale(float p_speed) { - speed_scale = p_speed; } float Tween::get_speed_scale() const { - return speed_scale; } bool Tween::start() { + // Are there any pending updates? if (pending_update != 0) { + // Start the tweens after deferring call_deferred("start"); return true; } + // We want to be activated set_active(true); return true; } bool Tween::reset(Object *p_object, StringName p_key) { - + // Find all interpolations that use the same object and target string pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target object InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + // Do we have the correct object and key? if (object == p_object && (data.concatenated_key == p_key || p_key == "")) { - + // Reset the tween to the initial state data.elapsed = 0; data.finish = false; + + // Also apply the initial state if there isn't a delay if (data.delay == 0) _apply_tween_value(data, data.initial_val); } @@ -712,13 +821,15 @@ bool Tween::reset(Object *p_object, StringName p_key) { } bool Tween::reset_all() { - + // Go through all interpolations pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target data and set it back to the initial state InterpolateData &data = E->get(); data.elapsed = 0; data.finish = false; + + // If there isn't a delay, apply the value to the object if (data.delay == 0) _apply_tween_value(data, data.initial_val); } @@ -727,15 +838,19 @@ bool Tween::reset_all() { } bool Tween::stop(Object *p_object, StringName p_key) { - + // Find the tween that has the given target object and string key pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // Get the object the tween is targeting InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + + // Is this the correct object and does it have the given key? if (object == p_object && (data.concatenated_key == p_key || p_key == "")) + // Disable the tween data.active = false; } pending_update--; @@ -743,12 +858,13 @@ bool Tween::stop(Object *p_object, StringName p_key) { } bool Tween::stop_all() { - + // We no longer need to be active since all tweens have been stopped set_active(false); + // For each interpolation... pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Simply set it inactive InterpolateData &data = E->get(); data.active = false; } @@ -757,16 +873,20 @@ bool Tween::stop_all() { } bool Tween::resume(Object *p_object, StringName p_key) { - + // We need to be activated + // TODO: What if no tween is found?? set_active(true); + // Find the tween that uses the given target object and string key pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Grab the object InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + + // If the object and string key match, activate it if (object == p_object && (data.concatenated_key == p_key || p_key == "")) data.active = true; } @@ -775,12 +895,14 @@ bool Tween::resume(Object *p_object, StringName p_key) { } bool Tween::resume_all() { - + // Set ourselves active so we can process tweens + // TODO: What if there are no tweens? We get set to active for no reason! set_active(true); + // For each interpolation... pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Simply grab it and set it to active InterpolateData &data = E->get(); data.active = true; } @@ -789,35 +911,46 @@ bool Tween::resume_all() { } bool Tween::remove(Object *p_object, StringName p_key) { + // If we are still updating, call this function again later if (pending_update != 0) { call_deferred("remove", p_object, p_key); return true; } + + // For each interpolation... List<List<InterpolateData>::Element *> for_removal; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target object InterpolateData &data = E->get(); Object *object = ObjectDB::get_instance(data.id); if (object == NULL) continue; + + // If the target object and string key match, queue it for removal if (object == p_object && (data.concatenated_key == p_key || p_key == "")) { for_removal.push_back(E); } } + + // For each interpolation we wish to remove... for (List<List<InterpolateData>::Element *>::Element *E = for_removal.front(); E; E = E->next()) { + // Erase it interpolates.erase(E->get()); } return true; } void Tween::_remove_by_uid(int uid) { + // If we are still updating, call this function again later if (pending_update != 0) { call_deferred("_remove_by_uid", uid); return; } + // Find the interpolation that matches the given UID for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { if (uid == E->get().uid) { + // It matches, erase it and stop looking E->erase(); break; } @@ -826,49 +959,61 @@ void Tween::_remove_by_uid(int uid) { void Tween::_push_interpolate_data(InterpolateData &p_data) { pending_update++; + + // Add the new interpolation p_data.uid = ++uid; interpolates.push_back(p_data); + pending_update--; } bool Tween::remove_all() { - + // If we are still updating, call this function again later if (pending_update != 0) { call_deferred("remove_all"); return true; } + // We no longer need to be active set_active(false); + + // Clear out all interpolations and reset the uid interpolates.clear(); uid = 0; + return true; } bool Tween::seek(real_t p_time) { - + // Go through each interpolation... pending_update++; for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the target data InterpolateData &data = E->get(); + // Update the elapsed data to be set to the target time data.elapsed = p_time; - if (data.elapsed < data.delay) { + // Are we at the end? + if (data.elapsed < data.delay) { + // There is still time left to go data.finish = false; continue; } else if (data.elapsed >= (data.delay + data.duration)) { - - data.finish = true; + // We are past the end of it, set the elapsed time to the end and mark as finished data.elapsed = (data.delay + data.duration); + data.finish = true; } else { + // We are not finished with this interpolation yet data.finish = false; } + // If we are a callback, do nothing special if (data.type == INTER_CALLBACK) { continue; } + // Run the equation on the data and apply the value Variant result = _run_equation(data); - _apply_tween_value(data, result); } pending_update--; @@ -876,13 +1021,16 @@ bool Tween::seek(real_t p_time) { } real_t Tween::tell() const { - + // We want to grab the position of the furthest along tween pending_update++; real_t pos = 0; - for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // For each interpolation... + for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { + // Get the data and figure out if it's position is further along than the previous ones const InterpolateData &data = E->get(); if (data.elapsed > pos) + // Save it if so pos = data.elapsed; } pending_update--; @@ -890,55 +1038,63 @@ real_t Tween::tell() const { } real_t Tween::get_runtime() const { - + // If the tween isn't moving, it'll last forever if (speed_scale == 0) { return INFINITY; } pending_update++; + + // For each interpolation... real_t runtime = 0; for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) { - + // Get the tween data and see if it's runtime is greater than the previous tweens const InterpolateData &data = E->get(); real_t t = data.delay + data.duration; if (t > runtime) + // This is the longest running tween runtime = t; } pending_update--; + // Adjust the runtime for the current speed scale return runtime / speed_scale; } bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) { + // Get the initial, final, and delta values const Variant &initial_val = p_initial_val; const Variant &final_val = p_final_val; Variant &delta_val = p_delta_val; + // What kind of data are we interpolating? switch (initial_val.get_type()) { case Variant::BOOL: - //delta_val = p_final_val; - delta_val = (int)p_final_val - (int)p_initial_val; - break; - + // We'll treat booleans just like integers case Variant::INT: + // Compute the integer delta delta_val = (int)final_val - (int)initial_val; break; case Variant::REAL: + // Convert to REAL and find the delta delta_val = (real_t)final_val - (real_t)initial_val; break; case Variant::VECTOR2: + // Convert to Vectors and find the delta delta_val = final_val.operator Vector2() - initial_val.operator Vector2(); break; case Variant::VECTOR3: + // Convert to Vectors and find the delta delta_val = final_val.operator Vector3() - initial_val.operator Vector3(); break; case Variant::BASIS: { + // Build a new basis which is the delta between the initial and final values Basis i = initial_val; Basis f = final_val; delta_val = Basis(f.elements[0][0] - i.elements[0][0], @@ -953,6 +1109,7 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final } break; case Variant::TRANSFORM2D: { + // Build a new transform which is the difference between the initial and final values Transform2D i = initial_val; Transform2D f = final_val; Transform2D d = Transform2D(); @@ -964,15 +1121,21 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final d[2][1] = f.elements[2][1] - i.elements[2][1]; delta_val = d; } break; + case Variant::QUAT: + // Convert to quaternianls and find the delta delta_val = final_val.operator Quat() - initial_val.operator Quat(); break; + case Variant::AABB: { + // Build a new AABB and use the new position and sizes to make a delta AABB i = initial_val; AABB f = final_val; delta_val = AABB(f.position - i.position, f.size - i.size); } break; + case Variant::TRANSFORM: { + // Build a new transform which is the difference between the initial and final values Transform i = initial_val; Transform f = final_val; Transform d; @@ -991,124 +1154,148 @@ bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final delta_val = d; } break; + case Variant::COLOR: { + // Make a new color which is the difference between each the color's RGBA attributes Color i = initial_val; Color f = final_val; delta_val = Color(f.r - i.r, f.g - i.g, f.b - i.b, f.a - i.a); } break; default: + // TODO: Should move away from a 'magic string'? ERR_PRINT("Invalid param type, except(int/real/vector2/vector/matrix/matrix32/quat/aabb/transform/color)"); return false; }; return true; } -bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { - if (pending_update != 0) { - _add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); - return true; - } - p_property = p_property.get_as_property_path(); - - if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); - - // convert INT to REAL is better for interpolaters - if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); - if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); - - ERR_FAIL_COND_V(p_object == NULL, false); - ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); - ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); - ERR_FAIL_COND_V(p_duration <= 0, false); - ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); - ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); - ERR_FAIL_COND_V(p_delay < 0, false); +bool Tween::_build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { - bool prop_valid = false; - p_object->get_indexed(p_property.get_subnames(), &prop_valid); - ERR_FAIL_COND_V(!prop_valid, false); + // TODO: Add initialization+implementation for remaining interpolation types + // TODO: Fix this method's organization to take advantage of the type + // Make a new interpolation data InterpolateData data; data.active = true; - data.type = INTER_PROPERTY; + data.type = p_interpolation_type; data.finish = false; data.elapsed = 0; + // Validate and apply interpolation data + + // Give it the object + ERR_FAIL_COND_V_MSG(p_object == NULL, false, "Invalid object provided to Tween."); + ERR_FAIL_COND_V_MSG(!ObjectDB::instance_validate(p_object), false, "Invalid object provided to Tween."); data.id = p_object->get_instance_id(); - data.key = p_property.get_subnames(); - data.concatenated_key = p_property.get_concatenated_subnames(); + + // Validate the initial and final values + ERR_FAIL_COND_V_MSG(p_initial_val.get_type() != p_final_val.get_type(), false, "Initial value type '" + Variant::get_type_name(p_initial_val.get_type()) + "' does not match final value type '" + Variant::get_type_name(p_final_val.get_type()) + "'."); data.initial_val = p_initial_val; data.final_val = p_final_val; + + // Check the Duration + ERR_FAIL_COND_V_MSG(p_duration < 0, false, "Only non-negative duration values allowed in Tweens."); data.duration = p_duration; + + // Tween Delay + ERR_FAIL_COND_V_MSG(p_delay < 0, false, "Only non-negative delay values allowed in Tweens."); + data.delay = p_delay; + + // Transition type + ERR_FAIL_COND_V_MSG(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false, "Invalid transition type provided to Tween."); data.trans_type = p_trans_type; + + // Easing type + ERR_FAIL_COND_V_MSG(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false, "Invalid easing type provided to Tween."); data.ease_type = p_ease_type; - data.delay = p_delay; + // Is the property defined? + if (p_property) { + // Check that the object actually contains the given property + bool prop_valid = false; + p_object->get_indexed(p_property->get_subnames(), &prop_valid); + ERR_FAIL_COND_V_MSG(!prop_valid, false, "Tween target object has no property named: " + p_property->get_concatenated_subnames() + "."); + + data.key = p_property->get_subnames(); + data.concatenated_key = p_property->get_concatenated_subnames(); + } + + // Is the method defined? + if (p_method) { + // Does the object even have the requested method? + ERR_FAIL_COND_V_MSG(!p_object->has_method(*p_method), false, "Tween target object has no method named: " + *p_method + "."); + + data.key.push_back(*p_method); + data.concatenated_key = *p_method; + } + + // Is there not a valid delta? if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) return false; + // Add this interpolation to the total _push_interpolate_data(data); return true; } -bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { +bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are busy updating, call this function again later if (pending_update != 0) { - _add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + _add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); return true; } - // convert INT to REAL is better for interpolaters - if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); - if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); - ERR_FAIL_COND_V(p_object == NULL, false); - ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); - ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false); - ERR_FAIL_COND_V(p_duration <= 0, false); - ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); - ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); - ERR_FAIL_COND_V(p_delay < 0, false); + // Get the property from the node path + p_property = p_property.get_as_property_path(); - ERR_EXPLAIN("Object has no method named: %s" + p_method); - ERR_FAIL_COND_V(!p_object->has_method(p_method), false); + // If no initial value given, grab the initial value from the object + // TODO: Is this documented? This is very useful and removes a lot of clutter from tweens! + if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); - InterpolateData data; - data.active = true; - data.type = INTER_METHOD; - data.finish = false; - data.elapsed = 0; + // Convert any integers into REALs as they are better for interpolation + if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); - data.id = p_object->get_instance_id(); - data.key.push_back(p_method); - data.concatenated_key = p_method; - data.initial_val = p_initial_val; - data.final_val = p_final_val; - data.duration = p_duration; - data.trans_type = p_trans_type; - data.ease_type = p_ease_type; - data.delay = p_delay; + // Build the interpolation data + bool result = _build_interpolation(INTER_PROPERTY, p_object, &p_property, NULL, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + return result; +} - if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) - return false; +bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are busy updating, call this function again later + if (pending_update != 0) { + _add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + return true; + } - _push_interpolate_data(data); - return true; + // Convert any integers into REALs as they are better for interpolation + if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); + + // Build the interpolation data + bool result = _build_interpolation(INTER_METHOD, p_object, NULL, &p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); + return result; } bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { - + // If we are already updating, call this function again later if (pending_update != 0) { _add_pending_command("interpolate_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); return true; } + // Check that the target object is valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); + + // Duration cannot be negative ERR_FAIL_COND_V(p_duration < 0, false); - ERR_EXPLAIN("Object has no callback named: %s" + p_callback); - ERR_FAIL_COND_V(!p_object->has_method(p_callback), false); + // Check whether the object even has the callback + ERR_FAIL_COND_V_MSG(!p_object->has_method(p_callback), false, "Object has no callback named: " + p_callback + "."); + // Build a new InterpolationData InterpolateData data; data.active = true; data.type = INTER_CALLBACK; @@ -1116,12 +1303,14 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c data.call_deferred = false; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key.push_back(p_callback); data.concatenated_key = p_callback; data.duration = p_duration; data.delay = 0; + // Add arguments to the interpolation int args = 0; if (p_arg5.get_type() != Variant::NIL) args = 5; @@ -1143,23 +1332,29 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c data.arg[3] = p_arg4; data.arg[4] = p_arg5; + // Add the new interpolation _push_interpolate_data(data); return true; } bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { - + // If we are already updating, call this function again later if (pending_update != 0) { _add_pending_command("interpolate_deferred_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); return true; } + + // Check that the target object is valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); + + // No negative durations allowed ERR_FAIL_COND_V(p_duration < 0, false); - ERR_EXPLAIN("Object has no callback named: %s" + p_callback); - ERR_FAIL_COND_V(!p_object->has_method(p_callback), false); + // Confirm the callback exists on the object + ERR_FAIL_COND_V_MSG(!p_object->has_method(p_callback), false, "Object has no callback named: " + p_callback + "."); + // Create a new InterpolateData for the callback InterpolateData data; data.active = true; data.type = INTER_CALLBACK; @@ -1167,12 +1362,14 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S data.call_deferred = true; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key.push_back(p_callback); data.concatenated_key = p_callback; data.duration = p_duration; data.delay = 0; + // Collect arguments for the callback int args = 0; if (p_arg5.get_type() != Variant::NIL) args = 5; @@ -1194,32 +1391,46 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S data.arg[3] = p_arg4; data.arg[4] = p_arg5; + // Add the new interpolation _push_interpolate_data(data); return true; } bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are already updating, call this function again later if (pending_update != 0) { _add_pending_command("follow_property", p_object, p_property, p_initial_val, p_target, p_target_property, p_duration, p_trans_type, p_ease_type, p_delay); return true; } + + // Get the two properties from their paths p_property = p_property.get_as_property_path(); p_target_property = p_target_property.get_as_property_path(); + // If no initial value is given, grab it from the source object + // TODO: Is this documented? It's really helpful for decluttering tweens if (p_initial_val.get_type() == Variant::NIL) p_initial_val = p_object->get_indexed(p_property.get_subnames()); - // convert INT to REAL is better for interpolaters + // Convert initial INT values to REAL as they are better for interpolation if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + // Confirm the source and target objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_target == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure transition and easing types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); + // Confirm the source and target objects have the desired properties bool prop_valid = false; p_object->get_indexed(p_property.get_subnames(), &prop_valid); ERR_FAIL_COND_V(!prop_valid, false); @@ -1228,16 +1439,20 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini Variant target_val = p_target->get_indexed(p_target_property.get_subnames(), &target_prop_valid); ERR_FAIL_COND_V(!target_prop_valid, false); - // convert INT to REAL is better for interpolaters + // Convert target INT to REAL since it is better for interpolation if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t(); + + // Verify that the target value and initial value are the same type ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); + // Create a new InterpolateData InterpolateData data; data.active = true; data.type = FOLLOW_PROPERTY; data.finish = false; data.elapsed = 0; + // Give the InterpolateData it's configuration data.id = p_object->get_instance_id(); data.key = p_property.get_subnames(); data.concatenated_key = p_property.get_concatenated_subnames(); @@ -1249,46 +1464,57 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini data.ease_type = p_ease_type; data.delay = p_delay; + // Add the interpolation _push_interpolate_data(data); return true; } bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are currently updating, call this function again later if (pending_update != 0) { _add_pending_command("follow_method", p_object, p_method, p_initial_val, p_target, p_target_method, p_duration, p_trans_type, p_ease_type, p_delay); return true; } - // convert INT to REAL is better for interpolaters + // Convert initial INT values to REAL as they are better for interpolation if (p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t(); + // Verify the source and target objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_target == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_target), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure that the transition and ease types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); - ERR_EXPLAIN("Object has no method named: %s" + p_method); - ERR_FAIL_COND_V(!p_object->has_method(p_method), false); - ERR_EXPLAIN("Target has no method named: %s" + p_target_method); - ERR_FAIL_COND_V(!p_target->has_method(p_target_method), false); + // Confirm both objects have the target methods + ERR_FAIL_COND_V_MSG(!p_object->has_method(p_method), false, "Object has no method named: " + p_method + "."); + ERR_FAIL_COND_V_MSG(!p_target->has_method(p_target_method), false, "Target has no method named: " + p_target_method + "."); + // Call the method to get the target value Variant::CallError error; Variant target_val = p_target->call(p_target_method, NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false); - // convert INT to REAL is better for interpolaters + // Convert target INT values to REAL as they are better for interpolation if (target_val.get_type() == Variant::INT) target_val = target_val.operator real_t(); ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); + // Make the new InterpolateData for the method follow InterpolateData data; data.active = true; data.type = FOLLOW_METHOD; data.finish = false; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key.push_back(p_method); data.concatenated_key = p_method; @@ -1300,31 +1526,41 @@ bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initi data.ease_type = p_ease_type; data.delay = p_delay; + // Add the new interpolation _push_interpolate_data(data); return true; } bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { - + // If we are currently updating, call this function again later if (pending_update != 0) { _add_pending_command("targeting_property", p_object, p_property, p_initial, p_initial_property, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); return true; } + // Grab the target property and the target property p_property = p_property.get_as_property_path(); p_initial_property = p_initial_property.get_as_property_path(); - // convert INT to REAL is better for interpolaters + // Convert the initial INT values to REAL as they are better for Interpolation if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); + // Verify both objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_initial == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure transition and easing types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); + // Ensure the initial and target properties exist on their objects bool prop_valid = false; p_object->get_indexed(p_property.get_subnames(), &prop_valid); ERR_FAIL_COND_V(!prop_valid, false); @@ -1333,16 +1569,18 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_ Variant initial_val = p_initial->get_indexed(p_initial_property.get_subnames(), &initial_prop_valid); ERR_FAIL_COND_V(!initial_prop_valid, false); - // convert INT to REAL is better for interpolaters + // Convert the initial INT value to REAL as it is better for interpolation if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t(); ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); + // Build the InterpolateData object InterpolateData data; data.active = true; data.type = TARGETING_PROPERTY; data.finish = false; data.elapsed = 0; + // Give the data it's configuration data.id = p_object->get_instance_id(); data.key = p_property.get_subnames(); data.concatenated_key = p_property.get_concatenated_subnames(); @@ -1355,49 +1593,62 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_ data.ease_type = p_ease_type; data.delay = p_delay; + // Ensure there is a valid delta if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) return false; + // Add the interpolation _push_interpolate_data(data); return true; } bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { + // If we are currently updating, call this function again later if (pending_update != 0) { _add_pending_command("targeting_method", p_object, p_method, p_initial, p_initial_method, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); return true; } - // convert INT to REAL is better for interpolaters + + // Convert final INT values to REAL as they are better for interpolation if (p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t(); + // Make sure the given objects are valid ERR_FAIL_COND_V(p_object == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_object), false); ERR_FAIL_COND_V(p_initial == NULL, false); ERR_FAIL_COND_V(!ObjectDB::instance_validate(p_initial), false); - ERR_FAIL_COND_V(p_duration <= 0, false); + + // No negative durations + ERR_FAIL_COND_V(p_duration < 0, false); + + // Ensure transition and easing types are valid ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); + + // No negative delays ERR_FAIL_COND_V(p_delay < 0, false); - ERR_EXPLAIN("Object has no method named: %s" + p_method); - ERR_FAIL_COND_V(!p_object->has_method(p_method), false); - ERR_EXPLAIN("Initial Object has no method named: %s" + p_initial_method); - ERR_FAIL_COND_V(!p_initial->has_method(p_initial_method), false); + // Make sure both objects have the given method + ERR_FAIL_COND_V_MSG(!p_object->has_method(p_method), false, "Object has no method named: " + p_method + "."); + ERR_FAIL_COND_V_MSG(!p_initial->has_method(p_initial_method), false, "Initial Object has no method named: " + p_initial_method + "."); + // Call the method to get the initial value Variant::CallError error; Variant initial_val = p_initial->call(p_initial_method, NULL, 0, error); ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false); - // convert INT to REAL is better for interpolaters + // Convert initial INT values to REAL as they aer better for interpolation if (initial_val.get_type() == Variant::INT) initial_val = initial_val.operator real_t(); ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); + // Build the new InterpolateData object InterpolateData data; data.active = true; data.type = TARGETING_METHOD; data.finish = false; data.elapsed = 0; + // Configure the data data.id = p_object->get_instance_id(); data.key.push_back(p_method); data.concatenated_key = p_method; @@ -1410,16 +1661,17 @@ bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_in data.ease_type = p_ease_type; data.delay = p_delay; + // Ensure there is a valid delta if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) return false; + // Add the interpolation _push_interpolate_data(data); return true; } Tween::Tween() { - - //String autoplay; + // Initialize tween attributes tween_process_mode = TWEEN_PROCESS_IDLE; repeat = false; speed_scale = 1; diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 6fe3bffdbe..64ce099ecd 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -135,6 +135,7 @@ private: void _tween_process(float p_delta); void _remove_by_uid(int uid); void _push_interpolate_data(InterpolateData &p_data); + bool _build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay); protected: bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/audio/audio_player.cpp b/scene/audio/audio_stream_player.cpp index 4eae3b04e7..c053fceb74 100644 --- a/scene/audio/audio_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* audio_player.cpp */ +/* audio_stream_player.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,38 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "audio_player.h" +#include "audio_stream_player.h" #include "core/engine.h" -void AudioStreamPlayer::_mix_internal(bool p_fadeout) { +void AudioStreamPlayer::_mix_to_bus(const AudioFrame *p_frames, int p_amount) { int bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); - //get data - AudioFrame *buffer = mix_buffer.ptrw(); - int buffer_size = mix_buffer.size(); - - if (p_fadeout) { - // Short fadeout ramp - buffer_size = MIN(buffer_size, 128); - } - - 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); - - for (int i = 0; i < buffer_size; i++) { - buffer[i] *= vol; - vol += vol_inc; - } - - //set volume for next mix - mix_volume_db = target_volume; - AudioFrame *targets[4] = { NULL, NULL, NULL, NULL }; if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) { @@ -83,16 +59,52 @@ void AudioStreamPlayer::_mix_internal(bool p_fadeout) { for (int c = 0; c < 4; c++) { if (!targets[c]) break; - for (int i = 0; i < buffer_size; i++) { - targets[c][i] += buffer[i]; + for (int i = 0; i < p_amount; i++) { + targets[c][i] += p_frames[i]; } } } +void AudioStreamPlayer::_mix_internal(bool p_fadeout) { + + //get data + AudioFrame *buffer = mix_buffer.ptrw(); + int buffer_size = mix_buffer.size(); + + if (p_fadeout) { + // Short fadeout ramp + buffer_size = MIN(buffer_size, 128); + } + + 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); + + for (int i = 0; i < buffer_size; i++) { + buffer[i] *= vol; + vol += vol_inc; + } + + //set volume for next mix + mix_volume_db = target_volume; + + _mix_to_bus(buffer, buffer_size); +} + void AudioStreamPlayer::_mix_audio() { - if (!stream_playback.is_valid() || !active) + if (use_fadeout) { + _mix_to_bus(fadeout_buffer.ptr(), fadeout_buffer.size()); + use_fadeout = false; + } + + if (!stream_playback.is_valid() || !active || + (stream_paused && !stream_paused_fade)) { return; + } if (stream_paused) { if (stream_paused_fade) { @@ -102,17 +114,26 @@ void AudioStreamPlayer::_mix_audio() { return; } - if (setseek >= 0.0) { + if (setstop) { + _mix_internal(true); + stream_playback->stop(); + setstop = false; + } + + if (setseek >= 0.0 && !stop_has_priority) { if (stream_playback->is_playing()) { //fade out to avoid pops _mix_internal(true); } + stream_playback->start(setseek); setseek = -1.0; //reset seek mix_volume_db = volume_db; //reset ramp } + stop_has_priority = false; + _mix_internal(false); } @@ -156,6 +177,28 @@ void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { AudioServer::get_singleton()->lock(); + if (active && stream_playback.is_valid() && !stream_paused) { + //changing streams out of the blue is not a great idea, but at least + //lets try to somehow avoid a click + + AudioFrame *buffer = fadeout_buffer.ptrw(); + int buffer_size = fadeout_buffer.size(); + + stream_playback->mix(buffer, pitch_scale, buffer_size); + + //multiply volume interpolating to avoid clicks if this changes + float target_volume = -80.0; + float vol = Math::db2linear(mix_volume_db); + float vol_inc = (Math::db2linear(target_volume) - vol) / float(buffer_size); + + for (int i = 0; i < buffer_size; i++) { + buffer[i] *= vol; + vol += vol_inc; + } + + use_fadeout = true; + } + mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); if (stream_playback.is_valid()) { @@ -163,6 +206,7 @@ void AudioStreamPlayer::set_stream(Ref<AudioStream> p_stream) { stream.unref(); active = false; setseek = -1; + setstop = false; } if (p_stream.is_valid()) { @@ -204,6 +248,7 @@ void AudioStreamPlayer::play(float p_from_pos) { if (stream_playback.is_valid()) { //mix_volume_db = volume_db; do not reset volume ramp here, can cause clicks setseek = p_from_pos; + stop_has_priority = false; active = true; set_process_internal(true); } @@ -218,17 +263,16 @@ void AudioStreamPlayer::seek(float p_seconds) { void AudioStreamPlayer::stop() { - if (stream_playback.is_valid()) { - stream_playback->stop(); - active = false; - set_process_internal(false); + if (stream_playback.is_valid() && active) { + setstop = true; + stop_has_priority = true; } } bool AudioStreamPlayer::is_playing() const { if (stream_playback.is_valid()) { - return active; //&& stream_playback->is_playing(); + return active && !setstop; //&& stream_playback->is_playing(); } return false; @@ -295,7 +339,7 @@ void AudioStreamPlayer::set_stream_paused(bool p_pause) { if (p_pause != stream_paused) { stream_paused = p_pause; - stream_paused_fade = p_pause ? true : false; + stream_paused_fade = p_pause; } } @@ -325,6 +369,10 @@ void AudioStreamPlayer::_bus_layout_changed() { _change_notify(); } +Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() { + return stream_playback; +} + void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer::set_stream); @@ -360,6 +408,8 @@ void AudioStreamPlayer::_bind_methods() { 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); + ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback); + 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"); @@ -387,6 +437,9 @@ AudioStreamPlayer::AudioStreamPlayer() { stream_paused = false; stream_paused_fade = false; mix_target = MIX_TARGET_STEREO; + fadeout_buffer.resize(512); + setstop = false; + use_fadeout = false; AudioServer::get_singleton()->connect("bus_layout_changed", this, "_bus_layout_changed"); } diff --git a/scene/audio/audio_player.h b/scene/audio/audio_stream_player.h index 2e9526c335..ab9161c798 100644 --- a/scene/audio/audio_player.h +++ b/scene/audio/audio_stream_player.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* audio_player.h */ +/* audio_stream_player.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,15 +28,15 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef AUDIOPLAYER_H -#define AUDIOPLAYER_H +#ifndef AUDIO_STREAM_PLAYER_H +#define AUDIO_STREAM_PLAYER_H #include "scene/main/node.h" #include "servers/audio/audio_stream.h" class AudioStreamPlayer : public Node { - GDCLASS(AudioStreamPlayer, Node) + GDCLASS(AudioStreamPlayer, Node); public: enum MixTarget { @@ -49,9 +49,13 @@ private: Ref<AudioStreamPlayback> stream_playback; Ref<AudioStream> stream; Vector<AudioFrame> mix_buffer; + Vector<AudioFrame> fadeout_buffer; + bool use_fadeout; volatile float setseek; volatile bool active; + volatile bool setstop; + volatile bool stop_has_priority; float mix_volume_db; float pitch_scale; @@ -71,6 +75,7 @@ private: bool _is_active() const; void _bus_layout_changed(); + void _mix_to_bus(const AudioFrame *p_frames, int p_amount); protected: void _validate_property(PropertyInfo &property) const; @@ -105,9 +110,12 @@ public: void set_stream_paused(bool p_pause); bool get_stream_paused() const; + Ref<AudioStreamPlayback> get_stream_playback(); + AudioStreamPlayer(); ~AudioStreamPlayer(); }; VARIANT_ENUM_CAST(AudioStreamPlayer::MixTarget) -#endif // AUDIOPLAYER_H + +#endif // AUDIO_STREAM_PLAYER_H diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 806c8afa5b..4f71481280 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -56,185 +56,23 @@ void BaseButton::_gui_input(Ref<InputEvent> p_event) { if (status.disabled) // no interaction with disabled button return; - Ref<InputEventMouseButton> b = p_event; + Ref<InputEventMouseButton> mouse_button = p_event; + bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo(); - if (b.is_valid()) { - if (status.disabled || ((1 << (b->get_button_index() - 1)) & button_mask) == 0) - return; - - if (status.pressing_button) - return; - - if (action_mode == ACTION_MODE_BUTTON_PRESS) { - - if (b->is_pressed()) { - - emit_signal("button_down"); - - if (!toggle_mode) { //mouse press attempt - - status.press_attempt = true; - status.pressing_inside = true; - - pressed(); - if (get_script_instance()) { - Variant::CallError ce; - get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce); - } - - _unpress_group(); - emit_signal("pressed"); - - } else { - - status.pressed = !status.pressed; - pressed(); - - _unpress_group(); - emit_signal("pressed"); - - toggled(status.pressed); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed); - } - emit_signal("toggled", status.pressed); - } - - } else { - - emit_signal("button_up"); - - /* this is pointless if (status.press_attempt && status.pressing_inside) { - //released(); - emit_signal("released"); - } -*/ - status.press_attempt = false; - } - update(); - return; - } - - if (b->is_pressed()) { - - status.press_attempt = true; - status.pressing_inside = true; - emit_signal("button_down"); - - } else { - - emit_signal("button_up"); - - if (status.press_attempt && status.pressing_inside) { - - 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); - } - - _unpress_group(); - emit_signal("pressed"); - - } else { - - status.pressed = !status.pressed; - - pressed(); - _unpress_group(); - emit_signal("pressed"); - - toggled(status.pressed); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed); - } - emit_signal("toggled", status.pressed); - } - } - - status.press_attempt = false; - } - - update(); + bool button_masked = mouse_button.is_valid() && ((1 << (mouse_button->get_button_index() - 1)) & button_mask) > 0; + if (button_masked || ui_accept) { + on_action_event(p_event); + return; } - Ref<InputEventMouseMotion> mm = p_event; - - if (mm.is_valid()) { - if (status.press_attempt && status.pressing_button == 0) { + Ref<InputEventMouseMotion> mouse_motion = p_event; + if (mouse_motion.is_valid()) { + if (status.press_attempt) { bool last_press_inside = status.pressing_inside; - status.pressing_inside = has_point(mm->get_position()); - if (last_press_inside != status.pressing_inside) + status.pressing_inside = has_point(mouse_motion->get_position()); + if (last_press_inside != status.pressing_inside) { update(); - } - } - - if (!mm.is_valid() && !b.is_valid()) { - - if (p_event->is_echo()) { - return; - } - - if (status.disabled) { - return; - } - - if (status.press_attempt && status.pressing_button == 0) { - return; - } - - if (p_event->is_action("ui_accept")) { - - if (p_event->is_pressed()) { - - status.pressing_button++; - status.press_attempt = true; - status.pressing_inside = true; - emit_signal("button_down"); - - } else if (status.press_attempt) { - - if (status.pressing_button) - status.pressing_button--; - - if (status.pressing_button) - return; - - status.press_attempt = false; - status.pressing_inside = false; - - emit_signal("button_up"); - - 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); - } - - _unpress_group(); - emit_signal("pressed"); - } else { - - status.pressed = !status.pressed; - - pressed(); - _unpress_group(); - emit_signal("pressed"); - - toggled(status.pressed); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed); - } - emit_signal("toggled", status.pressed); - } } - - accept_event(); - update(); } } } @@ -255,7 +93,6 @@ void BaseButton::_notification(int p_what) { if (status.press_attempt) { status.press_attempt = false; - status.pressing_button = 0; update(); } } @@ -268,9 +105,8 @@ void BaseButton::_notification(int p_what) { if (p_what == NOTIFICATION_FOCUS_EXIT) { - if (status.pressing_button && status.press_attempt) { + if (status.press_attempt) { status.press_attempt = false; - status.pressing_button = 0; status.hovering = false; update(); } else if (status.hovering) { @@ -279,13 +115,7 @@ void BaseButton::_notification(int p_what) { } } - if (p_what == NOTIFICATION_ENTER_TREE) { - } - - if (p_what == NOTIFICATION_EXIT_TREE) { - } - - if (p_what == NOTIFICATION_VISIBILITY_CHANGED && !is_visible_in_tree()) { + if (p_what == NOTIFICATION_EXIT_TREE || (p_what == NOTIFICATION_VISIBILITY_CHANGED && !is_visible_in_tree())) { if (!toggle_mode) { status.pressed = false; @@ -293,21 +123,66 @@ void BaseButton::_notification(int p_what) { status.hovering = false; status.press_attempt = false; status.pressing_inside = false; - status.pressing_button = 0; } } -void BaseButton::pressed() { +void BaseButton::_pressed() { - if (get_script_instance()) - get_script_instance()->call("pressed"); + if (get_script_instance()) { + get_script_instance()->call(SceneStringNames::get_singleton()->_pressed); + } + pressed(); + emit_signal("pressed"); } -void BaseButton::toggled(bool p_pressed) { +void BaseButton::_toggled(bool p_pressed) { if (get_script_instance()) { - get_script_instance()->call("toggled", p_pressed); + get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, p_pressed); } + toggled(p_pressed); + emit_signal("toggled", p_pressed); +} + +void BaseButton::on_action_event(Ref<InputEvent> p_event) { + + if (p_event->is_pressed()) { + status.press_attempt = true; + status.pressing_inside = true; + emit_signal("button_down"); + } + + if (status.press_attempt && status.pressing_inside) { + if (toggle_mode) { + if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) { + if (action_mode == ACTION_MODE_BUTTON_PRESS) { + status.press_attempt = false; + status.pressing_inside = false; + } + status.pressed = !status.pressed; + _unpress_group(); + _toggled(status.pressed); + _pressed(); + } + } else { + if (!p_event->is_pressed()) { + _pressed(); + } + } + } + + if (!p_event->is_pressed()) { // pressed state should be correct with button_up signal + emit_signal("button_up"); + status.press_attempt = false; + } + + update(); +} + +void BaseButton::pressed() { +} + +void BaseButton::toggled(bool p_pressed) { } void BaseButton::set_disabled(bool p_disabled) { @@ -321,7 +196,6 @@ void BaseButton::set_disabled(bool p_disabled) { } status.press_attempt = false; status.pressing_inside = false; - status.pressing_button = 0; } update(); _change_notify("disabled"); @@ -344,6 +218,8 @@ void BaseButton::set_pressed(bool p_pressed) { if (p_pressed) { _unpress_group(); } + _toggled(status.pressed); + update(); } @@ -379,7 +255,7 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const { bool pressing; if (status.press_attempt) { - pressing = status.pressing_inside; + pressing = (status.pressing_inside || keep_pressed_outside); if (status.pressed) pressing = !pressing; } else { @@ -449,10 +325,17 @@ Control::FocusMode BaseButton::get_enabled_focus_mode() const { return enabled_focus_mode; } -void BaseButton::set_shortcut(const Ref<ShortCut> &p_shortcut) { +void BaseButton::set_keep_pressed_outside(bool p_on) { - if (shortcut.is_null() == p_shortcut.is_null()) - return; + keep_pressed_outside = p_on; +} + +bool BaseButton::is_keep_pressed_outside() const { + + return keep_pressed_outside; +} + +void BaseButton::set_shortcut(const Ref<ShortCut> &p_shortcut) { shortcut = p_shortcut; set_process_unhandled_input(shortcut.is_valid()); @@ -464,17 +347,12 @@ Ref<ShortCut> BaseButton::get_shortcut() const { void BaseButton::_unhandled_input(Ref<InputEvent> p_event) { - if (!is_disabled() && is_visible_in_tree() && p_event->is_pressed() && !p_event->is_echo() && shortcut.is_valid() && shortcut->is_shortcut(p_event)) { + if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->is_shortcut(p_event)) { if (get_viewport()->get_modal_stack_top() && !get_viewport()->get_modal_stack_top()->is_a_parent_of(this)) return; //ignore because of modal window - if (is_toggle_mode()) { - set_pressed(!is_pressed()); - emit_signal("toggled", is_pressed()); - } - - emit_signal("pressed"); + on_action_event(p_event); } } @@ -531,6 +409,8 @@ void BaseButton::_bind_methods() { 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); + ClassDB::bind_method(D_METHOD("set_keep_pressed_outside", "enabled"), &BaseButton::set_keep_pressed_outside); + ClassDB::bind_method(D_METHOD("is_keep_pressed_outside"), &BaseButton::is_keep_pressed_outside); ClassDB::bind_method(D_METHOD("set_shortcut", "shortcut"), &BaseButton::set_shortcut); ClassDB::bind_method(D_METHOD("get_shortcut"), &BaseButton::get_shortcut); @@ -552,6 +432,7 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "action_mode", PROPERTY_HINT_ENUM, "Button Press,Button Release"), "set_action_mode", "get_action_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_enabled_focus_mode", "get_enabled_focus_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "ShortCut"), "set_shortcut", "get_shortcut"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); @@ -569,12 +450,12 @@ BaseButton::BaseButton() { toggle_mode = false; shortcut_in_tooltip = true; + keep_pressed_outside = false; status.pressed = false; status.press_attempt = false; status.hovering = false; status.pressing_inside = false; status.disabled = false; - status.pressing_button = 0; set_focus_mode(FOCUS_ALL); enabled_focus_mode = FOCUS_ALL; action_mode = ACTION_MODE_BUTTON_RELEASE; diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index 9a00cc79f2..2773f024df 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -32,9 +32,6 @@ #define BASE_BUTTON_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class ButtonGroup; @@ -52,6 +49,7 @@ private: int button_mask; bool toggle_mode; bool shortcut_in_tooltip; + bool keep_pressed_outside; FocusMode enabled_focus_mode; Ref<ShortCut> shortcut; @@ -64,13 +62,16 @@ private: bool pressing_inside; bool disabled; - int pressing_button; } status; Ref<ButtonGroup> button_group; void _unpress_group(); + void _pressed(); + void _toggled(bool p_pressed); + + void on_action_event(Ref<InputEvent> p_event); protected: virtual void pressed(); @@ -110,6 +111,9 @@ public: void set_action_mode(ActionMode p_mode); ActionMode get_action_mode() const; + void set_keep_pressed_outside(bool p_on); + bool is_keep_pressed_outside() const; + void set_button_mask(int p_mask); int get_button_mask() const; @@ -133,7 +137,7 @@ VARIANT_ENUM_CAST(BaseButton::ActionMode) class ButtonGroup : public Resource { - GDCLASS(ButtonGroup, Resource) + GDCLASS(ButtonGroup, Resource); friend class BaseButton; Set<BaseButton *> buttons; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index cc37d4cf7d..b7d2131ee9 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -91,7 +91,6 @@ void BoxContainer::_resort() { int stretch_diff = stretch_max - stretch_min; if (stretch_diff < 0) { //avoid negative stretch space - stretch_max = stretch_min; stretch_diff = 0; } diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index b77b57ddd4..4ce3f18505 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "button.h" + #include "core/translation.h" #include "servers/visual_server.h" @@ -102,6 +103,7 @@ void Button::_notification(int p_what) { break; } + FALLTHROUGH; } case DRAW_PRESSED: { @@ -140,8 +142,8 @@ void Button::_notification(int p_what) { if (has_focus()) { - Ref<StyleBox> style = get_stylebox("focus"); - style->draw(ci, Rect2(Point2(), size)); + Ref<StyleBox> style2 = get_stylebox("focus"); + style2->draw(ci, Rect2(Point2(), size)); } Ref<Font> font = get_font("font"); @@ -157,7 +159,11 @@ void Button::_notification(int p_what) { switch (align) { case ALIGN_LEFT: { - text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x + _internal_margin[MARGIN_LEFT] + get_constant("hseparation"); + if (_internal_margin[MARGIN_LEFT] > 0) { + text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x + _internal_margin[MARGIN_LEFT] + get_constant("hseparation"); + } else { + text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x; + } text_ofs.y += style->get_offset().y; } break; case ALIGN_CENTER: { diff --git a/scene/gui/button.h b/scene/gui/button.h index 6ba3475e5a..370809060e 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -32,9 +32,6 @@ #define BUTTON_H #include "scene/gui/base_button.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Button : public BaseButton { diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index 1d8b74d9db..8744407763 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -79,7 +79,7 @@ void CheckBox::_notification(int p_what) { Vector2 ofs; ofs.x = sb->get_margin(MARGIN_LEFT); - ofs.y = int((get_size().height - get_icon_size().height) / 2); + ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_constant("check_vadjust"); if (is_pressed()) on->draw(ci, ofs); diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index 35e3119473..f47547f2cc 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -34,13 +34,15 @@ #include "servers/visual_server.h" Size2 CheckButton::get_icon_size() const { - Ref<Texture> on = Control::get_icon("on"); - Ref<Texture> off = Control::get_icon("off"); + + Ref<Texture> on = Control::get_icon(is_disabled() ? "on_disabled" : "on"); + Ref<Texture> off = Control::get_icon(is_disabled() ? "off_disabled" : "off"); Size2 tex_size = Size2(0, 0); if (!on.is_null()) tex_size = Size2(on->get_width(), on->get_height()); if (!off.is_null()) tex_size = Size2(MAX(tex_size.width, off->get_width()), MAX(tex_size.height, off->get_height())); + return tex_size; } @@ -49,9 +51,8 @@ Size2 CheckButton::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; - if (get_text().length() > 0) { + if (get_text().length() > 0) minsize.width += get_constant("hseparation"); - } Ref<StyleBox> sb = get_stylebox("normal"); minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(MARGIN_TOP) + sb->get_margin(MARGIN_BOTTOM)); @@ -67,15 +68,15 @@ void CheckButton::_notification(int p_what) { RID ci = get_canvas_item(); - Ref<Texture> on = Control::get_icon("on"); - Ref<Texture> off = Control::get_icon("off"); + Ref<Texture> on = Control::get_icon(is_disabled() ? "on_disabled" : "on"); + Ref<Texture> off = Control::get_icon(is_disabled() ? "off_disabled" : "off"); Ref<StyleBox> sb = get_stylebox("normal"); Vector2 ofs; Size2 tex_size = get_icon_size(); ofs.x = get_size().width - (tex_size.width + sb->get_margin(MARGIN_RIGHT)); - ofs.y = (get_size().height - tex_size.height) / 2; + ofs.y = (get_size().height - tex_size.height) / 2 + get_constant("check_vadjust"); if (is_pressed()) on->draw(ci, ofs); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 6ed465562e..1d529f4e72 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -35,10 +35,9 @@ #include "core/os/os.h" #ifdef TOOLS_ENABLED +#include "editor_scale.h" #include "editor_settings.h" #endif - -#include "scene/gui/separator.h" #include "scene/main/viewport.h" void ColorPicker::_notification(int p_what) { @@ -81,11 +80,8 @@ void ColorPicker::_notification(int p_what) { } break; case MainLoop::NOTIFICATION_WM_QUIT_REQUEST: { - if (screen != NULL) { - if (screen->is_visible()) { - screen->hide(); - } - } + if (screen != NULL && screen->is_visible()) + screen->hide(); } break; } } @@ -97,6 +93,28 @@ void ColorPicker::set_focus_on_line_edit() { void ColorPicker::_update_controls() { + const char *rgb[3] = { "R", "G", "B" }; + const char *hsv[3] = { "H", "S", "V" }; + + if (hsv_mode_enabled) { + for (int i = 0; i < 3; i++) + labels[i]->set_text(hsv[i]); + } else { + for (int i = 0; i < 3; i++) + labels[i]->set_text(rgb[i]); + } + + if (hsv_mode_enabled) { + set_raw_mode(false); + btn_raw->set_disabled(true); + } else if (raw_mode_enabled) { + set_hsv_mode(false); + btn_hsv->set_disabled(true); + } else { + btn_raw->set_disabled(false); + btn_hsv->set_disabled(false); + } + if (edit_alpha) { values[3]->show(); scroll[3]->show(); @@ -108,7 +126,7 @@ void ColorPicker::_update_controls() { } } -void ColorPicker::set_pick_color(const Color &p_color) { +void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { color = p_color; if (color != last_hsv) { @@ -121,7 +139,12 @@ void ColorPicker::set_pick_color(const Color &p_color) { if (!is_inside_tree()) return; - _update_color(); + _update_color(p_update_sliders); +} + +void ColorPicker::set_pick_color(const Color &p_color) { + + _set_pick_color(p_color, true); //because setters can't have more arguments } void ColorPicker::set_edit_alpha(bool p_show) { @@ -146,17 +169,24 @@ void ColorPicker::_value_changed(double) { if (updating) return; - for (int i = 0; i < 4; i++) { - color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0); + if (hsv_mode_enabled) { + color.set_hsv(scroll[0]->get_value() / 360.0, + scroll[1]->get_value() / 100.0, + scroll[2]->get_value() / 100.0, + scroll[3]->get_value() / 255.0); + } else { + for (int i = 0; i < 4; i++) { + color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0); + } } - set_pick_color(color); + _set_pick_color(color, false); emit_signal("color_changed", color); } void ColorPicker::_html_entered(const String &p_html) { - if (updating) + if (updating || text_is_constructor || !c_text->is_visible()) return; float last_alpha = color.a; @@ -171,22 +201,40 @@ void ColorPicker::_html_entered(const String &p_html) { emit_signal("color_changed", color); } -void ColorPicker::_update_color() { +void ColorPicker::_update_color(bool p_update_sliders) { updating = true; - for (int i = 0; i < 4; i++) { - if (raw_mode_enabled) { - scroll[i]->set_step(0.01); - scroll[i]->set_max(100); - if (i == 3) - scroll[i]->set_max(1); - scroll[i]->set_value(color.components[i]); + if (p_update_sliders) { + + if (hsv_mode_enabled) { + for (int i = 0; i < 4; i++) { + scroll[i]->set_step(1.0); + } + + scroll[0]->set_max(359); + scroll[0]->set_value(h * 360.0); + scroll[1]->set_max(100); + scroll[1]->set_value(s * 100.0); + scroll[2]->set_max(100); + scroll[2]->set_value(v * 100.0); + scroll[3]->set_max(255); + scroll[3]->set_value(color.components[3] * 255.0); } else { - scroll[i]->set_step(1); - const float byte_value = color.components[i] * 255.0; - scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); - scroll[i]->set_value(byte_value); + for (int i = 0; i < 4; i++) { + if (raw_mode_enabled) { + scroll[i]->set_step(0.01); + scroll[i]->set_max(100); + if (i == 3) + scroll[i]->set_max(1); + scroll[i]->set_value(color.components[i]); + } else { + scroll[i]->set_step(1); + const float byte_value = color.components[i] * 255.0; + scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); + scroll[i]->set_value(byte_value); + } + } } } @@ -199,27 +247,34 @@ void ColorPicker::_update_color() { } void ColorPicker::_update_presets() { + presets_per_row = 10; Size2 size = bt_add_preset->get_size(); - Size2 preset_size = Size2(size.width * presets.size(), size.height); + Size2 preset_size = Size2(MIN(size.width * presets.size(), presets_per_row * size.width), size.height * (Math::ceil((float)presets.size() / presets_per_row))); preset->set_custom_minimum_size(preset_size); - - preset->draw_texture_rect(get_icon("preset_bg", "ColorPicker"), Rect2(Point2(), preset_size), true); + preset_container->set_custom_minimum_size(preset_size); + preset->draw_rect(Rect2(Point2(), preset_size), Color(1, 1, 1, 0)); for (int i = 0; i < presets.size(); i++) { - preset->draw_rect(Rect2(Point2(size.width * i, 0), size), presets[i]); + int x = (i % presets_per_row) * size.width; + int y = (Math::floor((float)i / presets_per_row)) * size.height; + preset->draw_rect(Rect2(Point2(x, y), size), presets[i]); } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } void ColorPicker::_text_type_toggled() { - if (!Engine::get_singleton()->is_editor_hint()) - return; + text_is_constructor = !text_is_constructor; if (text_is_constructor) { text_type->set_text(""); text_type->set_icon(get_icon("Script", "EditorIcons")); + + c_text->set_editable(false); } else { text_type->set_text("#"); text_type->set_icon(NULL); + + c_text->set_editable(true); } _update_color(); } @@ -237,8 +292,6 @@ void ColorPicker::add_preset(const Color &p_color) { presets.push_back(p_color); } preset->update(); - if (presets.size() == 10) - bt_add_preset->hide(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -273,13 +326,33 @@ PoolColorArray ColorPicker::get_presets() const { return arr; } +void ColorPicker::set_hsv_mode(bool p_enabled) { + + if (hsv_mode_enabled == p_enabled || raw_mode_enabled) + return; + hsv_mode_enabled = p_enabled; + if (btn_hsv->is_pressed() != p_enabled) + btn_hsv->set_pressed(p_enabled); + + if (!is_inside_tree()) + return; + + _update_controls(); + _update_color(); +} + +bool ColorPicker::is_hsv_mode() const { + + return hsv_mode_enabled; +} + void ColorPicker::set_raw_mode(bool p_enabled) { - if (raw_mode_enabled == p_enabled) + if (raw_mode_enabled == p_enabled || hsv_mode_enabled) return; raw_mode_enabled = p_enabled; - if (btn_mode->is_pressed() != p_enabled) - btn_mode->set_pressed(p_enabled); + if (btn_raw->is_pressed() != p_enabled) + btn_raw->set_pressed(p_enabled); if (!is_inside_tree()) return; @@ -304,19 +377,21 @@ bool ColorPicker::is_deferred_mode() const { 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); + String t = "Color(" + String::num(color.r) + ", " + String::num(color.g) + ", " + String::num(color.b); if (edit_alpha && color.a < 1) - t += ("," + String::num(color.a) + ")"); + t += ", " + String::num(color.a) + ")"; else t += ")"; c_text->set_text(t); - } else { - 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)); - } } + + if (color.r > 1 || color.g > 1 || color.b > 1 || color.r < 0 || color.g < 0 || color.b < 0) { + visible = false; + } else if (!text_is_constructor) { + c_text->set_text(color.to_html(edit_alpha && color.a < 1)); + } + + text_type->set_visible(visible); c_text->set_visible(visible); } @@ -460,14 +535,20 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - + int index = 0; if (bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { - int index = bev->get_position().x / (preset->get_size().x / presets.size()); + for (int i = 0; i < presets.size(); i++) { + int x = (i % presets_per_row) * bt_add_preset->get_size().x; + int y = (Math::floor((float)i / presets_per_row)) * bt_add_preset->get_size().y; + if (bev->get_position().x > x && bev->get_position().x < x + preset->get_size().x && bev->get_position().y > y && bev->get_position().y < y + preset->get_size().y) { + index = i; + } + } set_pick_color(presets[index]); _update_color(); emit_signal("color_changed", color); - } else if (bev->is_pressed() && bev->get_button_index() == BUTTON_RIGHT) { - int index = bev->get_position().x / (preset->get_size().x / presets.size()); + } else if (bev->is_pressed() && bev->get_button_index() == BUTTON_RIGHT && presets_enabled) { + index = bev->get_position().x / (preset->get_size().x / presets.size()); Color clicked_preset = presets[index]; erase_preset(clicked_preset); emit_signal("preset_removed", clicked_preset); @@ -494,21 +575,17 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; - - if (bev.is_valid()) { - - if (bev->get_button_index() == BUTTON_LEFT && !bev->is_pressed()) { - emit_signal("color_changed", color); - screen->hide(); - } + if (bev.is_valid() && bev->get_button_index() == BUTTON_LEFT && !bev->is_pressed()) { + emit_signal("color_changed", color); + screen->hide(); } Ref<InputEventMouseMotion> mev = p_event; - if (mev.is_valid()) { Viewport *r = get_tree()->get_root(); if (!r->get_visible_rect().has_point(Point2(mev->get_global_position().x, mev->get_global_position().y))) return; + Ref<Image> img = r->get_texture()->get_data(); if (img.is_valid() && !img->empty()) { img->lock(); @@ -534,6 +611,8 @@ void ColorPicker::_screen_pick_pressed() { screen->set_anchors_and_margins_preset(Control::PRESET_WIDE); screen->set_default_cursor_shape(CURSOR_POINTING_HAND); screen->connect("gui_input", this, "_screen_input"); + // It immediately toggles off in the first press otherwise. + screen->call_deferred("connect", "hide", btn_pick, "set_pressed", varray(false)); } screen->raise(); screen->show_modal(); @@ -564,16 +643,47 @@ void ColorPicker::_html_focus_exit() { _focus_exit(); } +void ColorPicker::set_presets_enabled(bool p_enabled) { + presets_enabled = p_enabled; + if (!p_enabled) { + bt_add_preset->set_disabled(true); + bt_add_preset->set_focus_mode(FOCUS_NONE); + } else { + bt_add_preset->set_disabled(false); + bt_add_preset->set_focus_mode(FOCUS_ALL); + } +} + +bool ColorPicker::are_presets_enabled() const { + return presets_enabled; +} + +void ColorPicker::set_presets_visible(bool p_visible) { + presets_visible = p_visible; + preset_separator->set_visible(p_visible); + preset_container->set_visible(p_visible); +} + +bool ColorPicker::are_presets_visible() const { + return presets_visible; +} + void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPicker::set_pick_color); ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); + ClassDB::bind_method(D_METHOD("set_hsv_mode", "mode"), &ColorPicker::set_hsv_mode); + ClassDB::bind_method(D_METHOD("is_hsv_mode"), &ColorPicker::is_hsv_mode); 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("set_presets_enabled", "enabled"), &ColorPicker::set_presets_enabled); + ClassDB::bind_method(D_METHOD("are_presets_enabled"), &ColorPicker::are_presets_enabled); + ClassDB::bind_method(D_METHOD("set_presets_visible", "visible"), &ColorPicker::set_presets_visible); + ClassDB::bind_method(D_METHOD("are_presets_visible"), &ColorPicker::are_presets_visible); ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); ClassDB::bind_method(D_METHOD("erase_preset", "color"), &ColorPicker::erase_preset); ClassDB::bind_method(D_METHOD("get_presets"), &ColorPicker::get_presets); @@ -595,8 +705,11 @@ void ColorPicker::_bind_methods() { 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, "hsv_mode"), "set_hsv_mode", "is_hsv_mode"); 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_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible"); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); ADD_SIGNAL(MethodInfo("preset_added", PropertyInfo(Variant::COLOR, "color"))); @@ -609,28 +722,34 @@ ColorPicker::ColorPicker() : updating = true; edit_alpha = true; text_is_constructor = false; + hsv_mode_enabled = false; raw_mode_enabled = false; deferred_mode_enabled = false; changing_color = false; + presets_enabled = true; + presets_visible = true; screen = NULL; HBoxContainer *hb_smpl = memnew(HBoxContainer); - btn_pick = memnew(ToolButton); - btn_pick->connect("pressed", this, "_screen_pick_pressed"); + add_child(hb_smpl); sample = memnew(TextureRect); + hb_smpl->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); sample->connect("draw", this, "_sample_draw"); - hb_smpl->add_child(sample); + btn_pick = memnew(ToolButton); hb_smpl->add_child(btn_pick); - add_child(hb_smpl); + btn_pick->set_toggle_mode(true); + btn_pick->set_tooltip(TTR("Pick a color from the screen.")); + btn_pick->connect("pressed", this, "_screen_pick_pressed"); HBoxContainer *hb_edit = memnew(HBoxContainer); + add_child(hb_edit); hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); uv_edit = memnew(Control); - + hb_edit->add_child(uv_edit); uv_edit->connect("gui_input", this, "_uv_input"); uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); uv_edit->set_h_size_flags(SIZE_EXPAND_FILL); @@ -638,19 +757,14 @@ ColorPicker::ColorPicker() : uv_edit->set_custom_minimum_size(Size2(get_constant("sv_width"), get_constant("sv_height"))); uv_edit->connect("draw", this, "_hsv_draw", make_binds(0, uv_edit)); - add_child(hb_edit); - w_edit = memnew(Control); + hb_edit->add_child(w_edit); w_edit->set_custom_minimum_size(Size2(get_constant("h_width"), 0)); w_edit->set_h_size_flags(SIZE_FILL); w_edit->set_v_size_flags(SIZE_EXPAND_FILL); w_edit->connect("gui_input", this, "_w_input"); w_edit->connect("draw", this, "_hsv_draw", make_binds(1, w_edit)); - hb_edit->add_child(uv_edit); - hb_edit->add_child(memnew(VSeparator)); - hb_edit->add_child(w_edit); - VBoxContainer *vbl = memnew(VBoxContainer); add_child(vbl); @@ -659,13 +773,12 @@ ColorPicker::ColorPicker() : VBoxContainer *vbr = memnew(VBoxContainer); add_child(vbr); vbr->set_h_size_flags(SIZE_EXPAND_FILL); - const char *lt[4] = { "R", "G", "B", "A" }; for (int i = 0; i < 4; i++) { HBoxContainer *hbc = memnew(HBoxContainer); - labels[i] = memnew(Label(lt[i])); + labels[i] = memnew(Label()); labels[i]->set_custom_minimum_size(Size2(get_constant("label_width"), 0)); labels[i]->set_v_size_flags(SIZE_SHRINK_CENTER); hbc->add_child(labels[i]); @@ -689,45 +802,68 @@ ColorPicker::ColorPicker() : vbr->add_child(hbc); } + labels[3]->set_text("A"); HBoxContainer *hhb = memnew(HBoxContainer); - - btn_mode = memnew(CheckButton); - btn_mode->set_text(TTR("Raw Mode")); - btn_mode->connect("toggled", this, "set_raw_mode"); - hhb->add_child(btn_mode); vbr->add_child(hhb); + + btn_hsv = memnew(CheckButton); + hhb->add_child(btn_hsv); + btn_hsv->set_text(TTR("HSV")); + btn_hsv->connect("toggled", this, "set_hsv_mode"); + + btn_raw = memnew(CheckButton); + hhb->add_child(btn_raw); + btn_raw->set_text(TTR("Raw")); + btn_raw->connect("toggled", this, "set_raw_mode"); + text_type = memnew(Button); - text_type->set_flat(true); - text_type->connect("pressed", this, "_text_type_toggled"); hhb->add_child(text_type); + text_type->set_text("#"); + text_type->set_tooltip(TTR("Switch between hexadecimal and code values.")); + if (Engine::get_singleton()->is_editor_hint()) { + +#ifdef TOOLS_ENABLED + text_type->set_custom_minimum_size(Size2(28 * EDSCALE, 0)); // Adjust for the width of the "Script" icon. +#endif + text_type->connect("pressed", this, "_text_type_toggled"); + } else { + + text_type->set_flat(true); + text_type->set_mouse_filter(MOUSE_FILTER_IGNORE); + } c_text = memnew(LineEdit); hhb->add_child(c_text); + c_text->set_h_size_flags(SIZE_EXPAND_FILL); 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); - _update_controls(); updating = false; set_pick_color(Color(1, 1, 1)); - HBoxContainer *bbc = memnew(HBoxContainer); - add_child(bbc); + preset_separator = memnew(HSeparator); + add_child(preset_separator); + + preset_container = memnew(HBoxContainer); + preset_container->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(preset_container); preset = memnew(TextureRect); - bbc->add_child(preset); + preset_container->add_child(preset); preset->connect("gui_input", this, "_preset_input"); preset->connect("draw", this, "_update_presets"); + preset_container2 = memnew(HBoxContainer); + preset_container2->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(preset_container2); bt_add_preset = memnew(Button); - bt_add_preset->set_tooltip(TTR("Add current color as a preset")); + preset_container2->add_child(bt_add_preset); + bt_add_preset->set_tooltip(TTR("Add current color as a preset.")); bt_add_preset->connect("pressed", this, "_add_preset_pressed"); - bbc->add_child(bt_add_preset); } ///////////////// @@ -747,23 +883,33 @@ void ColorPickerButton::_modal_closed() { void ColorPickerButton::pressed() { _update_picker(); - popup->set_position(get_global_position() - picker->get_combined_minimum_size()); + popup->set_position(get_global_position() - picker->get_combined_minimum_size() * get_global_transform().get_scale()); + popup->set_scale(get_global_transform().get_scale()); popup->popup(); picker->set_focus_on_line_edit(); } void ColorPickerButton::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { + switch (p_what) { + case NOTIFICATION_DRAW: { + + 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, color); + } break; + case MainLoop::NOTIFICATION_WM_QUIT_REQUEST: { - 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, color); + if (popup) + popup->hide(); + } break; } - if (p_what == MainLoop::NOTIFICATION_WM_QUIT_REQUEST && popup) { - popup->hide(); + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + if (popup && !is_visible_in_tree()) { + popup->hide(); + } } } @@ -814,6 +960,8 @@ void ColorPickerButton::_update_picker() { add_child(popup); picker->connect("color_changed", this, "_color_changed"); popup->connect("modal_closed", this, "_modal_closed"); + popup->connect("about_to_show", this, "set_pressed", varray(true)); + popup->connect("popup_hide", this, "set_pressed", varray(false)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); } @@ -838,10 +986,12 @@ void ColorPickerButton::_bind_methods() { ColorPickerButton::ColorPickerButton() { - //Initialization is now done deferred - //this improves performance in the inspector as the color picker - //can be expensive to initialize + // 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; + + set_toggle_mode(true); } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index b78844839a..167f7b33b3 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -37,6 +37,7 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/popup.h" +#include "scene/gui/separator.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" @@ -52,10 +53,14 @@ private: Control *w_edit; TextureRect *sample; TextureRect *preset; + HBoxContainer *preset_container; + HBoxContainer *preset_container2; + HSeparator *preset_separator; Button *bt_add_preset; List<Color> presets; ToolButton *btn_pick; - CheckButton *btn_mode; + CheckButton *btn_hsv; + CheckButton *btn_raw; HSlider *scroll[4]; SpinBox *values[4]; Label *labels[4]; @@ -64,19 +69,23 @@ private: bool edit_alpha; Size2i ms; bool text_is_constructor; + int presets_per_row; Color color; bool raw_mode_enabled; + bool hsv_mode_enabled; bool deferred_mode_enabled; bool updating; bool changing_color; + bool presets_enabled; + bool presets_visible; float h, s, v; Color last_hsv; void _html_entered(const String &p_html); void _value_changed(double); void _update_controls(); - void _update_color(); + void _update_color(bool p_update_sliders = true); void _update_presets(); void _update_text_value(); void _text_type_toggled(); @@ -101,6 +110,7 @@ public: void set_edit_alpha(bool p_show); bool is_editing_alpha() const; + void _set_pick_color(const Color &p_color, bool p_update_sliders); void set_pick_color(const Color &p_color); Color get_pick_color() const; @@ -108,12 +118,21 @@ public: void erase_preset(const Color &p_color); PoolColorArray get_presets() const; + void set_hsv_mode(bool p_enabled); + bool is_hsv_mode() const; + 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_presets_enabled(bool p_enabled); + bool are_presets_enabled() const; + + void set_presets_visible(bool p_visible); + bool are_presets_visible() const; + void set_focus_on_line_edit(); ColorPicker(); diff --git a/scene/gui/color_rect.h b/scene/gui/color_rect.h index d7f9ef275d..7a7bbe1029 100644 --- a/scene/gui/color_rect.h +++ b/scene/gui/color_rect.h @@ -34,7 +34,7 @@ #include "scene/gui/control.h" class ColorRect : public Control { - GDCLASS(ColorRect, Control) + GDCLASS(ColorRect, Control); Color color; diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 2579321773..449076f863 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -169,6 +169,19 @@ void Container::_notification(int p_what) { } } +String Container::get_configuration_warning() const { + + String warning = Control::get_configuration_warning(); + + if (get_class() == "Container" && get_script().is_null()) { + if (warning != String()) { + warning += "\n\n"; + } + warning += TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."); + } + return warning; +} + void Container::_bind_methods() { ClassDB::bind_method(D_METHOD("_sort_children"), &Container::_sort_children); diff --git a/scene/gui/container.h b/scene/gui/container.h index 0b014137f4..80d3f6ee5d 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -57,6 +57,8 @@ public: void fit_child_in_rect(Control *p_child, const Rect2 &p_rect); + virtual String get_configuration_warning() const; + Container(); }; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 513befb8f3..034e9266f6 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -29,22 +29,23 @@ /*************************************************************************/ #include "control.h" -#include "core/project_settings.h" -#include "scene/main/canvas_layer.h" -#include "scene/main/viewport.h" -#include "servers/visual_server.h" #include "core/message_queue.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/print_string.h" +#include "core/project_settings.h" #include "scene/gui/label.h" #include "scene/gui/panel.h" +#include "scene/main/canvas_layer.h" +#include "scene/main/viewport.h" #include "scene/scene_string_names.h" +#include "servers/visual_server.h" + #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" +#include "editor/plugins/canvas_item_editor_plugin.h" #endif -#include <stdio.h> Dictionary Control::_edit_get_state() const { @@ -66,6 +67,7 @@ Dictionary Control::_edit_get_state() const { s["margins"] = margins; return s; } + void Control::_edit_set_state(const Dictionary &p_state) { Dictionary state = p_state; @@ -87,7 +89,12 @@ void Control::_edit_set_state(const Dictionary &p_state) { } void Control::_edit_set_position(const Point2 &p_position) { +#ifdef TOOLS_ENABLED + set_position(p_position, CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); +#else + // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED set_position(p_position); +#endif }; Point2 Control::_edit_get_position() const { @@ -103,8 +110,14 @@ Size2 Control::_edit_get_scale() const { } void Control::_edit_set_rect(const Rect2 &p_edit_rect) { +#ifdef TOOLS_ENABLED + set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); + set_size(p_edit_rect.size.snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); +#else + // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED 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))); +#endif } Rect2 Control::_edit_get_rect() const { @@ -206,65 +219,62 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { if (name.begins_with("custom_icons/")) { String dname = name.get_slicec('/', 1); + if (data.icon_override.has(dname)) { + data.icon_override[dname]->disconnect("changed", this, "_override_changed"); + } data.icon_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_shaders/")) { String dname = name.get_slicec('/', 1); + if (data.shader_override.has(dname)) { + data.shader_override[dname]->disconnect("changed", this, "_override_changed"); + } data.shader_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_styles/")) { String dname = name.get_slicec('/', 1); + if (data.style_override.has(dname)) { + data.style_override[dname]->disconnect("changed", this, "_override_changed"); + } data.style_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_fonts/")) { String dname = name.get_slicec('/', 1); if (data.font_override.has(dname)) { - _unref_font(data.font_override[dname]); + data.font_override[dname]->disconnect("changed", this, "_override_changed"); } data.font_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_colors/")) { String dname = name.get_slicec('/', 1); data.color_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_constants/")) { String dname = name.get_slicec('/', 1); data.constant_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else return false; } else { if (name.begins_with("custom_icons/")) { String dname = name.get_slicec('/', 1); - notification(NOTIFICATION_THEME_CHANGED); add_icon_override(dname, p_value); } else if (name.begins_with("custom_shaders/")) { String dname = name.get_slicec('/', 1); add_shader_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_styles/")) { String dname = name.get_slicec('/', 1); add_style_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_fonts/")) { String dname = name.get_slicec('/', 1); add_font_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_colors/")) { String dname = name.get_slicec('/', 1); add_color_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_constants/")) { String dname = name.get_slicec('/', 1); add_constant_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else return false; } @@ -840,6 +850,12 @@ Ref<Texture> Control::get_icon(const StringName &p_name, const StringName &p_typ theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_icon(p_name, type)) { + return Theme::get_project_default()->get_icon(p_name, type); + } + } + return Theme::get_default()->get_icon(p_name, type); } @@ -876,6 +892,12 @@ Ref<Shader> Control::get_shader(const StringName &p_name, const StringName &p_ty theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_shader(p_name, type)) { + return Theme::get_project_default()->get_shader(p_name, type); + } + } + return Theme::get_default()->get_shader(p_name, type); } @@ -915,6 +937,9 @@ Ref<StyleBox> Control::get_stylebox(const StringName &p_name, const StringName & } while (class_name != StringName()) { + if (Theme::get_project_default().is_valid() && Theme::get_project_default()->has_stylebox(p_name, type)) + return Theme::get_project_default()->get_stylebox(p_name, type); + if (Theme::get_default()->has_stylebox(p_name, class_name)) return Theme::get_default()->get_stylebox(p_name, class_name); @@ -991,6 +1016,11 @@ Color Control::get_color(const StringName &p_name, const StringName &p_type) con theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_color(p_name, type)) { + return Theme::get_project_default()->get_color(p_name, type); + } + } return Theme::get_default()->get_color(p_name, type); } @@ -1026,61 +1056,48 @@ int Control::get_constant(const StringName &p_name, const StringName &p_type) co theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_constant(p_name, type)) { + return Theme::get_project_default()->get_constant(p_name, type); + } + } return Theme::get_default()->get_constant(p_name, type); } bool Control::has_icon_override(const StringName &p_name) const { const Ref<Texture> *tex = data.icon_override.getptr(p_name); - if (tex) - return true; - else - return false; + return tex != NULL; } bool Control::has_shader_override(const StringName &p_name) const { const Ref<Shader> *sdr = data.shader_override.getptr(p_name); - if (sdr) - return true; - else - return false; + return sdr != NULL; } bool Control::has_stylebox_override(const StringName &p_name) const { const Ref<StyleBox> *style = data.style_override.getptr(p_name); - if (style) - return true; - else - return false; + return style != NULL; } bool Control::has_font_override(const StringName &p_name) const { const Ref<Font> *font = data.font_override.getptr(p_name); - if (font) - return true; - else - return false; + return font != NULL; } bool Control::has_color_override(const StringName &p_name) const { const Color *color = data.color_override.getptr(p_name); - if (color) - return true; - else - return false; + return color != NULL; } bool Control::has_constant_override(const StringName &p_name) const { const int *constant = data.constant_override.getptr(p_name); - if (constant) - return true; - else - return false; + return constant != NULL; } bool Control::has_icon(const StringName &p_name, const StringName &p_type) const { @@ -1114,6 +1131,11 @@ bool Control::has_icon(const StringName &p_name, const StringName &p_type) const theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_color(p_name, type)) { + return true; + } + } return Theme::get_default()->has_icon(p_name, type); } @@ -1148,6 +1170,11 @@ bool Control::has_shader(const StringName &p_name, const StringName &p_type) con theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_shader(p_name, type)) { + return true; + } + } return Theme::get_default()->has_shader(p_name, type); } bool Control::has_stylebox(const StringName &p_name, const StringName &p_type) const { @@ -1181,6 +1208,11 @@ bool Control::has_stylebox(const StringName &p_name, const StringName &p_type) c theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_stylebox(p_name, type)) { + return true; + } + } return Theme::get_default()->has_stylebox(p_name, type); } bool Control::has_font(const StringName &p_name, const StringName &p_type) const { @@ -1214,6 +1246,11 @@ bool Control::has_font(const StringName &p_name, const StringName &p_type) const theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_font(p_name, type)) { + return true; + } + } return Theme::get_default()->has_font(p_name, type); } @@ -1248,6 +1285,11 @@ bool Control::has_color(const StringName &p_name, const StringName &p_type) cons theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_color(p_name, type)) { + return true; + } + } return Theme::get_default()->has_color(p_name, type); } @@ -1282,6 +1324,11 @@ bool Control::has_constant(const StringName &p_name, const StringName &p_type) c theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_constant(p_name, type)) { + return true; + } + } return Theme::get_default()->has_constant(p_name, type); } @@ -1369,7 +1416,7 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo 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; - data.anchor[p_margin] = CLAMP(p_anchor, 0.0, 1.0); + data.anchor[p_margin] = p_anchor; if (((p_margin == MARGIN_LEFT || p_margin == MARGIN_TOP) && data.anchor[p_margin] > data.anchor[(p_margin + 2) % 4]) || ((p_margin == MARGIN_RIGHT || p_margin == MARGIN_BOTTOM) && data.anchor[p_margin] < data.anchor[(p_margin + 2) % 4])) { @@ -1391,19 +1438,14 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo } update(); - _change_notify("anchor"); + _change_notify("anchor_left"); + _change_notify("anchor_right"); + _change_notify("anchor_top"); + _change_notify("anchor_bottom"); } void Control::_set_anchor(Margin p_margin, float p_anchor) { -#ifdef TOOLS_ENABLED - if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) { - set_anchor(p_margin, p_anchor, EDITOR_DEF("editors/2d/keep_margins_when_changing_anchors", false)); - } else { - set_anchor(p_margin, p_anchor, false); - } -#else - set_anchor(p_margin, p_anchor, false); -#endif + set_anchor(p_margin, p_anchor); } void Control::set_anchor_and_margin(Margin p_margin, float p_anchor, float p_pos, bool p_push_opposite_anchor) { @@ -1412,7 +1454,7 @@ void Control::set_anchor_and_margin(Margin p_margin, float p_anchor, float p_pos set_margin(p_margin, p_pos); } -void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { +void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margins) { //Left switch (p_preset) { case PRESET_TOP_LEFT: @@ -1423,21 +1465,21 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_LEFT, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_LEFT, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - set_anchor(MARGIN_LEFT, 0.5, p_keep_margin); + set_anchor(MARGIN_LEFT, 0.5, p_keep_margins); break; case PRESET_TOP_RIGHT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_RIGHT: case PRESET_RIGHT_WIDE: - set_anchor(MARGIN_LEFT, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_LEFT, ANCHOR_END, p_keep_margins); break; } @@ -1451,21 +1493,21 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_TOP, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_TOP, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - set_anchor(MARGIN_TOP, 0.5, p_keep_margin); + set_anchor(MARGIN_TOP, 0.5, p_keep_margins); break; case PRESET_BOTTOM_LEFT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_BOTTOM: case PRESET_BOTTOM_WIDE: - set_anchor(MARGIN_TOP, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_TOP, ANCHOR_END, p_keep_margins); break; } @@ -1475,14 +1517,14 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_BOTTOM_LEFT: case PRESET_CENTER_LEFT: case PRESET_LEFT_WIDE: - set_anchor(MARGIN_RIGHT, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_RIGHT, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - set_anchor(MARGIN_RIGHT, 0.5, p_keep_margin); + set_anchor(MARGIN_RIGHT, 0.5, p_keep_margins); break; case PRESET_TOP_RIGHT: @@ -1493,7 +1535,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_RIGHT, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_RIGHT, ANCHOR_END, p_keep_margins); break; } @@ -1503,14 +1545,14 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_TOP_RIGHT: case PRESET_CENTER_TOP: case PRESET_TOP_WIDE: - set_anchor(MARGIN_BOTTOM, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_BOTTOM, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - set_anchor(MARGIN_BOTTOM, 0.5, p_keep_margin); + set_anchor(MARGIN_BOTTOM, 0.5, p_keep_margins); break; case PRESET_BOTTOM_LEFT: @@ -1521,7 +1563,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_BOTTOM, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_BOTTOM, ANCHOR_END, p_keep_margins); break; } } @@ -1714,7 +1756,11 @@ Point2 Control::get_global_position() const { return get_global_transform().get_origin(); } -void Control::set_global_position(const Point2 &p_point) { +void Control::_set_global_position(const Point2 &p_point) { + set_global_position(p_point); +} + +void Control::set_global_position(const Point2 &p_point, bool p_keep_margins) { Transform2D inv; @@ -1723,18 +1769,19 @@ void Control::set_global_position(const Point2 &p_point) { inv = data.parent_canvas_item->get_global_transform().affine_inverse(); } - set_position(inv.xform(p_point)); + set_position(inv.xform(p_point), p_keep_margins); } -Rect2 Control::_compute_child_rect(const float p_anchors[4], const float p_margins[4]) const { +void Control::_compute_anchors(Rect2 p_rect, const float p_margins[4], float (&r_anchors)[4]) { - 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 parent_rect_size = get_parent_anchorable_rect().size; + ERR_FAIL_COND(parent_rect_size.x == 0.0); + ERR_FAIL_COND(parent_rect_size.y == 0.0); - return result; + r_anchors[0] = (p_rect.position.x - p_margins[0]) / parent_rect_size.x; + r_anchors[1] = (p_rect.position.y - p_margins[1]) / parent_rect_size.y; + r_anchors[2] = (p_rect.position.x + p_rect.size.x - p_margins[2]) / parent_rect_size.x; + r_anchors[3] = (p_rect.position.y + p_rect.size.y - p_margins[3]) / parent_rect_size.y; } void Control::_compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]) { @@ -1746,13 +1793,28 @@ void Control::_compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r r_margins[3] = p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y); } -void Control::set_position(const Size2 &p_point) { +void Control::_set_position(const Size2 &p_point) { + set_position(p_point); +} - _compute_margins(Rect2(p_point, data.size_cache), data.anchor, data.margin); +void Control::set_position(const Size2 &p_point, bool p_keep_margins) { + if (p_keep_margins) { + _compute_anchors(Rect2(p_point, data.size_cache), data.margin, data.anchor); + _change_notify("anchor_left"); + _change_notify("anchor_right"); + _change_notify("anchor_top"); + _change_notify("anchor_bottom"); + } else { + _compute_margins(Rect2(p_point, data.size_cache), data.anchor, data.margin); + } _size_changed(); } -void Control::set_size(const Size2 &p_size) { +void Control::_set_size(const Size2 &p_size) { + set_size(p_size); +} + +void Control::set_size(const Size2 &p_size, bool p_keep_margins) { Size2 new_size = p_size; Size2 min = get_combined_minimum_size(); @@ -1761,7 +1823,15 @@ void Control::set_size(const Size2 &p_size) { if (new_size.y < min.y) new_size.y = min.y; - _compute_margins(Rect2(data.pos_cache, new_size), data.anchor, data.margin); + if (p_keep_margins) { + _compute_anchors(Rect2(data.pos_cache, new_size), data.margin, data.anchor); + _change_notify("anchor_left"); + _change_notify("anchor_right"); + _change_notify("anchor_top"); + _change_notify("anchor_bottom"); + } else { + _compute_margins(Rect2(data.pos_cache, new_size), data.anchor, data.margin); + } _size_changed(); } @@ -1799,52 +1869,83 @@ Rect2 Control::get_anchorable_rect() const { void Control::add_icon_override(const StringName &p_name, const Ref<Texture> &p_icon) { - ERR_FAIL_COND(p_icon.is_null()); - data.icon_override[p_name] = p_icon; + if (data.icon_override.has(p_name)) { + data.icon_override[p_name]->disconnect("changed", this, "_override_changed"); + } + + // clear if "null" is passed instead of a icon + if (p_icon.is_null()) { + data.icon_override.erase(p_name); + } else { + data.icon_override[p_name] = p_icon; + if (data.icon_override[p_name].is_valid()) { + data.icon_override[p_name]->connect("changed", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } + } notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_shader_override(const StringName &p_name, const Ref<Shader> &p_shader) { - ERR_FAIL_COND(p_shader.is_null()); - data.shader_override[p_name] = p_shader; + + if (data.shader_override.has(p_name)) { + data.shader_override[p_name]->disconnect("changed", this, "_override_changed"); + } + + // clear if "null" is passed instead of a shader + if (p_shader.is_null()) { + data.shader_override.erase(p_name); + } else { + data.shader_override[p_name] = p_shader; + if (data.shader_override[p_name].is_valid()) { + data.shader_override[p_name]->connect("changed", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } + } notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { - ERR_FAIL_COND(p_style.is_null()); - data.style_override[p_name] = p_style; + if (data.style_override.has(p_name)) { + data.style_override[p_name]->disconnect("changed", this, "_override_changed"); + } + + // clear if "null" is passed instead of a style + if (p_style.is_null()) { + data.style_override.erase(p_name); + } else { + data.style_override[p_name] = p_style; + if (data.style_override[p_name].is_valid()) { + data.style_override[p_name]->connect("changed", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } + } notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_font_override(const StringName &p_name, const Ref<Font> &p_font) { - ERR_FAIL_COND(p_font.is_null()); if (data.font_override.has(p_name)) { - _unref_font(data.font_override[p_name]); + data.font_override[p_name]->disconnect("changed", this, "_override_changed"); } - data.font_override[p_name] = p_font; - if (p_font.is_valid()) { - _ref_font(p_font); + // clear if "null" is passed instead of a font + if (p_font.is_null()) { + data.font_override.erase(p_name); + } else { + data.font_override[p_name] = p_font; + if (data.font_override[p_name].is_valid()) { + data.font_override[p_name]->connect("changed", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } } - notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_color_override(const StringName &p_name, const Color &p_color) { data.color_override[p_name] = p_color; notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_constant_override(const StringName &p_name, int p_constant) { data.constant_override[p_name] = p_constant; notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::set_focus_mode(FocusMode p_focus_mode) { @@ -1894,12 +1995,7 @@ Control *Control::find_next_valid_focus() const { Node *n = get_node(data.focus_next); if (n) { from = Object::cast_to<Control>(n); - - if (!from) { - - ERR_EXPLAIN("Next focus node is not a control: " + n->get_name()); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!from, NULL, "Next focus node is not a control: " + n->get_name() + "."); } else { return NULL; } @@ -1922,10 +2018,7 @@ Control *Control::find_next_valid_focus() const { break; } - if (next_child) { - - from = next_child; - } else { + if (!next_child) { next_child = _next_control(from); if (!next_child) { //nothing else.. go up and find either window or subwindow @@ -1992,12 +2085,7 @@ Control *Control::find_prev_valid_focus() const { Node *n = get_node(data.focus_prev); if (n) { from = Object::cast_to<Control>(n); - - if (!from) { - - ERR_EXPLAIN("Previous focus node is not a control: " + n->get_name()); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!from, NULL, "Previous focus node is not a control: " + n->get_name() + "."); } else { return NULL; } @@ -2143,7 +2231,6 @@ void Control::_propagate_theme_changed(CanvasItem *p_at, Control *p_owner, bool c->data.theme_owner = p_owner; } c->notification(NOTIFICATION_THEME_CHANGED); - c->update(); } } @@ -2196,6 +2283,7 @@ Ref<Theme> Control::get_theme() const { void Control::set_tooltip(const String &p_tooltip) { data.tooltip = p_tooltip; + update_configuration_warning(); } String Control::get_tooltip(const Point2 &p_pos) const { @@ -2279,12 +2367,7 @@ Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { Node *n = get_node(data.focus_neighbour[p_margin]); if (n) { c = Object::cast_to<Control>(n); - - if (!c) { - - ERR_EXPLAIN("Neighbour focus node is not a control: " + n->get_name()); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!c, NULL, "Neighbor focus node is not a control: " + n->get_name() + "."); } else { return NULL; } @@ -2487,6 +2570,7 @@ void Control::set_mouse_filter(MouseFilter p_filter) { ERR_FAIL_INDEX(p_filter, 3); data.mouse_filter = p_filter; + update_configuration_warning(); } Control::MouseFilter Control::get_mouse_filter() const { @@ -2550,32 +2634,10 @@ float Control::get_rotation_degrees() const { return Math::rad2deg(get_rotation()); } -//needed to update the control if the font changes.. -void Control::_ref_font(Ref<Font> p_sc) { - - if (!data.font_refcount.has(p_sc)) { - data.font_refcount[p_sc] = 1; - p_sc->connect("changed", this, "_font_changed"); - } else { - data.font_refcount[p_sc] += 1; - } -} - -void Control::_unref_font(Ref<Font> p_sc) { - - ERR_FAIL_COND(!data.font_refcount.has(p_sc)); - data.font_refcount[p_sc]--; - if (data.font_refcount[p_sc] == 0) { - p_sc->disconnect("changed", this, "_font_changed"); - data.font_refcount.erase(p_sc); - } -} - -void Control::_font_changed() { +void Control::_override_changed() { - update(); notification(NOTIFICATION_THEME_CHANGED); - minimum_size_changed(); //fonts affect minimum size pretty much almost always + minimum_size_changed(); // overrides are likely to affect minimum size } void Control::set_pivot_offset(const Vector2 &p_pivot) { @@ -2645,6 +2707,12 @@ bool Control::is_visibility_clip_disabled() const { void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { +#ifdef TOOLS_ENABLED + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; +#else + const String quote_style = "\""; +#endif + Node::get_argument_options(p_function, p_idx, r_options); if (p_idx == 0) { @@ -2662,10 +2730,24 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List sn.sort_custom<StringName::AlphCompare>(); for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { - r_options->push_back("\"" + E->get() + "\""); + r_options->push_back(quote_style + E->get() + quote_style); } } } + +String Control::get_configuration_warning() const { + String warning = CanvasItem::get_configuration_warning(); + + if (data.mouse_filter == MOUSE_FILTER_IGNORE && data.tooltip != "") { + if (warning != String()) { + warning += "\n\n"; + } + warning += TTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."); + } + + return warning; +} + void Control::set_clip_contents(bool p_clip) { data.clip_contents = p_clip; @@ -2707,20 +2789,23 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("accept_event"), &Control::accept_event); ClassDB::bind_method(D_METHOD("get_minimum_size"), &Control::get_minimum_size); ClassDB::bind_method(D_METHOD("get_combined_minimum_size"), &Control::get_combined_minimum_size); - ClassDB::bind_method(D_METHOD("set_anchors_preset", "preset", "keep_margin"), &Control::set_anchors_preset, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_anchors_preset", "preset", "keep_margins"), &Control::set_anchors_preset, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_margins_preset", "preset", "resize_mode", "margin"), &Control::set_margins_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_anchors_and_margins_preset", "preset", "resize_mode", "margin"), &Control::set_anchors_and_margins_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_anchor", "margin", "anchor", "keep_margin", "push_opposite_anchor"), &Control::set_anchor, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("_set_anchor", "margin", "anchor"), &Control::_set_anchor); + ClassDB::bind_method(D_METHOD("set_anchor", "margin", "anchor", "keep_margin", "push_opposite_anchor"), &Control::set_anchor, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("get_anchor", "margin"), &Control::get_anchor); ClassDB::bind_method(D_METHOD("set_margin", "margin", "offset"), &Control::set_margin); ClassDB::bind_method(D_METHOD("set_anchor_and_margin", "margin", "anchor", "offset", "push_opposite_anchor"), &Control::set_anchor_and_margin, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_begin", "position"), &Control::set_begin); ClassDB::bind_method(D_METHOD("set_end", "position"), &Control::set_end); - ClassDB::bind_method(D_METHOD("set_position", "position"), &Control::set_position); - ClassDB::bind_method(D_METHOD("set_size", "size"), &Control::set_size); + ClassDB::bind_method(D_METHOD("set_position", "position", "keep_margins"), &Control::set_position, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("_set_position", "margin"), &Control::_set_position); + ClassDB::bind_method(D_METHOD("set_size", "size", "keep_margins"), &Control::set_size, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("_set_size", "size"), &Control::_set_size); ClassDB::bind_method(D_METHOD("set_custom_minimum_size", "size"), &Control::set_custom_minimum_size); - ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Control::set_global_position); + ClassDB::bind_method(D_METHOD("set_global_position", "position", "keep_margins"), &Control::set_global_position, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("_set_global_position", "position"), &Control::_set_global_position); ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Control::set_rotation); ClassDB::bind_method(D_METHOD("set_rotation_degrees", "degrees"), &Control::set_rotation_degrees); ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Control::set_scale); @@ -2735,7 +2820,7 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_scale"), &Control::get_scale); ClassDB::bind_method(D_METHOD("get_pivot_offset"), &Control::get_pivot_offset); ClassDB::bind_method(D_METHOD("get_custom_minimum_size"), &Control::get_custom_minimum_size); - ClassDB::bind_method(D_METHOD("get_parent_area_size"), &Control::get_size); + ClassDB::bind_method(D_METHOD("get_parent_area_size"), &Control::get_parent_area_size); ClassDB::bind_method(D_METHOD("get_global_position"), &Control::get_global_position); ClassDB::bind_method(D_METHOD("get_rect"), &Control::get_rect); ClassDB::bind_method(D_METHOD("get_global_rect"), &Control::get_global_rect); @@ -2829,21 +2914,25 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("_theme_changed"), &Control::_theme_changed); - ClassDB::bind_method(D_METHOD("_font_changed"), &Control::_font_changed); + ClassDB::bind_method(D_METHOD("_override_changed"), &Control::_override_changed); BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size")); - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "get_drag_data", PropertyInfo(Variant::VECTOR2, "position"))); + + MethodInfo get_drag_data = MethodInfo("get_drag_data", PropertyInfo(Variant::VECTOR2, "position")); + get_drag_data.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_VMETHOD(get_drag_data); + BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); BIND_VMETHOD(MethodInfo("drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text"))); BIND_VMETHOD(MethodInfo(Variant::BOOL, "_clips_input")); ADD_GROUP("Anchor", "anchor_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_BOTTOM); ADD_GROUP("Margin", "margin_"); ADD_PROPERTYI(PropertyInfo(Variant::INT, "margin_left", PROPERTY_HINT_RANGE, "-4096,4096"), "set_margin", "get_margin", MARGIN_LEFT); @@ -2856,9 +2945,9 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction"); ADD_GROUP("Rect", "rect_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", 0), "set_global_position", "get_global_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", 0), "_set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_min_size"), "set_custom_minimum_size", "get_custom_minimum_size"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "rect_rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.01"), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_scale"), "set_scale", "get_scale"); diff --git a/scene/gui/control.h b/scene/gui/control.h index 0812457bd2..7305b3ce93 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -38,9 +38,6 @@ #include "scene/main/node.h" #include "scene/main/timer.h" #include "scene/resources/theme.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Viewport; class Label; @@ -139,8 +136,8 @@ private: bool operator()(const Control *p_a, const Control *p_b) const { if (p_a->get_canvas_layer() == p_b->get_canvas_layer()) return p_b->is_greater_than(p_a); - else - return p_a->get_canvas_layer() < p_b->get_canvas_layer(); + + return p_a->get_canvas_layer() < p_b->get_canvas_layer(); } }; @@ -207,7 +204,6 @@ private: HashMap<StringName, Ref<Font> > font_override; HashMap<StringName, Color> color_override; HashMap<StringName, int> constant_override; - Map<Ref<Font>, int> font_refcount; } data; @@ -218,6 +214,9 @@ private: Control *_get_focus_neighbour(Margin p_margin, int p_count = 0); void _set_anchor(Margin p_margin, float p_anchor); + void _set_position(const Point2 &p_point); + void _set_global_position(const Point2 &p_point); + void _set_size(const Size2 &p_size); void _propagate_theme_changed(CanvasItem *p_at, Control *p_owner, bool p_assign = true); void _theme_changed(); @@ -228,15 +227,13 @@ 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 _compute_anchors(Rect2 p_rect, const float p_margins[4], float (&r_anchors)[4]); void _size_changed(); String _get_tooltip() const; - void _ref_font(Ref<Font> p_sc); - void _unref_font(Ref<Font> p_sc); - void _font_changed(); + void _override_changed(); void _update_canvas_item_transform(); @@ -328,7 +325,7 @@ public: /* POSITIONING */ - void set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin = true); + void set_anchors_preset(LayoutPreset p_preset, bool p_keep_margins = true); void set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); void set_anchors_and_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); @@ -346,12 +343,12 @@ public: Point2 get_begin() const; Point2 get_end() const; - void set_position(const Point2 &p_point); - void set_global_position(const Point2 &p_point); + void set_position(const Point2 &p_point, bool p_keep_margins = false); + void set_global_position(const Point2 &p_point, bool p_keep_margins = false); Point2 get_position() const; Point2 get_global_position() const; - void set_size(const Size2 &p_size); + void set_size(const Size2 &p_size, bool p_keep_margins = false); Size2 get_size() const; Rect2 get_rect() const; @@ -486,6 +483,7 @@ public: bool is_visibility_clip_disabled() const; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const; + virtual String get_configuration_warning() const; Control(); ~Control(); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 89f3d509d0..59bbdad97a 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -219,6 +219,14 @@ void WindowDialog::_notification(int p_what) { close_button->set_begin(Point2(-get_constant("close_h_ofs", "WindowDialog"), -get_constant("close_v_ofs", "WindowDialog"))); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + String new_title = tr(title); + if (title != new_title) { + title = new_title; + update(); + } + } break; + case NOTIFICATION_MOUSE_EXIT: { // Reset the mouse cursor when leaving the resizable window border. if (resizable && !drag_type) { @@ -226,11 +234,13 @@ void WindowDialog::_notification(int p_what) { set_default_cursor_shape(CURSOR_ARROW); } } break; + #ifdef TOOLS_ENABLED case NOTIFICATION_POST_POPUP: { if (get_tree() && Engine::get_singleton()->is_editor_hint() && EditorNode::get_singleton()) EditorNode::get_singleton()->dim_editor(true); } break; + case NOTIFICATION_POPUP_HIDE: { if (get_tree() && Engine::get_singleton()->is_editor_hint() && EditorNode::get_singleton()) EditorNode::get_singleton()->dim_editor(false); @@ -272,8 +282,11 @@ int WindowDialog::_drag_hit_test(const Point2 &pos) const { void WindowDialog::set_title(const String &p_title) { - title = tr(p_title); - update(); + String new_title = tr(p_title); + if (title != new_title) { + title = new_title; + update(); + } } String WindowDialog::get_title() const { @@ -296,8 +309,8 @@ Size2 WindowDialog::get_minimum_size() const { const int padding = button_width / 2; const int button_area = button_width + padding; - // as the title gets centered, title_width + close_button_width is not enough. - // we want a width w, such that w / 2 - title_width / 2 >= button_area, i.e. + // As the title gets centered, title_width + close_button_width is not enough. + // We want a width w, such that w / 2 - title_width / 2 >= button_area, i.e. // w >= 2 * button_area + title_width return Size2(2 * button_area + title_width, 1); @@ -324,7 +337,6 @@ void WindowDialog::_bind_methods() { WindowDialog::WindowDialog() { - //title="Hello!"; drag_type = DRAG_NONE; resizable = false; close_button = memnew(TextureButton); @@ -340,7 +352,6 @@ WindowDialog::~WindowDialog() { void PopupDialog::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { - RID ci = get_canvas_item(); get_stylebox("panel", "PopupMenu")->draw(ci, Rect2(Point2(), get_size())); } @@ -362,15 +373,15 @@ void AcceptDialog::_post_popup() { void AcceptDialog::_notification(int p_what) { - if (p_what == NOTIFICATION_MODAL_CLOSE) { - - cancel_pressed(); - } else if (p_what == NOTIFICATION_READY) { - - _update_child_rects(); - } else if (p_what == NOTIFICATION_RESIZED) { + switch (p_what) { + case NOTIFICATION_MODAL_CLOSE: { + cancel_pressed(); + } break; - _update_child_rects(); + case NOTIFICATION_READY: + case NOTIFICATION_RESIZED: { + _update_child_rects(); + } break; } } @@ -406,12 +417,20 @@ void AcceptDialog::set_hide_on_ok(bool p_hide) { hide_on_ok = p_hide; } - bool AcceptDialog::get_hide_on_ok() const { return hide_on_ok; } +void AcceptDialog::set_autowrap(bool p_autowrap) { + + label->set_autowrap(p_autowrap); +} +bool AcceptDialog::has_autowrap() { + + return label->has_autowrap(); +} + void AcceptDialog::register_text_enter(Node *p_line_edit) { ERR_FAIL_NULL(p_line_edit); @@ -530,6 +549,8 @@ void AcceptDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_custom_action"), &AcceptDialog::_custom_action); ClassDB::bind_method(D_METHOD("set_text", "text"), &AcceptDialog::set_text); ClassDB::bind_method(D_METHOD("get_text"), &AcceptDialog::get_text); + ClassDB::bind_method(D_METHOD("set_autowrap", "autowrap"), &AcceptDialog::set_autowrap); + ClassDB::bind_method(D_METHOD("has_autowrap"), &AcceptDialog::has_autowrap); ADD_SIGNAL(MethodInfo("confirmed")); ADD_SIGNAL(MethodInfo("custom_action", PropertyInfo(Variant::STRING, "action"))); @@ -537,6 +558,7 @@ void AcceptDialog::_bind_methods() { ADD_GROUP("Dialog", "dialog"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "dialog_text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_hide_on_ok"), "set_hide_on_ok", "get_hide_on_ok"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_autowrap"), "set_autowrap", "has_autowrap"); } bool AcceptDialog::swap_ok_cancel = false; @@ -555,7 +577,6 @@ AcceptDialog::AcceptDialog() { label->set_anchor(MARGIN_BOTTOM, ANCHOR_END); label->set_begin(Point2(margin, margin)); label->set_end(Point2(-margin, -button_margin - 10)); - //label->set_autowrap(true); add_child(label); hbc = memnew(HBoxContainer); diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 4b89ac54c5..1a0350ba18 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -37,9 +37,6 @@ #include "scene/gui/panel.h" #include "scene/gui/popup.h" #include "scene/gui/texture_button.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class WindowDialog : public Popup { @@ -145,6 +142,9 @@ public: void set_text(String p_text); String get_text() const; + void set_autowrap(bool p_autowrap); + bool has_autowrap(); + AcceptDialog(); ~AcceptDialog(); }; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 5599c64daa..9bc593ea3b 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -48,8 +48,9 @@ void FileDialog::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - refresh->set_icon(get_icon("reload")); dir_up->set_icon(get_icon("parent_folder")); + refresh->set_icon(get_icon("reload")); + show_hidden->set_icon(get_icon("toggle_hidden")); } if (p_what == NOTIFICATION_POPUP_HIDE) { @@ -86,7 +87,9 @@ void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { _dir_entered(".."); } break; - default: { handled = false; } + default: { + handled = false; + } } if (handled) @@ -299,11 +302,8 @@ bool FileDialog::_is_open_should_be_disabled() { Dictionary d = ti->get_metadata(0); // Opening a file, but selected a folder? Forbidden. - if (((mode == MODE_OPEN_FILE || mode == MODE_OPEN_FILES) && d["dir"]) || // Flipped case, also forbidden. - (mode == MODE_OPEN_DIR && !d["dir"])) - return true; - - return false; + return ((mode == MODE_OPEN_FILE || mode == MODE_OPEN_FILES) && d["dir"]) || // Flipped case, also forbidden. + (mode == MODE_OPEN_DIR && !d["dir"]); } void FileDialog::_go_up() { @@ -381,6 +381,18 @@ void FileDialog::_tree_item_activated() { } } +void FileDialog::update_file_name() { + int idx = filter->get_selected() - 1; + if ((idx == -1 && filter->get_item_count() == 2) || (filter->get_item_count() > 2 && idx >= 0 && idx < filter->get_item_count() - 2)) { + if (idx == -1) idx += 1; + String filter_str = filters[idx]; + String file_str = file->get_text(); + String base_name = file_str.get_basename(); + file_str = base_name + "." + filter_str.strip_edges().to_lower(); + file->set_text(file_str); + } +} + void FileDialog::update_file_list() { tree->clear(); @@ -388,23 +400,22 @@ void FileDialog::update_file_list() { TreeItem *root = tree->create_item(); Ref<Texture> folder = get_icon("folder"); + const Color folder_color = get_color("folder_icon_modulate"); List<String> files; List<String> dirs; - bool isdir; - bool ishidden; - bool show_hidden = show_hidden_files; + bool is_hidden; String item; - while ((item = dir_access->get_next(&isdir)) != "") { + while ((item = dir_access->get_next()) != "") { if (item == "." || item == "..") continue; - ishidden = dir_access->current_is_hidden(); + is_hidden = dir_access->current_is_hidden(); - if (show_hidden || !ishidden) { - if (!isdir) + if (show_hidden_files || !is_hidden) { + if (!dir_access->current_is_dir()) files.push_back(item); else dirs.push_back(item); @@ -419,6 +430,7 @@ void FileDialog::update_file_list() { TreeItem *ti = tree->create_item(root); ti->set_text(0, dir_name); ti->set_icon(0, folder); + ti->set_icon_modulate(0, folder_color); Dictionary d; d["name"] = dir_name; @@ -429,8 +441,6 @@ void FileDialog::update_file_list() { dirs.pop_front(); } - dirs.clear(); - List<String> patterns; // build filter if (filter->get_selected() == filter->get_item_count() - 1) { @@ -505,12 +515,11 @@ void FileDialog::update_file_list() { if (tree->get_root() && tree->get_root()->get_children() && tree->get_selected() == NULL) tree->get_root()->get_children()->select(0); - - files.clear(); } void FileDialog::_filter_selected(int) { + update_file_name(); update_file_list(); } @@ -802,6 +811,7 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_select_drive"), &FileDialog::_select_drive); ClassDB::bind_method(D_METHOD("_make_dir"), &FileDialog::_make_dir); ClassDB::bind_method(D_METHOD("_make_dir_confirm"), &FileDialog::_make_dir_confirm); + ClassDB::bind_method(D_METHOD("_update_file_name"), &FileDialog::update_file_name); ClassDB::bind_method(D_METHOD("_update_file_list"), &FileDialog::update_file_list); ClassDB::bind_method(D_METHOD("_update_dir"), &FileDialog::update_dir); ClassDB::bind_method(D_METHOD("_go_up"), &FileDialog::_go_up); @@ -861,7 +871,7 @@ FileDialog::FileDialog() { HBoxContainer *hbc = memnew(HBoxContainer); dir_up = memnew(ToolButton); - dir_up->set_tooltip(RTR("Go to parent folder")); + dir_up->set_tooltip(RTR("Go to parent folder.")); hbc->add_child(dir_up); dir_up->connect("pressed", this, "_go_up"); @@ -871,9 +881,17 @@ FileDialog::FileDialog() { dir->set_h_size_flags(SIZE_EXPAND_FILL); refresh = memnew(ToolButton); + refresh->set_tooltip(RTR("Refresh files.")); refresh->connect("pressed", this, "_update_file_list"); hbc->add_child(refresh); + show_hidden = memnew(ToolButton); + show_hidden->set_toggle_mode(true); + show_hidden->set_pressed(is_showing_hidden_files()); + show_hidden->set_tooltip(RTR("Toggle the visibility of hidden files.")); + show_hidden->connect("toggled", this, "set_show_hidden_files"); + hbc->add_child(show_hidden); + drives = memnew(OptionButton); hbc->add_child(drives); drives->connect("item_selected", this, "_select_drive"); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 85edac0b12..4fd6d0d13c 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -38,9 +38,7 @@ #include "scene/gui/option_button.h" #include "scene/gui/tool_button.h" #include "scene/gui/tree.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class FileDialog : public ConfirmationDialog { GDCLASS(FileDialog, ConfirmationDialog); @@ -90,6 +88,7 @@ private: ToolButton *dir_up; ToolButton *refresh; + ToolButton *show_hidden; Vector<String> filters; @@ -101,6 +100,7 @@ private: bool invalidated; void update_dir(); + void update_file_name(); void update_file_list(); void update_filters(); diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index cfbc9d6d18..75f5f79873 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -29,10 +29,17 @@ /*************************************************************************/ #include "gradient_edit.h" + #include "core/os/keyboard.h" -#include "editor/editor_scale.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_scale.h" #define SPACING (3 * EDSCALE) +#define POINT_WIDTH (8 * EDSCALE) +#else +#define SPACING 3 +#define POINT_WIDTH 8 +#endif GradientEdit::GradientEdit() { grabbed = -1; diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index fd340b3f6c..6f31107729 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -33,10 +33,8 @@ #include "scene/gui/color_picker.h" #include "scene/gui/popup.h" -#include "scene/resources/color_ramp.h" #include "scene/resources/default_theme/theme_data.h" - -#define POINT_WIDTH (8 * EDSCALE) +#include "scene/resources/gradient.h" class GradientEdit : public Control { diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 94c65b3e64..fdffb26cb5 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -221,8 +221,8 @@ void GraphEdit::_graph_node_raised(Node *p_gn) { } int first_not_comment = 0; for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (gn && !gn->is_comment()) { + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (gn2 && !gn2->is_comment()) { first_not_comment = i; break; } @@ -261,8 +261,9 @@ void GraphEdit::add_child_notify(Node *p_child) { void GraphEdit::remove_child_notify(Node *p_child) { Control::remove_child_notify(p_child); - - top_layer->call_deferred("raise"); //top layer always on top! + if (is_inside_tree()) { + top_layer->call_deferred("raise"); //top layer always on top! + } GraphNode *gn = Object::cast_to<GraphNode>(p_child); if (gn) { gn->disconnect("offset_changed", this, "_graph_node_moved"); @@ -478,7 +479,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = gn->get_connection_input_color(j); connecting_target = false; connecting_to = pos; - just_disconnected = true; + just_disconnected = false; return; } @@ -549,11 +550,18 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { emit_signal("connection_request", from, from_slot, to, to_slot); } else if (!just_disconnected) { + String from = connecting_from; int from_slot = connecting_index; Vector2 ofs = Vector2(mb->get_position().x, mb->get_position().y); - emit_signal("connection_to_empty", from, from_slot, ofs); + + if (!connecting_out) { + emit_signal("connection_from_empty", from, from_slot, ofs); + } else { + emit_signal("connection_to_empty", from, from_slot, ofs); + } } + connecting = false; top_layer->update(); update(); @@ -768,8 +776,16 @@ void GraphEdit::_top_layer_draw() { _draw_cos_line(top_layer, pos, topos, col, col); } - if (box_selecting) - top_layer->draw_rect(box_selecting_rect, Color(0.7, 0.7, 1.0, 0.3)); + if (box_selecting) { + top_layer->draw_rect( + box_selecting_rect, + get_color("box_selection_fill_color", "Editor")); + + top_layer->draw_rect( + box_selecting_rect, + get_color("box_selection_stroke_color", "Editor"), + false); + } } void GraphEdit::set_selected(Node *p_child) { @@ -958,33 +974,33 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { previus_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn || !gn->is_selected()) + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (!gn2 || !gn2->is_selected()) continue; - previus_selected.push_back(gn); + previus_selected.push_back(gn2); } } else if (b->get_shift()) { box_selection_mode_aditive = false; previus_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn || !gn->is_selected()) + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (!gn2 || !gn2->is_selected()) continue; - previus_selected.push_back(gn); + previus_selected.push_back(gn2); } } else { box_selection_mode_aditive = true; previus_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (!gn2) continue; - gn->set_selected(false); + gn2->set_selected(false); } } } @@ -1020,14 +1036,28 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } Ref<InputEventKey> k = p_ev; - if (k.is_valid() && k->get_scancode() == KEY_D && k->is_pressed() && k->get_command()) { - emit_signal("duplicate_nodes_request"); - accept_event(); - } - if (k.is_valid() && k->get_scancode() == KEY_DELETE && k->is_pressed()) { - emit_signal("delete_nodes_request"); - accept_event(); + if (k.is_valid()) { + + if (k->get_scancode() == KEY_D && k->is_pressed() && k->get_command()) { + emit_signal("duplicate_nodes_request"); + accept_event(); + } + + if (k->get_scancode() == KEY_C && k->is_pressed() && k->get_command()) { + emit_signal("copy_nodes_request"); + accept_event(); + } + + if (k->get_scancode() == KEY_V && k->is_pressed() && k->get_command()) { + emit_signal("paste_nodes_request"); + accept_event(); + } + + if (k->get_scancode() == KEY_DELETE && k->is_pressed()) { + emit_signal("delete_nodes_request"); + accept_event(); + } } Ref<InputEventMagnifyGesture> magnify_gesture = p_ev; @@ -1050,7 +1080,7 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por 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) < CMP_EPSILON) { + if (Math::is_equal_approx(E->get().activity, p_activity)) { //update only if changed top_layer->update(); connections_layer->update(); @@ -1287,8 +1317,11 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position"))); ADD_SIGNAL(MethodInfo("duplicate_nodes_request")); + ADD_SIGNAL(MethodInfo("copy_nodes_request")); + ADD_SIGNAL(MethodInfo("paste_nodes_request")); ADD_SIGNAL(MethodInfo("node_selected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::VECTOR2, "release_position"))); + ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot"), PropertyInfo(Variant::VECTOR2, "release_position"))); ADD_SIGNAL(MethodInfo("delete_nodes_request")); ADD_SIGNAL(MethodInfo("_begin_node_move")); ADD_SIGNAL(MethodInfo("_end_node_move")); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 8c588109d6..f7bef4ed39 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -159,12 +159,9 @@ void GraphNode::_resort() { fit_child_in_rect(c, r); cache_y.push_back(vofs + size.y * 0.5); - if (vofs > 0) - vofs += sep; - vofs += size.y; + vofs += size.y + sep; } - _change_notify(); update(); connpos_dirty = true; } @@ -409,9 +406,12 @@ Size2 GraphNode::get_minimum_size() const { void GraphNode::set_title(const String &p_title) { + if (title == p_title) + return; title = p_title; - minimum_size_changed(); update(); + _change_notify("title"); + minimum_size_changed(); } String GraphNode::get_title() const { @@ -592,13 +592,14 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid()) { - ERR_EXPLAIN("GraphNode must be the child of a GraphEdit node."); - ERR_FAIL_COND(get_parent_control() == NULL); + ERR_FAIL_COND_MSG(get_parent_control() == NULL, "GraphNode must be the child of a GraphEdit node."); if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { Vector2 mpos = Vector2(mb->get_position().x, mb->get_position().y); if (close_rect.size != Size2() && close_rect.has_point(mpos)) { + //send focus to parent + get_parent_control()->grab_focus(); emit_signal("close_request"); accept_event(); return; @@ -615,9 +616,7 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { return; } - //send focus to parent emit_signal("raise_request"); - get_parent_control()->grab_focus(); } if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index efe452305b..a3bc68ffcd 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -470,6 +470,8 @@ Size2 ItemList::Item::get_icon_size() const { void ItemList::_gui_input(const Ref<InputEvent> &p_event) { + double prev_scroll = scroll_bar->get_value(); + Ref<InputEventMouseMotion> mm = p_event; if (defer_select_single >= 0 && mm.is_valid()) { defer_select_single = -1; @@ -747,9 +749,21 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { search_string = ""; } - search_string += String::chr(k->get_unicode()); - for (int i = 0; i < items.size(); i++) { - if (items[i].text.begins_with(search_string)) { + if (String::chr(k->get_unicode()) != search_string) + search_string += String::chr(k->get_unicode()); + + for (int i = current + 1; i <= items.size(); i++) { + if (i == items.size()) { + if (current == 0 || current == -1) + break; + else + i = 0; + } + + if (i == current) + break; + + if (items[i].text.findn(search_string) == 0) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -767,6 +781,9 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * pan_gesture->get_delta().y / 8); } + + if (scroll_bar->get_value() != prev_scroll) + accept_event(); //accept event if scroll changed } void ItemList::ensure_current_is_visible() { @@ -1094,7 +1111,7 @@ void ItemList::_notification(int p_what) { if (items[i].disabled) modulate.a *= 0.5; - // If the icon is transposed, we have to swith the size so that it is drawn correctly + // If the icon is transposed, we have to switch the size so that it is drawn correctly if (items[i].icon_transposed) { Size2 size_tmp = draw_rect.size; draw_rect.size.x = size_tmp.y; @@ -1114,13 +1131,13 @@ void ItemList::_notification(int p_what) { int max_len = -1; - Vector2 size = font->get_string_size(items[i].text); + Vector2 size2 = font->get_string_size(items[i].text); if (fixed_column_width) max_len = fixed_column_width; else if (same_column_width) max_len = items[i].rect_cache.size.x; else - max_len = size.x; + max_len = size2.x; Color modulate = items[i].selected ? font_color_selected : (items[i].custom_fg != Color() ? items[i].custom_fg : font_color); if (items[i].disabled) @@ -1170,12 +1187,12 @@ void ItemList::_notification(int p_what) { } else { if (fixed_column_width > 0) - size.x = MIN(size.x, fixed_column_width); + size2.x = MIN(size2.x, fixed_column_width); if (icon_mode == ICON_MODE_TOP) { - text_ofs.x += (items[i].rect_cache.size.width - size.x) / 2; + text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2; } else { - text_ofs.y += (items[i].rect_cache.size.height - size.y) / 2; + text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2; } text_ofs.y += font->get_ascent(); @@ -1243,7 +1260,7 @@ int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { Rect2 rc = items[i].rect_cache; if (i % current_columns == current_columns - 1) { - rc.size.width = get_size().width; //not right but works + rc.size.width = get_size().width - rc.position.x; //make sure you can still select the last item when clicking past the column } if (rc.has_point(pos)) { diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index e3e9368a12..510f1b18ad 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -247,11 +247,11 @@ void Label::_notification(int p_what) { n = String::char_uppercase(n); } - float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow, false); + float move = drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow); 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, 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); + drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow); + drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow); + drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow); } x_ofs_shadow += move; chars_total_shadow++; @@ -470,7 +470,6 @@ void Label::regenerate_word_cache() { wc->word_len = i - word_pos; wc->space_count = space_count; current_word_size = char_width; - space_count = 0; word_pos = i; } } diff --git a/scene/gui/label.h b/scene/gui/label.h index 561c42ef9e..2cc55a47ef 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -32,9 +32,7 @@ #define LABEL_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Label : public Control { GDCLASS(Label, Control); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 4a2bb3dad1..4a763844f8 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "line_edit.h" + #include "core/message_queue.h" #include "core/os/keyboard.h" #include "core/os/os.h" @@ -43,7 +44,7 @@ static bool _is_text_char(CharType c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; + return !is_symbol(c); } void LineEdit::_gui_input(Ref<InputEvent> p_event) { @@ -55,8 +56,10 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (b->is_pressed() && b->get_button_index() == BUTTON_RIGHT && context_menu_enabled) { menu->set_position(get_global_transform().xform(get_local_mouse_position())); menu->set_size(Vector2(1, 1)); + menu->set_scale(get_global_transform().get_scale()); menu->popup(); grab_focus(); + accept_event(); return; } @@ -66,6 +69,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { _reset_caret_blink_timer(); if (b->is_pressed()) { + accept_event(); //don't pass event further when clicked on text field if (!text.empty() && is_editable() && _is_over_clear_button(b->get_position())) { clear_button_status.press_attempt = true; clear_button_status.pressing_inside = true; @@ -157,6 +161,38 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (!k->is_pressed()) return; + +#ifdef APPLE_STYLE_KEYS + if (k->get_control() && !k->get_shift() && !k->get_alt() && !k->get_command()) { + uint32_t remap_key = KEY_UNKNOWN; + switch (k->get_scancode()) { + case KEY_F: { + remap_key = KEY_RIGHT; + } break; + case KEY_B: { + remap_key = KEY_LEFT; + } break; + case KEY_P: { + remap_key = KEY_UP; + } break; + case KEY_N: { + remap_key = KEY_DOWN; + } break; + case KEY_D: { + remap_key = KEY_DELETE; + } break; + case KEY_H: { + remap_key = KEY_BACKSPACE; + } break; + } + + if (remap_key != KEY_UNKNOWN) { + k->set_scancode(remap_key); + k->set_control(false); + } + } +#endif + unsigned int code = k->get_scancode(); if (k->get_command()) { @@ -320,7 +356,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_left + FALLTHROUGH; } case KEY_LEFT: { @@ -367,7 +403,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_right + FALLTHROUGH; } case KEY_RIGHT: { @@ -474,7 +510,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_home + FALLTHROUGH; } case KEY_HOME: { @@ -487,7 +523,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_end + FALLTHROUGH; } case KEY_END: { @@ -504,7 +540,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (handled) { accept_event(); - } else if (!k->get_alt() && !k->get_command()) { + } else if (!k->get_command()) { if (k->get_unicode() >= 32 && k->get_scancode() != KEY_DELETE) { if (editable) { @@ -588,10 +624,7 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { } Ref<Texture> icon = Control::get_icon("clear"); int x_ofs = get_stylebox("normal")->get_offset().x; - if (p_pos.x > get_size().width - icon->get_width() - x_ofs) { - return true; - } - return false; + return p_pos.x > get_size().width - icon->get_width() - x_ofs; } void LineEdit::_notification(int p_what) { @@ -611,12 +644,15 @@ 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 - } + window_pos = 0; set_cursor_position(get_cursor_position()); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + placeholder_translated = tr(placeholder); + update_placeholder_width(); + update(); + } break; case MainLoop::NOTIFICATION_WM_FOCUS_IN: { window_has_focus = true; draw_caret = true; @@ -642,10 +678,8 @@ void LineEdit::_notification(int p_what) { RID ci = get_canvas_item(); Ref<StyleBox> style = get_stylebox("normal"); - float disabled_alpha = 1.0; // used to set the disabled input text color if (!is_editable()) { style = get_stylebox("read_only"); - disabled_alpha = .5; draw_caret = false; } @@ -691,20 +725,19 @@ void LineEdit::_notification(int p_what) { int font_ascent = font->get_ascent(); Color selection_color = get_color("selection_color"); - Color font_color = get_color("font_color"); + Color font_color = is_editable() ? get_color("font_color") : get_color("font_color_uneditable"); Color font_color_selected = get_color("font_color_selected"); Color cursor_color = get_color("cursor_color"); - const String &t = using_placeholder ? placeholder : text; + const String &t = using_placeholder ? placeholder_translated : text; // draw placeholder color if (using_placeholder) font_color.a *= placeholder_alpha; - font_color.a *= disabled_alpha; bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { Ref<Texture> r_icon = display_clear_icon ? Control::get_icon("clear") : right_icon; - Color color_icon(1, 1, 1, disabled_alpha * .9); + Color color_icon(1, 1, 1, !is_editable() ? .5 * .9 : .9); if (display_clear_icon) { if (clear_button_status.press_attempt && clear_button_status.pressing_inside) { color_icon = get_color("clear_button_color_pressed"); @@ -721,6 +754,8 @@ void LineEdit::_notification(int p_what) { } else { x_ofs = MAX(style->get_margin(MARGIN_LEFT), x_ofs - r_icon->get_width() - style->get_margin(MARGIN_RIGHT)); } + + ofs_max -= r_icon->get_width(); } int caret_height = font->get_height() > y_area ? y_area : font->get_height(); @@ -888,7 +923,8 @@ void LineEdit::cut_text() { void LineEdit::paste_text() { - String paste_buffer = OS::get_singleton()->get_clipboard(); + // Strip escape characters like \n and \t as they can't be displayed on LineEdit. + String paste_buffer = OS::get_singleton()->get_clipboard().strip_escapes(); if (paste_buffer != "") { @@ -917,6 +953,10 @@ void LineEdit::undo() { TextOperation op = undo_stack_pos->get(); text = op.text; set_cursor_position(op.cursor_pos); + + if (expand_to_text_length) + minimum_size_changed(); + _emit_text_change(); } @@ -931,6 +971,10 @@ void LineEdit::redo() { TextOperation op = undo_stack_pos->get(); text = op.text; set_cursor_position(op.cursor_pos); + + if (expand_to_text_length) + minimum_size_changed(); + _emit_text_change(); } @@ -956,6 +1000,8 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) { Ref<StyleBox> style = get_stylebox("normal"); int pixel_ofs = 0; Size2 size = get_size(); + bool display_clear_icon = !text.empty() && is_editable() && clear_button_enabled; + int r_icon_width = Control::get_icon("clear")->get_width(); switch (align) { @@ -970,10 +1016,16 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) { pixel_ofs = int(style->get_offset().x); else pixel_ofs = int(size.width - (cached_width)) / 2; + + if (display_clear_icon) + pixel_ofs -= int(r_icon_width / 2 + style->get_margin(MARGIN_RIGHT)); } break; case ALIGN_RIGHT: { pixel_ofs = int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_width)); + + if (display_clear_icon) + pixel_ofs -= int(r_icon_width + style->get_margin(MARGIN_RIGHT)); } break; } @@ -1104,16 +1156,9 @@ 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; - } - } - } + placeholder = p_text; + placeholder_translated = tr(placeholder); + update_placeholder_width(); update(); } @@ -1154,14 +1199,14 @@ void LineEdit::set_cursor_position(int p_pos) { if (cursor_pos <= window_pos) { /* Adjust window if cursor goes too much to the left */ - if (window_pos > 0) - set_window_pos(window_pos - 1); - - } else if (cursor_pos > window_pos) { + set_window_pos(MAX(0, cursor_pos - 1)); + } else { /* Adjust window if cursor goes too much to the right */ int window_width = get_size().width - style->get_minimum_size().width; - if (right_icon.is_valid()) { - window_width -= right_icon->get_width(); + bool display_clear_icon = !text.empty() && is_editable() && clear_button_enabled; + if (right_icon.is_valid() || display_clear_icon) { + Ref<Texture> r_icon = display_clear_icon ? Control::get_icon("clear") : right_icon; + window_width -= r_icon->get_width(); } if (window_width < 0) @@ -1225,6 +1270,7 @@ void LineEdit::append_at_cursor(String p_text) { void LineEdit::clear_internal() { + deselect(); _clear_undo_stack(); cached_width = 0; cursor_pos = 0; @@ -1315,7 +1361,37 @@ void LineEdit::select_all() { void LineEdit::set_editable(bool p_editable) { + if (editable == p_editable) + return; + editable = p_editable; + + // Reorganize context menu. + menu->clear(); + + if (editable) { + 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); + } + + if (editable) { + menu->add_separator(); + menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + } + + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); + + if (editable) { + menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + } + + menu->add_separator(); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + + if (editable) { + menu->add_item(RTR("Clear"), MENU_CLEAR); + } + update(); } @@ -1339,8 +1415,7 @@ 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); + ERR_FAIL_COND_MSG(p_string.length() != 1, "Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given)."); secret_character = p_string; update(); @@ -1481,6 +1556,18 @@ void LineEdit::_emit_text_change() { text_changed_dirty = false; } +void LineEdit::update_placeholder_width() { + if ((max_length <= 0) || (placeholder_translated.length() <= max_length)) { + Ref<Font> font = get_font("font"); + cached_placeholder_width = 0; + if (font != NULL) { + for (int i = 0; i < placeholder_translated.length(); i++) { + cached_placeholder_width += font->get_char_size(placeholder_translated[i]).width; + } + } + } +} + void LineEdit::_clear_redo() { _create_undo_state(); if (undo_stack_pos == NULL) { @@ -1611,7 +1698,6 @@ LineEdit::LineEdit() { deselect(); set_focus_mode(FOCUS_ALL); - editable = true; set_default_cursor_shape(CURSOR_IBEAM); set_mouse_filter(MOUSE_FILTER_STOP); @@ -1626,15 +1712,8 @@ LineEdit::LineEdit() { context_menu_enabled = true; menu = memnew(PopupMenu); add_child(menu); - 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(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); - menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - 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); + editable = false; // initialise to opposite first, so we get past the early-out in set_editable + set_editable(true); 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 3b29780dc0..1d33f7d4ce 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -34,9 +34,6 @@ #include "scene/gui/control.h" #include "scene/gui/popup_menu.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class LineEdit : public Control { GDCLASS(LineEdit, Control); @@ -72,6 +69,7 @@ private: String undo_text; String text; String placeholder; + String placeholder_translated; String secret_character; float placeholder_alpha; String ime_text; @@ -126,6 +124,8 @@ private: void _emit_text_change(); bool expand_to_text_length; + void update_placeholder_width(); + bool caret_blink_enabled; bool draw_caret; bool window_has_focus; diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index ffe248ac5e..17c4bca67b 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -32,7 +32,7 @@ #define LINKBUTTON_H #include "scene/gui/base_button.h" -#include "scene/resources/bit_mask.h" +#include "scene/resources/bit_map.h" class LinkButton : public BaseButton { diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index b67d8c00d6..e12cd55e6f 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -55,8 +55,9 @@ void MenuButton::pressed() { Size2 size = get_size(); Point2 gp = get_global_position(); - popup->set_global_position(gp + Size2(0, size.height)); + popup->set_global_position(gp + Size2(0, size.height * get_global_transform().get_scale().y)); popup->set_size(Size2(size.width, 0)); + popup->set_scale(get_global_transform().get_scale()); popup->set_parent_rect(Rect2(Point2(gp - popup->get_global_position()), get_size())); popup->popup(); } @@ -91,6 +92,16 @@ bool MenuButton::is_switch_on_hover() { return switch_on_hover; } +void MenuButton::_notification(int p_what) { + + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + + if (!is_visible_in_tree()) { + popup->hide(); + } + } +} + void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); @@ -116,15 +127,18 @@ MenuButton::MenuButton() { switch_on_hover = false; set_flat(true); + set_toggle_mode(true); set_disable_shortcuts(false); set_enabled_focus_mode(FOCUS_NONE); + set_process_unhandled_key_input(true); + set_action_mode(ACTION_MODE_BUTTON_PRESS); + popup = memnew(PopupMenu); popup->hide(); add_child(popup); - popup->set_as_toplevel(true); popup->set_pass_on_modal_close_click(false); - set_process_unhandled_key_input(true); - set_action_mode(ACTION_MODE_BUTTON_PRESS); + popup->connect("about_to_show", this, "set_pressed", varray(true)); // For when switching from another MenuButton. + popup->connect("popup_hide", this, "set_pressed", varray(false)); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index 794840035e..5448ff13f2 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -33,9 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class MenuButton : public Button { GDCLASS(MenuButton, Button); @@ -52,6 +50,7 @@ class MenuButton : public Button { void _gui_input(Ref<InputEvent> p_event); protected: + void _notification(int p_what); static void _bind_methods(); public: diff --git a/scene/gui/nine_patch_rect.h b/scene/gui/nine_patch_rect.h index ac17e52fc1..f31a09a482 100644 --- a/scene/gui/nine_patch_rect.h +++ b/scene/gui/nine_patch_rect.h @@ -32,9 +32,7 @@ #define NINE_PATCH_RECT_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class NinePatchRect : public Control { GDCLASS(NinePatchRect, Control); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 98f427cc07..d1840e43a3 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -43,35 +43,42 @@ Size2 OptionButton::get_minimum_size() const { void OptionButton::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - - if (!has_icon("arrow")) - return; - - RID ci = get_canvas_item(); - Ref<Texture> arrow = Control::get_icon("arrow"); - Ref<StyleBox> normal = get_stylebox("normal"); - Color clr = Color(1, 1, 1); - if (get_constant("modulate_arrow")) { - switch (get_draw_mode()) { - case DRAW_PRESSED: - clr = get_color("font_color_pressed"); - break; - case DRAW_HOVER: - clr = get_color("font_color_hover"); - break; - case DRAW_DISABLED: - clr = get_color("font_color_disabled"); - break; - default: - clr = get_color("font_color"); + switch (p_what) { + case NOTIFICATION_DRAW: { + + if (!has_icon("arrow")) + return; + + RID ci = get_canvas_item(); + Ref<Texture> arrow = Control::get_icon("arrow"); + Color clr = Color(1, 1, 1); + if (get_constant("modulate_arrow")) { + switch (get_draw_mode()) { + case DRAW_PRESSED: + clr = get_color("font_color_pressed"); + break; + case DRAW_HOVER: + clr = get_color("font_color_hover"); + break; + case DRAW_DISABLED: + clr = get_color("font_color_disabled"); + break; + default: + clr = get_color("font_color"); + } } - } - Size2 size = get_size(); + Size2 size = get_size(); - Point2 ofs(size.width - arrow->get_width() - get_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2))); - arrow->draw(ci, ofs, clr); + Point2 ofs(size.width - arrow->get_width() - get_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2))); + arrow->draw(ci, ofs, clr); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + + if (!is_visible_in_tree()) { + popup->hide(); + } + } break; } } @@ -81,44 +88,27 @@ void OptionButton::_focused(int p_which) { void OptionButton::_selected(int p_which) { - int selid = -1; - for (int i = 0; i < popup->get_item_count(); i++) { - - bool is_clicked = popup->get_item_id(i) == p_which; - if (is_clicked) { - selid = i; - break; - } - } - - if (selid == -1 && p_which >= 0 && p_which < popup->get_item_count()) { - _select(p_which, true); - } else { - - ERR_FAIL_COND(selid == -1); - - _select(selid, true); - } + _select(p_which, true); } void OptionButton::pressed() { Size2 size = get_size(); - popup->set_global_position(get_global_position() + Size2(0, size.height)); + popup->set_global_position(get_global_position() + Size2(0, size.height * get_global_transform().get_scale().y)); popup->set_size(Size2(size.width, 0)); - + popup->set_scale(get_global_transform().get_scale()); popup->popup(); } -void OptionButton::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID) { +void OptionButton::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id) { - popup->add_icon_radio_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) { +void OptionButton::add_item(const String &p_label, int p_id) { - popup->add_radio_check_item(p_label, p_ID); + popup->add_radio_check_item(p_label, p_id); if (popup->get_item_count() == 1) select(0); } @@ -131,9 +121,9 @@ void OptionButton::set_item_icon(int p_idx, const Ref<Texture> &p_icon) { popup->set_item_icon(p_idx, p_icon); } -void OptionButton::set_item_id(int p_idx, int p_ID) { +void OptionButton::set_item_id(int p_idx, int p_id) { - popup->set_item_id(p_idx, p_ID); + popup->set_item_id(p_idx, p_id); } void OptionButton::set_item_metadata(int p_idx, const Variant &p_metadata) { @@ -160,6 +150,12 @@ int OptionButton::get_item_id(int p_idx) const { return popup->get_item_id(p_idx); } + +int OptionButton::get_item_index(int p_id) const { + + return popup->get_item_index(p_id); +} + Variant OptionButton::get_item_metadata(int p_idx) const { return popup->get_item_metadata(p_idx); @@ -288,7 +284,7 @@ void OptionButton::_set_items(const Array &p_items) { void OptionButton::get_translatable_strings(List<String> *p_strings) const { - return popup->get_translatable_strings(p_strings); + popup->get_translatable_strings(p_strings); } void OptionButton::_bind_methods() { @@ -306,6 +302,7 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &OptionButton::get_item_text); ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &OptionButton::get_item_icon); ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &OptionButton::get_item_id); + ClassDB::bind_method(D_METHOD("get_item_index", "id"), &OptionButton::get_item_index); ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata); ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled); ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count); @@ -326,23 +323,26 @@ void OptionButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); // "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"))); + ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "id"))); } OptionButton::OptionButton() { current = -1; + set_toggle_mode(true); set_text_align(ALIGN_LEFT); set_action_mode(ACTION_MODE_BUTTON_PRESS); popup = memnew(PopupMenu); popup->hide(); add_child(popup); - popup->set_as_toplevel(true); popup->set_pass_on_modal_close_click(false); - popup->connect("id_pressed", this, "_selected"); + popup->set_notify_transform(true); + popup->set_allow_search(true); + popup->connect("index_pressed", this, "_selected"); popup->connect("id_focused", this, "_focused"); + popup->connect("popup_hide", this, "set_pressed", varray(false)); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 121b4c002d..7210708042 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -33,9 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class OptionButton : public Button { GDCLASS(OptionButton, Button); @@ -59,18 +57,19 @@ protected: static void _bind_methods(); public: - void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1); - void add_item(const String &p_label, int p_ID = -1); + void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1); + void add_item(const String &p_label, int p_id = -1); void set_item_text(int p_idx, const String &p_text); void set_item_icon(int p_idx, const Ref<Texture> &p_icon); - void set_item_id(int p_idx, int p_ID); + void set_item_id(int p_idx, int p_id); void set_item_metadata(int p_idx, const Variant &p_metadata); void set_item_disabled(int p_idx, bool p_disabled); String get_item_text(int p_idx) const; Ref<Texture> get_item_icon(int p_idx) const; int get_item_id(int p_idx) const; + int get_item_index(int p_id) const; Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; diff --git a/scene/gui/panel.h b/scene/gui/panel.h index f8d15e4261..84bf6e75f5 100644 --- a/scene/gui/panel.h +++ b/scene/gui/panel.h @@ -32,9 +32,7 @@ #define PANEL_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Panel : public Control { GDCLASS(Panel, Control); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 2d2c54594a..32380b6457 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -48,6 +48,14 @@ void Popup::_notification(int p_what) { update_configuration_warning(); } + if (p_what == NOTIFICATION_EXIT_TREE) { + if (popped_up) { + popped_up = false; + notification(NOTIFICATION_POPUP_HIDE); + emit_signal("popup_hide"); + } + } + if (p_what == NOTIFICATION_ENTER_TREE) { //small helper to make editing of these easier in editor #ifdef TOOLS_ENABLED @@ -65,8 +73,8 @@ void Popup::_notification(int p_what) { void Popup::_fix_size() { Point2 pos = get_global_position(); - Size2 size = get_size(); - Point2 window_size = get_viewport_rect().size; + Size2 size = get_size() * get_scale(); + Point2 window_size = get_viewport_rect().size - get_viewport_transform().get_origin(); if (pos.x + size.width > window_size.width) pos.x = window_size.width - size.width; @@ -115,6 +123,18 @@ void Popup::set_as_minsize() { set_size(total_minsize); } +void Popup::popup_centered_clamped(const Size2 &p_size, float p_fallback_ratio) { + + Size2 popup_size = p_size; + Size2 window_size = get_viewport_rect().size; + + // clamp popup size in each dimension if window size is too small (using fallback ratio) + popup_size.x = MIN(window_size.x * p_fallback_ratio, popup_size.x); + popup_size.y = MIN(window_size.y * p_fallback_ratio, popup_size.y); + + popup_centered(popup_size); +} + void Popup::popup_centered_minsize(const Size2 &p_minsize) { set_custom_minimum_size(p_minsize); @@ -124,60 +144,44 @@ void Popup::popup_centered_minsize(const Size2 &p_minsize) { void Popup::popup_centered(const Size2 &p_size) { - Point2 window_size = get_viewport_rect().size; - - emit_signal("about_to_show"); Rect2 rect; + Size2 window_size = get_viewport_rect().size; rect.size = p_size == Size2() ? get_size() : p_size; - rect.position = ((window_size - rect.size) / 2.0).floor(); - set_position(rect.position); - set_size(rect.size); - - show_modal(exclusive); - _fix_size(); - Control *focusable = find_next_valid_focus(); - if (focusable) - focusable->grab_focus(); - - _post_popup(); - notification(NOTIFICATION_POST_POPUP); - popped_up = true; + _popup(rect, true); } void Popup::popup_centered_ratio(float p_screen_ratio) { - emit_signal("about_to_show"); - Rect2 rect; - Point2 window_size = get_viewport_rect().size; + Size2 window_size = get_viewport_rect().size; rect.size = (window_size * p_screen_ratio).floor(); rect.position = ((window_size - rect.size) / 2.0).floor(); - set_position(rect.position); - set_size(rect.size); - show_modal(exclusive); - _fix_size(); + _popup(rect, true); +} - Control *focusable = find_next_valid_focus(); - if (focusable) - focusable->grab_focus(); +void Popup::popup(const Rect2 &p_bounds) { - _post_popup(); - notification(NOTIFICATION_POST_POPUP); - popped_up = true; + _popup(p_bounds); } -void Popup::popup(const Rect2 &p_bounds) { +void Popup::_popup(const Rect2 &p_bounds, const bool p_centered) { emit_signal("about_to_show"); show_modal(exclusive); // Fit the popup into the optionally provided bounds. if (!p_bounds.has_no_area()) { - set_position(p_bounds.position); set_size(p_bounds.size); + + // check if p_bounds.size was using an outdated cached values + if (p_centered && p_bounds.size != get_size()) { + set_position(p_bounds.position - ((get_size() - p_bounds.size) / 2.0).floor()); + } else { + set_position(p_bounds.position); + } } _fix_size(); @@ -203,9 +207,11 @@ bool Popup::is_exclusive() const { void Popup::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_as_minsize"), &Popup::set_as_minsize); ClassDB::bind_method(D_METHOD("popup_centered", "size"), &Popup::popup_centered, DEFVAL(Size2())); ClassDB::bind_method(D_METHOD("popup_centered_ratio", "ratio"), &Popup::popup_centered_ratio, DEFVAL(0.75)); ClassDB::bind_method(D_METHOD("popup_centered_minsize", "minsize"), &Popup::popup_centered_minsize, DEFVAL(Size2())); + ClassDB::bind_method(D_METHOD("popup_centered_clamped", "size", "fallback_ratio"), &Popup::popup_centered_clamped, DEFVAL(Size2()), DEFVAL(0.75)); ClassDB::bind_method(D_METHOD("popup", "bounds"), &Popup::popup, DEFVAL(Rect2())); ClassDB::bind_method(D_METHOD("set_exclusive", "enable"), &Popup::set_exclusive); ClassDB::bind_method(D_METHOD("is_exclusive"), &Popup::is_exclusive); @@ -229,7 +235,7 @@ Popup::Popup() { String Popup::get_configuration_warning() const { if (is_visible_in_tree()) { - return TTR("Popups will hide by default unless you call popup() or any of the popup*() functions. Making them visible for editing is fine though, but they will hide upon running."); + return TTR("Popups will hide by default unless you call popup() or any of the popup*() functions. Making them visible for editing is fine, but they will hide upon running."); } return String(); diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 7ccefe1d75..925760984e 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -33,9 +33,6 @@ #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Popup : public Control { GDCLASS(Popup, Control); @@ -43,6 +40,9 @@ class Popup : public Control { bool exclusive; bool popped_up; +private: + void _popup(const Rect2 &p_bounds = Rect2(), const bool p_centered = false); + protected: virtual void _post_popup() {} @@ -64,6 +64,7 @@ public: void popup_centered(const Size2 &p_size = Size2()); void popup_centered_minsize(const Size2 &p_minsize = Size2()); void set_as_minsize(); + void popup_centered_clamped(const Size2 &p_size = Size2(), float p_fallback_ratio = 0.75); virtual void popup(const Rect2 &p_bounds = Rect2()); virtual String get_configuration_warning() const; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index ac94ac63ec..a7c6c5ccab 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -31,6 +31,7 @@ #include "popup_menu.h" #include "core/os/input.h" #include "core/os/keyboard.h" +#include "core/os/os.h" #include "core/print_string.h" #include "core/translation.h" @@ -54,9 +55,11 @@ Size2 PopupMenu::get_minimum_size() const { Ref<Font> font = get_font("font"); float max_w = 0; + float icon_w = 0; int font_h = font->get_height(); - int check_w = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()); + int check_w = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()) + hseparation; int accel_max_w = 0; + bool has_check = false; for (int i = 0; i < items.size(); i++) { @@ -65,8 +68,7 @@ Size2 PopupMenu::get_minimum_size() const { Size2 icon_size = items[i].icon->get_size(); size.height = MAX(icon_size.height, font_h); - size.width += icon_size.width; - size.width += hseparation; + icon_w = MAX(icon_size.width + hseparation, icon_w); } else { size.height = font_h; @@ -74,10 +76,8 @@ Size2 PopupMenu::get_minimum_size() const { size.width += items[i].h_ofs; - if (items[i].checkable_type) { - - size.width += check_w + hseparation; - } + if (items[i].checkable_type) + has_check = true; String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].xl_text; size.width += font->get_string_size(text).width; @@ -91,16 +91,17 @@ Size2 PopupMenu::get_minimum_size() const { accel_max_w = MAX(accel_w, accel_max_w); } - if (items[i].submenu != "") { - + if (items[i].submenu != "") size.width += get_icon("submenu")->get_width(); - } - minsize.height += size.height; max_w = MAX(max_w, size.width); + + minsize.height += size.height; } - minsize.width += max_w + accel_max_w; + minsize.width += max_w + icon_w + accel_max_w; + if (has_check) + minsize.width += check_w; return minsize; } @@ -147,11 +148,9 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { void PopupMenu::_activate_submenu(int over) { Node *n = get_node(items[over].submenu); - ERR_EXPLAIN("item subnode does not exist: " + items[over].submenu); - ERR_FAIL_COND(!n); + ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[over].submenu + "."); Popup *pm = Object::cast_to<Popup>(n); - ERR_EXPLAIN("item subnode is not a Popup: " + items[over].submenu); - ERR_FAIL_COND(!pm); + ERR_FAIL_COND_MSG(!pm, "Item subnode is not a Popup: " + items[over].submenu + "."); if (pm->is_visible_in_tree()) return; //already visible! @@ -159,13 +158,14 @@ void PopupMenu::_activate_submenu(int over) { Rect2 pr(p, get_size()); Ref<StyleBox> style = get_stylebox("panel"); - Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y); + Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y) * get_global_transform().get_scale(); Size2 size = pm->get_size(); // fix pos if (pos.x + size.width > get_viewport_rect().size.width) pos.x = p.x - size.width; pm->set_position(pos); + pm->set_scale(get_global_transform().get_scale()); pm->popup(); PopupMenu *pum = Object::cast_to<PopupMenu>(pm); @@ -196,11 +196,11 @@ void PopupMenu::_scroll(float p_factor, const Point2 &p_over) { int vseparation = get_constant("vseparation"); Ref<Font> font = get_font("font"); - float dy = (vseparation + font->get_height()) * 3 * p_factor; + float dy = (vseparation + font->get_height()) * 3 * p_factor * get_global_transform().get_scale().y; if (dy > 0 && global_y < 0) dy = MIN(dy, -global_y - 1); - else if (dy < 0 && global_y + get_size().y > get_viewport_rect().size.y) - dy = -MIN(-dy, global_y + get_size().y - get_viewport_rect().size.y - 1); + else if (dy < 0 && global_y + get_size().y * get_global_transform().get_scale().y > get_viewport_rect().size.y) + dy = -MIN(-dy, global_y + get_size().y * get_global_transform().get_scale().y - get_viewport_rect().size.y - 1); set_position(get_position() + Vector2(0, dy)); Ref<InputEventMouseMotion> ie; @@ -239,7 +239,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { for (int i = search_from; i >= 0; i--) { - if (i < 0 || i >= items.size()) + if (i >= items.size()) continue; if (!items[i].separator && !items[i].disabled) { @@ -289,7 +289,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { case BUTTON_WHEEL_DOWN: { - if (get_global_position().y + get_size().y > get_viewport_rect().size.y) { + if (get_global_position().y + get_size().y * get_global_transform().get_scale().y > get_viewport_rect().size.y) { _scroll(-b->get_factor(), b->get_position()); } } break; @@ -354,7 +354,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { } int over = _get_mouse_over(m->get_position()); - int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].ID >= 0 ? items[over].ID : over); + int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].id >= 0 ? items[over].id : over); if (id < 0) { mouse_over = -1; @@ -379,6 +379,43 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { _scroll(-pan_gesture->get_delta().y, pan_gesture->get_position()); } } + + Ref<InputEventKey> k = p_event; + + if (allow_search && k.is_valid() && k->get_unicode()) { + + uint64_t now = OS::get_singleton()->get_ticks_msec(); + uint64_t diff = now - search_time_msec; + uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000)); + search_time_msec = now; + + if (diff > max_interval) { + search_string = ""; + } + + if (String::chr(k->get_unicode()) != search_string) + search_string += String::chr(k->get_unicode()); + + for (int i = mouse_over + 1; i <= items.size(); i++) { + if (i == items.size()) { + if (mouse_over <= 0) + break; + else + i = 0; + } + + if (i == mouse_over) + break; + + if (items[i].text.findn(search_string) == 0) { + mouse_over = i; + emit_signal("id_focused", i); + update(); + accept_event(); + break; + } + } + } } bool PopupMenu::has_point(const Point2 &p_point) const { @@ -415,7 +452,6 @@ void PopupMenu::_notification(int p_what) { minimum_size_changed(); update(); - } break; case NOTIFICATION_DRAW: { @@ -443,15 +479,31 @@ void PopupMenu::_notification(int p_what) { Color font_color_hover = get_color("font_color_hover"); float font_h = font->get_height(); + // Add the check and the wider icon to the offset of all items. + float icon_ofs = 0.0; + bool has_check = false; + for (int i = 0; i < items.size(); i++) { + + if (!items[i].icon.is_null()) + icon_ofs = MAX(items[i].icon->get_size().width, icon_ofs); + + if (items[i].checkable_type) + has_check = true; + } + if (icon_ofs > 0.0) + icon_ofs += hseparation; + + float check_ofs = 0.0; + if (has_check) + check_ofs = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()) + hseparation; + for (int i = 0; i < items.size(); i++) { if (i > 0) ofs.y += vseparation; Point2 item_ofs = ofs; - float h; Size2 icon_size; - - Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + float h; if (!items[i].icon.is_null()) { @@ -489,16 +541,15 @@ void PopupMenu::_notification(int p_what) { } } + Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + 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)), icon_color); - item_ofs.x += items[i].icon->get_width(); - item_ofs.x += hseparation; + items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); } if (items[i].submenu != "") { @@ -514,21 +565,21 @@ void PopupMenu::_notification(int p_what) { } } else { + item_ofs.x += icon_ofs + check_ofs; font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color)); } if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) { //accelerator - String text = _get_accel_text(i); - item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text).width; - font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, i == mouse_over ? font_color_hover : font_color_accel); + String text2 = _get_accel_text(i); + item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text2).width; + font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text2, i == mouse_over ? font_color_hover : font_color_accel); } items.write[i]._ofs_cache = ofs.y; ofs.y += h; } - } break; case MainLoop::NOTIFICATION_WM_FOCUS_OUT: { @@ -576,93 +627,93 @@ void PopupMenu::_notification(int p_what) { } } -void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) { +void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { Item item; item.icon = p_icon; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + 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) { +void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) { Item item; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id == -1 ? items.size() : 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) { +void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { Item item; item.text = p_label; item.xl_text = tr(p_label); - item.ID = p_ID; + item.id = p_id; 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) { +void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { Item item; item.icon = p_icon; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id; 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) { +void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) { Item item; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id == -1 ? items.size() : p_id; 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) { +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); + add_check_item(p_label, p_id, p_accel); items.write[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) { +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); + add_icon_check_item(p_icon, p_label, p_id, p_accel); items.write[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) { +void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.icon = p_icon; item.shortcut = p_shortcut; item.shortcut_is_global = p_global; @@ -671,14 +722,14 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut minimum_size_changed(); } -void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.shortcut = p_shortcut; item.shortcut_is_global = p_global; items.push_back(item); @@ -686,14 +737,14 @@ void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_g 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) { +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()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.shortcut = p_shortcut; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; item.icon = p_icon; @@ -703,14 +754,14 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<Sh minimum_size_changed(); } -void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.shortcut = p_shortcut; item.shortcut_is_global = p_global; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; @@ -719,21 +770,21 @@ void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bo minimum_size_changed(); } -void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +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); + add_check_shortcut(p_shortcut, p_id, p_global); items.write[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) { +void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) { Item item; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id; item.max_states = p_max_states; item.state = p_default_state; items.push_back(item); @@ -767,10 +818,10 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) { update(); minimum_size_changed(); } -void PopupMenu::set_item_id(int p_idx, int p_ID) { +void PopupMenu::set_item_id(int p_idx, int p_id) { ERR_FAIL_INDEX(p_idx, items.size()); - items.write[p_idx].ID = p_ID; + items.write[p_idx].id = p_id; update(); minimum_size_changed(); @@ -866,14 +917,14 @@ bool PopupMenu::is_item_checked(int p_idx) const { int PopupMenu::get_item_id(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), 0); - return items[p_idx].ID; + return items[p_idx].id; } -int PopupMenu::get_item_index(int p_ID) const { +int PopupMenu::get_item_index(int p_id) const { for (int i = 0; i < items.size(); i++) { - if (items[i].ID == p_ID) + if (items[i].id == p_id) return i; } @@ -1062,7 +1113,7 @@ void PopupMenu::activate_item(int p_item) { ERR_FAIL_INDEX(p_item, items.size()); ERR_FAIL_COND(items[p_item].separator); - int id = items[p_item].ID >= 0 ? items[p_item].ID : p_item; + int id = items[p_item].id >= 0 ? items[p_item].id : p_item; //hide all parent PopupMenus Node *next = get_parent(); @@ -1124,7 +1175,7 @@ void PopupMenu::add_separator(const String &p_text) { Item sep; sep.separator = true; - sep.ID = -1; + sep.id = -1; if (p_text != String()) { sep.text = p_text; sep.xl_text = tr(p_text); @@ -1274,6 +1325,16 @@ float PopupMenu::get_submenu_popup_delay() const { return submenu_timer->get_wait_time(); } +void PopupMenu::set_allow_search(bool p_allow) { + + allow_search = p_allow; +} + +bool PopupMenu::get_allow_search() const { + + return allow_search; +} + void PopupMenu::set_hide_on_window_lose_focus(bool p_enabled) { hide_on_window_lose_focus = p_enabled; @@ -1392,6 +1453,9 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hide_on_window_lose_focus", "enable"), &PopupMenu::set_hide_on_window_lose_focus); ClassDB::bind_method(D_METHOD("is_hide_on_window_lose_focus"), &PopupMenu::is_hide_on_window_lose_focus); + ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search); + ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search); + ClassDB::bind_method(D_METHOD("_submenu_timeout"), &PopupMenu::_submenu_timeout); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); @@ -1399,9 +1463,10 @@ void PopupMenu::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "submenu_popup_delay"), "set_submenu_popup_delay", "get_submenu_popup_delay"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); - ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "ID"))); - ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "ID"))); + 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"))); } @@ -1420,6 +1485,10 @@ PopupMenu::PopupMenu() { initial_button_mask = 0; during_grabbed_click = false; + allow_search = false; + search_time_msec = 0; + search_string = ""; + set_focus_mode(FOCUS_ALL); set_as_toplevel(true); set_hide_on_item_selection(true); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 767ff568fe..8bfe8fc607 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -33,10 +33,6 @@ #include "scene/gui/popup.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class PopupMenu : public Popup { GDCLASS(PopupMenu, Popup); @@ -55,7 +51,7 @@ class PopupMenu : public Popup { int state; bool separator; bool disabled; - int ID; + int id; Variant metadata; String submenu; String tooltip; @@ -112,6 +108,10 @@ class PopupMenu : public Popup { void _ref_shortcut(Ref<ShortCut> p_sc); void _unref_shortcut(Ref<ShortCut> p_sc); + bool allow_search; + uint64_t search_time_msec; + String search_string; + protected: virtual bool has_point(const Point2 &p_point) const; @@ -120,26 +120,26 @@ protected: static void _bind_methods(); public: - void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); - 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); + void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); + 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); void set_item_text(int p_idx, const String &p_text); void set_item_icon(int p_idx, const Ref<Texture> &p_icon); void set_item_checked(int p_idx, bool p_checked); - void set_item_id(int p_idx, int p_ID); + void set_item_id(int p_idx, int p_id); void set_item_accelerator(int p_idx, uint32_t p_accel); void set_item_metadata(int p_idx, const Variant &p_meta); void set_item_disabled(int p_idx, bool p_disabled); @@ -161,7 +161,7 @@ public: Ref<Texture> get_item_icon(int p_idx) const; bool is_item_checked(int p_idx) const; int get_item_id(int p_idx) const; - int get_item_index(int p_ID) const; + int get_item_index(int p_id) const; uint32_t get_item_accelerator(int p_idx) const; Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; @@ -206,6 +206,9 @@ public: void set_submenu_popup_delay(float p_time); float get_submenu_popup_delay() const; + void set_allow_search(bool p_allow); + bool get_allow_search() const; + virtual void popup(const Rect2 &p_bounds = Rect2()); void set_hide_on_window_lose_focus(bool p_enabled); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 2195ec9778..0154a452ad 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -39,9 +39,12 @@ Size2 ProgressBar::get_minimum_size() const { 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()); - //} + if (percent_visible) { + minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height()); + } else { // this is needed, else the progressbar will collapse + minimum_size.width = MAX(minimum_size.width, 1); + minimum_size.height = MAX(minimum_size.height, 1); + } return minimum_size; } @@ -57,7 +60,7 @@ void ProgressBar::_notification(int p_what) { draw_style_box(bg, Rect2(Point2(), get_size())); float r = get_as_ratio(); int mp = fg->get_minimum_size().width; - int p = r * get_size().width - mp; + int p = r * (get_size().width - mp); if (p > 0) { draw_style_box(fg, Rect2(Point2(), Size2(p + fg->get_minimum_size().width, get_size().height))); diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index c24e62c8cb..ed5dd77f53 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -35,9 +35,9 @@ String Range::get_configuration_warning() const { if (shared->exp_ratio && shared->min <= 0) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } - warning += TTR("If exp_edit is true min_value must be > 0."); + warning += TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."); } return warning; @@ -63,7 +63,7 @@ void Range::Shared::emit_value_changed() { void Range::_changed_notify(const char *p_what) { - emit_signal("changed", shared->val); + emit_signal("changed"); update(); _change_notify(p_what); } @@ -79,6 +79,7 @@ void Range::Shared::emit_changed(const char *p_what) { } void Range::set_value(double p_val) { + if (shared->step > 0) p_val = Math::round(p_val / shared->step) * shared->step; @@ -179,12 +180,12 @@ double Range::get_as_ratio() const { 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); + return CLAMP((v - exp_min) / (exp_max - exp_min), 0, 1); } else { float value = CLAMP(get_value(), shared->min, shared->max); - return (value - get_min()) / (get_max() - get_min()); + return CLAMP((value - get_min()) / (get_max() - get_min()), 0, 1); } } @@ -303,22 +304,27 @@ bool Range::is_ratio_exp() const { } 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; shared->max = 100; diff --git a/scene/gui/range.h b/scene/gui/range.h index cf0add8c89..8ce450f8fc 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -32,9 +32,7 @@ #define RANGE_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Range : public Control { GDCLASS(Range, Control); diff --git a/scene/gui/reference_rect.cpp b/scene/gui/reference_rect.cpp index 553e133946..052c8ccd05 100644 --- a/scene/gui/reference_rect.cpp +++ b/scene/gui/reference_rect.cpp @@ -38,26 +38,41 @@ void ReferenceRect::_notification(int p_what) { if (!is_inside_tree()) return; - if (Engine::get_singleton()->is_editor_hint()) + if (Engine::get_singleton()->is_editor_hint() || !editor_only) draw_rect(Rect2(Point2(), get_size()), border_color, false); } } -void ReferenceRect::set_border_color(const Color &color) { - border_color = color; +void ReferenceRect::set_border_color(const Color &p_color) { + border_color = p_color; + update(); } Color ReferenceRect::get_border_color() const { return border_color; } +void ReferenceRect::set_editor_only(const bool &p_enabled) { + editor_only = p_enabled; + update(); +} + +bool ReferenceRect::get_editor_only() const { + return editor_only; +} + void ReferenceRect::_bind_methods() { ClassDB::bind_method(D_METHOD("get_border_color"), &ReferenceRect::get_border_color); ClassDB::bind_method(D_METHOD("set_border_color", "color"), &ReferenceRect::set_border_color); + ClassDB::bind_method(D_METHOD("get_editor_only"), &ReferenceRect::get_editor_only); + ClassDB::bind_method(D_METHOD("set_editor_only", "enabled"), &ReferenceRect::set_editor_only); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "get_editor_only"); } ReferenceRect::ReferenceRect() { border_color = Color(1, 0, 0); + editor_only = true; } diff --git a/scene/gui/reference_rect.h b/scene/gui/reference_rect.h index de3ccaeeca..7a88333cf2 100644 --- a/scene/gui/reference_rect.h +++ b/scene/gui/reference_rect.h @@ -37,6 +37,7 @@ class ReferenceRect : public Control { GDCLASS(ReferenceRect, Control); Color border_color; + bool editor_only; protected: void _notification(int p_what); @@ -45,8 +46,11 @@ protected: public: ReferenceRect(); - void set_border_color(const Color &color); + void set_border_color(const Color &p_color); Color get_border_color() const; + + void set_editor_only(const bool &p_enabled); + bool get_editor_only() const; }; #endif // REFERENCE_RECT_H diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 2e466fd582..8223ea6d1e 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -307,6 +307,25 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & switch (it->type) { + case ITEM_ALIGN: { + + ItemAlign *align_it = static_cast<ItemAlign *>(it); + + align = align_it->align; + + } break; + case ITEM_INDENT: { + + if (it != l.from) { + ItemIndent *indent_it = static_cast<ItemIndent *>(it); + + int indent = indent_it->level * tab_size * cfont->get_char_size(' ').width; + margin += indent; + begin += indent; + wofs += indent; + } + + } break; case ITEM_TEXT: { ItemText *text = static_cast<ItemText *>(it); @@ -440,14 +459,13 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & 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); + 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; } if (selected) { @@ -544,7 +562,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & 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")); + Point2 shadow_ofs2(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); if (p_mode == PROCESS_CACHE) { @@ -568,7 +586,7 @@ 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(), font_color_shadow, use_outline, shadow_ofs); + _process_line(frame, Point2(), ly, available_width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs2); table->columns.write[column].min_width = MAX(table->columns[column].min_width, frame->lines[i].minimum_width); table->columns.write[column].max_width = MAX(table->columns[column].max_width, frame->lines[i].maximum_width); } @@ -592,7 +610,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & //assign actual widths for (int i = 0; i < table->columns.size(); i++) { table->columns.write[i].width = table->columns[i].min_width; - if (table->columns[i].expand) + if (table->columns[i].expand && total_ratio > 0) table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; table->total_width += table->columns[i].width + hseparation; } @@ -641,7 +659,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(), font_color_shadow, use_outline, shadow_ofs); + _process_line(frame, Point2(), ly, table->columns[column].width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs2); frame->lines.write[i].height_cache = ly; //actual height frame->lines.write[i].height_accum_cache = ly; //actual height } @@ -674,9 +692,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, font_color_shadow, use_outline, shadow_ofs); + 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_ofs2); } 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, font_color_shadow, use_outline, shadow_ofs, 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_ofs2, p_click_pos, r_click_item, r_click_char, r_outside); if (r_click_item && *r_click_item) { RETURN; // exit early } @@ -711,7 +729,8 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & } break; - default: {} + default: { + } } Item *itp = it; @@ -752,6 +771,8 @@ void RichTextLabel::_scroll_changed(double) { else scroll_following = false; + scroll_updated = true; + update(); } @@ -777,7 +798,6 @@ void RichTextLabel::_update_scroll() { main->first_invalid_line = 0; //invalidate ALL _validate_line_caches(main); } - scroll_updated = true; } void RichTextLabel::_notification(int p_what) { @@ -801,11 +821,7 @@ void RichTextLabel::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { - if (is_inside_tree() && use_bbcode) { - parse_bbcode(bbcode); - //first_invalid_line=0; //invalidate ALL - //update(); - } + update(); } break; case NOTIFICATION_DRAW: { @@ -926,84 +942,80 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { return; if (b->get_button_index() == BUTTON_LEFT) { + if (b->is_pressed() && !b->is_doubleclick()) { + scroll_updated = false; + int line = 0; + Item *item = NULL; - if (true) { - - if (b->is_pressed() && !b->is_doubleclick()) { - scroll_updated = false; - int line = 0; - Item *item = NULL; - - bool outside; - _find_click(main, b->get_position(), &item, &line, &outside); + bool outside; + _find_click(main, b->get_position(), &item, &line, &outside); - if (item) { + if (item) { - if (selection.enabled) { + if (selection.enabled) { - selection.click = item; - selection.click_char = line; + selection.click = item; + selection.click_char = line; - // Erase previous selection. - if (selection.active) { - selection.from = NULL; - selection.from_char = '\0'; - selection.to = NULL; - selection.to_char = '\0'; - selection.active = false; + // Erase previous selection. + if (selection.active) { + selection.from = NULL; + selection.from_char = '\0'; + selection.to = NULL; + selection.to_char = '\0'; + selection.active = false; - update(); - } + update(); } } - } else if (b->is_pressed() && b->is_doubleclick() && selection.enabled) { + } + } else if (b->is_pressed() && b->is_doubleclick() && selection.enabled) { - //doubleclick: select word - int line = 0; - Item *item = NULL; - bool outside; + //doubleclick: select word + int line = 0; + Item *item = NULL; + bool outside; - _find_click(main, b->get_position(), &item, &line, &outside); + _find_click(main, b->get_position(), &item, &line, &outside); - while (item && item->type != ITEM_TEXT) { + while (item && item->type != ITEM_TEXT) { - item = _get_next_item(item, true); - } + item = _get_next_item(item, true); + } - if (item && item->type == ITEM_TEXT) { + if (item && item->type == ITEM_TEXT) { - String itext = static_cast<ItemText *>(item)->text; + String itext = static_cast<ItemText *>(item)->text; - int beg, end; - if (select_word(itext, line, beg, end)) { + int beg, end; + if (select_word(itext, line, beg, end)) { - selection.from = item; - selection.to = item; - selection.from_char = beg; - selection.to_char = end - 1; - selection.active = true; - update(); - } + selection.from = item; + selection.to = item; + selection.from_char = beg; + selection.to_char = end - 1; + selection.active = true; + update(); } - } else if (!b->is_pressed()) { + } + } else if (!b->is_pressed()) { - selection.click = NULL; + selection.click = NULL; - if (!b->is_doubleclick() && !scroll_updated) { - int line = 0; - Item *item = NULL; + if (!b->is_doubleclick() && !scroll_updated) { + int line = 0; + Item *item = NULL; - bool outside; - _find_click(main, b->get_position(), &item, &line, &outside); + bool outside; + _find_click(main, b->get_position(), &item, &line, &outside); - if (item) { + if (item) { - Variant meta; - if (!outside && _find_meta(item, &meta)) { - //meta clicked + Variant meta; + if (!outside && _find_meta(item, &meta)) { + //meta clicked - emit_signal("meta_clicked", meta); - } + emit_signal("meta_clicked", meta); } } } @@ -1133,18 +1145,19 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { } Variant meta; - if (item && !outside && _find_meta(item, &meta)) { - if (meta_hovering != item) { + ItemMeta *item_meta; + if (item && !outside && _find_meta(item, &meta, &item_meta)) { + if (meta_hovering != item_meta) { if (meta_hovering) { emit_signal("meta_hover_ended", current_meta); } - meta_hovering = static_cast<ItemMeta *>(item); + meta_hovering = item_meta; current_meta = meta; emit_signal("meta_hover_started", meta); } } else if (meta_hovering) { - emit_signal("meta_hover_ended", current_meta); meta_hovering = NULL; + emit_signal("meta_hover_ended", current_meta); current_meta = false; } } @@ -1269,7 +1282,7 @@ bool RichTextLabel::_find_strikethrough(Item *p_item) { return false; } -bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta) { +bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item) { Item *item = p_item; @@ -1280,6 +1293,8 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta) { ItemMeta *meta = static_cast<ItemMeta *>(item); if (r_meta) *r_meta = meta->meta; + if (r_item) + *r_item = meta; return true; } @@ -1289,6 +1304,23 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta) { return false; } +bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { + + if (from && from != to) { + if (from->type != ITEM_FONT && from->type != ITEM_COLOR && from->type != ITEM_UNDERLINE && from->type != ITEM_STRIKETHROUGH) + return true; + + for (List<Item *>::Element *E = from->subitems.front(); E; E = E->next()) { + bool layout = _find_layout_subitem(E->get(), to); + + if (layout) + return true; + } + } + + return false; +} + void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { if (p_frame->first_invalid_line == p_frame->lines.size()) @@ -1406,9 +1438,13 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) if (p_enter) current = p_item; - if (p_ensure_newline && current_frame->lines[current_frame->lines.size() - 1].from) { - _invalidate_current_line(current_frame); - current_frame->lines.resize(current_frame->lines.size() + 1); + if (p_ensure_newline) { + Item *from = current_frame->lines[current_frame->lines.size() - 1].from; + // only create a new line for Item types that generate content/layout, ignore those that represent formatting/styling + if (_find_layout_subitem(from, p_item)) { + _invalidate_current_line(current_frame); + current_frame->lines.resize(current_frame->lines.size() + 1); + } } if (current_frame->lines[current_frame->lines.size() - 1].from == NULL) { @@ -1902,37 +1938,37 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { if (col.begins_with("#")) color = Color::html(col); else if (col == "aqua") - color = Color::html("#00FFFF"); + color = Color(0, 1, 1); else if (col == "black") - color = Color::html("#000000"); + color = Color(0, 0, 0); else if (col == "blue") - color = Color::html("#0000FF"); + color = Color(0, 0, 1); else if (col == "fuchsia") - color = Color::html("#FF00FF"); + color = Color(1, 0, 1); else if (col == "gray" || col == "grey") - color = Color::html("#808080"); + color = Color(0.5, 0.5, 0.5); else if (col == "green") - color = Color::html("#008000"); + color = Color(0, 0.5, 0); else if (col == "lime") - color = Color::html("#00FF00"); + color = Color(0, 1, 0); else if (col == "maroon") - color = Color::html("#800000"); + color = Color(0.5, 0, 0); else if (col == "navy") - color = Color::html("#000080"); + color = Color(0, 0, 0.5); else if (col == "olive") - color = Color::html("#808000"); + color = Color(0.5, 0.5, 0); else if (col == "purple") - color = Color::html("#800080"); + color = Color(0.5, 0, 0.5); else if (col == "red") - color = Color::html("#FF0000"); + color = Color(1, 0, 0); else if (col == "silver") - color = Color::html("#C0C0C0"); + color = Color(0.75, 0.75, 0.75); else if (col == "teal") - color = Color::html("#008008"); + color = Color(0, 0.5, 0.5); else if (col == "white") - color = Color::html("#FFFFFF"); + color = Color(1, 1, 1); else if (col == "yellow") - color = Color::html("#FFFF00"); + color = Color(1, 1, 0); else color = base_color; @@ -2001,7 +2037,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p Item *it = main; int charidx = 0; - if (p_from_selection && selection.active && selection.enabled) { + if (p_from_selection && selection.active) { it = selection.to; charidx = selection.to_char + 1; } @@ -2331,6 +2367,7 @@ RichTextLabel::RichTextLabel() { tab_size = 4; default_align = ALIGN_LEFT; underline_meta = true; + meta_hovering = NULL; override_selected_font_color = false; scroll_visible = false; diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index bec2c5ac02..21d099c37a 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -285,7 +285,8 @@ private: Color _find_color(Item *p_item, const Color &p_default_color); bool _find_underline(Item *p_item); bool _find_strikethrough(Item *p_item); - bool _find_meta(Item *p_item, Variant *r_meta); + bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = NULL); + bool _find_layout_subitem(Item *from, Item *to); void _update_scroll(); void _scroll_changed(double); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 2938654ed9..a7c15151ae 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -53,29 +53,19 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { if (b.is_valid()) { accept_event(); - if (b->get_button_index() == 5 && b->is_pressed()) { + if (b->get_button_index() == BUTTON_WHEEL_DOWN && b->is_pressed()) { - /* - if (orientation==VERTICAL) - set_val( get_val() + get_page() / 4.0 ); - else - */ set_value(get_value() + get_page() / 4.0); accept_event(); } - if (b->get_button_index() == 4 && b->is_pressed()) { + if (b->get_button_index() == BUTTON_WHEEL_UP && b->is_pressed()) { - /* - if (orientation==HORIZONTAL) - set_val( get_val() - get_page() / 4.0 ); - else - */ set_value(get_value() - get_page() / 4.0); accept_event(); } - if (b->get_button_index() != 1) + if (b->get_button_index() != BUTTON_LEFT) return; if (b->is_pressed()) { @@ -449,27 +439,26 @@ double ScrollBar::get_grabber_size() const { } double ScrollBar::get_area_size() const { - - if (orientation == VERTICAL) { - - double area = get_size().height; - area -= get_stylebox("scroll")->get_minimum_size().height; - area -= get_icon("increment")->get_height(); - area -= get_icon("decrement")->get_height(); - area -= get_grabber_min_size(); - return area; - - } else if (orientation == HORIZONTAL) { - - double area = get_size().width; - area -= get_stylebox("scroll")->get_minimum_size().width; - area -= get_icon("increment")->get_width(); - area -= get_icon("decrement")->get_width(); - area -= get_grabber_min_size(); - return area; - } else { - - return 0; + switch (orientation) { + case VERTICAL: { + double area = get_size().height; + area -= get_stylebox("scroll")->get_minimum_size().height; + area -= get_icon("increment")->get_height(); + area -= get_icon("decrement")->get_height(); + area -= get_grabber_min_size(); + return area; + } break; + case HORIZONTAL: { + double area = get_size().width; + area -= get_stylebox("scroll")->get_minimum_size().width; + area -= get_icon("increment")->get_width(); + area -= get_icon("decrement")->get_width(); + area -= get_grabber_min_size(); + return area; + } break; + default: { + return 0.0; + } } } diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 5ceabfc06b..cbcee1dae3 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -33,10 +33,6 @@ #include "scene/gui/range.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class ScrollBar : public Range { GDCLASS(ScrollBar, Range); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 28292309b9..461281a4ed 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -88,13 +88,16 @@ void ScrollContainer::_cancel_drag() { void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { + double prev_v_scroll = v_scroll->get_value(); + double prev_h_scroll = h_scroll->get_value(); + Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && !v_scroll->is_visible()) { + if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor()); } else if (v_scroll->is_visible_in_tree()) { v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8 * mb->get_factor()); @@ -103,7 +106,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && !v_scroll->is_visible()) { + if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor()); } else if (v_scroll->is_visible()) { v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8 * mb->get_factor()); @@ -122,6 +125,9 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { } } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); //accept event if scroll changed + if (!OS::get_singleton()->has_touchscreen_ui_hint()) return; @@ -134,19 +140,17 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_drag(); } - if (true) { - drag_speed = Vector2(); - drag_accum = Vector2(); - last_drag_accum = Vector2(); - 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; + drag_speed = Vector2(); + drag_accum = Vector2(); + last_drag_accum = Vector2(); + 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_internal(true); time_since_motion = 0; - if (drag_touching) { - set_physics_process_internal(true); - time_since_motion = 0; - } } } else { @@ -204,6 +208,9 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8); } } + + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); //accept event if scroll changed } void ScrollContainer::_update_scrollbar_position() { @@ -368,8 +375,10 @@ void ScrollContainer::update_scrollbars() { 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(); + Size2 hmin; + Size2 vmin; + if (scroll_h) hmin = h_scroll->get_combined_minimum_size(); + if (scroll_v) vmin = v_scroll->get_combined_minimum_size(); Size2 min = child_max_size; @@ -477,7 +486,7 @@ String ScrollContainer::get_configuration_warning() const { } if (found != 1) - return TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox,HBox,etc), or a Control and set the custom minimum size manually."); + return TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."); else return ""; } diff --git a/scene/gui/separator.h b/scene/gui/separator.h index 54ad9b5bb5..89039f3112 100644 --- a/scene/gui/separator.h +++ b/scene/gui/separator.h @@ -31,10 +31,6 @@ #ifndef SEPARATOR_H #define SEPARATOR_H -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - #include "scene/gui/control.h" class Separator : public Control { diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index eb04b85931..b777e77bc3 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -34,8 +34,15 @@ Size2 Slider::get_minimum_size() const { Ref<StyleBox> style = get_stylebox("slider"); - Size2i ms = style->get_minimum_size() + style->get_center_size(); - return ms; + Size2i ss = style->get_minimum_size() + style->get_center_size(); + + Ref<Texture> grabber = get_icon("grabber"); + Size2i rs = grabber->get_size(); + + if (orientation == HORIZONTAL) + return Size2i(ss.width, MAX(ss.height, rs.height)); + else + return Size2i(MAX(ss.width, rs.width), ss.height); } void Slider::_gui_input(Ref<InputEvent> p_event) { @@ -134,7 +141,11 @@ void Slider::_gui_input(Ref<InputEvent> p_event) { void Slider::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + minimum_size_changed(); + update(); + } break; case NOTIFICATION_MOUSE_ENTER: { mouse_inside = true; @@ -284,6 +295,7 @@ Slider::Slider(Orientation p_orientation) { mouse_inside = false; grab.active = false; ticks = 0; + ticks_on_borders = false; custom_step = -1; editable = true; scrollable = true; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index d21143739c..3c63f6b2c7 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "spin_box.h" +#include "core/math/expression.h" #include "core/os/input.h" Size2 SpinBox::get_minimum_size() const { @@ -40,7 +41,7 @@ Size2 SpinBox::get_minimum_size() const { void SpinBox::_value_changed(double) { - String value = String::num(get_value(), Math::step_decimals(get_step())); + String value = String::num(get_value(), Math::range_step_decimals(get_step())); if (prefix != "") value = prefix + " " + value; if (suffix != "") @@ -50,15 +51,18 @@ void SpinBox::_value_changed(double) { void SpinBox::_text_entered(const String &p_string) { - /* - if (!p_string.is_numeric()) + Ref<Expression> expr; + expr.instance(); + Error err = expr->parse(p_string); + if (err != OK) { return; - */ - String value = p_string; - if (prefix != "" && p_string.begins_with(prefix)) - value = p_string.substr(prefix.length(), p_string.length() - prefix.length()); - set_value(value.to_double()); - _value_changed(0); + } + + Variant value = expr->execute(Array(), NULL, false); + if (value.get_type() != Variant::NIL) { + set_value(value); + _value_changed(0); + } } LineEdit *SpinBox::get_line_edit() { @@ -170,10 +174,14 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { void SpinBox::_line_edit_focus_exit() { + // discontinue because the focus_exit was caused by right-click context menu + if (line_edit->get_menu()->is_visible()) + return; + _text_entered(line_edit->get_text()); } -inline void SpinBox::_adjust_width_for_icon(const Ref<Texture> icon) { +inline void SpinBox::_adjust_width_for_icon(const Ref<Texture> &icon) { int w = icon->get_width(); if (w != last_w) { @@ -277,6 +285,7 @@ SpinBox::SpinBox() { add_child(line_edit); line_edit->set_anchors_and_margins_preset(Control::PRESET_WIDE); + line_edit->set_mouse_filter(MOUSE_FILTER_PASS); //connect("value_changed",this,"_value_changed"); line_edit->connect("text_entered", this, "_text_entered", Vector<Variant>(), CONNECT_DEFERRED); line_edit->connect("focus_exited", this, "_line_edit_focus_exit", Vector<Variant>(), CONNECT_DEFERRED); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 49dc6d950c..9cf977d2d6 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -62,7 +62,7 @@ class SpinBox : public Range { void _line_edit_focus_exit(); - inline void _adjust_width_for_icon(const Ref<Texture> icon); + inline void _adjust_width_for_icon(const Ref<Texture> &icon); protected: void _gui_input(const Ref<InputEvent> &p_event); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index d6a93238b2..e5d1844d39 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -164,34 +164,31 @@ void SplitContainer::_notification(int p_what) { _resort(); } break; - case NOTIFICATION_MOUSE_ENTER: { - - mouse_inside = true; - update(); - } break; case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; - update(); + if (get_constant("autohide")) + update(); } break; case NOTIFICATION_DRAW: { if (!_getch(0) || !_getch(1)) return; - if (collapsed || (!mouse_inside && get_constant("autohide"))) + if (collapsed || (!dragging && !mouse_inside && get_constant("autohide"))) + return; + + if (dragger_visibility != DRAGGER_VISIBLE) return; int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? get_constant("separation") : 0; Ref<Texture> tex = get_icon("grabber"); Size2 size = get_size(); - if (dragger_visibility == DRAGGER_VISIBLE) { - if (vertical) - draw_texture(tex, Point2i((size.x - tex->get_width()) / 2, middle_sep + (sep - tex->get_height()) / 2)); - else - draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2)); - } + if (vertical) + draw_texture(tex, Point2i((size.x - tex->get_width()) / 2, middle_sep + (sep - tex->get_height()) / 2)); + else + draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2)); } break; case NOTIFICATION_THEME_CHANGED: { @@ -241,7 +238,23 @@ void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && dragging) { + if (mm.is_valid()) { + + bool mouse_inside_state = false; + if (vertical) + mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + get_constant("separation"); + else + mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + get_constant("separation"); + + if (mouse_inside != mouse_inside_state) { + + mouse_inside = mouse_inside_state; + if (get_constant("autohide")) + update(); + } + + if (!dragging) + return; split_offset = drag_ofs + ((vertical ? mm->get_position().y : mm->get_position().x) - drag_from); should_clamp_split_offset = true; diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index 3c1ca09a9f..97838e19a3 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -35,7 +35,7 @@ class SplitContainer : public Container { - GDCLASS(SplitContainer, Container) + GDCLASS(SplitContainer, Container); public: enum DraggerVisibility { diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index e35967861c..292d80be9d 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -86,8 +86,8 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { emit_signal("pre_popup_pressed"); Vector2 popup_pos = get_global_position(); - popup_pos.x += size.width - popup->get_size().width; - popup_pos.y += menu->get_height(); + popup_pos.x += size.width * get_global_transform().get_scale().x - popup->get_size().width * popup->get_global_transform().get_scale().x; + popup_pos.y += menu->get_height() * get_global_transform().get_scale().y; popup->set_global_position(popup_pos); popup->popup(); @@ -127,6 +127,9 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { // Activate the clicked tab. pos.x -= tabs_ofs_cache; for (int i = first_tab_cache; i <= last_tab_cache; i++) { + if (get_tab_hidden(i)) { + continue; + } int tab_width = _get_tab_width(i); if (pos.x < tab_width) { if (!get_tab_disabled(i)) { @@ -143,6 +146,11 @@ void TabContainer::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_TRANSLATION_CHANGED: { + + minimum_size_changed(); + update(); + } break; case NOTIFICATION_RESIZED: { Vector<Control *> tabs = _get_tabs(); @@ -178,7 +186,6 @@ void TabContainer::_notification(int p_what) { first_tab_cache--; } } break; - case NOTIFICATION_DRAW: { RID canvas = get_canvas_item(); @@ -216,6 +223,9 @@ void TabContainer::_notification(int p_what) { // Check if all tabs would fit into the header area. int all_tabs_width = 0; for (int i = 0; i < tabs.size(); i++) { + if (get_tab_hidden(i)) { + continue; + } int tab_width = _get_tab_width(i); all_tabs_width += tab_width; @@ -241,6 +251,9 @@ void TabContainer::_notification(int p_what) { all_tabs_width = 0; Vector<int> tab_widths; for (int i = first_tab_cache; i < tabs.size(); i++) { + if (get_tab_hidden(i)) { + continue; + } int tab_width = _get_tab_width(i); if (all_tabs_width + tab_width > header_width && tab_widths.size() > 0) break; @@ -267,6 +280,9 @@ void TabContainer::_notification(int p_what) { // Draw all visible tabs. int x = 0; for (int i = 0; i < tab_widths.size(); i++) { + if (get_tab_hidden(i)) { + continue; + } Ref<StyleBox> tab_style; Color font_color; if (get_tab_disabled(i + first_tab_cache)) { @@ -287,7 +303,7 @@ void TabContainer::_notification(int p_what) { // Draw the tab contents. Control *control = Object::cast_to<Control>(tabs[i + first_tab_cache]); - String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(control->get_name()); + String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name())); int x_content = tab_rect.position.x + tab_style->get_margin(MARGIN_LEFT); int top_margin = tab_style->get_margin(MARGIN_TOP); @@ -338,6 +354,7 @@ void TabContainer::_notification(int p_what) { } } break; case NOTIFICATION_THEME_CHANGED: { + minimum_size_changed(); call_deferred("_on_theme_changed"); //wait until all changed theme } break; @@ -354,7 +371,7 @@ int TabContainer::_get_tab_width(int p_index) const { ERR_FAIL_INDEX_V(p_index, get_tab_count(), 0); Control *control = Object::cast_to<Control>(_get_tabs()[p_index]); - if (!control || control->is_set_as_toplevel()) + if (!control || control->is_set_as_toplevel() || get_tab_hidden(p_index)) return 0; // Get the width of the text displayed on the tab. @@ -719,6 +736,7 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { Control *child = _get_tab(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_name", p_title); + update(); } String TabContainer::get_tab_title(int p_tab) const { @@ -736,6 +754,7 @@ void TabContainer::set_tab_icon(int p_tab, const Ref<Texture> &p_icon) { Control *child = _get_tab(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_icon", p_icon); + update(); } Ref<Texture> TabContainer::get_tab_icon(int p_tab) const { @@ -765,6 +784,36 @@ bool TabContainer::get_tab_disabled(int p_tab) const { return false; } +void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { + + Control *child = _get_tab(p_tab); + ERR_FAIL_COND(!child); + child->set_meta("_tab_hidden", p_hidden); + update(); + for (int i = 0; i < get_tab_count(); i++) { + int try_tab = (p_tab + 1 + i) % get_tab_count(); + if (get_tab_disabled(try_tab) || get_tab_hidden(try_tab)) { + continue; + } + + set_current_tab(try_tab); + return; + } + + //assumed no other tab can be switched to, just hide + child->hide(); +} + +bool TabContainer::get_tab_hidden(int p_tab) const { + + Control *child = _get_tab(p_tab); + ERR_FAIL_COND_V(!child, false); + if (child->has_meta("_tab_hidden")) + return child->get_meta("_tab_hidden"); + else + return false; +} + void TabContainer::get_translatable_strings(List<String> *p_strings) const { Vector<Control *> tabs = _get_tabs(); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index dbf2bb9231..0314f86837 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -97,6 +97,9 @@ public: void set_tab_disabled(int p_tab, bool p_disabled); bool get_tab_disabled(int p_tab) const; + void set_tab_hidden(int p_tab, bool p_hidden); + bool get_tab_hidden(int p_tab) const; + int get_tab_count() const; void set_current_tab(int p_current); int get_current_tab() const; diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index ac643c1320..7b0836cd28 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -222,6 +222,10 @@ void Tabs::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_TRANSLATION_CHANGED: { + minimum_size_changed(); + update(); + } break; case NOTIFICATION_MOUSE_EXIT: { rb_hover = -1; cb_hover = -1; @@ -232,7 +236,6 @@ void Tabs::_notification(int p_what) { _update_cache(); _ensure_no_over_offset(); ensure_tab_visible(current); - } break; case NOTIFICATION_DRAW: { _update_cache(); @@ -394,7 +397,6 @@ void Tabs::_notification(int p_what) { } else { buttons_visible = false; } - } break; } } @@ -890,6 +892,8 @@ void Tabs::ensure_tab_visible(int p_idx) { } Rect2 Tabs::get_tab_rect(int p_tab) const { + + ERR_FAIL_INDEX_V(p_tab, tabs.size(), Rect2()); return Rect2(tabs[p_tab].ofs_cache, 0, tabs[p_tab].size_cache, get_size().height); } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 09fbb39866..1d434e5a2a 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -35,6 +35,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/project_settings.h" +#include "core/script_language.h" #include "scene/main/viewport.h" #ifdef TOOLS_ENABLED @@ -50,7 +51,7 @@ inline bool _is_symbol(CharType c) { static bool _is_text_char(CharType c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; + return !is_symbol(c); } static bool _is_whitespace(CharType c) { @@ -104,6 +105,13 @@ static CharType _get_right_pair_symbol(CharType c) { return 0; } +static int _find_first_non_whitespace_column_of_line(const String &line) { + int left = 0; + while (left < line.length() && _is_whitespace(line[left])) + left++; + return left; +} + void TextEdit::Text::set_font(const Ref<Font> &p_font) { font = p_font; @@ -121,7 +129,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { int len = text[p_line].data.length(); const CharType *str = text[p_line].data.c_str(); - //update width + // Update width. for (int i = 0; i < len; i++) { w += get_char_width(str[i], str[i + 1], w); @@ -131,7 +139,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { text.write[p_line].wrap_amount_cache = -1; - //update regions + // Update regions. text.write[p_line].region_info.clear(); @@ -140,7 +148,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { if (!_is_symbol(str[i])) continue; if (str[i] == '\\') { - i++; //skip quoted anything + i++; // Skip quoted anything. continue; } @@ -267,7 +275,7 @@ void TextEdit::Text::clear() { } int TextEdit::Text::get_max_width(bool p_exclude_hidden) const { - //quite some work.. but should be fast enough. + // Quite some work, but should be fast enough. int max = 0; for (int i = 0; i < text.size(); i++) { @@ -292,6 +300,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) { line.marked = false; line.safe = false; line.breakpoint = false; + line.bookmark = false; line.hidden = false; line.width_cache = -1; line.wrap_amount_cache = -1; @@ -314,7 +323,7 @@ int TextEdit::Text::get_char_width(CharType c, CharType next_c, int px) const { if (left == 0) w = tab_w; else - w = tab_w - px % tab_w; // is right... + w = tab_w - px % tab_w; // Is right. } else { w = font->get_char_size(c, next_c).width; @@ -346,31 +355,37 @@ void TextEdit::_update_scrollbars() { if (line_numbers) total_width += cache.line_number_w; - if (draw_breakpoint_gutter) { + if (draw_breakpoint_gutter || draw_bookmark_gutter) { total_width += cache.breakpoint_gutter_width; } + if (draw_info_gutter) { + total_width += cache.info_gutter_width; + } + if (draw_fold_gutter) { total_width += cache.fold_gutter_width; } + if (draw_minimap) { + total_width += cache.minimap_width; + } + bool use_hscroll = true; bool use_vscroll = true; + // Thanks yessopie for this clever bit of logic. if (total_rows <= visible_rows && total_width <= visible_width) { - //thanks yessopie for this clever bit of logic + use_hscroll = false; use_vscroll = false; - } else { if (total_rows > visible_rows && total_width <= visible_width) { - //thanks yessopie for this clever bit of logic use_hscroll = false; } if (total_rows <= visible_rows && total_width > visible_width) { - //thanks yessopie for this clever bit of logic use_vscroll = false; } } @@ -394,6 +409,7 @@ void TextEdit::_update_scrollbars() { cursor.line_ofs = 0; cursor.wrap_ofs = 0; v_scroll->set_value(0); + v_scroll->set_max(0); v_scroll->hide(); } @@ -412,6 +428,7 @@ void TextEdit::_update_scrollbars() { cursor.x_ofs = 0; h_scroll->set_value(0); + h_scroll->set_max(0); h_scroll->hide(); } @@ -444,6 +461,7 @@ void TextEdit::_click_selection_held() { } void TextEdit::_update_selection_mode_pointer() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -459,6 +477,7 @@ void TextEdit::_update_selection_mode_pointer() { } void TextEdit::_update_selection_mode_word() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -466,7 +485,7 @@ void TextEdit::_update_selection_mode_word() { String line = text[row]; int beg = CLAMP(col, 0, line.length()); - // if its the first selection and on whitespace make sure we grab the word instead.. + // If its the first selection and on whitespace make sure we grab the word instead. if (!selection.active) { while (beg > 0 && line[beg] <= 32) { beg--; @@ -475,7 +494,7 @@ void TextEdit::_update_selection_mode_word() { int end = beg; bool symbol = beg < line.length() && _is_symbol(line[beg]); - // get the word end and begin points + // Get the word end and begin points. while (beg > 0 && line[beg - 1] > 32 && (symbol == _is_symbol(line[beg - 1]))) { beg--; } @@ -486,7 +505,7 @@ void TextEdit::_update_selection_mode_word() { end += 1; } - // initial selection + // Initial selection. if (!selection.active) { select(row, beg, row, end); selection.selecting_column = beg; @@ -515,6 +534,7 @@ void TextEdit::_update_selection_mode_word() { } void TextEdit::_update_selection_mode_line() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -522,11 +542,11 @@ void TextEdit::_update_selection_mode_line() { col = 0; if (row < selection.selecting_line) { - // cursor is above us + // Cursor is above us. cursor_set_line(row - 1, false); selection.selecting_column = text[selection.selecting_line].length(); } else { - // cursor is below us + // Cursor is below us. cursor_set_line(row + 1, false); selection.selecting_column = 0; col = text[row].length(); @@ -539,6 +559,48 @@ void TextEdit::_update_selection_mode_line() { click_select_held->start(); } +void TextEdit::_update_minimap_click() { + Point2 mp = get_local_mouse_position(); + + int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); + if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) { + minimap_clicked = false; + return; + } + minimap_clicked = true; + dragging_minimap = true; + + int row; + _get_minimap_mouse_row(Point2i(mp.x, mp.y), row); + + if (row >= get_first_visible_line() && (row < get_last_visible_line() || row >= (text.size() - 1))) { + minimap_scroll_ratio = v_scroll->get_as_ratio(); + minimap_scroll_click_pos = mp.y; + can_drag_minimap = true; + return; + } + + int wi; + int first_line = row - num_lines_from_rows(row, 0, -get_visible_rows() / 2, wi) + 1; + double delta = get_scroll_pos_for_line(first_line, wi) - get_v_scroll(); + if (delta < 0) { + _scroll_up(-delta); + } else { + _scroll_down(delta); + } +} + +void TextEdit::_update_minimap_drag() { + + if (!can_drag_minimap) { + return; + } + + Point2 mp = get_local_mouse_position(); + double diff = (mp.y - minimap_scroll_click_pos) / _get_control_height(); + v_scroll->set_as_ratio(minimap_scroll_ratio + diff); +} + void TextEdit::_notification(int p_what) { switch (p_what) { @@ -554,12 +616,13 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_RESIZED: { _update_scrollbars(); - call_deferred("_update_wrap_at"); + _update_wrap_at(); } break; case NOTIFICATION_THEME_CHANGED: { _update_caches(); _update_wrap_at(); + syntax_highlighting_cache.clear(); } break; case MainLoop::NOTIFICATION_WM_FOCUS_IN: { window_has_focus = true; @@ -575,33 +638,48 @@ void TextEdit::_notification(int p_what) { 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(); + // To ensure minimap is responsive overide the speed setting. + double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { set_v_scroll(target_v_scroll); scrolling = false; + minimap_clicked = false; set_physics_process_internal(false); } else { set_v_scroll(get_v_scroll() + vel); } } else { scrolling = false; + minimap_clicked = false; set_physics_process_internal(false); } } break; case NOTIFICATION_DRAW: { + if (first_draw) { + // Size may not be the final one, so attempts to ensure cursor was visible may have failed. + adjust_viewport_to_cursor(); + first_draw = false; + } Size2 size = get_size(); if ((!has_focus() && !menu->has_focus()) || !window_has_focus) { draw_caret = false; } - if (draw_breakpoint_gutter) { + if (draw_breakpoint_gutter || draw_bookmark_gutter) { breakpoint_gutter_width = (get_row_height() * 55) / 100; cache.breakpoint_gutter_width = breakpoint_gutter_width; } else { cache.breakpoint_gutter_width = 0; } + if (draw_info_gutter) { + info_gutter_width = (get_row_height()); + cache.info_gutter_width = info_gutter_width; + } else { + cache.info_gutter_width = 0; + } + if (draw_fold_gutter) { fold_gutter_width = (get_row_height() * 55) / 100; cache.fold_gutter_width = fold_gutter_width; @@ -609,6 +687,11 @@ void TextEdit::_notification(int p_what) { cache.fold_gutter_width = 0; } + cache.minimap_width = 0; + if (draw_minimap) { + cache.minimap_width = minimap_width; + } + int line_number_char_count = 0; { @@ -631,14 +714,13 @@ void TextEdit::_notification(int p_what) { RID ci = get_canvas_item(); VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); - int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width; - int xmargin_end = size.width - cache.style_normal->get_margin(MARGIN_RIGHT); - //let's do it easy for now: + int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; + + int xmargin_end = size.width - cache.style_normal->get_margin(MARGIN_RIGHT) - cache.minimap_width; + // Let's do it easy for now. cache.style_normal->draw(ci, Rect2(Point2(), size)); - float readonly_alpha = 1.0; // used to set the input text color when in read-only mode if (readonly) { cache.style_readonly->draw(ci, Rect2(Point2(), size)); - readonly_alpha = .5; draw_caret = false; } if (has_focus()) @@ -648,8 +730,7 @@ void TextEdit::_notification(int p_what) { int visible_rows = get_visible_rows() + 1; - Color color = cache.font_color; - color.a *= readonly_alpha; + Color color = readonly ? cache.font_color_readonly : cache.font_color; if (syntax_coloring) { if (cache.background_color.a > 0.01) { @@ -657,6 +738,13 @@ void TextEdit::_notification(int p_what) { } } + if (line_length_guideline) { + int x = xmargin_beg + (int)cache.font->get_char_size('0').width * line_length_guideline_col - cursor.x_ofs; + if (x > xmargin_beg && x < xmargin_end) { + VisualServer::get_singleton()->canvas_item_add_line(ci, Point2(x, 0), Point2(x, size.height), cache.line_length_guideline_color); + } + } + int brace_open_match_line = -1; int brace_open_match_column = -1; bool brace_open_matching = false; @@ -666,10 +754,10 @@ void TextEdit::_notification(int p_what) { bool brace_close_matching = false; bool brace_close_mismatch = false; - if (brace_matching_enabled) { + if (brace_matching_enabled && cursor.line >= 0 && cursor.line < text.size() && cursor.column >= 0) { if (cursor.column < text[cursor.line].length()) { - //check for open + // Check for open. CharType c = text[cursor.line][cursor.column]; CharType closec = 0; @@ -691,7 +779,7 @@ void TextEdit::_notification(int p_what) { for (int j = from; j < text[i].length(); j++) { CharType cc = text[i][j]; - //ignore any brackets inside a string + // Ignore any brackets inside a string. if (cc == '"' || cc == '\'') { CharType quotation = cc; do { @@ -700,7 +788,7 @@ void TextEdit::_notification(int p_what) { break; } cc = text[i][j]; - //skip over escaped quotation marks inside strings + // Skip over escaped quotation marks inside strings. if (cc == '\\') { bool escaped = true; while (j + 1 < text[i].length() && text[i][j + 1] == '\\') { @@ -757,7 +845,7 @@ void TextEdit::_notification(int p_what) { for (int j = from; j >= 0; j--) { CharType cc = text[i][j]; - //ignore any brackets inside a string + // Ignore any brackets inside a string. if (cc == '"' || cc == '\'') { CharType quotation = cc; do { @@ -766,7 +854,7 @@ void TextEdit::_notification(int p_what) { break; } cc = text[i][j]; - //skip over escaped quotation marks inside strings + // Skip over escaped quotation marks inside strings. if (cc == quotation) { bool escaped = false; while (j - 1 >= 0 && text[i][j - 1] == '\\') { @@ -805,18 +893,168 @@ void TextEdit::_notification(int p_what) { Point2 cursor_pos; int cursor_insert_offset_y = 0; - // get the highlighted words + // Get the highlighted words. String highlighted_text = get_selection_text(); + // Check if highlighted words contains only whitespaces (tabs or spaces). + bool only_whitespaces_highlighted = highlighted_text.strip_edges() == String(); + String line_num_padding = line_numbers_zero_padded ? "0" : " "; int cursor_wrap_index = get_cursor_wrap_index(); FontDrawer drawer(cache.font, Color(1, 1, 1)); - int line = get_first_visible_line() - 1; + int first_visible_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += times_line_wraps(line + 1); + draw_amount += times_line_wraps(first_visible_line + 1); + + // minimap + if (draw_minimap) { + int minimap_visible_lines = _get_minimap_visible_rows(); + int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); + int minimap_tab_size = minimap_char_size.x * indent_size; + + // calculate viewport size and y offset + int viewport_height = (draw_amount - 1) * minimap_line_height; + int control_height = _get_control_height() - viewport_height; + int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + + // calculate the first line. + int num_lines_before = round((viewport_offset_y) / minimap_line_height); + int wi; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + if (minimap_line >= 0) { + minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); + minimap_line -= (smooth_scroll_enabled ? 1 : 0); + } + int minimap_draw_amount = minimap_visible_lines + times_line_wraps(minimap_line + 1); + + // draw the minimap + Color viewport_color = (cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, cache.minimap_width, viewport_height), viewport_color); + for (int i = 0; i < minimap_draw_amount; i++) { + + minimap_line++; + + if (minimap_line < 0 || minimap_line >= (int)text.size()) { + break; + } + + while (is_line_hidden(minimap_line)) { + minimap_line++; + if (minimap_line < 0 || minimap_line >= (int)text.size()) { + break; + } + } + + Map<int, HighlighterInfo> color_map; + if (syntax_coloring) { + color_map = _get_line_syntax_highlighting(minimap_line); + } + + Color current_color = cache.font_color; + if (readonly) { + current_color = cache.font_color_readonly; + } + + Vector<String> wrap_rows = get_wrap_rows_text(minimap_line); + int line_wrap_amount = times_line_wraps(minimap_line); + int last_wrap_column = 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 >= minimap_draw_amount) + break; + } + + const String &str = wrap_rows[line_wrap_index]; + int indent_px = line_wrap_index != 0 ? get_indent_level(minimap_line) : 0; + if (indent_px >= wrap_at) { + indent_px = 0; + } + indent_px = minimap_char_size.x * indent_px; + + if (line_wrap_index > 0) { + last_wrap_column += wrap_rows[line_wrap_index - 1].length(); + } + + if (minimap_line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color); + } + + Color previous_color; + int characters = 0; + int tabs = 0; + for (int j = 0; j < str.length(); j++) { + 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 = cache.font_color_readonly.a; + } + } + color = current_color; + } + + if (j == 0) { + previous_color = color; + } + + int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs; + bool out_of_bounds = (xpos >= xmargin_end + cache.minimap_width); + + bool is_whitespace = _is_whitespace(str[j]); + if (!is_whitespace) { + characters++; + + if (j < str.length() - 1 && color == previous_color && !out_of_bounds) { + continue; + } + + // If we've changed colour we are at the start of a new section, therefore we need to go back to the end + // of the previous section to draw it, we'll also add the character back on. + if (color != previous_color) { + characters--; + j--; + + if (str[j] == '\t') { + tabs -= minimap_tab_size; + } + } + } + + if (characters > 0) { + previous_color.a *= 0.6; + // take one for zero indexing, and if we hit whitespace / the end of a word. + int chars = MAX(0, (j - (characters - 1)) - (is_whitespace ? 1 : 0)) + 1; + int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color); + } + + if (out_of_bounds) { + break; + } + + // re-adjust if we went backwards. + if (color != previous_color && !is_whitespace) { + characters++; + } + + if (str[j] == '\t') { + tabs += minimap_tab_size; + } + + previous_color = color; + characters = 0; + } + } + } + } + + // draw main text + int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -840,11 +1078,8 @@ void TextEdit::_notification(int p_what) { 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) { - current_color.a *= readonly_alpha; - } + // Ensure we at least use the font color. + Color current_color = readonly ? cache.font_color_readonly : cache.font_color; bool underlined = false; @@ -861,6 +1096,9 @@ void TextEdit::_notification(int p_what) { 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 (indent_px >= wrap_at) { + indent_px = 0; + } if (line_wrap_index > 0) last_wrap_column += wrap_rows[line_wrap_index - 1].length(); @@ -881,7 +1119,7 @@ void TextEdit::_notification(int p_what) { if (smooth_scroll_enabled) ofs_y += (-get_v_scroll_offset()) * get_row_height(); - // check if line contains highlighted word + // Check if line contains highlighted word. int highlighted_text_col = -1; int search_text_col = -1; int highlighted_word_col = -1; @@ -903,25 +1141,25 @@ void TextEdit::_notification(int p_what) { } if (str.length() == 0) { - // draw line background if empty as we won't loop at at all + // 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); } - // give visual indication of empty selected line + // 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 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); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg + ofs_x, get_row_height()), cache.current_line_color); } } if (line_wrap_index == 0) { - // only do these if we are on the first wrapped part of a line + // Only do these if we are on the first wrapped part of a line. if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { #ifdef TOOLS_ENABLED @@ -931,22 +1169,75 @@ void TextEdit::_notification(int p_what) { #endif } - // draw breakpoint marker + // Draw bookmark marker. + if (text.is_bookmark(line)) { + if (draw_bookmark_gutter) { + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; + int marker_radius = get_row_height() - (vertical_gap * 2); + VisualServer::get_singleton()->canvas_item_add_circle(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2 + marker_radius / 2, ofs_y + vertical_gap + marker_radius / 2), marker_radius, Color(cache.bookmark_color.r, cache.bookmark_color.g, cache.bookmark_color.b)); + } + } + + // 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 + // 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)); } } - // draw fold markers + // Draw info icons. + if (draw_info_gutter && text.has_info_icon(line)) { + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.info_gutter_width * 30) / 100; + int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width; + + Ref<Texture> info_icon = text.get_info_icon(line); + // Ensure the icon fits the gutter size. + Size2i icon_size = info_icon->get_size(); + if (icon_size.width > cache.info_gutter_width - horizontal_gap) { + icon_size.width = cache.info_gutter_width - horizontal_gap; + } + if (icon_size.height > get_row_height() - horizontal_gap) { + icon_size.height = get_row_height() - horizontal_gap; + } + + Size2i icon_pos; + int xofs = horizontal_gap - (info_icon->get_width() / 4); + int yofs = vertical_gap - (info_icon->get_height() / 4); + icon_pos.x = gutter_left + xofs + ofs_x; + icon_pos.y = ofs_y + yofs; + + draw_texture_rect(info_icon, Rect2(icon_pos, icon_size)); + } + + // Draw execution marker. + if (executing_line == line) { + if (draw_breakpoint_gutter) { + int icon_extra_size = 4; + 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) + icon_extra_size; + int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2) + icon_extra_size; + cache.executing_icon->draw_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2 - icon_extra_size / 2, ofs_y + vertical_gap - icon_extra_size / 2, marker_width, marker_height), false, Color(cache.executing_line_color.r, cache.executing_line_color.g, cache.executing_line_color.b)); + } else { +#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.executing_line_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.executing_line_color); +#endif + } + } + + // 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; + int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w + cache.info_gutter_width; 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; @@ -958,7 +1249,7 @@ void TextEdit::_notification(int p_what) { } } - // draw line numbers + // Draw line numbers. if (cache.line_number_w) { int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; String fc = String::num(line + 1); @@ -966,18 +1257,18 @@ void TextEdit::_notification(int p_what) { fc = line_num_padding + fc; } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color); + cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.info_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color); } } - //loop through characters in one line + // Loop through characters in one line. for (int j = 0; j < str.length(); j++) { 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; + if (readonly && current_color.a > cache.font_color_readonly.a) { + current_color.a = cache.font_color_readonly.a; } } color = current_color; @@ -985,20 +1276,20 @@ void TextEdit::_notification(int p_what) { int char_w; - //handle tabulator + // Handle tabulator. char_w = text.get_char_width(str[j], str[j + 1], char_ofs); if ((char_ofs + char_margin) < xmargin_beg) { char_ofs += char_w; - // line highlighting handle horizontal clipping + // Line highlighting handle horizontal clipping. if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { if (j == str.length() - 1) { - // end of line when last char is skipped + // 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 + // 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); } } @@ -1006,16 +1297,13 @@ void TextEdit::_notification(int p_what) { } if ((char_ofs + char_margin + char_w) >= xmargin_end) { - if (syntax_coloring) - continue; - else - break; + break; } bool in_search_result = false; if (search_text_col != -1) { - // if we are at the end check for new search result on same line + // 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); @@ -1026,19 +1314,19 @@ void TextEdit::_notification(int p_what) { } } - //current line highlighting + // 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 (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { - // draw the wrap indent offset highlight + // 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); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + ofs_x - indent_px, ofs_y, indent_px, get_row_height()), cache.current_line_color); } - // if its the last char draw to end of the 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); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); } - // actual text + // 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); } @@ -1060,17 +1348,17 @@ void TextEdit::_notification(int p_what) { 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 (highlight_all_occurrences && !only_whitespaces_highlighted) { if (highlighted_text_col != -1) { - // if we are at the end check for new word on same line + // 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 in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length()); - // if this is the original highlighted text we don't want to highlight it again + // 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; } @@ -1095,7 +1383,7 @@ void TextEdit::_notification(int p_what) { if (brace_open_mismatch) color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color); } if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) || @@ -1103,7 +1391,7 @@ void TextEdit::_notification(int p_what) { if (brace_close_mismatch) color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color); } } @@ -1169,24 +1457,28 @@ void TextEdit::_notification(int p_what) { 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; + color = readonly ? cache.font_color_readonly : cache.font_color; } if (str[j] >= 32) { int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; - int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color); if (underlined) { float line_width = 1.0; #ifdef TOOLS_ENABLED line_width *= EDSCALE; #endif - draw_rect(Rect2(char_ofs + char_margin + ofs_x, yofs + ascent + 2, w, line_width), in_selection && override_selected_font_color ? cache.font_selected_color : color); + draw_rect(Rect2(char_ofs + char_margin + ofs_x, yofs + ascent + 2, w, line_width), in_selection && override_selected_font_color ? cache.font_color_selected : 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); + cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_color_selected : color); + } + + if (draw_spaces && str[j] == ' ') { + int yofs = (get_row_height() - cache.space_icon->get_height()) / 2; + cache.space_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_color_selected : color); } char_ofs += char_w; @@ -1261,16 +1553,9 @@ void TextEdit::_notification(int p_what) { } } - if (line_length_guideline) { - int x = xmargin_beg + cache.font->get_char_size('0').width * line_length_guideline_col - cursor.x_ofs; - if (x > xmargin_beg && x < xmargin_end) { - VisualServer::get_singleton()->canvas_item_add_line(ci, Point2(x, 0), Point2(x, size.height), cache.line_length_guideline_color); - } - } - bool completion_below = false; if (completion_active) { - // code completion box + // Code completion box. Ref<StyleBox> csb = get_stylebox("completion"); int maxlines = get_constant("completion_lines"); int cmax_width = get_constant("completion_max_width") * cache.font->get_char_size('x').x; @@ -1284,7 +1569,7 @@ void TextEdit::_notification(int p_what) { if (completion_options.size() < 50) { for (int i = 0; i < completion_options.size(); i++) { - int w2 = MIN(cache.font->get_string_size(completion_options[i]).x, cmax_width); + int w2 = MIN(cache.font->get_string_size(completion_options[i].display).x, cmax_width); if (w2 > w) w = w2; } @@ -1292,6 +1577,11 @@ void TextEdit::_notification(int p_what) { w = cmax_width; } + // Add space for completion icons. + const int icon_hsep = get_constant("hseparation", "ItemList"); + Size2 icon_area_size(get_row_height(), get_row_height()); + w += icon_area_size.width + icon_hsep; + int th = h + csb->get_minimum_size().y; if (cursor_pos.y + get_row_height() + th > get_size().height) { @@ -1319,7 +1609,7 @@ void TextEdit::_notification(int p_what) { } int line_from = CLAMP(completion_index - lines / 2, 0, completion_options.size() - lines); VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(completion_rect.position.x, completion_rect.position.y + (completion_index - line_from) * get_row_height()), Size2(completion_rect.size.width, get_row_height())), cache.completion_selected_color); - draw_rect(Rect2(completion_rect.position, Size2(nofs, completion_rect.size.height)), cache.completion_existing_color); + draw_rect(Rect2(completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(nofs, completion_rect.size.width - (icon_area_size.x + icon_hsep)), completion_rect.size.height)), cache.completion_existing_color); for (int i = 0; i < lines; i++) { @@ -1327,16 +1617,30 @@ void TextEdit::_notification(int p_what) { ERR_CONTINUE(l < 0 || l >= completion_options.size()); Color text_color = cache.completion_font_color; for (int j = 0; j < color_regions.size(); j++) { - if (completion_options[l].begins_with(color_regions[j].begin_key)) { + if (completion_options[l].insert_text.begins_with(color_regions[j].begin_key)) { text_color = color_regions[j].color; } } int yofs = (get_row_height() - cache.font->get_height()) / 2; - draw_string(cache.font, Point2(completion_rect.position.x, completion_rect.position.y + i * get_row_height() + cache.font->get_ascent() + yofs), completion_options[l], text_color, completion_rect.size.width); + Point2 title_pos(completion_rect.position.x, completion_rect.position.y + i * get_row_height() + cache.font->get_ascent() + yofs); + + // Draw completion icon if it is valid. + Ref<Texture> icon = completion_options[l].icon; + Rect2 icon_area(completion_rect.position.x, completion_rect.position.y + i * get_row_height(), icon_area_size.width, icon_area_size.height); + if (icon.is_valid()) { + const real_t max_scale = 0.7f; + const real_t side = max_scale * icon_area.size.width; + real_t scale = MIN(side / icon->get_width(), side / icon->get_height()); + Size2 icon_size = icon->get_size() * scale; + draw_texture_rect(icon, Rect2(icon_area.position + (icon_area.size - icon_size) / 2, icon_size)); + } + + title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep; + draw_string(cache.font, title_pos, completion_options[l].display, text_color, completion_rect.size.width - (icon_area_size.x + icon_hsep)); } if (scrollw) { - //draw a small scroll rectangle to show a position in the options + // Draw a small scroll rectangle to show a position in the options. float r = maxlines / (float)completion_options.size(); float o = line_from / (float)completion_options.size(); draw_rect(Rect2(completion_rect.position.x + completion_rect.size.width, completion_rect.position.y + o * completion_rect.size.y, scrollw, completion_rect.size.y * r), scrollc); @@ -1345,7 +1649,7 @@ void TextEdit::_notification(int p_what) { completion_line_ofs = line_from; } - // check to see if the hint should be drawn + // Check to see if the hint should be drawn. bool show_hint = false; if (completion_hint != "") { if (completion_active) { @@ -1381,8 +1685,8 @@ void TextEdit::_notification(int p_what) { } } - Size2 size = Size2(max_w, sc * font->get_height() + spacing); - Size2 minsize = size + sb->get_minimum_size(); + Size2 size2 = Size2(max_w, sc * font->get_height() + spacing); + Size2 minsize = size2 + sb->get_minimum_size(); if (completion_hint_offset == -0xFFFF) { completion_hint_offset = cursor_pos.x - offset; @@ -1422,7 +1726,6 @@ void TextEdit::_notification(int p_what) { OS::get_singleton()->set_ime_active(true); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height())); } - } break; case NOTIFICATION_FOCUS_ENTER: { @@ -1494,8 +1797,7 @@ void TextEdit::_consume_pair_symbol(CharType ch) { } if ((ch == '\'' || ch == '"') && - cursor_get_column() > 0 && - _is_text_char(text[cursor.line][cursor_get_column() - 1])) { + cursor_get_column() > 0 && _is_text_char(text[cursor.line][cursor_get_column() - 1]) && !_is_pair_right_symbol(text[cursor.line][cursor_get_column()])) { insert_text_at_cursor(ch_single); cursor_set_column(cursor_position_to_move); return; @@ -1516,7 +1818,6 @@ void TextEdit::_consume_pair_symbol(CharType ch) { insert_text_at_cursor(ch_pair); cursor_set_column(cursor_position_to_move); - return; } void TextEdit::_consume_backspace_for_pair_symbol(int prev_line, int prev_column) { @@ -1557,37 +1858,34 @@ void TextEdit::backspace_at_cursor() { set_line_as_breakpoint(prev_line, true); } + if (text.has_info_icon(cursor.line)) { + set_line_info_icon(prev_line, text.get_info_icon(cursor.line), text.get_info(cursor.line)); + } + if (auto_brace_completion_enabled && cursor.column > 0 && _is_pair_left_symbol(text[cursor.line][cursor.column - 1])) { _consume_backspace_for_pair_symbol(prev_line, prev_column); } else { - // handle space indentation - if (cursor.column - indent_size >= 0 && indent_using_spaces) { - - // if there is enough spaces to count as a tab + // Handle space indentation. + if (cursor.column != 0 && indent_using_spaces) { + // Check if there are no other chars before cursor, just indentation. bool unindent = true; - for (int i = 1; i <= indent_size; i++) { - if (text[cursor.line][cursor.column - i] != ' ') { - unindent = false; - break; - } - } - - // and it is before the first character int i = 0; while (i < cursor.column && i < text[cursor.line].length()) { - if (text[cursor.line][i] != ' ' && text[cursor.line][i] != '\t') { + if (!_is_whitespace(text[cursor.line][i])) { unindent = false; break; } i++; } - // then we can remove it as a single character. + // Then we can remove all spaces as a single character. if (unindent) { - _remove_text(cursor.line, cursor.column - indent_size, cursor.line, cursor.column); - prev_column = cursor.column - indent_size; + // We want to remove spaces up to closest indent, or whole indent if cursor is pointing at it. + int spaces_to_delete = _calculate_spaces_till_next_left_indent(cursor.column); + prev_column = cursor.column - spaces_to_delete; + _remove_text(cursor.line, prev_column, cursor.line, cursor.column); } else { _remove_text(prev_line, prev_column, cursor.line, cursor.column); } @@ -1604,6 +1902,10 @@ void TextEdit::indent_right() { int start_line; int end_line; + + // This value informs us by how much we changed selection position by indenting right. + // Default is 1 for tab indentation. + int selection_offset = 1; begin_complex_operation(); if (is_selection_active()) { @@ -1614,7 +1916,7 @@ void TextEdit::indent_right() { end_line = start_line; } - // ignore if the cursor is not past the first column + // Ignore if the cursor is not past the first column. if (is_selection_active() && get_selection_to_column() == 0) { end_line--; } @@ -1622,18 +1924,24 @@ void TextEdit::indent_right() { for (int i = start_line; i <= end_line; i++) { String line_text = get_line(i); if (indent_using_spaces) { - line_text = space_indent + line_text; + // We don't really care where selection is - we just need to know indentation level at the beginning of the line. + int left = _find_first_non_whitespace_column_of_line(line_text); + int spaces_to_add = _calculate_spaces_till_next_right_indent(left); + // Since we will add this much spaces we want move whole selection and cursor by this much. + selection_offset = spaces_to_add; + for (int j = 0; j < spaces_to_add; j++) + line_text = ' ' + line_text; } else { line_text = '\t' + line_text; } set_line(i, line_text); } - // fix selection and cursor being off by one on the last line + // Fix selection and cursor being off after shifting selection right. if (is_selection_active()) { - select(selection.from_line, selection.from_column + 1, selection.to_line, selection.to_column + 1); + select(selection.from_line, selection.from_column + selection_offset, selection.to_line, selection.to_column + selection_offset); } - cursor_set_column(cursor.column + 1, false); + cursor_set_column(cursor.column + selection_offset, false); end_complex_operation(); update(); } @@ -1642,6 +1950,14 @@ void TextEdit::indent_left() { int start_line; int end_line; + + // Moving cursor and selection after unindenting can get tricky because + // changing content of line can move cursor and selection on it's own (if new line ends before previous position of either), + // therefore we just remember initial values and at the end of the operation offset them by number of removed characters. + int removed_characters = 0; + int initial_selection_end_column = selection.to_column; + int initial_cursor_column = cursor.column; + begin_complex_operation(); if (is_selection_active()) { @@ -1652,7 +1968,7 @@ void TextEdit::indent_left() { end_line = start_line; } - // ignore if the cursor is not past the first column + // Ignore if the cursor is not past the first column. if (is_selection_active() && get_selection_to_column() == 0) { end_line--; } @@ -1664,21 +1980,43 @@ void TextEdit::indent_left() { if (line_text.begins_with("\t")) { line_text = line_text.substr(1, line_text.length()); set_line(i, line_text); - } else if (line_text.begins_with(space_indent)) { - line_text = line_text.substr(indent_size, line_text.length()); + removed_characters = 1; + } else if (line_text.begins_with(" ")) { + // When unindenting we aim to remove spaces before line that has selection no matter what is selected, + // so we start of by finding first non whitespace character of line + int left = _find_first_non_whitespace_column_of_line(line_text); + + // Here we remove only enough spaces to align text to nearest full multiple of indentation_size. + // In case where selection begins at the start of indentation_size multiple we remove whole indentation level. + int spaces_to_remove = _calculate_spaces_till_next_left_indent(left); + + line_text = line_text.substr(spaces_to_remove, line_text.length()); set_line(i, line_text); + removed_characters = spaces_to_remove; } } - // fix selection and cursor being off by one on the last line + // Fix selection and cursor being off by one on the last line. if (is_selection_active() && last_line_text != get_line(end_line)) { - select(selection.from_line, selection.from_column - 1, selection.to_line, selection.to_column - 1); + select(selection.from_line, selection.from_column - removed_characters, + selection.to_line, initial_selection_end_column - removed_characters); } - cursor_set_column(cursor.column - 1, false); + cursor_set_column(initial_cursor_column - removed_characters, false); end_complex_operation(); update(); } +int TextEdit::_calculate_spaces_till_next_left_indent(int column) { + int spaces_till_indent = column % indent_size; + if (spaces_till_indent == 0) + spaces_till_indent = indent_size; + return spaces_till_indent; +} + +int TextEdit::_calculate_spaces_till_next_right_indent(int column) { + return indent_size - column % indent_size; +} + void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const { float rows = p_mouse.y; @@ -1699,7 +2037,7 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co } if (row < 0) - row = 0; //todo + row = 0; // TODO. int col = 0; @@ -1709,15 +2047,15 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co col = text[row].size(); } else { - int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); + int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_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); + // Move back one if we are at the end of the row. + Vector<String> rows2 = 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(); + row_end_col += rows2[i].length(); } if (col >= row_end_col) col -= 1; @@ -1728,8 +2066,66 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co r_col = col; } +void TextEdit::_get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const { + + float rows = p_mouse.y; + rows -= cache.style_normal->get_margin(MARGIN_TOP); + rows /= (minimap_char_size.y + minimap_line_spacing); + rows += get_v_scroll_offset(); + + // calculate visible lines + int minimap_visible_lines = _get_minimap_visible_rows(); + int visible_rows = get_visible_rows() + 1; + int first_visible_line = get_first_visible_line() - 1; + int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + draw_amount += times_line_wraps(first_visible_line + 1); + int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); + + // calculate viewport size and y offset + int viewport_height = (draw_amount - 1) * minimap_line_height; + int control_height = _get_control_height() - viewport_height; + int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + + // calculate the first line. + int num_lines_before = round((viewport_offset_y) / minimap_line_height); + int wi; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + if (first_visible_line > 0 && minimap_line >= 0) { + minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); + minimap_line -= (smooth_scroll_enabled ? 1 : 0); + } else { + minimap_line = 0; + } + + int row = minimap_line + Math::floor(rows); + int wrap_index = 0; + + if (is_wrap_enabled() || is_hiding_enabled()) { + + int f_ofs = num_lines_from_rows(minimap_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; + if (rows < 0) { + row = minimap_line - f_ofs; + } else { + row = minimap_line + f_ofs; + } + } + + if (row < 0) { + row = 0; + } + + if (row >= text.size()) { + row = text.size() - 1; + } + + r_row = row; +} + void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { + double prev_v_scroll = v_scroll->get_value(); + double prev_h_scroll = h_scroll->get_value(); + Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { @@ -1804,21 +2200,31 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // toggle breakpoint on gutter click + // Toggle breakpoint on gutter click. if (draw_breakpoint_gutter) { int gutter = cache.style_normal->get_margin(MARGIN_LEFT); - if (mb->get_position().x > gutter && mb->get_position().x <= gutter + cache.breakpoint_gutter_width + 3) { + if (mb->get_position().x > gutter - 6 && mb->get_position().x <= gutter + cache.breakpoint_gutter_width - 3) { set_line_as_breakpoint(row, !is_line_set_as_breakpoint(row)); emit_signal("breakpoint_toggled", row); return; } } - // toggle fold on gutter click if can + // Emit info clicked. + if (draw_info_gutter && text.has_info_icon(row)) { + int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); + int gutter_left = left_margin + cache.breakpoint_gutter_width; + if (mb->get_position().x > gutter_left - 6 && mb->get_position().x <= gutter_left + cache.info_gutter_width - 3) { + emit_signal("info_clicked", row, text.get_info(row)); + return; + } + } + + // Toggle fold on gutter click if can. if (draw_fold_gutter) { int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); - int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w; + int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w + cache.info_gutter_width; if (mb->get_position().x > gutter_left - 6 && mb->get_position().x <= gutter_left + cache.fold_gutter_width - 3) { if (is_folded(row)) { unfold_line(row); @@ -1829,16 +2235,24 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } - // unfold on folded icon click + // Unfold on folded icon click. if (is_folded(row)) { int line_width = text.get_line_width(row); - line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width - cursor.x_ofs; + line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.info_gutter_width + cache.fold_gutter_width - cursor.x_ofs; if (mb->get_position().x > line_width - 3 && mb->get_position().x <= line_width + cache.folded_eol_icon->get_width() + 3) { unfold_line(row); return; } } + // minimap + if (draw_minimap) { + _update_minimap_click(); + if (dragging_minimap) { + return; + } + } + int prev_col = cursor.column; int prev_line = cursor.line; @@ -1896,9 +2310,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } else { - //if sel active and dblick last time < something - - //else selection.active = false; selection.selecting_mode = Selection::MODE_POINTER; selection.selecting_line = row; @@ -1906,14 +2317,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } if (!mb->is_doubleclick() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < 600 && cursor.line == prev_line) { - //tripleclick select line + + // Triple-click select line. selection.selecting_mode = Selection::MODE_LINE; _update_selection_mode_line(); last_dblclk = 0; - } else if (mb->is_doubleclick() && text[cursor.line].length()) { - //doubleclick select word + // Double-click select word. selection.selecting_mode = Selection::MODE_WORD; _update_selection_mode_word(); last_dblclk = OS::get_singleton()->get_ticks_msec(); @@ -1938,7 +2349,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int to_column = get_selection_to_column(); if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { - // Right click is outside the selected text + // Right click is outside the selected text. deselect(); } } @@ -1950,15 +2361,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { menu->set_position(get_global_transform().xform(get_local_mouse_position())); menu->set_size(Vector2(1, 1)); + menu->set_scale(get_global_transform().get_scale()); menu->popup(); grab_focus(); } } else { - if (mb->get_button_index() == BUTTON_LEFT) + if (mb->get_button_index() == BUTTON_LEFT) { + dragging_minimap = false; + dragging_selection = false; + can_drag_minimap = false; click_select_held->stop(); + } - // notify to show soft keyboard + // Notify to show soft keyboard. notification(NOTIFICATION_FOCUS_ENTER); } } @@ -1973,6 +2389,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _scroll_down(delta); } h_scroll->set_value(h_scroll->get_value() + pan_gesture->get_delta().x * 100); + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); // Accept event if scroll changed. + return; } @@ -1996,31 +2415,40 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } - if (mm->get_button_mask() & BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { //ignore if dragging + if (mm->get_button_mask() & BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); - switch (selection.selecting_mode) { - case Selection::MODE_POINTER: { - _update_selection_mode_pointer(); - } break; - case Selection::MODE_WORD: { - _update_selection_mode_word(); - } break; - case Selection::MODE_LINE: { - _update_selection_mode_line(); - } break; - default: { - break; + if (draw_minimap && !dragging_selection) { + _update_minimap_drag(); + } + + if (!dragging_minimap) { + switch (selection.selecting_mode) { + case Selection::MODE_POINTER: { + _update_selection_mode_pointer(); + } break; + case Selection::MODE_WORD: { + _update_selection_mode_word(); + } break; + case Selection::MODE_LINE: { + _update_selection_mode_line(); + } break; + default: { + break; + } } } } } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); // Accept event if scroll changed. + Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { - k = k->duplicate(); //it will be modified later on + k = k->duplicate(); // It will be modified later on. #ifdef OSX_ENABLED if (k->get_scancode() == KEY_META) { @@ -2156,11 +2584,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _consume_pair_symbol(chr[0]); } else { - // remove the old character if in insert mode + // Remove the old character if in insert mode. if (insert_mode) { begin_complex_operation(); - // make sure we don't try and remove empty space + // Make sure we don't try and remove empty space. if (cursor.column < get_line(cursor.line).length()) { _remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); } @@ -2182,9 +2610,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_completion(); } - /* TEST CONTROL FIRST!! */ + /* TEST CONTROL FIRST! */ - // some remaps for duplicate functions.. + // Some remaps for duplicate functions. if (k->get_command() && !k->get_shift() && !k->get_alt() && !k->get_metakey() && k->get_scancode() == KEY_INSERT) { k->set_scancode(KEY_C); @@ -2195,17 +2623,44 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { k->set_command(true); k->set_shift(false); } +#ifdef APPLE_STYLE_KEYS + if (k->get_control() && !k->get_shift() && !k->get_alt() && !k->get_command()) { + uint32_t remap_key = KEY_UNKNOWN; + switch (k->get_scancode()) { + case KEY_F: { + remap_key = KEY_RIGHT; + } break; + case KEY_B: { + remap_key = KEY_LEFT; + } break; + case KEY_P: { + remap_key = KEY_UP; + } break; + case KEY_N: { + remap_key = KEY_DOWN; + } break; + case KEY_D: { + remap_key = KEY_DELETE; + } break; + case KEY_H: { + remap_key = KEY_BACKSPACE; + } break; + } + + if (remap_key != KEY_UNKNOWN) { + k->set_scancode(remap_key); + k->set_control(false); + } + } +#endif _reset_caret_blink_timer(); - // save here for insert mode, just in case it is cleared in the following section + // Save here for insert mode, just in case it is cleared in the following section. bool had_selection = selection.active; - // stuff to do when selection is active.. - if (selection.active) { - - if (readonly) - return; + // Stuff to do when selection is active. + if (!readonly && selection.active) { bool clear = false; bool unselect = false; @@ -2224,7 +2679,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_X: case KEY_C: - //special keys often used with control, wait... + // Special keys often used with control, wait. clear = (!k->get_command() || k->get_shift() || k->get_alt()); break; case KEY_DELETE: @@ -2249,7 +2704,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { case KEY_PAGEDOWN: case KEY_HOME: case KEY_END: - // ignore arrows if any modifiers are held (shift = selecting, others may be used for editor hotkeys) + // Ignore arrows if any modifiers are held (shift = selecting, others may be used for editor hotkeys). if (k->get_command() || k->get_shift() || k->get_alt()) break; unselect = true; @@ -2287,7 +2742,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { bool scancode_handled = true; - // special scancode test... + // Special scancode test. switch (k->get_scancode()) { @@ -2299,7 +2754,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { String ins = "\n"; - //keep indentation + // Keep indentation. int space_count = 0; for (int i = 0; i < cursor.column; i++) { if (text[cursor.line][i] == '\t') { @@ -2330,10 +2785,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { bool brace_indent = false; - // no need to indent if we are going upwards. + // No need to indent if we are going upwards. if (auto_indent && !(k->get_command() && k->get_shift())) { - // indent once again if previous line will end with ':' or '{' and the line is not a comment - // (i.e. colon/brace precedes current cursor position) + // Indent once again if previous line will end with ':' or '{' and the line is not a comment + // (i.e. colon/brace precedes current cursor position). if (cursor.column > 0 && (text[cursor.line][cursor.column - 1] == ':' || text[cursor.line][cursor.column - 1] == '{') && !is_line_comment(cursor.line)) { if (indent_using_spaces) { ins += space_indent; @@ -2341,7 +2796,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { ins += "\t"; } - // no need to move the brace below if we are not taking the text with us. + // No need to move the brace below if we are not taking the text with us. if (text[cursor.line][cursor.column] == '}' && !k->get_command()) { brace_indent = true; ins += "\n" + ins.substr(1, ins.length() - 2); @@ -2383,7 +2838,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } break; case KEY_TAB: { - if (k->get_command()) break; // avoid tab when command + if (k->get_command()) break; // Avoid tab when command. if (readonly) break; @@ -2397,15 +2852,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } else { if (k->get_shift()) { - //simple unindent + // Simple unindent. int cc = cursor.column; - - const int len = text[cursor.line].length(); const String &line = text[cursor.line]; - int left = 0; // number of whitespace chars at beginning of line - while (left < len && (line[left] == '\t' || line[left] == ' ')) - left++; + int left = _find_first_non_whitespace_column_of_line(line); cc = MIN(cc, left); while (cc < indent_size && cc < left && line[cc] == ' ') @@ -2413,24 +2864,18 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (cc > 0 && cc <= text[cursor.line].length()) { if (text[cursor.line][cc - 1] == '\t') { + // Tabs unindentation. _remove_text(cursor.line, cc - 1, cursor.line, cc); if (cursor.column >= left) cursor_set_column(MAX(0, cursor.column - 1)); update(); } else { - int n = 0; - - for (int i = 1; i <= MIN(cc, indent_size); i++) { - if (line[cc - i] != ' ') { - break; - } - n++; - } - - if (n > 0) { - _remove_text(cursor.line, cc - n, cursor.line, cc); - if (cursor.column > left - n) // inside text? - cursor_set_column(MAX(0, cursor.column - n)); + // Spaces unindentation. + int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); + if (spaces_to_remove > 0) { + _remove_text(cursor.line, cc - spaces_to_remove, cursor.line, cc); + if (cursor.column > left - spaces_to_remove) // Inside text? + cursor_set_column(MAX(0, cursor.column - spaces_to_remove)); update(); } } @@ -2439,9 +2884,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { update(); } } else { - //simple indent + // Simple indent. if (indent_using_spaces) { - _insert_text_at_cursor(space_indent); + // Insert only as much spaces as needed till next indentation level. + int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor.column); + String indent_to_insert = String(); + for (int i = 0; i < spaces_to_add; i++) + indent_to_insert = ' ' + indent_to_insert; + _insert_text_at_cursor(indent_to_insert); } else { _insert_text_at_cursor("\t"); } @@ -2464,20 +2914,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int line = cursor.line; int column = cursor.column; - // check if we are removing a single whitespace, if so remove it and the next char type - // else we just remove the whitespace + // Check if we are removing a single whitespace, if so remove it and the next char type, + // else we just remove the whitespace. bool only_whitespace = false; if (_is_whitespace(text[line][column - 1]) && _is_whitespace(text[line][column - 2])) { only_whitespace = true; } else if (_is_whitespace(text[line][column - 1])) { - // remove the single whitespace + // Remove the single whitespace. column--; } - // check if its a text char + // Check if its a text char. bool only_char = (_is_text_char(text[line][column - 1]) && !only_whitespace); - // if its not whitespace or char then symbol. + // If its not whitespace or char then symbol. bool only_symbols = !(only_whitespace || only_char); while (column > 0) { @@ -2517,7 +2967,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_left + FALLTHROUGH; } case KEY_LEFT: { @@ -2532,9 +2982,22 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { #ifdef APPLE_STYLE_KEYS if (k->get_command()) { - cursor_set_column(0); + // Start at first column (it's slightly faster that way) and look for the first non-whitespace character. + int new_cursor_pos = 0; + for (int i = 0; i < text[cursor.line].length(); ++i) { + if (!_is_whitespace(text[cursor.line][i])) { + new_cursor_pos = i; + break; + } + } + if (new_cursor_pos == cursor.column) { + // We're already at the first text character, so move to the very beginning of the line. + cursor_set_column(0); + } else { + // We're somewhere to the right of the first text character; move to the first one. + cursor_set_column(new_cursor_pos); + } } else if (k->get_alt()) { - #else if (k->get_alt()) { scancode_handled = false; @@ -2580,7 +3043,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_right + FALLTHROUGH; } case KEY_RIGHT: { @@ -2641,28 +3104,30 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_up + FALLTHROUGH; } case KEY_UP: { - if (k->get_shift()) - _pre_shift_selection(); if (k->get_alt()) { scancode_handled = false; break; } #ifndef APPLE_STYLE_KEYS if (k->get_command()) { - _scroll_lines_up(); - break; - } #else if (k->get_command() && k->get_alt()) { +#endif _scroll_lines_up(); break; } + if (k->get_shift()) { + _pre_shift_selection(); + } + +#ifdef APPLE_STYLE_KEYS if (k->get_command()) { + cursor_set_line(0); } else #endif @@ -2692,28 +3157,28 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_down + FALLTHROUGH; } case KEY_DOWN: { - if (k->get_shift()) - _pre_shift_selection(); if (k->get_alt()) { scancode_handled = false; break; } #ifndef APPLE_STYLE_KEYS if (k->get_command()) { - _scroll_lines_down(); - break; - } - #else if (k->get_command() && k->get_alt()) { +#endif _scroll_lines_down(); break; } + if (k->get_shift()) { + _pre_shift_selection(); + } + +#ifdef APPLE_STYLE_KEYS if (k->get_command()) { cursor_set_line(get_last_unhidden_line(), true, false, 9999); } else @@ -2748,7 +3213,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int curline_len = text[cursor.line].length(); if (cursor.line == text.size() - 1 && cursor.column == curline_len) - break; //nothing to do + break; // Nothing to do. int next_line = cursor.column < curline_len ? cursor.line : cursor.line + 1; int next_column; @@ -2765,20 +3230,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int line = cursor.line; int column = cursor.column; - // check if we are removing a single whitespace, if so remove it and the next char type - // else we just remove the whitespace + // Check if we are removing a single whitespace, if so remove it and the next char type, + // else we just remove the whitespace. bool only_whitespace = false; if (_is_whitespace(text[line][column]) && _is_whitespace(text[line][column + 1])) { only_whitespace = true; } else if (_is_whitespace(text[line][column])) { - // remove the single whitespace + // Remove the single whitespace. column++; } - // check if its a text char + // Check if its a text char. bool only_char = (_is_text_char(text[line][column]) && !only_whitespace); - // if its not whitespace or char then symbol. + // If its not whitespace or char then symbol. bool only_symbols = !(only_whitespace || only_char); while (column < curline_len) { @@ -2815,11 +3280,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_home + FALLTHROUGH; } -#ifdef APPLE_STYLE_KEYS case KEY_HOME: { - +#ifdef APPLE_STYLE_KEYS if (k->get_shift()) _pre_shift_selection(); @@ -2829,11 +3293,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _post_shift_selection(); else if (k->get_command() || k->get_control()) deselect(); - - } break; #else - case KEY_HOME: { - if (k->get_shift()) _pre_shift_selection(); @@ -2842,7 +3302,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_column(0); } else { - // move cursor column to start of wrapped row and then to start of text + // 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; @@ -2850,7 +3310,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { row_start_col += rows[i].length(); } if (cursor.column == row_start_col || wi == 0) { - // compute whitespace symbols seq length + // 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]; @@ -2874,19 +3334,17 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { deselect(); _cancel_completion(); completion_hint = ""; - - } break; #endif + } break; case KEY_KP_1: { if (k->get_unicode() != 0) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_end + FALLTHROUGH; } -#ifdef APPLE_STYLE_KEYS case KEY_END: { - +#ifdef APPLE_STYLE_KEYS if (k->get_shift()) _pre_shift_selection(); @@ -2896,18 +3354,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _post_shift_selection(); else if (k->get_command() || k->get_control()) deselect(); - - } break; #else - case KEY_END: { - if (k->get_shift()) _pre_shift_selection(); if (k->get_command()) cursor_set_line(get_last_unhidden_line(), true, false, 9999); - // move cursor column to end of wrapped row and then to end of text + // 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; @@ -2927,15 +3381,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_completion(); completion_hint = ""; - - } break; #endif + } break; case KEY_KP_9: { if (k->get_unicode() != 0) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_pageup + FALLTHROUGH; } case KEY_PAGEUP: { @@ -2958,7 +3411,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_pagedown + FALLTHROUGH; } case KEY_PAGEDOWN: { @@ -3061,6 +3514,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_Z: { + if (readonly) { + break; + } + if (!k->get_command()) { scancode_handled = false; break; @@ -3073,6 +3530,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_Y: { + if (readonly) { + break; + } + if (!k->get_command()) { scancode_handled = false; break; @@ -3094,7 +3555,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_SPACE: { #ifdef OSX_ENABLED - if (completion_enabled && k->get_metakey()) { //cmd-space is spotlight shortcut in OSX + if (completion_enabled && k->get_metakey()) { // cmd-space is spotlight shortcut in OSX #else if (completion_enabled && k->get_command()) { #endif @@ -3107,28 +3568,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; - case KEY_U: { - if (!k->get_command() || k->get_shift()) { - scancode_handled = false; - break; - } else { - if (selection.active) { - int ini = selection.from_line; - int end = selection.to_line; - - for (int i = ini; i <= end; i++) { - _uncomment_line(i); - } - } else { - _uncomment_line(cursor.line); - if (cursor.column >= get_line(cursor.line).length()) { - cursor.column = MAX(0, get_line(cursor.line).length() - 1); - } - } - update(); - } - } break; - default: { scancode_handled = false; @@ -3137,39 +3576,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (scancode_handled) accept_event(); - /* - if (!scancode_handled && !k->get_command() && !k->get_alt()) { - if (k->get_unicode()>=32) { - - if (readonly) - break; - - accept_event(); - } else { - - break; - } - } -*/ if (k->get_scancode() == KEY_INSERT) { set_insert_mode(!insert_mode); accept_event(); return; } - if (!scancode_handled && !k->get_command()) { //for German kbds + if (!scancode_handled && !k->get_command()) { // For German keyboards. if (k->get_unicode() >= 32) { if (readonly) return; - // remove the old character if in insert mode and no selection + // Remove the old character if in insert mode and no selection. if (insert_mode && !had_selection) { begin_complex_operation(); - // make sure we don't try and remove empty space + // Make sure we don't try and remove empty space. if (cursor.column < get_line(cursor.line).length()) { _remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); } @@ -3194,7 +3619,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { end_complex_operation(); } accept_event(); - } else { } } @@ -3202,28 +3626,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } -void TextEdit::_uncomment_line(int p_line) { - String line_text = get_line(p_line); - for (int i = 0; i < line_text.length(); i++) { - if (line_text[i] == '#') { - _remove_text(p_line, i, p_line, i + 1); - if (p_line == selection.to_line && selection.to_column > line_text.length() - 1) { - selection.to_column -= 1; - if (selection.to_column >= selection.from_column) { - selection.active = false; - } - } - return; - } else if (line_text[i] != '\t' && line_text[i] != ' ') { - return; - } - } -} - void TextEdit::_scroll_up(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) { scrolling = false; + minimap_clicked = false; + } if (scrolling) { target_v_scroll = (target_v_scroll - p_delta); @@ -3248,8 +3656,10 @@ void TextEdit::_scroll_up(real_t p_delta) { void TextEdit::_scroll_down(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) { scrolling = false; + minimap_clicked = false; + } if (scrolling) { target_v_scroll = (target_v_scroll + p_delta); @@ -3298,11 +3708,12 @@ void TextEdit::_post_shift_selection() { void TextEdit::_scroll_lines_up() { scrolling = false; + minimap_clicked = false; - // adjust the vertical scroll + // Adjust the vertical scroll. set_v_scroll(get_v_scroll() - 1); - // adjust the cursor to viewport + // Adjust the cursor to viewport. if (!selection.active) { int cur_line = cursor.line; int cur_wrap = get_cursor_wrap_index(); @@ -3317,11 +3728,12 @@ void TextEdit::_scroll_lines_up() { void TextEdit::_scroll_lines_down() { scrolling = false; + minimap_clicked = false; - // adjust the vertical scroll + // Adjust the vertical scroll. set_v_scroll(get_v_scroll() + 1); - // adjust the cursor to viewport + // Adjust the cursor to viewport. if (!selection.active) { int cur_line = cursor.line; int cur_wrap = get_cursor_wrap_index(); @@ -3338,15 +3750,15 @@ void TextEdit::_scroll_lines_down() { void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column) { - //save for undo... + // Save for undo. ERR_FAIL_INDEX(p_line, text.size()); ERR_FAIL_COND(p_char < 0); - /* STEP 1 remove \r from source text and separate in substrings */ + /* STEP 1: Remove \r from source text and separate in substrings. */ Vector<String> substrings = p_text.replace("\r", "").split("\n"); - /* STEP 2 fire breakpoint_toggled signals */ + /* STEP 2: Fire breakpoint_toggled signals. */ // Is this just a new empty line? bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; @@ -3362,39 +3774,42 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } } - /* STEP 3 add spaces if the char is greater than the end of the line */ + /* STEP 3: Add spaces if the char is greater than the end of the line. */ while (p_char > text[p_line].length()) { text.set(p_line, text[p_line] + String::chr(' ')); } - /* STEP 4 separate dest string in pre and post text */ + /* STEP 4: Separate dest string in pre and post text. */ String preinsert_text = text[p_line].substr(0, p_char); String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); - for (int i = 0; i < substrings.size(); i++) { - //insert the substrings + for (int j = 0; j < substrings.size(); j++) { + // Insert the substrings. - if (i == 0) { + if (j == 0) { - text.set(p_line, preinsert_text + substrings[i]); + text.set(p_line, preinsert_text + substrings[j]); } else { - text.insert(p_line + i, substrings[i]); + text.insert(p_line + j, substrings[j]); } - if (i == substrings.size() - 1) { + if (j == substrings.size() - 1) { - text.set(p_line + i, text[p_line + i] + postinsert_text); + text.set(p_line + j, text[p_line + j] + postinsert_text); } } if (shift_first_line) { text.set_breakpoint(p_line + 1, text.is_breakpoint(p_line)); text.set_hidden(p_line + 1, text.is_hidden(p_line)); + text.set_info_icon(p_line + 1, text.get_info_icon(p_line), text.get_info(p_line)); + text.set_breakpoint(p_line, false); text.set_hidden(p_line, false); + text.set_info_icon(p_line, NULL, ""); } text.set_line_wrap_amount(p_line, -1); @@ -3416,8 +3831,8 @@ String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_lin ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, String()); ERR_FAIL_INDEX_V(p_to_line, text.size(), String()); ERR_FAIL_INDEX_V(p_to_column, text[p_to_line].length() + 1, String()); - ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // from > to - ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // from > to + ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // 'from > to'. + ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // 'from > to'. String ret; @@ -3440,8 +3855,8 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li ERR_FAIL_INDEX(p_from_column, text[p_from_line].length() + 1); ERR_FAIL_INDEX(p_to_line, text.size()); ERR_FAIL_INDEX(p_to_column, text[p_to_line].length() + 1); - ERR_FAIL_COND(p_to_line < p_from_line); // from > to - ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); // from > to + ERR_FAIL_COND(p_to_line < p_from_line); // 'from > to'. + ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); // 'from > to'. String pre_text = text[p_from_line].substr(0, p_from_column); String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); @@ -3503,22 +3918,22 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r op.chain_forward = false; op.chain_backward = false; - //see if it shold just be set as current op + // See if it should just be set as current op. if (current_op.type != op.type) { op.prev_version = get_version(); _push_current_op(); current_op = op; - return; //set as current op, return + return; // Set as current op, return. } - //see if it can be merged + // See if it can be merged. if (current_op.to_line != p_line || current_op.to_column != p_char) { op.prev_version = get_version(); _push_current_op(); current_op = op; - return; //set as current op, return + return; // Set as current op, return. } - //merge current op + // Merge current op. current_op.text += p_text; current_op.to_column = retchar; @@ -3542,7 +3957,7 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i if (!undo_enabled) return; - /* UNDO!! */ + /* UNDO! */ TextOperation op; op.type = TextOperation::TYPE_REMOVE; op.from_line = p_from_line; @@ -3554,27 +3969,20 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i op.chain_forward = false; op.chain_backward = false; - //see if it shold just be set as current op + // See if it should just be set as current op. if (current_op.type != op.type) { op.prev_version = get_version(); _push_current_op(); current_op = op; - return; //set as current op, return + return; // Set as current op, return. } - //see if it can be merged + // See if it can be merged. if (current_op.from_line == p_to_line && current_op.from_column == p_to_column) { - //basckace or similar + // Backspace or similar. current_op.text = text + current_op.text; current_op.from_line = p_from_line; current_op.from_column = p_from_column; - return; //update current op - } - if (current_op.from_line == p_from_line && current_op.from_column == p_from_column) { - - //current_op.text=text+current_op.text; - //current_op.from_line=p_from_line; - //current_op.from_column=p_from_column; - //return; //update current op + return; // Update current op. } op.prev_version = get_version(); @@ -3597,6 +4005,15 @@ void TextEdit::_line_edited_from(int p_line) { for (int i = p_line; i < cache_size; i++) { color_region_cache.erase(i); } + + if (syntax_highlighting_cache.size() > 0) { + cache_size = syntax_highlighting_cache.back()->key(); + for (int i = p_line - 1; i <= cache_size; i++) { + if (syntax_highlighting_cache.has(i)) { + syntax_highlighting_cache.erase(i); + } + } + } } int TextEdit::get_char_count() { @@ -3606,11 +4023,11 @@ int TextEdit::get_char_count() { for (int i = 0; i < text.size(); i++) { if (i > 0) - totalsize++; // incliude \n + totalsize++; // Include \n. totalsize += text[i].length(); } - return totalsize; // omit last \n + return totalsize; // Omit last \n. } Size2 TextEdit::get_minimum_size() const { @@ -3618,19 +4035,26 @@ Size2 TextEdit::get_minimum_size() const { return cache.style_normal->get_minimum_size(); } +int TextEdit::_get_control_height() const { + int control_height = get_size().height; + control_height -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) { + control_height -= h_scroll->get_size().height; + } + return control_height; +} + int TextEdit::get_visible_rows() const { + return _get_control_height() / get_row_height(); +} - int total = get_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_minimap_visible_rows() const { + return _get_control_height() / (minimap_char_size.y + minimap_line_spacing); } int TextEdit::get_total_visible_rows() const { - // returns the total amount of rows we need in the editor. + // 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(); @@ -3647,12 +4071,12 @@ int TextEdit::get_total_visible_rows() const { void TextEdit::_update_wrap_at() { - wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - wrap_right_offset; + wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width - wrap_right_offset; update_cursor_wrap_offset(); text.clear_wrap_cache(); for (int i = 0; i < text.size(); i++) { - // update all values that wrap + // Update all values that wrap. if (!line_wraps(i)) continue; Vector<String> rows = get_wrap_rows_text(i); @@ -3662,8 +4086,9 @@ void TextEdit::_update_wrap_at() { void TextEdit::adjust_viewport_to_cursor() { - // make sure cursor is visible on the screen + // Make sure cursor is visible on the screen. scrolling = false; + minimap_clicked = false; int cur_line = cursor.line; int cur_wrap = get_cursor_wrap_index(); @@ -3674,20 +4099,20 @@ void TextEdit::adjust_viewport_to_cursor() { int last_vis_wrap = get_last_visible_line_wrap_index(); if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { - // cursor is above screen + // 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 + // Cursor is below screen. set_line_as_last_visible(cur_line, cur_wrap); } - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_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 + visible_width -= 20; // Give it a little more space. if (!is_wrap_enabled()) { - // adjust x offset + // Adjust x offset. int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); if (cursor_x > (cursor.x_ofs + visible_width)) @@ -3705,20 +4130,21 @@ void TextEdit::adjust_viewport_to_cursor() { void TextEdit::center_viewport_to_cursor() { - // move viewport so the cursor is in the center of the screen + // Move viewport so the cursor is in the center of the screen. scrolling = false; + minimap_clicked = 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 = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_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 + visible_width -= 20; // Give it a little more space. if (is_wrap_enabled()) { - // center x offset + // Center x offset. int cursor_x = get_column_x_offset_for_line(cursor.column, cursor.line); if (cursor_x > (cursor.x_ofs + visible_width)) @@ -3760,7 +4186,7 @@ int TextEdit::times_line_wraps(int line) const { int wrap_amount = text.get_line_wrap_amount(line); if (wrap_amount == -1) { - // update the value + // Update the value. Vector<String> rows = get_wrap_rows_text(line); wrap_amount = rows.size() - 1; text.set_line_wrap_amount(line, wrap_amount); @@ -3789,6 +4215,9 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { int cur_wrap_index = 0; int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (tab_offset_px >= wrap_at) { + tab_offset_px = 0; + } while (col < line_text.length()) { CharType c = line_text[col]; @@ -3796,37 +4225,44 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { 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 + if (indent_ofs + word_px + w > wrap_at) { + // Not enough space to add this char; start next line. wrap_substring += word_str; - px += word_px; - word_str = ""; - word_px = 0; - } + lines.push_back(wrap_substring); + cur_wrap_index++; + wrap_substring = ""; + 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 + word_str = ""; + word_str += c; + word_px = w; + } else { + 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; } - lines.push_back(wrap_substring); - // reset for next wrap - cur_wrap_index++; - wrap_substring = ""; - px = 0; + + if (indent_ofs + px + word_px > wrap_at) { + // This word will be moved to the next line. + 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 + // Line ends before hit wrap_at; add this word to the substring. wrap_substring += word_str; lines.push_back(wrap_substring); - // update cache + // Update cache. text.set_line_wrap_amount(p_line, lines.size() - 1); return lines; @@ -3844,7 +4280,7 @@ int TextEdit::get_line_wrap_index_at_col(int p_line, int p_column) const { if (!line_wraps(p_line)) return 0; - // loop through wraps in the line text until we get to the column + // 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); @@ -3909,7 +4345,7 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ cursor.line = p_row; 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)) { + if (n_col != 0 && 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++) { @@ -3985,6 +4421,7 @@ bool TextEdit::is_right_click_moving_caret() const { void TextEdit::_v_scroll_input() { scrolling = false; + minimap_clicked = false; } void TextEdit::_scroll_moved(double p_to_val) { @@ -3996,11 +4433,11 @@ void TextEdit::_scroll_moved(double p_to_val) { cursor.x_ofs = h_scroll->get_value(); if (v_scroll->is_visible_in_tree()) { - // set line ofs and wrap ofs + // 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++) { + for (n_line = 0; n_line < text.size() - 1; n_line++) { if (!is_line_hidden(n_line)) { sc++; sc += times_line_wraps(n_line); @@ -4031,6 +4468,9 @@ int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) cons 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 (wrap_offset_px >= wrap_at) { + wrap_offset_px = 0; + } if (p_wrap_index > line_wrap_amount) p_wrap_index = line_wrap_amount; if (p_wrap_index > 0) @@ -4072,6 +4512,9 @@ int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { 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_offset_px >= wrap_at) { + wrap_offset_px = 0; + } if (wrap_index != 0) px += wrap_offset_px; @@ -4135,7 +4578,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { if (highlighted_word != String()) return CURSOR_POINTING_HAND; - int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width; + int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; if ((completion_active && completion_rect.has_point(p_pos))) { return CURSOR_ARROW; } @@ -4145,27 +4588,41 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { _get_mouse_pos(p_pos, row, col); int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); - // breakpoint icon - if (draw_breakpoint_gutter && p_pos.x > left_margin && p_pos.x <= left_margin + cache.breakpoint_gutter_width + 3) { + // Breakpoint icon. + if (draw_breakpoint_gutter && p_pos.x > left_margin - 6 && p_pos.x <= left_margin + cache.breakpoint_gutter_width - 3) { return CURSOR_POINTING_HAND; } - // fold icon - int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w; - if (draw_fold_gutter && p_pos.x > gutter_left - 6 && p_pos.x <= gutter_left + cache.fold_gutter_width - 3) { + // Info icons. + int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.info_gutter_width; + if (draw_info_gutter && p_pos.x > left_margin + cache.breakpoint_gutter_width - 6 && p_pos.x <= gutter_left - 3) { + if (text.has_info_icon(row)) { + return CURSOR_POINTING_HAND; + } + return CURSOR_ARROW; + } + + // Fold icon. + if (draw_fold_gutter && p_pos.x > gutter_left + cache.line_number_w - 6 && p_pos.x <= gutter_left + cache.line_number_w + cache.fold_gutter_width - 3) { if (is_folded(row) || can_fold(row)) return CURSOR_POINTING_HAND; else return CURSOR_ARROW; } + return CURSOR_ARROW; } else { + int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); + if (p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) { + return CURSOR_ARROW; + } + int row, col; _get_mouse_pos(p_pos, row, col); - // eol fold icon + // EOL fold icon. if (is_folded(row)) { int line_width = text.get_line_width(row); - line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width - cursor.x_ofs; + line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width - cursor.x_ofs; if (p_pos.x > line_width - 3 && p_pos.x <= line_width + cache.folded_eol_icon->get_width() + 3) { return CURSOR_POINTING_HAND; } @@ -4178,21 +4635,24 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { void TextEdit::set_text(String p_text) { setting_text = true; - _clear(); - _insert_text_at_cursor(p_text); - clear_undo_history(); - cursor.column = 0; - cursor.line = 0; - cursor.x_ofs = 0; - cursor.line_ofs = 0; - cursor.wrap_ofs = 0; - cursor.last_fit_x = 0; - cursor_set_line(0); - cursor_set_column(0); + if (!undo_enabled) { + _clear(); + _insert_text_at_cursor(p_text); + } + + if (undo_enabled) { + cursor_set_line(0); + cursor_set_column(0); + + begin_complex_operation(); + _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); + _insert_text_at_cursor(p_text); + end_complex_operation(); + selection.active = false; + } + update(); setting_text = false; - - //get_range()->set(0); }; String TextEdit::get_text() { @@ -4219,7 +4679,7 @@ String TextEdit::get_text_for_lookup_completion() { if (i == row) { longthing += text[i].substr(0, col); - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. longthing += text[i].substr(col, text[i].size()); } else { @@ -4241,7 +4701,7 @@ String TextEdit::get_text_for_completion() { if (i == cursor.line) { longthing += text[i].substr(0, cursor.column); - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. longthing += text[i].substr(cursor.column, text[i].size()); } else { @@ -4285,7 +4745,37 @@ void TextEdit::clear() { void TextEdit::set_readonly(bool p_readonly) { + if (readonly == p_readonly) + return; + readonly = p_readonly; + + // Reorganize context menu. + menu->clear(); + + if (!readonly) { + 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); + } + + if (!readonly) { + menu->add_separator(); + menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + } + + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); + + if (!readonly) { + menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + } + + menu->add_separator(); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + + if (!readonly) { + menu->add_item(RTR("Clear"), MENU_CLEAR); + } + update(); } @@ -4345,7 +4835,8 @@ void TextEdit::_update_caches() { cache.line_number_color = get_color("line_number_color"); cache.safe_line_number_color = get_color("safe_line_number_color"); cache.font_color = get_color("font_color"); - cache.font_selected_color = get_color("font_selected_color"); + cache.font_color_selected = get_color("font_color_selected"); + cache.font_color_readonly = get_color("font_color_readonly"); cache.keyword_color = get_color("keyword_color"); cache.function_color = get_color("function_color"); cache.member_variable_color = get_color("member_variable_color"); @@ -4354,7 +4845,9 @@ void TextEdit::_update_caches() { cache.mark_color = get_color("mark_color"); cache.current_line_color = get_color("current_line_color"); cache.line_length_guideline_color = get_color("line_length_guideline_color"); + cache.bookmark_color = get_color("bookmark_color"); cache.breakpoint_color = get_color("breakpoint_color"); + cache.executing_line_color = get_color("executing_line_color"); cache.code_folding_color = get_color("code_folding_color"); cache.brace_mismatch_color = get_color("brace_mismatch_color"); cache.word_highlighted_color = get_color("word_highlighted_color"); @@ -4369,9 +4862,11 @@ void TextEdit::_update_caches() { #endif cache.row_height = cache.font->get_height() + cache.line_spacing; cache.tab_icon = get_icon("tab"); - cache.folded_icon = get_icon("GuiTreeArrowRight", "EditorIcons"); - cache.can_fold_icon = get_icon("GuiTreeArrowDown", "EditorIcons"); + cache.space_icon = get_icon("space"); + cache.folded_icon = get_icon("folded"); + cache.can_fold_icon = get_icon("fold"); cache.folded_eol_icon = get_icon("GuiEllipsis", "EditorIcons"); + cache.executing_icon = get_icon("MainPlay", "EditorIcons"); text.set_font(cache.font); if (syntax_highlighter) { @@ -4389,17 +4884,18 @@ void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter) syntax_highlighter->set_text_editor(this); syntax_highlighter->_update_cache(); } + syntax_highlighting_cache.clear(); update(); } int TextEdit::_is_line_in_region(int p_line) { - // do we have in cache? + // 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 + // If not find the closest line we have. int previous_line = p_line - 1; for (; previous_line > -1; previous_line--) { if (color_region_cache.has(p_line)) { @@ -4407,7 +4903,7 @@ int TextEdit::_is_line_in_region(int p_line) { } } - // calculate up to line we need and update the cache along the way. + // 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; @@ -4455,6 +4951,7 @@ void TextEdit::clear_colors() { keywords.clear(); color_regions.clear(); color_region_cache.clear(); + syntax_highlighting_cache.clear(); text.clear_width_cache(); } @@ -4469,6 +4966,8 @@ bool TextEdit::has_keyword_color(String p_keyword) const { } Color TextEdit::get_keyword_color(String p_keyword) const { + + ERR_FAIL_COND_V(!keywords.has(p_keyword), Color()); return keywords[p_keyword]; } @@ -4533,7 +5032,7 @@ void TextEdit::cut() { OS::get_singleton()->set_clipboard(clipboard); _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line); // set afterwards else it causes the view to be offset + cursor_set_line(selection.from_line); // Set afterwards else it causes the view to be offset. cursor_set_column(selection.from_column); selection.active = false; @@ -4612,14 +5111,18 @@ void TextEdit::deselect() { void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { - if (p_from_line >= text.size()) + if (p_from_line < 0) + p_from_line = 0; + else if (p_from_line >= text.size()) p_from_line = text.size() - 1; if (p_from_column >= text[p_from_line].length()) p_from_column = text[p_from_line].length(); if (p_from_column < 0) p_from_column = 0; - if (p_to_line >= text.size()) + if (p_to_line < 0) + p_to_line = 0; + else if (p_to_line >= text.size()) p_to_line = text.size() - 1; if (p_to_column >= text[p_to_line].length()) p_to_column = text[p_to_line].length(); @@ -4758,7 +5261,7 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc col = p_search.findn(p_key, p_from_column); } - // whole words only + // Whole words only. if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) { p_from_column = col; @@ -4798,14 +5301,12 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l ERR_FAIL_INDEX_V(p_from_line, text.size(), false); ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, false); - //search through the whole document, but start by current line + // Search through the whole document, but start by current line. int line = p_from_line; int pos = -1; for (int i = 0; i < text.size() + 1; i++) { - //backwards is broken... - //int idx=(p_search_flags&SEARCH_BACKWARDS)?(text.size()-i):i; //do backwards seearch if (line < 0) { line = text.size() - 1; @@ -4819,7 +5320,7 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l if (line == p_from_line) { if (i == text.size()) { - //wrapped + // Wrapped. if (p_search_flags & SEARCH_BACKWARDS) { from_column = text_line.length(); @@ -4867,7 +5368,7 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l bool is_match = true; if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) { - //validate for whole words + // Validate for whole words. if (pos > 0 && _is_text_char(text_line[pos - 1])) is_match = false; else if (pos + p_key.length() < text_line.length() && _is_text_char(text_line[pos + p_key.length()])) @@ -4937,6 +5438,48 @@ bool TextEdit::is_line_set_as_safe(int p_line) const { return text.is_safe(p_line); } +void TextEdit::set_executing_line(int p_line) { + ERR_FAIL_INDEX(p_line, text.size()); + executing_line = p_line; + update(); +} + +void TextEdit::clear_executing_line() { + executing_line = -1; + update(); +} + +bool TextEdit::is_line_set_as_bookmark(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), false); + return text.is_bookmark(p_line); +} + +void TextEdit::set_line_as_bookmark(int p_line, bool p_bookmark) { + + ERR_FAIL_INDEX(p_line, text.size()); + text.set_bookmark(p_line, p_bookmark); + update(); +} + +void TextEdit::get_bookmarks(List<int> *p_bookmarks) const { + + for (int i = 0; i < text.size(); i++) { + if (text.is_bookmark(i)) + p_bookmarks->push_back(i); + } +} + +Array TextEdit::get_bookmarks_array() const { + + Array arr; + for (int i = 0; i < text.size(); i++) { + if (text.is_bookmark(i)) + arr.append(i); + } + return arr; +} + bool TextEdit::is_line_set_as_breakpoint(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), false); @@ -4976,6 +5519,19 @@ void TextEdit::remove_breakpoints() { } } +void TextEdit::set_line_info_icon(int p_line, Ref<Texture> p_icon, String p_info) { + ERR_FAIL_INDEX(p_line, text.size()); + text.set_info_icon(p_line, p_icon, p_info); + update(); +} + +void TextEdit::clear_info_icons() { + for (int i = 0; i < text.size(); i++) { + text.set_info_icon(i, NULL, ""); + } + update(); +} + void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) { ERR_FAIL_INDEX(p_line, text.size()); @@ -5010,7 +5566,7 @@ void TextEdit::unhide_all_lines() { int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { - // returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines) + // 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()) @@ -5043,8 +5599,8 @@ int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const { - // 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 + // 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)); @@ -5090,7 +5646,7 @@ int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int vi int TextEdit::get_last_unhidden_line() const { - // returns the last line in the text that is not hidden + // Returns the last line in the text that is not hidden. if (!is_hiding_enabled()) return text.size() - 1; @@ -5107,7 +5663,7 @@ int TextEdit::get_indent_level(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); - // counts number of tabs and spaces before line starts + // Counts number of tabs and spaces before line starts. int tab_count = 0; int whitespace_count = 0; int line_length = text[p_line].size(); @@ -5125,7 +5681,7 @@ int TextEdit::get_indent_level(int p_line) const { bool TextEdit::is_line_comment(int p_line) const { - // checks to see if this line is the start of a comment + // Checks to see if this line is the start of a comment. ERR_FAIL_INDEX_V(p_line, text.size(), false); const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(p_line); @@ -5134,11 +5690,7 @@ bool TextEdit::is_line_comment(int p_line) const { for (int i = 0; i < line_length - 1; i++) { if (_is_symbol(text[p_line][i]) && cri_map.has(i)) { const Text::ColorRegionInfo &cri = cri_map[i]; - if (color_regions[cri.region].begin_key == "#" || color_regions[cri.region].begin_key == "//") { - return true; - } else { - return false; - } + return color_regions[cri.region].begin_key == "#" || color_regions[cri.region].begin_key == "//"; } else if (_is_whitespace(text[p_line][i])) { continue; } else { @@ -5187,9 +5739,18 @@ bool TextEdit::is_folded(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), false); if (p_line + 1 >= text.size()) return false; - if (!is_line_hidden(p_line) && is_line_hidden(p_line + 1)) - return true; - return false; + return !is_line_hidden(p_line) && is_line_hidden(p_line + 1); +} + +Vector<int> TextEdit::get_folded_lines() const { + Vector<int> folded_lines; + + for (int i = 0; i < text.size(); i++) { + if (is_folded(i)) { + folded_lines.push_back(i); + } + } + return folded_lines; } void TextEdit::fold_line(int p_line) { @@ -5200,7 +5761,7 @@ void TextEdit::fold_line(int p_line) { if (!can_fold(p_line)) return; - // hide lines below this one + // Hide lines below this one. int start_indent = get_indent_level(p_line); int last_line = start_indent; for (int i = p_line + 1; i < text.size(); i++) { @@ -5218,7 +5779,7 @@ void TextEdit::fold_line(int p_line) { set_line_as_hidden(i, true); } - // fix selection + // Fix selection. if (is_selection_active()) { if (is_line_hidden(selection.from_line) && is_line_hidden(selection.to_line)) { deselect(); @@ -5229,7 +5790,7 @@ void TextEdit::fold_line(int p_line) { } } - // reset cursor + // Reset cursor. if (is_line_hidden(cursor.line)) { cursor_set_line(p_line, false, false); cursor_set_column(get_line(p_line).length(), false); @@ -5290,8 +5851,8 @@ void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) { int check_line; int check_column; _base_insert_text(p_op.from_line, p_op.from_column, p_op.text, check_line, check_column); - ERR_FAIL_COND(check_line != p_op.to_line); // BUG - ERR_FAIL_COND(check_column != p_op.to_column); // BUG + ERR_FAIL_COND(check_line != p_op.to_line); // BUG. + ERR_FAIL_COND(check_column != p_op.to_column); // BUG. } else { _base_remove_text(p_op.from_line, p_op.from_column, p_op.to_line, p_op.to_column); @@ -5301,7 +5862,7 @@ void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) { void TextEdit::_clear_redo() { if (undo_stack_pos == NULL) - return; //nothing to clear + return; // Nothing to clear. _push_current_op(); @@ -5319,12 +5880,12 @@ void TextEdit::undo() { if (undo_stack_pos == NULL) { if (!undo_stack.size()) - return; //nothing to undo + return; // Nothing to undo. undo_stack_pos = undo_stack.back(); } else if (undo_stack_pos == undo_stack.front()) - return; // at the bottom of the undo stack + return; // At the bottom of the undo stack. else undo_stack_pos = undo_stack_pos->prev(); @@ -5332,6 +5893,9 @@ void TextEdit::undo() { TextOperation op = undo_stack_pos->get(); _do_text_op(op, true); + if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) + select(op.from_line, op.from_column, op.to_line, op.to_column); + current_op.version = op.prev_version; if (undo_stack_pos->get().chain_backward) { while (true) { @@ -5362,7 +5926,7 @@ void TextEdit::redo() { _push_current_op(); if (undo_stack_pos == NULL) - return; //nothing to do. + return; // Nothing to do. deselect(); @@ -5416,7 +5980,7 @@ void TextEdit::end_complex_operation() { void TextEdit::_push_current_op() { if (current_op.type == TextOperation::TYPE_NONE) - return; // do nothing + return; // Nothing to do. if (next_operation_is_complex) { current_op.chain_forward = true; @@ -5458,6 +6022,7 @@ int TextEdit::get_indent_size() { void TextEdit::set_draw_tabs(bool p_draw) { draw_tabs = p_draw; + update(); } bool TextEdit::is_drawing_tabs() const { @@ -5465,6 +6030,16 @@ bool TextEdit::is_drawing_tabs() const { return draw_tabs; } +void TextEdit::set_draw_spaces(bool p_draw) { + + draw_spaces = p_draw; +} + +bool TextEdit::is_drawing_spaces() const { + + return draw_spaces; +} + void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { override_selected_font_color = p_override_selected_font_color; } @@ -5505,7 +6080,7 @@ double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { if (!is_wrap_enabled() && !is_hiding_enabled()) return p_line; - // count the number of visible lines up to this line + // 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++) { @@ -5565,10 +6140,7 @@ int TextEdit::get_last_visible_line_wrap_index() const { double TextEdit::get_visible_rows_offset() const { - double total = get_size().height; - total -= cache.style_normal->get_minimum_size().height; - if (h_scroll->is_visible_in_tree()) - total -= h_scroll->get_size().height; + double total = _get_control_height(); total /= (double)get_row_height(); total = total - floor(total); total = -CLAMP(total, 0.001, 1) + 1; @@ -5642,21 +6214,31 @@ void TextEdit::_confirm_completion() { _remove_text(cursor.line, cursor.column - completion_base.length(), cursor.line, cursor.column); cursor_set_column(cursor.column - completion_base.length(), false); - insert_text_at_cursor(completion_current); + insert_text_at_cursor(completion_current.insert_text); - // When inserted into the middle of an existing string, don't add an unnecessary quote + // When inserted into the middle of an existing string/method, don't add an unnecessary quote/bracket. String line = text[cursor.line]; CharType next_char = line[cursor.column]; - CharType last_completion_char = completion_current[completion_current.length() - 1]; + CharType last_completion_char = completion_current.insert_text[completion_current.insert_text.length() - 1]; - if ((last_completion_char == '"' || last_completion_char == '\'') && - last_completion_char == next_char) { + if ((last_completion_char == '"' || last_completion_char == '\'') && last_completion_char == next_char) { _base_remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); } - if (last_completion_char == '(' && auto_brace_completion_enabled) { - insert_text_at_cursor(")"); - cursor.column--; + if (last_completion_char == '(') { + + if (next_char == last_completion_char) { + _base_remove_text(cursor.line, cursor.column - 1, cursor.line, cursor.column); + } else if (auto_brace_completion_enabled) { + insert_text_at_cursor(")"); + cursor.column--; + } + } else if (last_completion_char == ')' && next_char == '(') { + + _base_remove_text(cursor.line, cursor.column - 2, cursor.line, cursor.column); + if (line[cursor.column + 1] != ')') { + cursor.column--; + } } end_complex_operation(); @@ -5696,10 +6278,11 @@ void TextEdit::_update_completion_candidates() { String s; - //look for keywords first + // Look for keywords first. bool inquote = false; int first_quote = -1; + int restore_quotes = -1; int c = cofs - 1; while (c >= 0) { @@ -5707,6 +6290,11 @@ void TextEdit::_update_completion_candidates() { inquote = !inquote; if (first_quote == -1) first_quote = c; + restore_quotes = 0; + } else if (restore_quotes == 0 && l[c] == '$') { + restore_quotes = 1; + } else if (restore_quotes == 0 && !_is_whitespace(l[c])) { + restore_quotes = -1; } c--; } @@ -5715,7 +6303,7 @@ void TextEdit::_update_completion_candidates() { bool cancel = false; if (!inquote && first_quote == cofs - 1) { - //no completion here + // No completion here. cancel = true; } else if (inquote && first_quote != -1) { @@ -5753,11 +6341,12 @@ void TextEdit::_update_completion_candidates() { bool prev_is_prefix = false; if (cofs > 0 && completion_prefixes.has(String::chr(l[cofs - 1]))) prev_is_prefix = true; - if (cofs > 1 && l[cofs - 1] == ' ' && completion_prefixes.has(String::chr(l[cofs - 2]))) //check with one space before prefix, to allow indent + // Check with one space before prefix, to allow indent. + if (cofs > 1 && l[cofs - 1] == ' ' && completion_prefixes.has(String::chr(l[cofs - 2]))) prev_is_prefix = true; if (cancel || (!pre_keyword && s == "" && (cofs == 0 || !prev_is_prefix))) { - //none to complete, cancel + // None to complete, cancel. _cancel_completion(); return; } @@ -5767,51 +6356,58 @@ void TextEdit::_update_completion_candidates() { completion_base = s; Vector<float> sim_cache; bool single_quote = s.begins_with("'"); - Vector<String> completion_options_casei; + Vector<ScriptCodeCompletionOption> completion_options_casei; + + for (List<ScriptCodeCompletionOption>::Element *E = completion_sources.front(); E; E = E->next()) { + ScriptCodeCompletionOption &option = E->get(); + + if (single_quote && option.display.is_quoted()) { + option.display = option.display.unquote().quote("'"); + } - for (int i = 0; i < completion_strings.size(); i++) { - if (single_quote && completion_strings[i].is_quoted()) { - completion_strings.write[i] = completion_strings[i].unquote().quote("'"); + if (inquote && restore_quotes == 1 && !option.display.is_quoted()) { + String quote = single_quote ? "'" : "\""; + option.display = option.display.quote(quote); } - if (completion_strings[i].begins_with(s)) { - completion_options.push_back(completion_strings[i]); - } else if (completion_strings[i].to_lower().begins_with(s.to_lower())) { - completion_options_casei.push_back(completion_strings[i]); + if (option.display.begins_with(s)) { + completion_options.push_back(option); + } else if (option.display.to_lower().begins_with(s.to_lower())) { + completion_options_casei.push_back(option); } } completion_options.append_array(completion_options_casei); if (completion_options.size() == 0) { - for (int i = 0; i < completion_strings.size(); i++) { - if (s.is_subsequence_of(completion_strings[i])) { - completion_options.push_back(completion_strings[i]); + for (int i = 0; i < completion_sources.size(); i++) { + if (s.is_subsequence_of(completion_sources[i].display)) { + completion_options.push_back(completion_sources[i]); } } } if (completion_options.size() == 0) { - for (int i = 0; i < completion_strings.size(); i++) { - if (s.is_subsequence_ofi(completion_strings[i])) { - completion_options.push_back(completion_strings[i]); + for (int i = 0; i < completion_sources.size(); i++) { + if (s.is_subsequence_ofi(completion_sources[i].display)) { + completion_options.push_back(completion_sources[i]); } } } if (completion_options.size() == 0) { - //no options to complete, cancel + // No options to complete, cancel. _cancel_completion(); return; } - if (completion_options.size() == 1 && s == completion_options[0]) { - // A perfect match, stop completion + if (completion_options.size() == 1 && s == completion_options[0].display) { + // A perfect match, stop completion. _cancel_completion(); return; } - // The top of the list is the best match + // The top of the list is the best match. completion_current = completion_options[0]; completion_enabled = true; } @@ -5830,10 +6426,30 @@ void TextEdit::query_code_comple() { c--; } - if (ofs > 0 && (inquote || _is_completable(l[ofs - 1]) || completion_prefixes.has(String::chr(l[ofs - 1])))) - emit_signal("request_completion"); - else if (ofs > 1 && l[ofs - 1] == ' ' && completion_prefixes.has(String::chr(l[ofs - 2]))) //make it work with a space too, it's good enough - emit_signal("request_completion"); + bool ignored = completion_active && !completion_options.empty(); + if (ignored) { + ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT; + const ScriptCodeCompletionOption *previous_option = NULL; + for (int i = 0; i < completion_options.size(); i++) { + const ScriptCodeCompletionOption ¤t_option = completion_options[i]; + if (!previous_option) { + previous_option = ¤t_option; + kind = current_option.kind; + } + if (previous_option->kind != current_option.kind) { + ignored = false; + break; + } + } + ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL); + } + + if (!ignored) { + if (ofs > 0 && (inquote || _is_completable(l[ofs - 1]) || completion_prefixes.has(String::chr(l[ofs - 1])))) + emit_signal("request_completion"); + else if (ofs > 1 && l[ofs - 1] == ' ' && completion_prefixes.has(String::chr(l[ofs - 2]))) // Make it work with a space too, it's good enough. + emit_signal("request_completion"); + } } void TextEdit::set_code_hint(const String &p_hint) { @@ -5843,15 +6459,14 @@ void TextEdit::set_code_hint(const String &p_hint) { update(); } -void TextEdit::code_complete(const Vector<String> &p_strings, bool p_forced) { +void TextEdit::code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced) { - completion_strings = p_strings; + completion_sources = p_strings; completion_active = true; completion_forced = p_forced; - completion_current = ""; + completion_current = ScriptCodeCompletionOption(); completion_index = 0; _update_completion_candidates(); - // } String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { @@ -5935,9 +6550,21 @@ void TextEdit::set_line(int line, String new_text) { } void TextEdit::insert_at(const String &p_text, int at) { - cursor_set_column(0); - cursor_set_line(at, false, true); _insert_text(at, 0, p_text + "\n"); + if (cursor.line >= at) { + // offset cursor when located after inserted line + ++cursor.line; + } + if (is_selection_active()) { + if (selection.from_line >= at) { + // offset selection when located after inserted line + ++selection.from_line; + ++selection.to_line; + } else if (selection.to_line >= at) { + // extend selection that includes inserted line + ++selection.to_line; + } + } } void TextEdit::set_show_line_numbers(bool p_show) { @@ -5966,6 +6593,15 @@ void TextEdit::set_line_length_guideline_column(int p_column) { update(); } +void TextEdit::set_bookmark_gutter_enabled(bool p_draw) { + draw_bookmark_gutter = p_draw; + update(); +} + +bool TextEdit::is_bookmark_gutter_enabled() const { + return draw_bookmark_gutter; +} + void TextEdit::set_breakpoint_gutter_enabled(bool p_draw) { draw_breakpoint_gutter = p_draw; update(); @@ -6002,14 +6638,50 @@ int TextEdit::get_fold_gutter_width() const { return cache.fold_gutter_width; } -void TextEdit::set_hiding_enabled(int p_enabled) { +void TextEdit::set_draw_info_gutter(bool p_draw) { + draw_info_gutter = p_draw; + update(); +} + +bool TextEdit::is_drawing_info_gutter() const { + return draw_info_gutter; +} + +void TextEdit::set_info_gutter_width(int p_gutter_width) { + info_gutter_width = p_gutter_width; + update(); +} + +int TextEdit::get_info_gutter_width() const { + return info_gutter_width; +} + +void TextEdit::set_draw_minimap(bool p_draw) { + draw_minimap = p_draw; + update(); +} + +bool TextEdit::is_drawing_minimap() const { + return draw_minimap; +} + +void TextEdit::set_minimap_width(int p_minimap_width) { + minimap_width = p_minimap_width; + update(); +} + +int TextEdit::get_minimap_width() const { + return minimap_width; +} + +void TextEdit::set_hiding_enabled(bool p_enabled) { if (!p_enabled) unhide_all_lines(); hiding_enabled = p_enabled; update(); } -int TextEdit::is_hiding_enabled() const { +bool TextEdit::is_hiding_enabled() const { return hiding_enabled; } @@ -6110,6 +6782,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text); ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); + ClassDB::bind_method(D_METHOD("center_viewport_to_cursor"), &TextEdit::center_viewport_to_cursor); 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", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); @@ -6130,8 +6803,6 @@ void TextEdit::_bind_methods() { 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); ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &TextEdit::is_context_menu_enabled); @@ -6158,8 +6829,14 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_show_line_numbers", "enable"), &TextEdit::set_show_line_numbers); ClassDB::bind_method(D_METHOD("is_show_line_numbers_enabled"), &TextEdit::is_show_line_numbers_enabled); + ClassDB::bind_method(D_METHOD("set_draw_tabs"), &TextEdit::set_draw_tabs); + ClassDB::bind_method(D_METHOD("is_drawing_tabs"), &TextEdit::is_drawing_tabs); + ClassDB::bind_method(D_METHOD("set_draw_spaces"), &TextEdit::set_draw_spaces); + ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces); ClassDB::bind_method(D_METHOD("set_breakpoint_gutter_enabled", "enable"), &TextEdit::set_breakpoint_gutter_enabled); ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled); + ClassDB::bind_method(D_METHOD("set_draw_fold_gutter"), &TextEdit::set_draw_fold_gutter); + ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &TextEdit::is_drawing_fold_gutter); ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled); ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled); @@ -6201,12 +6878,20 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_breakpoints"), &TextEdit::get_breakpoints_array); ClassDB::bind_method(D_METHOD("remove_breakpoints"), &TextEdit::remove_breakpoints); + ClassDB::bind_method(D_METHOD("draw_minimap", "draw"), &TextEdit::set_draw_minimap); + ClassDB::bind_method(D_METHOD("is_drawing_minimap"), &TextEdit::is_drawing_minimap); + ClassDB::bind_method(D_METHOD("set_minimap_width", "width"), &TextEdit::set_minimap_width); + ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "syntax_highlighting"), "set_syntax_coloring", "is_syntax_coloring_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_line_numbers"), "set_show_line_numbers", "is_show_line_numbers_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "breakpoint_gutter"), "set_breakpoint_gutter_enabled", "is_breakpoint_gutter_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); @@ -6214,7 +6899,10 @@ void TextEdit::_bind_methods() { 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_enabled"), "set_wrap_enabled", "is_wrap_enabled"); - // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "max_chars"), "set_max_chars", "get_max_chars"); + + ADD_GROUP("Minimap", "minimap_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "draw_minimap", "is_drawing_minimap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width"), "set_minimap_width", "get_minimap_width"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode"); @@ -6227,6 +6915,7 @@ void TextEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("request_completion")); ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "row"))); ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::INT, "column"))); + ADD_SIGNAL(MethodInfo("info_clicked", PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::STRING, "info"))); BIND_ENUM_CONSTANT(MENU_CUT); BIND_ENUM_CONSTANT(MENU_COPY); @@ -6238,14 +6927,14 @@ void TextEdit::_bind_methods() { BIND_ENUM_CONSTANT(MENU_MAX); GLOBAL_DEF("gui/timers/text_edit_idle_detect_sec", 3); - ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/text_edit_idle_detect_sec", PropertyInfo(Variant::REAL, "gui/timers/text_edit_idle_detect_sec", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater")); // No negative numbers + ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/text_edit_idle_detect_sec", PropertyInfo(Variant::REAL, "gui/timers/text_edit_idle_detect_sec", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater")); // No negative numbers. } TextEdit::TextEdit() { - readonly = false; setting_row = false; draw_tabs = false; + draw_spaces = false; override_selected_font_color = false; draw_caret = true; max_chars = 0; @@ -6262,13 +6951,13 @@ TextEdit::TextEdit() { breakpoint_gutter_width = 0; cache.fold_gutter_width = 0; fold_gutter_width = 0; + info_gutter_width = 0; + cache.info_gutter_width = 0; set_default_cursor_shape(CURSOR_IBEAM); indent_size = 4; text.set_indent_size(indent_size); text.clear(); - //text.insert(1,"Mongolia..."); - //text.insert(2,"PAIS GENEROSO!!"); text.set_color_regions(&color_regions); h_scroll = memnew(HScrollBar); @@ -6332,8 +7021,10 @@ TextEdit::TextEdit() { line_numbers_zero_padded = false; line_length_guideline = false; line_length_guideline_col = 80; + draw_bookmark_gutter = false; draw_breakpoint_gutter = false; draw_fold_gutter = false; + draw_info_gutter = false; hiding_enabled = false; next_operation_is_complex = false; scroll_past_end_of_file_enabled = false; @@ -6349,22 +7040,28 @@ TextEdit::TextEdit() { select_identifiers_enabled = false; smooth_scroll_enabled = false; scrolling = false; + minimap_clicked = false; + dragging_minimap = false; + can_drag_minimap = false; + minimap_scroll_ratio = 0; + minimap_scroll_click_pos = 0; + dragging_selection = false; target_v_scroll = 0; v_scroll_speed = 80; + draw_minimap = false; + minimap_width = 80; + minimap_char_size = Point2(1, 2); + minimap_line_spacing = 1; context_menu_enabled = true; menu = memnew(PopupMenu); add_child(menu); - 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(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); - menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - 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); + readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly. + set_readonly(false); menu->connect("id_pressed", this, "menu_option"); + first_draw = true; + + executing_line = -1; } TextEdit::~TextEdit() { @@ -6373,8 +7070,14 @@ TextEdit::~TextEdit() { /////////////////////////////////////////////////////////////////////////////// Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighting_cache.has(p_line)) { + return syntax_highlighting_cache[p_line]; + } + if (syntax_highlighter != NULL) { - return syntax_highlighter->_get_line_syntax_highlighting(p_line); + Map<int, HighlighterInfo> color_map = syntax_highlighter->_get_line_syntax_highlighting(p_line); + syntax_highlighting_cache[p_line] = color_map; + return color_map; } Map<int, HighlighterInfo> color_map; @@ -6420,14 +7123,14 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int bool is_symbol = _is_symbol(str[j]); bool is_number = _is_number(str[j]); - // allow ABCDEF in hex notation + // 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 or 'e' for scientific notation + // Check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation. if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'f' || str[j] == 'e') && !in_word && prev_is_number && !is_number) { is_number = true; is_symbol = false; @@ -6457,7 +7160,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int if (!cri.end) { in_region = cri.region; } - } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise + } 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(); } @@ -6485,7 +7188,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int if (col) { for (int k = j - 1; k >= 0; k--) { if (str[k] == '.') { - col = NULL; //member indexing not allowed + col = NULL; // Member indexing not allowed. break; } else if (str[k] > 32) { break; @@ -6507,7 +7210,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int k++; } - // check for space between name and bracket + // Check for space between name and bracket. while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { k++; } @@ -6556,6 +7259,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int } } + syntax_highlighting_cache[p_line] = color_map; return color_map; } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 95f1fbbee5..9c568acd93 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -40,7 +40,7 @@ class SyntaxHighlighter; class TextEdit : public Control { - GDCLASS(TextEdit, Control) + GDCLASS(TextEdit, Control); public: struct HighlighterInfo { @@ -75,10 +75,13 @@ public: int width_cache : 24; bool marked : 1; bool breakpoint : 1; + bool bookmark : 1; bool hidden : 1; bool safe : 1; int wrap_amount_cache : 24; Map<int, ColorRegionInfo> region_info; + Ref<Texture> info_icon; + String info; String data; }; @@ -103,12 +106,21 @@ public: void set(int p_line, const String &p_text); void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; } bool is_marked(int p_line) const { return text[p_line].marked; } + void set_bookmark(int p_line, bool p_bookmark) { text.write[p_line].bookmark = p_bookmark; } + bool is_bookmark(int p_line) const { return text[p_line].bookmark; } void set_breakpoint(int p_line, bool p_breakpoint) { text.write[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.write[p_line].hidden = p_hidden; } bool is_hidden(int p_line) const { return text[p_line].hidden; } void set_safe(int p_line, bool p_safe) { text.write[p_line].safe = p_safe; } bool is_safe(int p_line) const { return text[p_line].safe; } + void set_info_icon(int p_line, Ref<Texture> p_icon, String p_info) { + text.write[p_line].info_icon = p_icon; + text.write[p_line].info = p_info; + } + bool has_info_icon(int p_line) const { return text[p_line].info_icon.is_valid(); } + const Ref<Texture> &get_info_icon(int p_line) const { return text[p_line].info_icon; } + const String &get_info(int p_line) const { return text[p_line].info; } void insert(int p_at, const String &p_text); void remove(int p_at); int size() const { return text.size(); } @@ -154,9 +166,11 @@ private: struct Cache { Ref<Texture> tab_icon; + Ref<Texture> space_icon; Ref<Texture> can_fold_icon; Ref<Texture> folded_icon; Ref<Texture> folded_eol_icon; + Ref<Texture> executing_icon; Ref<StyleBox> style_normal; Ref<StyleBox> style_focus; Ref<StyleBox> style_readonly; @@ -170,14 +184,17 @@ private: Color line_number_color; Color safe_line_number_color; Color font_color; - Color font_selected_color; + Color font_color_selected; + Color font_color_readonly; Color keyword_color; Color number_color; Color function_color; Color member_variable_color; Color selection_color; Color mark_color; + Color bookmark_color; Color breakpoint_color; + Color executing_line_color; Color code_folding_color; Color current_line_color; Color line_length_guideline_color; @@ -193,9 +210,12 @@ private: int line_number_w; int breakpoint_gutter_width; int fold_gutter_width; + int info_gutter_width; + int minimap_width; } cache; Map<int, int> color_region_cache; + Map<int, Map<int, HighlighterInfo> > syntax_highlighting_cache; struct TextOperation { @@ -237,11 +257,11 @@ private: Set<String> completion_prefixes; bool completion_enabled; - Vector<String> completion_strings; - Vector<String> completion_options; + List<ScriptCodeCompletionOption> completion_sources; + Vector<ScriptCodeCompletionOption> completion_options; bool completion_active; bool completion_forced; - String completion_current; + ScriptCodeCompletionOption completion_current; String completion_base; int completion_index; Rect2i completion_rect; @@ -275,8 +295,10 @@ private: int wrap_at; int wrap_right_offset; + bool first_draw; bool setting_row; bool draw_tabs; + bool draw_spaces; bool override_selected_font_color; bool cursor_changed_dirty; bool text_changed_dirty; @@ -285,11 +307,18 @@ private: bool line_numbers_zero_padded; bool line_length_guideline; int line_length_guideline_col; + bool draw_bookmark_gutter; bool draw_breakpoint_gutter; int breakpoint_gutter_width; bool draw_fold_gutter; int fold_gutter_width; bool hiding_enabled; + bool draw_info_gutter; + int info_gutter_width; + bool draw_minimap; + int minimap_width; + Point2 minimap_char_size; + int minimap_line_spacing; bool highlight_all_occurrences; bool scroll_past_end_of_file_enabled; @@ -303,6 +332,12 @@ private: bool smooth_scroll_enabled; bool scrolling; + bool dragging_selection; + bool dragging_minimap; + bool can_drag_minimap; + bool minimap_clicked; + double minimap_scroll_ratio; + double minimap_scroll_click_pos; float target_v_scroll; float v_scroll_speed; @@ -332,9 +367,13 @@ private: bool context_menu_enabled; + int executing_line; + int get_visible_rows() const; int get_total_visible_rows() const; + int _get_minimap_visible_rows() const; + void update_cursor_wrap_offset(); void _update_wrap_at(); bool line_wraps(int line) const; @@ -370,7 +409,8 @@ private: void _update_selection_mode_word(); void _update_selection_mode_line(); - void _uncomment_line(int p_line); + void _update_minimap_click(); + void _update_minimap_drag(); void _scroll_up(real_t p_delta); void _scroll_down(real_t p_delta); @@ -382,6 +422,7 @@ private: //void mouse_motion(const Point& p_pos, const Point& p_rel, int p_button_mask); Size2 get_minimum_size() const; + int _get_control_height() const; int get_row_height() const; @@ -413,6 +454,9 @@ private: void _confirm_completion(); void _update_completion_candidates(); + int _calculate_spaces_till_next_left_indent(int column); + int _calculate_spaces_till_next_right_indent(int column); + protected: virtual String get_tooltip(const Point2 &p_pos) const; @@ -457,6 +501,7 @@ public: virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const; void _get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const; + void _get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const; //void delete_char(); //void delete_line(); @@ -471,14 +516,23 @@ public: void insert_at(const String &p_text, int at); int get_line_count() const; void set_line_as_marked(int p_line, bool p_marked); + void set_line_as_bookmark(int p_line, bool p_bookmark); + bool is_line_set_as_bookmark(int p_line) const; + void get_bookmarks(List<int> *p_bookmarks) const; + Array get_bookmarks_array() const; void set_line_as_breakpoint(int p_line, bool p_breakpoint); bool is_line_set_as_breakpoint(int p_line) const; + void set_executing_line(int p_line); + void clear_executing_line(); void set_line_as_safe(int p_line, bool p_safe); bool is_line_set_as_safe(int p_line) const; void get_breakpoints(List<int> *p_breakpoints) const; Array get_breakpoints_array() const; void remove_breakpoints(); + void set_line_info_icon(int p_line, Ref<Texture> p_icon, String p_info = ""); + void clear_info_icons(); + void set_line_as_hidden(int p_line, bool p_hidden); bool is_line_hidden(int p_line) const; void fold_all_lines(); @@ -489,6 +543,7 @@ public: bool can_fold(int p_line) const; bool is_folded(int p_line) const; + Vector<int> get_folded_lines() const; void fold_line(int p_line); void unfold_line(int p_line); void toggle_fold_line(int p_line); @@ -590,6 +645,8 @@ public: int get_indent_size(); void set_draw_tabs(bool p_draw); bool is_drawing_tabs() const; + void set_draw_spaces(bool p_draw); + bool is_drawing_spaces() const; void set_override_selected_font_color(bool p_override_selected_font_color); bool is_overriding_selected_font_color() const; @@ -637,6 +694,9 @@ public: void set_show_line_length_guideline(bool p_show); void set_line_length_guideline_column(int p_column); + void set_bookmark_gutter_enabled(bool p_draw); + bool is_bookmark_gutter_enabled() const; + void set_breakpoint_gutter_enabled(bool p_draw); bool is_breakpoint_gutter_enabled() const; @@ -649,13 +709,25 @@ public: void set_fold_gutter_width(int p_gutter_width); int get_fold_gutter_width() const; - void set_hiding_enabled(int p_enabled); - int is_hiding_enabled() const; + void set_draw_info_gutter(bool p_draw); + bool is_drawing_info_gutter() const; + + void set_info_gutter_width(int p_gutter_width); + int get_info_gutter_width() const; + + void set_draw_minimap(bool p_draw); + bool is_drawing_minimap() const; + + void set_minimap_width(int p_minimap_width); + int get_minimap_width() const; + + void set_hiding_enabled(bool p_enabled); + bool is_hiding_enabled() const; void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata); void set_completion(bool p_enabled, const Vector<String> &p_prefixes); - void code_complete(const Vector<String> &p_strings, bool p_forced = false); + void code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced = false); void set_code_hint(const String &p_hint); void query_code_comple(); @@ -683,10 +755,11 @@ protected: TextEdit *text_editor; public: + virtual ~SyntaxHighlighter() {} virtual void _update_cache() = 0; virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line) = 0; - virtual String get_name() = 0; + virtual String get_name() const = 0; virtual List<String> get_supported_languages() = 0; void set_text_editor(TextEdit *p_text_editor); diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 95aab95253..b5f949aeb7 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -64,7 +64,9 @@ bool TextureButton::has_point(const Point2 &p_point) const { Rect2 rect = Rect2(); Size2 mask_size = click_mask->get_size(); - if (_tile) { + if (_position_rect.has_no_area()) { + rect.size = mask_size; + } else if (_tile) { // if the stretch mode is tile we offset the point to keep it inside the mask size rect.size = mask_size; if (_position_rect.has_point(point)) { @@ -206,8 +208,8 @@ void TextureButton::_notification(int p_what) { Size2 scaleSize(size.width / tex_size.width, size.height / tex_size.height); float scale = scaleSize.width > scaleSize.height ? scaleSize.width : scaleSize.height; Size2 scaledTexSize = tex_size * scale; - Point2 ofs = ((scaledTexSize - size) / scale).abs() / 2.0f; - _texture_region = Rect2(ofs, size / scale); + Point2 ofs2 = ((scaledTexSize - size) / scale).abs() / 2.0f; + _texture_region = Rect2(ofs2, size / scale); } break; } } @@ -216,11 +218,12 @@ void TextureButton::_notification(int p_what) { draw_texture_rect(texdraw, _position_rect, _tile); else draw_texture_rect_region(texdraw, _position_rect, _texture_region); + } else { + _position_rect = Rect2(); } if (has_focus() && focused.is_valid()) { - Rect2 drect(Point2(), get_size()); - draw_texture_rect(focused, drect, false); + draw_texture_rect(focused, _position_rect, false); }; } break; } diff --git a/scene/gui/texture_button.h b/scene/gui/texture_button.h index 4dc0de5358..d9224de686 100644 --- a/scene/gui/texture_button.h +++ b/scene/gui/texture_button.h @@ -32,7 +32,7 @@ #define TEXTURE_BUTTON_H #include "scene/gui/base_button.h" -#include "scene/resources/bit_mask.h" +#include "scene/resources/bit_map.h" class TextureButton : public BaseButton { GDCLASS(TextureButton, BaseButton); diff --git a/scene/gui/texture_progress.cpp b/scene/gui/texture_progress.cpp index 778d86d546..c534df5cbe 100644 --- a/scene/gui/texture_progress.cpp +++ b/scene/gui/texture_progress.cpp @@ -160,23 +160,27 @@ Point2 TextureProgress::unit_val_to_uv(float val) { if (edge == 0) { if (dir.x > 0) continue; - cp = -dir.x; cq = -(edgeLeft - p.x); + dir.x *= 2.0 * cq; + cp = -dir.x; } else if (edge == 1) { if (dir.x < 0) continue; - cp = dir.x; cq = (edgeRight - p.x); + dir.x *= 2.0 * cq; + cp = dir.x; } else if (edge == 2) { if (dir.y > 0) continue; - cp = -dir.y; cq = -(edgeBottom - p.y); + dir.y *= 2.0 * cq; + cp = -dir.y; } else if (edge == 3) { if (dir.y < 0) continue; - cp = dir.y; cq = (edgeTop - p.y); + dir.y *= 2.0 * cq; + cp = dir.y; } cr = cq / cp; if (cr >= 0 && cr < t1) @@ -247,12 +251,14 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size))); last_section_size = MAX(0.0, last_section_size - (width_total - width_filled)); + first_section_size = MIN(first_section_size, width_filled); width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); switch (mode) { case FILL_LEFT_TO_RIGHT: { src_rect.size.x = width_texture; dst_rect.size.x = width_filled; + topleft.x = first_section_size; bottomright.x = last_section_size; } break; case FILL_RIGHT_TO_LEFT: { @@ -261,11 +267,13 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F dst_rect.position.x += width_total - width_filled; dst_rect.size.x = width_filled; topleft.x = last_section_size; + bottomright.x = first_section_size; } break; case FILL_TOP_TO_BOTTOM: { src_rect.size.y = width_texture; dst_rect.size.y = width_filled; bottomright.y = last_section_size; + topleft.y = first_section_size; } break; case FILL_BOTTOM_TO_TOP: { src_rect.position.y += src_rect.size.y - width_texture; @@ -273,6 +281,7 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F dst_rect.position.y += width_total - width_filled; dst_rect.size.y = width_filled; topleft.y = last_section_size; + bottomright.y = first_section_size; } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { // TODO: Implement diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp index caae48336b..423794d8ba 100644 --- a/scene/gui/texture_rect.cpp +++ b/scene/gui/texture_rect.cpp @@ -38,30 +38,32 @@ void TextureRect::_notification(int p_what) { if (texture.is_null()) return; + Size2 size; + Point2 offset; + Rect2 region; + bool tile = false; + switch (stretch_mode) { case STRETCH_SCALE_ON_EXPAND: { - Size2 s = expand ? get_size() : texture->get_size(); - draw_texture_rect(texture, Rect2(Point2(), s), false); + size = expand ? get_size() : texture->get_size(); } break; case STRETCH_SCALE: { - draw_texture_rect(texture, Rect2(Point2(), get_size()), false); + size = get_size(); } break; case STRETCH_TILE: { - draw_texture_rect(texture, Rect2(Point2(), get_size()), true); + size = get_size(); + tile = true; } break; case STRETCH_KEEP: { - draw_texture_rect(texture, Rect2(Point2(), texture->get_size()), false); - + size = texture->get_size(); } break; case STRETCH_KEEP_CENTERED: { - - Vector2 ofs = (get_size() - texture->get_size()) / 2; - draw_texture_rect(texture, Rect2(ofs, texture->get_size()), false); + offset = (get_size() - texture->get_size()) / 2; + size = texture->get_size(); } break; case STRETCH_KEEP_ASPECT_CENTERED: case STRETCH_KEEP_ASPECT: { - - Size2 size = get_size(); + size = get_size(); int tex_width = texture->get_width() * size.height / texture->get_height(); int tex_height = size.height; @@ -70,26 +72,35 @@ void TextureRect::_notification(int p_what) { tex_height = texture->get_height() * tex_width / texture->get_width(); } - int ofs_x = 0; - int ofs_y = 0; - if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { - ofs_x += (size.width - tex_width) / 2; - ofs_y += (size.height - tex_height) / 2; + offset.x += (size.width - tex_width) / 2; + offset.y += (size.height - tex_height) / 2; } - draw_texture_rect(texture, Rect2(ofs_x, ofs_y, tex_width, tex_height)); + size.width = tex_width; + size.height = tex_height; } break; case STRETCH_KEEP_ASPECT_COVERED: { - Size2 size = get_size(); + size = get_size(); + Size2 tex_size = texture->get_size(); Size2 scaleSize(size.width / tex_size.width, size.height / tex_size.height); float scale = scaleSize.width > scaleSize.height ? scaleSize.width : scaleSize.height; Size2 scaledTexSize = tex_size * scale; - Point2 ofs = ((scaledTexSize - size) / scale).abs() / 2.0f; - draw_texture_rect_region(texture, Rect2(Point2(), size), Rect2(ofs, size / scale)); + + region.position = ((scaledTexSize - size) / scale).abs() / 2.0f; + region.size = size / scale; } break; } + + size.width *= hflip ? -1.0f : 1.0f; + size.height *= vflip ? -1.0f : 1.0f; + + if (region.has_no_area()) { + draw_texture_rect(texture, Rect2(offset, size), tile); + } else { + draw_texture_rect_region(texture, Rect2(offset, size), region); + } } } @@ -106,12 +117,18 @@ void TextureRect::_bind_methods() { ClassDB::bind_method(D_METHOD("get_texture"), &TextureRect::get_texture); ClassDB::bind_method(D_METHOD("set_expand", "enable"), &TextureRect::set_expand); ClassDB::bind_method(D_METHOD("has_expand"), &TextureRect::has_expand); + ClassDB::bind_method(D_METHOD("set_flip_h", "enable"), &TextureRect::set_flip_h); + ClassDB::bind_method(D_METHOD("is_flipped_h"), &TextureRect::is_flipped_h); + ClassDB::bind_method(D_METHOD("set_flip_v", "enable"), &TextureRect::set_flip_v); + ClassDB::bind_method(D_METHOD("is_flipped_v"), &TextureRect::is_flipped_v); ClassDB::bind_method(D_METHOD("set_stretch_mode", "stretch_mode"), &TextureRect::set_stretch_mode); ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureRect::get_stretch_mode); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand"); ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale On Expand (Compat),Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v"); BIND_ENUM_CONSTANT(STRETCH_SCALE_ON_EXPAND); BIND_ENUM_CONSTANT(STRETCH_SCALE); @@ -161,9 +178,31 @@ TextureRect::StretchMode TextureRect::get_stretch_mode() const { return stretch_mode; } +void TextureRect::set_flip_h(bool p_flip) { + + hflip = p_flip; + update(); +} +bool TextureRect::is_flipped_h() const { + + return hflip; +} + +void TextureRect::set_flip_v(bool p_flip) { + + vflip = p_flip; + update(); +} +bool TextureRect::is_flipped_v() const { + + return vflip; +} + TextureRect::TextureRect() { expand = false; + hflip = false; + vflip = false; set_mouse_filter(MOUSE_FILTER_PASS); stretch_mode = STRETCH_SCALE_ON_EXPAND; } diff --git a/scene/gui/texture_rect.h b/scene/gui/texture_rect.h index ddd101573b..1c5bd9d99c 100644 --- a/scene/gui/texture_rect.h +++ b/scene/gui/texture_rect.h @@ -32,9 +32,7 @@ #define TEXTURE_FRAME_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class TextureRect : public Control { GDCLASS(TextureRect, Control); @@ -53,6 +51,8 @@ public: private: bool expand; + bool hflip; + bool vflip; Ref<Texture> texture; StretchMode stretch_mode; @@ -71,6 +71,12 @@ public: void set_stretch_mode(StretchMode p_mode); StretchMode get_stretch_mode() const; + void set_flip_h(bool p_flip); + bool is_flipped_h() const; + + void set_flip_v(bool p_flip); + bool is_flipped_v() const; + TextureRect(); ~TextureRect(); }; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 514be00227..b7451faad3 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -29,7 +29,6 @@ /*************************************************************************/ #include "tree.h" -#include <limits.h> #include "core/math/math_funcs.h" #include "core/os/input.h" @@ -40,9 +39,11 @@ #include "scene/main/viewport.h" #ifdef TOOLS_ENABLED -#include "editor/editor_node.h" +#include "editor/editor_scale.h" #endif +#include <limits.h> + void TreeItem::move_to_top() { if (!parent || parent->children == this) @@ -223,14 +224,14 @@ Rect2 TreeItem::get_icon_region(int p_column) const { return cells[p_column].icon_region; } -void TreeItem::set_icon_color(int p_column, const Color &p_icon_color) { +void TreeItem::set_icon_modulate(int p_column, const Color &p_modulate) { ERR_FAIL_INDEX(p_column, cells.size()); - cells.write[p_column].icon_color = p_icon_color; + cells.write[p_column].icon_color = p_modulate; _changed_notify(p_column); } -Color TreeItem::get_icon_color(int p_column) const { +Color TreeItem::get_icon_modulate(int p_column) const { ERR_FAIL_INDEX_V(p_column, cells.size(), Color()); return cells[p_column].icon_color; @@ -317,7 +318,7 @@ void TreeItem::set_custom_draw(int p_column, Object *p_object, const StringName void TreeItem::set_collapsed(bool p_collapsed) { - if (collapsed == p_collapsed) + if (collapsed == p_collapsed || !tree) return; collapsed = p_collapsed; TreeItem *ci = tree->selected_item; @@ -343,8 +344,7 @@ void TreeItem::set_collapsed(bool p_collapsed) { } _changed_notify(); - if (tree) - tree->emit_signal("item_collapsed", this); + tree->emit_signal("item_collapsed", this); } bool TreeItem::is_collapsed() { @@ -388,7 +388,7 @@ TreeItem *TreeItem::get_children() { return children; } -TreeItem *TreeItem::get_prev_visible() { +TreeItem *TreeItem::get_prev_visible(bool p_wrap) { TreeItem *current = this; @@ -397,8 +397,20 @@ TreeItem *TreeItem::get_prev_visible() { if (!prev) { current = current->parent; - if (!current || (current == tree->root && tree->hide_root)) + if (current == tree->root && tree->hide_root) { return NULL; + } else if (!current) { + if (p_wrap) { + current = this; + TreeItem *temp = this->get_next_visible(); + while (temp) { + current = temp; + temp = temp->get_next_visible(); + } + } else { + return NULL; + } + } } else { current = prev; @@ -414,7 +426,7 @@ TreeItem *TreeItem::get_prev_visible() { return current; } -TreeItem *TreeItem::get_next_visible() { +TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *current = this; @@ -432,10 +444,14 @@ TreeItem *TreeItem::get_next_visible() { current = current->parent; } - if (current == NULL) - return NULL; - else + if (!current) { + if (p_wrap) + return tree->root; + else + return NULL; + } else { current = current->next; + } } return current; @@ -556,13 +572,6 @@ int TreeItem::get_button_by_id(int p_column, int p_id) const { return -1; } -bool TreeItem::is_button_disabled(int p_column, int p_idx) const { - - ERR_FAIL_INDEX_V(p_column, cells.size(), false); - ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), false); - - return cells[p_column].buttons[p_idx].disabled; -} void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture> &p_button) { ERR_FAIL_COND(p_button.is_null()); @@ -580,6 +589,23 @@ void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) { _changed_notify(p_column); } +void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) { + + ERR_FAIL_INDEX(p_column, cells.size()); + ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); + + cells.write[p_column].buttons.write[p_idx].disabled = p_disabled; + _changed_notify(p_column); +} + +bool TreeItem::is_button_disabled(int p_column, int p_idx) const { + + ERR_FAIL_INDEX_V(p_column, cells.size(), false); + ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), false); + + return cells[p_column].buttons[p_idx].disabled; +} + void TreeItem::set_editable(int p_column, bool p_editable) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -718,6 +744,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_icon_max_width", "column", "width"), &TreeItem::set_icon_max_width); ClassDB::bind_method(D_METHOD("get_icon_max_width", "column"), &TreeItem::get_icon_max_width); + ClassDB::bind_method(D_METHOD("set_icon_modulate", "column", "modulate"), &TreeItem::set_icon_modulate); + ClassDB::bind_method(D_METHOD("get_icon_modulate", "column"), &TreeItem::get_icon_modulate); + ClassDB::bind_method(D_METHOD("set_range", "column", "value"), &TreeItem::set_range); ClassDB::bind_method(D_METHOD("get_range", "column"), &TreeItem::get_range); ClassDB::bind_method(D_METHOD("set_range_config", "column", "min", "max", "step", "expr"), &TreeItem::set_range_config, DEFVAL(false)); @@ -739,8 +768,8 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent); ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children); - ClassDB::bind_method(D_METHOD("get_next_visible"), &TreeItem::get_next_visible); - ClassDB::bind_method(D_METHOD("get_prev_visible"), &TreeItem::get_prev_visible); + ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false)); ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child); @@ -769,6 +798,7 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_button", "column", "button_idx"), &TreeItem::get_button); ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button); ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button); + ClassDB::bind_method(D_METHOD("set_button_disabled", "column", "button_idx", "disabled"), &TreeItem::set_button_disabled); ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled); ClassDB::bind_method(D_METHOD("set_expand_right", "column", "enable"), &TreeItem::set_expand_right); @@ -940,6 +970,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { int check_icon_h = cache.checked->get_height(); if (height < check_icon_h) height = check_icon_h; + FALLTHROUGH; } case TreeItem::CELL_MODE_STRING: case TreeItem::CELL_MODE_CUSTOM: @@ -960,7 +991,8 @@ int Tree::compute_item_height(TreeItem *p_item) const { } } break; - default: {} + default: { + } } } int item_min_height = p_item->get_custom_minimum_height(); @@ -1071,11 +1103,11 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int font_ascent = font->get_ascent(); int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); - int skip = 0; + int skip2 = 0; for (int i = 0; i < columns.size(); i++) { - if (skip) { - skip--; + if (skip2) { + skip2--; continue; } @@ -1102,7 +1134,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 while (i + plus < columns.size() && !p_item->cells[i + plus].editable && p_item->cells[i + plus].mode == TreeItem::CELL_MODE_STRING && p_item->cells[i + plus].text == "" && p_item->cells[i + plus].icon.is_null()) { w += get_column_width(i + plus); plus++; - skip++; + skip2++; } } @@ -1110,6 +1142,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture> b = p_item->cells[i].buttons[j].texture; Size2 s = b->get_size() + cache.button_pressed->get_minimum_size(); + if (s.height < label_h) + s.height = label_h; Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs; @@ -1165,8 +1199,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 cache.selected->draw(ci, r); } if (text_editor->is_visible_in_tree()) { - Vector2 ofs(0, (text_editor->get_size().height - r.size.height) / 2); - text_editor->set_position(get_global_position() + r.position - ofs); + Vector2 ofs2(0, (text_editor->get_size().height - r.size.height) / 2); + text_editor->set_position(get_global_position() + r.position - ofs2); } } @@ -1228,10 +1262,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2); if (p_item->cells[i].checked) { - - checked->draw(ci, check_ofs, icon_col); + checked->draw(ci, check_ofs); } else { - unchecked->draw(ci, check_ofs, icon_col); + unchecked->draw(ci, check_ofs); } int check_w = checked->get_width() + cache.hseparation; @@ -1243,8 +1276,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 draw_item_rect(p_item->cells[i], item_rect, col, icon_col); - //font->draw( ci, text_pos, p_item->cells[i].text, col,item_rect.size.x-check_w ); - } break; case TreeItem::CELL_MODE_RANGE: { if (p_item->cells[i].text != "") { @@ -1274,18 +1305,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 font->draw(ci, text_pos, s, col, item_rect.size.x - downarrow->get_width()); - //? Point2i arrow_pos = item_rect.position; arrow_pos.x += item_rect.size.x - downarrow->get_width(); arrow_pos.y += Math::floor(((item_rect.size.y - downarrow->get_height())) / 2.0); - downarrow->draw(ci, arrow_pos, icon_col); + downarrow->draw(ci, arrow_pos); } else { Ref<Texture> updown = cache.updown; - String valtext = String::num(p_item->cells[i].val, Math::step_decimals(p_item->cells[i].step)); - //String valtext = rtos( p_item->cells[i].val ); + String valtext = String::num(p_item->cells[i].val, Math::range_step_decimals(p_item->cells[i].step)); if (p_item->cells[i].suffix != String()) valtext += " " + p_item->cells[i].suffix; @@ -1299,7 +1328,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 updown_pos.x += item_rect.size.x - updown->get_width(); updown_pos.y += Math::floor(((item_rect.size.y - updown->get_height())) / 2.0); - updown->draw(ci, updown_pos, icon_col); + updown->draw(ci, updown_pos); } } break; @@ -1317,13 +1346,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 icon_ofs += item_rect.position; draw_texture_rect(p_item->cells[i].icon, Rect2(icon_ofs, icon_size), false, icon_col); - //p_item->cells[i].icon->draw(ci, icon_ofs); } break; case TreeItem::CELL_MODE_CUSTOM: { - //int option = (int)p_item->cells[i].val; - if (p_item->cells[i].custom_draw_obj) { Object *cdo = ObjectDB::get_instance(p_item->cells[i].custom_draw_obj); @@ -1398,10 +1424,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 arrow->draw(ci, p_pos + p_draw_ofs + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset); } - //separator - //get_painter()->draw_fill_rect( Point2i(0,pos.y),Size2i(get_size().width,1),color( COLOR_TREE_GRID) ); - - //pos=p_pos; //reset pos } Point2 children_pos = p_pos; @@ -1416,12 +1438,15 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 TreeItem *c = p_item->children; + int prev_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y; + while (c) { if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); int parent_ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; + if (c->get_children() != NULL) root_pos -= Point2i(cache.arrow->get_width(), 0); @@ -1434,12 +1459,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (root_pos.y + line_width >= 0) { VisualServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x - Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); - VisualServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y), parent_pos, cache.relationship_line_color, line_width); + VisualServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y), Point2i(parent_pos.x, prev_ofs), cache.relationship_line_color, line_width); } if (htotal < 0) { return -1; } + prev_ofs = root_pos.y; } if (htotal >= 0) { @@ -1601,7 +1627,7 @@ void Tree::_range_click_timeout() { mb.instance(); ; - propagate_mouse_activated = false; //done from outside, so signal handler cant clear the tree in the middle of emit(which is a common case) + propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case) blocked++; propagate_mouse_event(pos + cache.offset, 0, 0, false, root, BUTTON_LEFT, mb); blocked--; @@ -1891,7 +1917,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool } else { - editor_text = String::num(p_item->cells[col].val, Math::step_decimals(p_item->cells[col].step)); + editor_text = String::num(p_item->cells[col].val, Math::range_step_decimals(p_item->cells[col].step)); if (select_mode == SELECT_MULTI && get_tree()->get_event_count() == focus_in_id) bring_up_editor = false; } @@ -1907,8 +1933,6 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool edited_col = col; bool on_arrow = x > col_width - cache.select_arrow->get_width(); - bring_up_editor = false; - custom_popup_rect = Rect2i(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h - cache.offset.y), Size2(get_column_width(col), item_h)); if (on_arrow || !p_item->cells[col].custom_button) { @@ -1963,6 +1987,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool item_h += child_h; } } + if (p_item == root && p_button == BUTTON_RIGHT) { + emit_signal("empty_rmb", get_local_mouse_position()); + } } return item_h; // nothing found @@ -2014,7 +2041,9 @@ void Tree::text_editor_enter(String p_text) { //popup_edited_item->edited_signal.call( popup_edited_item_col ); } break; - default: { ERR_FAIL(); } + default: { + ERR_FAIL(); + } } item_edited(popup_edited_item_col, popup_edited_item); @@ -2257,7 +2286,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { next = _n; } else { - return; + break; } } if (next == selected_item) @@ -2295,7 +2324,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { prev = _n; } else { - return; + break; } } if (prev == selected_item) @@ -2662,12 +2691,10 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { bool Tree::edit_selected() { TreeItem *s = get_selected(); - ERR_EXPLAIN("No item selected!"); - ERR_FAIL_COND_V(!s, false); + ERR_FAIL_COND_V_MSG(!s, false, "No item selected."); ensure_cursor_is_visible(); int col = get_selected_column(); - ERR_EXPLAIN("No item column selected!"); - ERR_FAIL_INDEX_V(col, columns.size(), false); + ERR_FAIL_INDEX_V_MSG(col, columns.size(), false, "No item column selected."); if (!s->cells[col].editable) return false; @@ -2698,8 +2725,8 @@ bool Tree::edit_selected() { popup_menu->clear(); for (int i = 0; i < c.text.get_slice_count(","); i++) { - String s = c.text.get_slicec(',', i); - popup_menu->add_item(s.get_slicec(':', 0), s.get_slicec(':', 1).empty() ? i : s.get_slicec(':', 1).to_int()); + String s2 = c.text.get_slicec(',', i); + popup_menu->add_item(s2.get_slicec(':', 0), s2.get_slicec(':', 1).empty() ? i : s2.get_slicec(':', 1).to_int()); } popup_menu->set_size(Size2(rect.size.width, 0)); @@ -2717,7 +2744,7 @@ bool Tree::edit_selected() { text_editor->set_position(textedpos); text_editor->set_size(rect.size); text_editor->clear(); - text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::step_decimals(c.step))); + text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step))); text_editor->select_all(); if (c.mode == TreeItem::CELL_MODE_RANGE) { @@ -2841,7 +2868,7 @@ void Tree::_notification(int p_what) { if (p_what == NOTIFICATION_DRAG_BEGIN) { single_select_defer = NULL; - if (cache.scroll_speed > 0 && get_rect().has_point(get_viewport()->get_mouse_position() - get_global_position())) { + if (cache.scroll_speed > 0) { scrolling = true; set_physics_process_internal(true); } @@ -2883,27 +2910,25 @@ void Tree::_notification(int p_what) { drag_touching = false; drag_touching_deaccel = false; } - - } else { } } - if (scrolling) { - Point2 point = get_viewport()->get_mouse_position() - get_global_position(); - if (point.x < cache.scroll_border) { - point.x -= cache.scroll_border; - } else if (point.x > get_size().width - cache.scroll_border) { - point.x -= get_size().width - cache.scroll_border; - } else { - point.x = 0; + Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position(); + if (scrolling && get_rect().grow(cache.scroll_border).has_point(mouse_position)) { + Point2 point; + + if ((ABS(mouse_position.x) < ABS(mouse_position.x - get_size().width)) && (ABS(mouse_position.x) < cache.scroll_border)) { + point.x = mouse_position.x - cache.scroll_border; + } else if (ABS(mouse_position.x - get_size().width) < cache.scroll_border) { + point.x = mouse_position.x - (get_size().width - cache.scroll_border); } - if (point.y < cache.scroll_border) { - point.y -= cache.scroll_border; - } else if (point.y > get_size().height - cache.scroll_border) { - point.y -= get_size().height - cache.scroll_border; - } else { - point.y = 0; + + if ((ABS(mouse_position.y) < ABS(mouse_position.y - get_size().height)) && (ABS(mouse_position.y) < cache.scroll_border)) { + point.y = mouse_position.y - cache.scroll_border; + } else if (ABS(mouse_position.y - get_size().height) < cache.scroll_border) { + point.y = mouse_position.y - (get_size().height - cache.scroll_border); } + point *= cache.scroll_speed * get_physics_process_delta_time(); point += get_scroll(); h_scroll->set_value(point.x); @@ -2951,14 +2976,14 @@ void Tree::_notification(int p_what) { if (show_column_titles) { //title buttons - int ofs = cache.bg->get_margin(MARGIN_LEFT); + int ofs2 = cache.bg->get_margin(MARGIN_LEFT); for (int i = 0; i < columns.size(); i++) { Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? cache.title_button_hover : cache.title_button); Ref<Font> f = cache.tb_font; - Rect2 tbrect = Rect2(ofs - cache.offset.x, bg->get_margin(MARGIN_TOP), get_column_width(i), tbh); + Rect2 tbrect = Rect2(ofs2 - cache.offset.x, bg->get_margin(MARGIN_TOP), get_column_width(i), tbh); sb->draw(ci, tbrect); - ofs += tbrect.size.width; + ofs2 += tbrect.size.width; //text int clip_w = tbrect.size.width - sb->get_minimum_size().width; f->draw_halign(ci, tbrect.position + Point2i(sb->get_offset().x, (tbrect.size.height - f->get_height()) / 2 + f->get_ascent()), HALIGN_CENTER, clip_w, columns[i].title, cache.title_button_color); @@ -3136,10 +3161,7 @@ bool Tree::is_anything_selected() { void Tree::clear() { - if (blocked > 0) { - - ERR_FAIL_COND(blocked > 0); - } + ERR_FAIL_COND(blocked > 0); if (pressing_for_editor) { if (range_drag_enabled) { @@ -3379,10 +3401,13 @@ void Tree::ensure_cursor_is_visible() { int h = compute_item_height(selected) + cache.vseparation; int screenh = get_size().height - h_scroll->get_combined_minimum_size().height; - if (ofs + h > v_scroll->get_value() + screenh) + if (h > screenh) { //screen size is too small, maybe it was not resized yet. + v_scroll->set_value(ofs); + } else if (ofs + h > v_scroll->get_value() + screenh) { v_scroll->call_deferred("set_value", ofs - screenh + h); - else if (ofs < v_scroll->get_value()) + } else if (ofs < v_scroll->get_value()) { v_scroll->set_value(ofs); + } } int Tree::get_pressed_button() const { @@ -3476,6 +3501,7 @@ void Tree::scroll_to_item(TreeItem *p_item) { TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) { + TreeItem *from = p_at; while (p_at) { for (int i = 0; i < columns.size(); i++) { @@ -3487,9 +3513,12 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c } if (p_backwards) - p_at = p_at->get_prev_visible(); + p_at = p_at->get_prev_visible(true); else - p_at = p_at->get_next_visible(); + p_at = p_at->get_next_visible(true); + + if ((p_at) == from) + break; } return NULL; @@ -3497,10 +3526,14 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c TreeItem *Tree::search_item_text(const String &p_find, int *r_col, bool p_selectable) { - if (!root) + TreeItem *from = get_selected(); + + if (!from) + from = root; + if (!from) return NULL; - return _search_item_text(root, p_find, r_col, p_selectable); + return _search_item_text(from->get_next_visible(true), p_find, r_col, p_selectable); } void Tree::_do_incr_search(const String &p_add) { @@ -3509,7 +3542,7 @@ void Tree::_do_incr_search(const String &p_add) { uint64_t diff = time - last_keypress; if (diff > uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000))) incr_search = p_add; - else + else if (incr_search != p_add) incr_search += p_add; last_keypress = time; @@ -3682,6 +3715,10 @@ String Tree::get_tooltip(const Point2 &p_pos) const { const TreeItem::Cell &c = it->cells[col]; int col_width = get_column_width(col); + + for (int i = 0; i < col; i++) + pos.x -= get_column_width(i); + for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture> b = c.buttons[j].texture; Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); @@ -3841,6 +3878,7 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("cell_selected")); ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::BOOL, "selected"))); ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::VECTOR2, "position"))); + ADD_SIGNAL(MethodInfo("empty_rmb", PropertyInfo(Variant::VECTOR2, "position"))); ADD_SIGNAL(MethodInfo("empty_tree_rmb_selected", PropertyInfo(Variant::VECTOR2, "position"))); ADD_SIGNAL(MethodInfo("item_edited")); ADD_SIGNAL(MethodInfo("item_rmb_edited")); @@ -3926,7 +3964,6 @@ Tree::Tree() { 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 26d64baafb..fdc6da5055 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -37,10 +37,6 @@ #include "scene/gui/scroll_bar.h" #include "scene/gui/slider.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class Tree; class TreeItem : public Object { @@ -197,8 +193,8 @@ public: void set_icon_region(int p_column, const Rect2 &p_icon_region); Rect2 get_icon_region(int p_column) const; - void set_icon_color(int p_column, const Color &p_icon_color); - Color get_icon_color(int p_column) const; + void set_icon_modulate(int p_column, const Color &p_modulate); + Color get_icon_modulate(int p_column) const; void set_icon_max_width(int p_column, int p_max); int get_icon_max_width(int p_column) const; @@ -209,9 +205,10 @@ public: int get_button_id(int p_column, int p_idx) const; void erase_button(int p_column, int p_idx); int get_button_by_id(int p_column, int p_id) const; - bool is_button_disabled(int p_column, int p_idx) const; void set_button(int p_column, int p_idx, const Ref<Texture> &p_button); void set_button_color(int p_column, int p_idx, const Color &p_color); + void set_button_disabled(int p_column, int p_idx, bool p_disabled); + bool is_button_disabled(int p_column, int p_idx) const; /* range works for mode number or mode combo */ @@ -238,8 +235,8 @@ public: TreeItem *get_parent(); TreeItem *get_children(); - TreeItem *get_prev_visible(); - TreeItem *get_next_visible(); + TreeItem *get_prev_visible(bool p_wrap = false); + TreeItem *get_next_visible(bool p_wrap = false); void remove_child(TreeItem *p_item); diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index 2b1991ebb0..8cab1b1280 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -153,6 +153,7 @@ void CanvasLayer::_notification(int p_what) { VisualServer::get_singleton()->viewport_attach_canvas(viewport, canvas); VisualServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_position_in_parent()); VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform); + _update_follow_viewport(); } break; case NOTIFICATION_EXIT_TREE: { @@ -160,6 +161,7 @@ void CanvasLayer::_notification(int p_what) { vp->_canvas_layer_remove(this); VisualServer::get_singleton()->viewport_remove_canvas(viewport, canvas); viewport = RID(); + _update_follow_viewport(false); } break; case NOTIFICATION_MOVED_IN_PARENT: { @@ -235,6 +237,41 @@ RID CanvasLayer::get_canvas() const { return canvas; } + +void CanvasLayer::set_follow_viewport(bool p_enable) { + if (follow_viewport == p_enable) { + return; + } + + follow_viewport = p_enable; + _update_follow_viewport(); +} + +bool CanvasLayer::is_following_viewport() const { + return follow_viewport; +} + +void CanvasLayer::set_follow_viewport_scale(float p_ratio) { + follow_viewport_scale = p_ratio; + _update_follow_viewport(); +} + +float CanvasLayer::get_follow_viewport_scale() const { + return follow_viewport_scale; +} + +void CanvasLayer::_update_follow_viewport(bool p_force_exit) { + + if (!is_inside_tree()) { + return; + } + if (p_force_exit || !follow_viewport) { + VS::get_singleton()->canvas_set_parent(canvas, RID(), 1.0); + } else { + VS::get_singleton()->canvas_set_parent(canvas, vp->get_world_2d()->get_canvas(), follow_viewport_scale); + } +} + void CanvasLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_layer", "layer"), &CanvasLayer::set_layer); @@ -255,18 +292,30 @@ void CanvasLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_scale", "scale"), &CanvasLayer::set_scale); ClassDB::bind_method(D_METHOD("get_scale"), &CanvasLayer::get_scale); + ClassDB::bind_method(D_METHOD("set_follow_viewport", "enable"), &CanvasLayer::set_follow_viewport); + ClassDB::bind_method(D_METHOD("is_following_viewport"), &CanvasLayer::is_following_viewport); + + ClassDB::bind_method(D_METHOD("set_follow_viewport_scale", "scale"), &CanvasLayer::set_follow_viewport_scale); + ClassDB::bind_method(D_METHOD("get_follow_viewport_scale"), &CanvasLayer::get_follow_viewport_scale); + 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_canvas"), &CanvasLayer::get_canvas); + ADD_GROUP("Layer", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "layer", PROPERTY_HINT_RANGE, "-128,128,1"), "set_layer", "get_layer"); + ADD_GROUP("Transform", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "rotation_degrees", PROPERTY_HINT_RANGE, "-1080,1080,0.1,or_lesser,or_greater", PROPERTY_USAGE_EDITOR), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform"), "set_transform", "get_transform"); + ADD_GROUP("", ""); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_viewport", PROPERTY_HINT_RESOURCE_TYPE, "Viewport", 0), "set_custom_viewport", "get_custom_viewport"); + ADD_GROUP("Follow Viewport", "follow_viewport"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_viewport_enable"), "set_follow_viewport", "is_following_viewport"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "follow_viewport_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001,or_greater,or_lesser"), "set_follow_viewport_scale", "get_follow_viewport_scale"); } CanvasLayer::CanvasLayer() { @@ -280,6 +329,8 @@ CanvasLayer::CanvasLayer() { custom_viewport = NULL; custom_viewport_id = 0; sort_index = 0; + follow_viewport = false; + follow_viewport_scale = 1.0; } CanvasLayer::~CanvasLayer() { diff --git a/scene/main/canvas_layer.h b/scene/main/canvas_layer.h index 5d67245102..fa2558556c 100644 --- a/scene/main/canvas_layer.h +++ b/scene/main/canvas_layer.h @@ -55,8 +55,12 @@ class CanvasLayer : public Node { int sort_index; + bool follow_viewport; + float follow_viewport_scale; + void _update_xform(); void _update_locrotscale(); + void _update_follow_viewport(bool p_force_exit = false); protected: void _notification(int p_what); @@ -91,6 +95,12 @@ public: void reset_sort_index(); int get_sort_index(); + void set_follow_viewport(bool p_enable); + bool is_following_viewport() const; + + void set_follow_viewport_scale(float p_ratio); + float get_follow_viewport_scale() const; + RID get_canvas() const; CanvasLayer(); diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index 8b68b3215c..e21e47f8a8 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -60,14 +60,10 @@ Error HTTPRequest::_parse_url(const String &p_url) { use_ssl = true; port = 443; } else { - ERR_EXPLAIN("Malformed URL"); - ERR_FAIL_V(ERR_INVALID_PARAMETER); + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Malformed URL."); } - if (url.length() < 1) { - ERR_EXPLAIN("URL too short"); - ERR_FAIL_V(ERR_INVALID_PARAMETER); - } + ERR_FAIL_COND_V_MSG(url.length() < 1, ERR_INVALID_PARAMETER, "URL too short."); int slash_pos = url.find("/"); @@ -91,9 +87,11 @@ Error HTTPRequest::_parse_url(const String &p_url) { Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_headers, bool p_ssl_validate_domain, HTTPClient::Method p_method, const String &p_request_data) { ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED); - if (requesting) { - ERR_EXPLAIN("HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one."); - ERR_FAIL_V(ERR_BUSY); + ERR_FAIL_COND_V_MSG(requesting, ERR_BUSY, "HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one."); + + if (timeout > 0) { + timer->stop(); + timer->start(timeout); } method = p_method; @@ -153,6 +151,8 @@ void HTTPRequest::_thread_func(void *p_userdata) { void HTTPRequest::cancel_request() { + timer->stop(); + if (!requesting) return; @@ -286,7 +286,7 @@ bool HTTPRequest::_update_connection() { call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, PoolByteArray()); return true; } - if (got_response && body_len < 0) { + if (body_len < 0) { // Chunked transfer is done call_deferred("_request_done", RESULT_SUCCESS, response_code, response_headers, body); return true; @@ -425,7 +425,7 @@ void HTTPRequest::_notification(int p_what) { void HTTPRequest::set_use_threads(bool p_use) { - ERR_FAIL_COND(status != HTTPClient::STATUS_DISCONNECTED); + ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); use_threads = p_use; } @@ -436,7 +436,7 @@ bool HTTPRequest::is_using_threads() const { void HTTPRequest::set_body_size_limit(int p_bytes) { - ERR_FAIL_COND(status != HTTPClient::STATUS_DISCONNECTED); + ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); body_size_limit = p_bytes; } @@ -448,7 +448,7 @@ int HTTPRequest::get_body_size_limit() const { void HTTPRequest::set_download_file(const String &p_file) { - ERR_FAIL_COND(status != HTTPClient::STATUS_DISCONNECTED); + ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED); download_to_file = p_file; } @@ -479,6 +479,23 @@ int HTTPRequest::get_body_size() const { return body_len; } +void HTTPRequest::set_timeout(int p_timeout) { + + ERR_FAIL_COND(p_timeout < 0); + timeout = p_timeout; +} + +int HTTPRequest::get_timeout() { + + return timeout; +} + +void HTTPRequest::_timeout() { + + cancel_request(); + call_deferred("_request_done", RESULT_TIMEOUT, 0, PoolStringArray(), PoolByteArray()); +} + void HTTPRequest::_bind_methods() { ClassDB::bind_method(D_METHOD("request", "url", "custom_headers", "ssl_validate_domain", "method", "request_data"), &HTTPRequest::request, DEFVAL(PoolStringArray()), DEFVAL(true), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String())); @@ -504,10 +521,16 @@ void HTTPRequest::_bind_methods() { ClassDB::bind_method(D_METHOD("_redirect_request"), &HTTPRequest::_redirect_request); ClassDB::bind_method(D_METHOD("_request_done"), &HTTPRequest::_request_done); + ClassDB::bind_method(D_METHOD("set_timeout", "timeout"), &HTTPRequest::set_timeout); + ClassDB::bind_method(D_METHOD("get_timeout"), &HTTPRequest::get_timeout); + + ClassDB::bind_method(D_METHOD("_timeout"), &HTTPRequest::_timeout); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "download_file", PROPERTY_HINT_FILE), "set_download_file", "get_download_file"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_threads"), "set_use_threads", "is_using_threads"); ADD_PROPERTY(PropertyInfo(Variant::INT, "body_size_limit", PROPERTY_HINT_RANGE, "-1,2000000000"), "set_body_size_limit", "get_body_size_limit"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redirects", PROPERTY_HINT_RANGE, "-1,64"), "set_max_redirects", "get_max_redirects"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "timeout", PROPERTY_HINT_RANGE, "0,86400"), "set_timeout", "get_timeout"); ADD_SIGNAL(MethodInfo("request_completed", PropertyInfo(Variant::INT, "result"), PropertyInfo(Variant::INT, "response_code"), PropertyInfo(Variant::POOL_STRING_ARRAY, "headers"), PropertyInfo(Variant::POOL_BYTE_ARRAY, "body"))); @@ -524,6 +547,7 @@ void HTTPRequest::_bind_methods() { BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_CANT_OPEN); BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_WRITE_ERROR); BIND_ENUM_CONSTANT(RESULT_REDIRECT_LIMIT_REACHED); + BIND_ENUM_CONSTANT(RESULT_TIMEOUT); } HTTPRequest::HTTPRequest() { @@ -546,7 +570,12 @@ HTTPRequest::HTTPRequest() { downloaded = 0; body_size_limit = -1; file = NULL; - status = HTTPClient::STATUS_DISCONNECTED; + + timer = memnew(Timer); + timer->set_one_shot(true); + timer->connect("timeout", this, "_timeout"); + add_child(timer); + timeout = 0; } HTTPRequest::~HTTPRequest() { diff --git a/scene/main/http_request.h b/scene/main/http_request.h index baabda4010..f1f91235a6 100644 --- a/scene/main/http_request.h +++ b/scene/main/http_request.h @@ -35,6 +35,7 @@ #include "core/os/file_access.h" #include "core/os/thread.h" #include "node.h" +#include "scene/main/timer.h" class HTTPRequest : public Node { @@ -53,7 +54,8 @@ public: RESULT_REQUEST_FAILED, RESULT_DOWNLOAD_FILE_CANT_OPEN, RESULT_DOWNLOAD_FILE_WRITE_ERROR, - RESULT_REDIRECT_LIMIT_REACHED + RESULT_REDIRECT_LIMIT_REACHED, + RESULT_TIMEOUT }; @@ -88,12 +90,12 @@ private: int redirections; - HTTPClient::Status status; - bool _update_connection(); int max_redirects; + int timeout; + void _redirect_request(const String &p_new_url); bool _handle_response(bool *ret_value); @@ -130,6 +132,13 @@ public: void set_max_redirects(int p_max); int get_max_redirects() const; + Timer *timer; + + void set_timeout(int p_timeout); + int get_timeout(); + + void _timeout(); + int get_downloaded_bytes() const; int get_body_size() const; diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index 71addd6fea..99ecc8bc37 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -92,6 +92,8 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene if (!ps.is_valid()) return NULL; Node *scene = ps->instance(); + if (!scene) + return NULL; scene->set_name(get_name()); int pos = get_position_in_parent(); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 6aa3891035..0b3a193d18 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -39,8 +39,14 @@ #include "scene/scene_string_names.h" #include "viewport.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + VARIANT_ENUM_CAST(Node::PauseMode); +int Node::orphan_node_count = 0; + void Node::_notification(int p_notification) { switch (p_notification) { @@ -65,6 +71,8 @@ void Node::_notification(int p_notification) { } break; case NOTIFICATION_ENTER_TREE: { + ERR_FAIL_COND(!get_viewport()); + ERR_FAIL_COND(!get_tree()); if (data.pause_mode == PAUSE_MODE_INHERIT) { @@ -84,11 +92,16 @@ void Node::_notification(int p_notification) { add_to_group("_vp_unhandled_key_input" + itos(get_viewport()->get_instance_id())); get_tree()->node_count++; + orphan_node_count--; } break; case NOTIFICATION_EXIT_TREE: { + ERR_FAIL_COND(!get_viewport()); + ERR_FAIL_COND(!get_tree()); get_tree()->node_count--; + orphan_node_count++; + if (data.input) remove_from_group("_vp_input" + itos(get_viewport()->get_instance_id())); if (data.unhandled_input) @@ -197,7 +210,7 @@ void Node::_propagate_enter_tree() { } data.viewport = Object::cast_to<Viewport>(this); - if (!data.viewport) + if (!data.viewport && data.parent) data.viewport = data.parent->data.viewport; data.inside_tree = true; @@ -314,14 +327,9 @@ void Node::_propagate_exit_tree() { void Node::move_child(Node *p_child, int p_pos) { ERR_FAIL_NULL(p_child); - ERR_EXPLAIN("Invalid new child position: " + itos(p_pos)); - ERR_FAIL_INDEX(p_pos, data.children.size() + 1); - ERR_EXPLAIN("child is not a child of this node."); - ERR_FAIL_COND(p_child->data.parent != this); - if (data.blocked > 0) { - ERR_EXPLAIN("Parent node is busy setting up children, move_child() failed. Consider using call_deferred(\"move_child\") instead (or \"popup\" if this is from a popup)."); - ERR_FAIL_COND(data.blocked > 0); - } + ERR_FAIL_INDEX_MSG(p_pos, data.children.size() + 1, "Invalid new child position: " + itos(p_pos) + "."); + ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node."); + ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, move_child() failed. Consider using call_deferred(\"move_child\") instead (or \"popup\" if this is from a popup)."); // Specifying one place beyond the end // means the same as moving to the last position @@ -395,7 +403,6 @@ void Node::set_physics_process(bool p_process) { else remove_from_group("physics_process"); - data.physics_process = p_process; _change_notify("physics_process"); } @@ -416,7 +423,6 @@ void Node::set_physics_process_internal(bool p_process_internal) { else remove_from_group("physics_process_internal"); - data.physics_process_internal = p_process_internal; _change_notify("physics_process_internal"); } @@ -798,7 +804,6 @@ void Node::set_process(bool p_idle_process) { else remove_from_group("idle_process"); - data.idle_process = p_idle_process; _change_notify("idle_process"); } @@ -819,7 +824,6 @@ void Node::set_process_internal(bool p_idle_process_internal) { else remove_from_group("idle_process_internal"); - data.idle_process_internal = p_idle_process_internal; _change_notify("idle_process_internal"); } @@ -831,6 +835,8 @@ bool Node::is_processing_internal() const { void Node::set_process_priority(int p_priority) { data.process_priority = p_priority; + ERR_FAIL_COND(!data.tree); + if (is_processing()) data.tree->make_group_changed("idle_process"); @@ -940,6 +946,7 @@ void Node::set_name(const String &p_name) { if (is_inside_tree()) { emit_signal("renamed"); + get_tree()->node_renamed(this); get_tree()->tree_changed(); } } @@ -1005,7 +1012,7 @@ void Node::_validate_child_name(Node *p_child, bool p_force_human_readable) { if (!unique) { - node_hrcr_count.ref(); + ERR_FAIL_COND(!node_hrcr_count.ref()); String name = "@" + String(p_child->get_name()) + "@" + itos(node_hrcr_count.get()); p_child->data.name = name; } @@ -1153,25 +1160,9 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) { void Node::add_child(Node *p_child, bool p_legible_unique_name) { ERR_FAIL_NULL(p_child); - - if (p_child == this) { - ERR_EXPLAIN("Can't add child '" + p_child->get_name() + "' to itself.") - ERR_FAIL_COND(p_child == this); // adding to itself! - } - - /* Fail if node has a parent */ - if (p_child->data.parent) { - ERR_EXPLAIN("Can't add child '" + p_child->get_name() + "' to '" + get_name() + "', already has a parent '" + p_child->data.parent->get_name() + "'."); - ERR_FAIL_COND(p_child->data.parent); - } - - if (data.blocked > 0) { - ERR_EXPLAIN("Parent node is busy setting up children, add_node() failed. Consider using call_deferred(\"add_child\", child) instead."); - ERR_FAIL_COND(data.blocked > 0); - } - - ERR_EXPLAIN("Can't add child while a notification is happening."); - ERR_FAIL_COND(data.blocked > 0); + ERR_FAIL_COND_MSG(p_child == this, "Can't add child '" + p_child->get_name() + "' to itself."); // adding to itself! + ERR_FAIL_COND_MSG(p_child->data.parent, "Can't add child '" + p_child->get_name() + "' to '" + get_name() + "', already has a parent '" + p_child->data.parent->get_name() + "'."); //Fail if node has a parent + ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, add_node() failed. Consider using call_deferred(\"add_child\", child) instead."); /* Validate name */ _validate_child_name(p_child, p_legible_unique_name); @@ -1189,7 +1180,7 @@ void Node::add_child_below_node(Node *p_node, Node *p_child, bool p_legible_uniq if (is_a_parent_of(p_node)) { move_child(p_child, p_node->get_position_in_parent() + 1); } else { - WARN_PRINTS("Cannot move under node " + p_node->get_name() + " as " + p_child->get_name() + " does not share a parent.") + WARN_PRINTS("Cannot move under node " + p_node->get_name() + " as " + p_child->get_name() + " does not share a parent."); } } @@ -1227,10 +1218,7 @@ void Node::_propagate_validate_owner() { void Node::remove_child(Node *p_child) { ERR_FAIL_NULL(p_child); - if (data.blocked > 0) { - ERR_EXPLAIN("Parent node is busy setting up children, remove_node() failed. Consider using call_deferred(\"remove_child\",child) instead."); - ERR_FAIL_COND(data.blocked > 0); - } + ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, remove_node() failed. Consider using call_deferred(\"remove_child\", child) instead."); int child_count = data.children.size(); Node **children = data.children.ptrw(); @@ -1253,7 +1241,7 @@ void Node::remove_child(Node *p_child) { } } - ERR_FAIL_COND(idx == -1); + ERR_FAIL_COND_MSG(idx == -1, "Cannot remove child node " + p_child->get_name() + " as it is not a child of this node."); //ERR_FAIL_COND( p_child->data.blocked > 0 ); //if (data.scene) { does not matter @@ -1311,13 +1299,14 @@ Node *Node::_get_child_by_name(const StringName &p_name) const { return NULL; } -Node *Node::_get_node(const NodePath &p_path) const { +Node *Node::get_node_or_null(const NodePath &p_path) const { - if (!data.inside_tree && p_path.is_absolute()) { - ERR_EXPLAIN("Can't use get_node() with absolute paths from outside the active scene tree."); - ERR_FAIL_V(NULL); + if (p_path.is_empty()) { + return NULL; } + ERR_FAIL_COND_V_MSG(!data.inside_tree && p_path.is_absolute(), NULL, "Can't use get_node() with absolute paths from outside the active scene tree."); + Node *current = NULL; Node *root = NULL; @@ -1376,17 +1365,14 @@ Node *Node::_get_node(const NodePath &p_path) const { Node *Node::get_node(const NodePath &p_path) const { - Node *node = _get_node(p_path); - if (!node) { - ERR_EXPLAIN("Node not found: " + p_path); - ERR_FAIL_COND_V(!node, NULL); - } + Node *node = get_node_or_null(p_path); + ERR_FAIL_COND_V_MSG(!node, NULL, "Node not found: " + p_path + "."); return node; } bool Node::has_node(const NodePath &p_path) const { - return _get_node(p_path) != NULL; + return get_node_or_null(p_path) != NULL; } Node *Node::find_node(const String &p_mask, bool p_recursive, bool p_owned) const { @@ -1648,7 +1634,7 @@ NodePath Node::get_path_to(const Node *p_node) const { NodePath Node::get_path() const { - ERR_FAIL_COND_V(!is_inside_tree(), NodePath()); + ERR_FAIL_COND_V_MSG(!is_inside_tree(), NodePath(), "Cannot get path of node as it is not in a scene tree."); if (data.path_cache) return *data.path_cache; @@ -1730,16 +1716,19 @@ void Node::get_groups(List<GroupInfo> *p_groups) const { } } -bool Node::has_persistent_groups() const { +int Node::get_persistent_group_count() const { + + int count = 0; for (const Map<StringName, GroupData>::Element *E = data.grouped.front(); E; E = E->next()) { - if (E->get().persistent) - return true; + if (E->get().persistent) { + count += 1; + } } - return false; + return count; } -void Node::_print_tree_pretty(const String prefix, const bool last) { +void Node::_print_tree_pretty(const String &prefix, const bool last) { String new_prefix = last ? String::utf8(" â”–â•´") : String::utf8(" â” â•´"); print_line(prefix + new_prefix + String(get_name())); @@ -2063,7 +2052,9 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const } } - node->set_name(get_name()); + if (get_name() != String()) { + node->set_name(get_name()); + } #ifdef TOOLS_ENABLED if ((p_flags & DUPLICATE_FROM_EDITOR) && r_duplimap) @@ -2173,11 +2164,12 @@ void Node::_duplicate_and_reown(Node *p_new_parent, const Map<Node *, Node *> &p } else { Object *obj = ClassDB::instance(get_class()); - ERR_EXPLAIN("Node: Could not duplicate: " + String(get_class())); - ERR_FAIL_COND(!obj); + ERR_FAIL_COND_MSG(!obj, "Node: Could not duplicate: " + String(get_class()) + "."); node = Object::cast_to<Node>(obj); - if (!node) + if (!node) { memdelete(obj); + ERR_FAIL_MSG("Node: Could not duplicate: " + String(get_class()) + "."); + } } List<PropertyInfo> plist; @@ -2273,16 +2265,14 @@ Node *Node::duplicate_and_reown(const Map<Node *, Node *> &p_reown_map) const { ERR_FAIL_COND_V(get_filename() != "", NULL); - Node *node = NULL; - Object *obj = ClassDB::instance(get_class()); - ERR_EXPLAIN("Node: Could not duplicate: " + String(get_class())); - ERR_FAIL_COND_V(!obj, NULL); - node = Object::cast_to<Node>(obj); - if (!node) - memdelete(obj); - ERR_FAIL_COND_V(!node, NULL); + ERR_FAIL_COND_V_MSG(!obj, NULL, "Node: Could not duplicate: " + String(get_class()) + "."); + Node *node = Object::cast_to<Node>(obj); + if (!node) { + memdelete(obj); + ERR_FAIL_V_MSG(NULL, "Node: Could not duplicate: " + String(get_class()) + "."); + } node->set_name(get_name()); List<PropertyInfo> plist; @@ -2417,9 +2407,8 @@ void Node::_replace_connections_target(Node *p_new_target) { if (c.flags & CONNECT_PERSIST) { c.source->disconnect(c.signal, this, c.method); - bool valid = p_new_target->has_method(c.method) || p_new_target->get_script().is_null() || Ref<Script>(p_new_target->get_script())->has_method(c.method); - ERR_EXPLAIN("Attempt to connect signal \'" + c.source->get_class() + "." + c.signal + "\' to nonexistent method \'" + c.target->get_class() + "." + c.method + "\'"); - ERR_CONTINUE(!valid); + bool valid = p_new_target->has_method(c.method) || Ref<Script>(p_new_target->get_script()).is_null() || Ref<Script>(p_new_target->get_script())->has_method(c.method); + ERR_CONTINUE_MSG(!valid, "Attempt to connect signal '" + c.source->get_class() + "." + c.signal + "' to nonexistent method '" + c.target->get_class() + "." + c.method + "'."); c.source->connect(c.signal, p_new_target, c.method, c.binds, c.flags); } } @@ -2461,21 +2450,18 @@ bool Node::has_node_and_resource(const NodePath &p_path) const { if (!has_node(p_path)) return false; - Node *node = get_node(p_path); - - bool result = false; - - node->get_indexed(p_path.get_subnames(), &result); + RES res; + Vector<StringName> leftover_path; + Node *node = get_node_and_resource(p_path, res, leftover_path, false); - return result; + return node; } Array Node::_get_node_and_resource(const NodePath &p_path) { - Node *node; RES res; Vector<StringName> leftover_path; - node = get_node_and_resource(p_path, res, leftover_path); + Node *node = get_node_and_resource(p_path, res, leftover_path, false); Array result; if (node) @@ -2505,10 +2491,16 @@ Node *Node::get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<Str int j = 0; // If not p_last_is_property, we shouldn't consider the last one as part of the resource - for (; j < p_path.get_subname_count() - p_last_is_property; j++) { - RES new_res = j == 0 ? node->get(p_path.get_subname(j)) : r_res->get(p_path.get_subname(j)); + for (; j < p_path.get_subname_count() - (int)p_last_is_property; j++) { + Variant new_res_v = j == 0 ? node->get(p_path.get_subname(j)) : r_res->get(p_path.get_subname(j)); - if (new_res.is_null()) { + if (new_res_v.get_type() == Variant::NIL) { // Found nothing on that path + return NULL; + } + + RES new_res = new_res_v; + + if (new_res.is_null()) { // No longer a resource, assume property break; } @@ -2628,10 +2620,16 @@ NodePath Node::get_import_path() const { static void _add_nodes_to_options(const Node *p_base, const Node *p_node, List<String> *r_options) { +#ifdef TOOLS_ENABLED + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; +#else + const String quote_style = "\""; +#endif + if (p_node != p_base && !p_node->get_owner()) return; String n = p_base->get_path_to(p_node); - r_options->push_back("\"" + n + "\""); + r_options->push_back(quote_style + n + quote_style); for (int i = 0; i < p_node->get_child_count(); i++) { _add_nodes_to_options(p_base, p_node->get_child(i), r_options); } @@ -2709,6 +2707,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_child", "idx"), &Node::get_child); ClassDB::bind_method(D_METHOD("has_node", "path"), &Node::has_node); ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node); + ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null); ClassDB::bind_method(D_METHOD("get_parent"), &Node::get_parent); ClassDB::bind_method(D_METHOD("find_node", "mask", "recursive", "owned"), &Node::find_node, DEFVAL(true), DEFVAL(true)); ClassDB::bind_method(D_METHOD("find_parent", "mask"), &Node::find_parent); @@ -2754,6 +2753,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("can_process"), &Node::can_process); ClassDB::bind_method(D_METHOD("print_stray_nodes"), &Node::_print_stray_nodes); ClassDB::bind_method(D_METHOD("get_position_in_parent"), &Node::get_position_in_parent); + ClassDB::bind_method(D_METHOD("set_display_folded", "fold"), &Node::set_display_folded); ClassDB::bind_method(D_METHOD("is_displayed_folded"), &Node::is_displayed_folded); @@ -2829,10 +2829,22 @@ void Node::_bind_methods() { BIND_CONSTANT(NOTIFICATION_DRAG_BEGIN); BIND_CONSTANT(NOTIFICATION_DRAG_END); BIND_CONSTANT(NOTIFICATION_PATH_CHANGED); - BIND_CONSTANT(NOTIFICATION_TRANSLATION_CHANGED); BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); + BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER); + BIND_CONSTANT(NOTIFICATION_WM_MOUSE_EXIT); + BIND_CONSTANT(NOTIFICATION_WM_FOCUS_IN); + BIND_CONSTANT(NOTIFICATION_WM_FOCUS_OUT); + BIND_CONSTANT(NOTIFICATION_WM_QUIT_REQUEST); + BIND_CONSTANT(NOTIFICATION_WM_GO_BACK_REQUEST); + BIND_CONSTANT(NOTIFICATION_WM_UNFOCUS_REQUEST); + BIND_CONSTANT(NOTIFICATION_OS_MEMORY_WARNING); + BIND_CONSTANT(NOTIFICATION_TRANSLATION_CHANGED); + BIND_CONSTANT(NOTIFICATION_WM_ABOUT); + BIND_CONSTANT(NOTIFICATION_CRASH); + BIND_CONSTANT(NOTIFICATION_OS_IME_UPDATE); + BIND_ENUM_CONSTANT(PAUSE_MODE_INHERIT); BIND_ENUM_CONSTANT(PAUSE_MODE_STOP); BIND_ENUM_CONSTANT(PAUSE_MODE_PROCESS); @@ -2854,7 +2866,12 @@ void Node::_bind_methods() { //ADD_PROPERTY( PropertyInfo( Variant::BOOL, "process/unhandled_input" ), "set_process_unhandled_input","is_processing_unhandled_input" ) ; ADD_GROUP("Pause", "pause_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "pause_mode", PROPERTY_HINT_ENUM, "Inherit,Stop,Process"), "set_pause_mode", "get_pause_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor/display_folded", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_display_folded", "is_displayed_folded"); + +#ifdef ENABLE_DEPRECATED + //no longer exists, but remains for compatibility (keep previous scenes folded + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor/display_folded", PROPERTY_HINT_NONE, "", 0), "set_display_folded", "is_displayed_folded"); +#endif + ADD_PROPERTY(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "", 0), "set_name", "get_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename", PROPERTY_HINT_NONE, "", 0), "set_filename", "get_filename"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_owner", "get_owner"); @@ -2915,6 +2932,8 @@ Node::Node() { data.use_placeholder = false; data.display_folded = false; data.ready_first = true; + + orphan_node_count++; } Node::~Node() { @@ -2925,6 +2944,8 @@ Node::~Node() { ERR_FAIL_COND(data.parent); ERR_FAIL_COND(data.children.size()); + + orphan_node_count--; } //////////////////////////////// diff --git a/scene/main/node.h b/scene/main/node.h index 1c451ef567..67b40f6dfc 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -75,6 +75,8 @@ public: bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.process_priority == p_a->data.process_priority ? p_b->is_greater_than(p_a) : p_b->data.process_priority > p_a->data.process_priority; } }; + static int orphan_node_count; + private: struct GroupData { @@ -150,10 +152,9 @@ private: Ref<MultiplayerAPI> multiplayer; - void _print_tree_pretty(const String prefix, const bool last); + 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; Node *_get_child_by_name(const StringName &p_name) const; void _replace_connections_target(Node *p_new_target); @@ -217,6 +218,7 @@ protected: public: enum { + // you can make your own, but don't use the same numbers as other notifications in other nodes NOTIFICATION_ENTER_TREE = 10, NOTIFICATION_EXIT_TREE = 11, @@ -232,10 +234,23 @@ public: NOTIFICATION_DRAG_BEGIN = 21, NOTIFICATION_DRAG_END = 22, NOTIFICATION_PATH_CHANGED = 23, - NOTIFICATION_TRANSLATION_CHANGED = 24, + //NOTIFICATION_TRANSLATION_CHANGED = 24, moved below NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, NOTIFICATION_POST_ENTER_TREE = 27, + //keep these linked to node + NOTIFICATION_WM_MOUSE_ENTER = MainLoop::NOTIFICATION_WM_MOUSE_ENTER, + NOTIFICATION_WM_MOUSE_EXIT = MainLoop::NOTIFICATION_WM_MOUSE_EXIT, + NOTIFICATION_WM_FOCUS_IN = MainLoop::NOTIFICATION_WM_FOCUS_IN, + NOTIFICATION_WM_FOCUS_OUT = MainLoop::NOTIFICATION_WM_FOCUS_OUT, + NOTIFICATION_WM_QUIT_REQUEST = MainLoop::NOTIFICATION_WM_QUIT_REQUEST, + NOTIFICATION_WM_GO_BACK_REQUEST = MainLoop::NOTIFICATION_WM_GO_BACK_REQUEST, + NOTIFICATION_WM_UNFOCUS_REQUEST = MainLoop::NOTIFICATION_WM_UNFOCUS_REQUEST, + NOTIFICATION_OS_MEMORY_WARNING = MainLoop::NOTIFICATION_OS_MEMORY_WARNING, + NOTIFICATION_TRANSLATION_CHANGED = MainLoop::NOTIFICATION_TRANSLATION_CHANGED, + NOTIFICATION_WM_ABOUT = MainLoop::NOTIFICATION_WM_ABOUT, + NOTIFICATION_CRASH = MainLoop::NOTIFICATION_CRASH, + NOTIFICATION_OS_IME_UPDATE = MainLoop::NOTIFICATION_OS_IME_UPDATE }; @@ -252,6 +267,7 @@ public: Node *get_child(int p_index) const; bool has_node(const NodePath &p_path) const; Node *get_node(const NodePath &p_path) const; + Node *get_node_or_null(const NodePath &p_path) const; Node *find_node(const String &p_mask, bool p_recursive = true, bool p_owned = true) const; bool has_node_and_resource(const NodePath &p_path) const; Node *get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property = true) const; @@ -284,7 +300,7 @@ public: }; void get_groups(List<GroupInfo> *p_groups) const; - bool has_persistent_groups() const; + int get_persistent_group_count() const; void move_child(Node *p_child, int p_pos); void raise(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 16817a2846..617a703855 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -37,7 +37,6 @@ #include "core/os/os.h" #include "core/print_string.h" #include "core/project_settings.h" -#include "editor/editor_node.h" #include "main/input_default.h" #include "node.h" #include "scene/resources/dynamic_font.h" @@ -105,6 +104,11 @@ void SceneTree::node_removed(Node *p_node) { call_skip.insert(p_node); } +void SceneTree::node_renamed(Node *p_node) { + + emit_signal(node_renamed_name, p_node); +} + SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_node) { Map<StringName, Group>::Element *E = group_map.find(p_group); @@ -112,10 +116,7 @@ SceneTree::Group *SceneTree::add_to_group(const StringName &p_group, Node *p_nod E = group_map.insert(p_group, Group()); } - if (E->get().nodes.find(p_node) != -1) { - ERR_EXPLAIN("Already in group: " + p_group); - ERR_FAIL_V(&E->get()); - } + ERR_FAIL_COND_V_MSG(E->get().nodes.find(p_node) != -1, &E->get(), "Already in group: " + p_group + "."); E->get().nodes.push_back(p_node); //E->get().last_tree_version=0; E->get().changed = true; @@ -369,8 +370,7 @@ void SceneTree::set_group_flags(uint32_t p_call_flags, const StringName &p_group } void SceneTree::call_group(const StringName &p_group, const StringName &p_function, VARIANT_ARG_DECLARE) { - - call_group_flags(0, p_group, VARIANT_ARG_PASS); + call_group_flags(0, p_group, p_function, VARIANT_ARG_PASS); } void SceneTree::notify_group(const StringName &p_group, int p_notification) { @@ -449,9 +449,6 @@ void SceneTree::init() { //_quit=false; initialized = true; - input_handled = false; - - pause = false; root->_set_tree(this); MainLoop::init(); @@ -610,6 +607,7 @@ void SceneTree::finish() { root->_set_tree(NULL); root->_propagate_after_exit_tree(); memdelete(root); //delete root + root = NULL; } } @@ -645,7 +643,8 @@ void SceneTree::_notification(int p_notification) { case NOTIFICATION_WM_MOUSE_ENTER: case NOTIFICATION_WM_MOUSE_EXIT: case NOTIFICATION_WM_FOCUS_IN: - case NOTIFICATION_WM_FOCUS_OUT: { + case NOTIFICATION_WM_FOCUS_OUT: + case NOTIFICATION_WM_ABOUT: { if (p_notification == NOTIFICATION_WM_FOCUS_IN) { InputDefault *id = Object::cast_to<InputDefault>(Input::get_singleton()); @@ -658,26 +657,15 @@ void SceneTree::_notification(int p_notification) { } break; case NOTIFICATION_TRANSLATION_CHANGED: { if (!Engine::get_singleton()->is_editor_hint()) { - get_root()->propagate_notification(Node::NOTIFICATION_TRANSLATION_CHANGED); + get_root()->propagate_notification(p_notification); } } break; case NOTIFICATION_WM_UNFOCUS_REQUEST: { notify_group_flags(GROUP_CALL_REALTIME | GROUP_CALL_MULTILEVEL, "input", NOTIFICATION_WM_UNFOCUS_REQUEST); - } break; - - case NOTIFICATION_WM_ABOUT: { + get_root()->propagate_notification(p_notification); -#ifdef TOOLS_ENABLED - if (EditorNode::get_singleton()) { - EditorNode::get_singleton()->show_about(); - } else { -#endif - get_root()->propagate_notification(p_notification); -#ifdef TOOLS_ENABLED - } -#endif } break; case NOTIFICATION_CRASH: { @@ -1085,7 +1073,7 @@ void SceneTree::get_nodes_in_group(const StringName &p_group, List<Node *> *p_li static void _fill_array(Node *p_node, Array &array, int p_level) { - array.push_back(p_level); + array.push_back(p_node->get_child_count()); array.push_back(p_node->get_name()); array.push_back(p_node->get_class()); array.push_back(p_node->get_instance_id()); @@ -1154,13 +1142,14 @@ void SceneTree::_update_root_rect() { float viewport_aspect = desired_res.aspect(); float video_mode_aspect = video_mode.aspect(); - if (stretch_aspect == STRETCH_ASPECT_IGNORE || ABS(viewport_aspect - video_mode_aspect) < CMP_EPSILON) { + if (use_font_oversampling && stretch_aspect == STRETCH_ASPECT_IGNORE) { + WARN_PRINT("Font oversampling only works with the resize modes 'Keep Width', 'Keep Height', and 'Expand'."); + } + + if (stretch_aspect == STRETCH_ASPECT_IGNORE || Math::is_equal_approx(viewport_aspect, video_mode_aspect)) { //same aspect or ignore aspect viewport_size = desired_res; screen_size = video_mode; - if (use_font_oversampling) { - WARN_PRINT("Font oversampling only works with the following resize modes 'Keep Width', 'Keep Height', and 'Expand'.") - } } else if (viewport_aspect < video_mode_aspect) { // screen ratio is smaller vertically @@ -1237,14 +1226,14 @@ void SceneTree::_update_root_rect() { root->update_canvas_items(); //force them to update just in case if (use_font_oversampling) { - WARN_PRINT("Font oversampling does not work in 'Viewport' stretch mode, only '2D'.") + WARN_PRINT("Font oversampling does not work in 'Viewport' stretch mode, only '2D'."); } } break; } } -void SceneTree::set_screen_stretch(StretchMode p_mode, StretchAspect p_aspect, const Size2 p_minsize, real_t p_shrink) { +void SceneTree::set_screen_stretch(StretchMode p_mode, StretchAspect p_aspect, const Size2 &p_minsize, real_t p_shrink) { stretch_mode = p_mode; stretch_aspect = p_aspect; @@ -1502,8 +1491,11 @@ void SceneTree::_live_edit_instance_node_func(const NodePath &p_parent, const St Node *n2 = n->get_node(p_parent); Node *no = ps->instance(); - no->set_name(p_name); + if (!no) { + continue; + } + no->set_name(p_name); n2->add_child(no); } } @@ -1681,6 +1673,12 @@ void SceneTree::drop_files(const Vector<String> &p_files, int p_from_screen) { MainLoop::drop_files(p_files, p_from_screen); } +void SceneTree::global_menu_action(const Variant &p_id, const Variant &p_meta) { + + emit_signal("global_menu_action", p_id, p_meta); + MainLoop::global_menu_action(p_id, p_meta); +} + Ref<SceneTreeTimer> SceneTree::create_timer(float p_delay_sec, bool p_process_pause) { Ref<SceneTreeTimer> stt; @@ -1882,6 +1880,7 @@ void SceneTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_navigation_hint"), "set_debug_navigation_hint", "is_debugging_navigation_hint"); 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_DEFAULT("refuse_new_network_connections", false); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_font_oversampling"), "set_use_font_oversampling", "is_using_font_oversampling"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_scene_root", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_edited_scene_root", "get_edited_scene_root"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "current_scene", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_current_scene", "get_current_scene"); @@ -1893,6 +1892,7 @@ void SceneTree::_bind_methods() { ADD_SIGNAL(MethodInfo("tree_changed")); ADD_SIGNAL(MethodInfo("node_added", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("node_removed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("node_renamed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("screen_resized")); ADD_SIGNAL(MethodInfo("node_configuration_warning_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); @@ -1900,6 +1900,7 @@ void SceneTree::_bind_methods() { ADD_SIGNAL(MethodInfo("physics_frame")); ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::POOL_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "screen"))); + ADD_SIGNAL(MethodInfo("global_menu_action", PropertyInfo(Variant::NIL, "id"), PropertyInfo(Variant::NIL, "meta"))); ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("connected_to_server")); @@ -1941,12 +1942,11 @@ void SceneTree::add_idle_callback(IdleCallback p_callback) { void SceneTree::set_use_font_oversampling(bool p_oversampling) { + if (use_font_oversampling == p_oversampling) + return; + use_font_oversampling = p_oversampling; - if (use_font_oversampling) { - DynamicFontAtSize::font_oversampling = OS::get_singleton()->get_window_size().width / root->get_visible_rect().size.width; - } else { - DynamicFontAtSize::font_oversampling = 1.0; - } + _update_root_rect(); } bool SceneTree::is_using_font_oversampling() const { @@ -1955,11 +1955,12 @@ bool SceneTree::is_using_font_oversampling() const { SceneTree::SceneTree() { - singleton = this; + if (singleton == NULL) singleton = this; _quit = false; accept_quit = true; quit_on_go_back = true; initialized = false; + use_font_oversampling = false; #ifdef DEBUG_ENABLED debug_collisions_hint = false; debug_navigation_hint = false; @@ -1976,11 +1977,14 @@ SceneTree::SceneTree() { idle_process_time = 1; root = NULL; + input_handled = false; + pause = false; current_frame = 0; current_event = 0; tree_changed_name = "tree_changed"; node_added_name = "node_added"; node_removed_name = "node_removed"; + node_renamed_name = "node_renamed"; ugc_locked = false; call_lock = 0; root_lock = 0; @@ -2095,9 +2099,14 @@ SceneTree::SceneTree() { live_edit_root = NodePath("/root"); #endif - - use_font_oversampling = false; } SceneTree::~SceneTree() { + if (root) { + root->_set_tree(NULL); + root->_propagate_after_exit_tree(); + memdelete(root); + } + + if (singleton == this) singleton = NULL; } diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 3a1ff5cb06..42a87545a6 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -39,11 +39,6 @@ #include "scene/resources/world.h" #include "scene/resources/world_2d.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - -class SceneTree; class PackedScene; class Node; class Viewport; @@ -127,6 +122,7 @@ private: StringName tree_changed_name; StringName node_added_name; StringName node_removed_name; + StringName node_renamed_name; bool use_font_oversampling; int64_t current_frame; @@ -202,6 +198,7 @@ private: void tree_changed(); void node_added(Node *p_node); void node_removed(Node *p_node); + void node_renamed(Node *p_node); Group *add_to_group(const StringName &p_group, Node *p_node); void remove_from_group(const StringName &p_group, Node *p_node); @@ -288,7 +285,7 @@ protected: public: enum { - NOTIFICATION_TRANSFORM_CHANGED = 29 + NOTIFICATION_TRANSFORM_CHANGED = 2000 }; enum GroupCallFlags { @@ -385,7 +382,7 @@ public: void get_nodes_in_group(const StringName &p_group, List<Node *> *p_list); bool has_group(const StringName &p_identifier) const; - void set_screen_stretch(StretchMode p_mode, StretchAspect p_aspect, const Size2 p_minsize, real_t p_shrink = 1); + void set_screen_stretch(StretchMode p_mode, StretchAspect p_aspect, const Size2 &p_minsize, real_t p_shrink = 1); void set_use_font_oversampling(bool p_oversampling); bool is_using_font_oversampling() const; @@ -410,6 +407,7 @@ public: static SceneTree *get_singleton() { return singleton; } void drop_files(const Vector<String> &p_files, int p_from_screen = 0); + void global_menu_action(const Variant &p_id, const Variant &p_meta); //network API diff --git a/scene/main/timer.cpp b/scene/main/timer.cpp index 2ae950dad5..03d46fd28d 100755 --- a/scene/main/timer.cpp +++ b/scene/main/timer.cpp @@ -80,8 +80,7 @@ void Timer::_notification(int p_what) { } void Timer::set_wait_time(float p_time) { - ERR_EXPLAIN("time should be greater than zero."); - ERR_FAIL_COND(p_time <= 0); + ERR_FAIL_COND_MSG(p_time <= 0, "Time should be greater than zero."); wait_time = p_time; } float Timer::get_wait_time() const { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 61d6fc7401..93eea2ad0b 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -37,8 +37,8 @@ #include "scene/3d/camera.h" #include "scene/3d/collision_object.h" #include "scene/3d/listener.h" -#include "scene/3d/scenario_fx.h" #include "scene/3d/spatial.h" +#include "scene/3d/world_environment.h" #include "scene/gui/control.h" #include "scene/gui/label.h" #include "scene/gui/menu_button.h" @@ -65,13 +65,11 @@ void ViewportTexture::setup_local_to_scene() { } Node *vpn = local_scene->get_node(path); - ERR_EXPLAIN("ViewportTexture: Path to node is invalid"); - ERR_FAIL_COND(!vpn); + ERR_FAIL_COND_MSG(!vpn, "ViewportTexture: Path to node is invalid."); vp = Object::cast_to<Viewport>(vpn); - ERR_EXPLAIN("ViewportTexture: Path to node does not point to a viewport"); - ERR_FAIL_COND(!vp); + ERR_FAIL_COND_MSG(!vp, "ViewportTexture: Path to node does not point to a viewport."); vp->viewport_textures.insert(this); @@ -172,14 +170,16 @@ ViewportTexture::~ViewportTexture() { class TooltipPanel : public PanelContainer { - GDCLASS(TooltipPanel, PanelContainer) + GDCLASS(TooltipPanel, PanelContainer); + public: TooltipPanel(){}; }; class TooltipLabel : public Label { - GDCLASS(TooltipLabel, Label) + GDCLASS(TooltipLabel, Label); + public: TooltipLabel(){}; }; @@ -232,16 +232,16 @@ void Viewport::update_worlds() { find_world()->_update(get_tree()->get_frame()); } -void Viewport::_collision_object_input_event(CollisionObject *p_object, Camera *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape, bool p_discard_empty_motion) { +void Viewport::_collision_object_input_event(CollisionObject *p_object, Camera *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) { Transform object_transform = p_object->get_global_transform(); Transform camera_transform = p_camera->get_global_transform(); ObjectID id = p_object->get_instance_id(); - if (p_discard_empty_motion) { - //avoid sending the event unnecesarily if nothing really changed in the context + //avoid sending the fake event unnecessarily if nothing really changed in the context + if (object_transform == physics_last_object_transform && camera_transform == physics_last_camera_transform && physics_last_id == id) { Ref<InputEventMouseMotion> mm = p_input_event; - if (mm.is_valid() && object_transform == physics_last_object_transform && camera_transform == physics_last_camera_transform && physics_last_id == id) { + if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { return; //discarded } } @@ -251,31 +251,6 @@ void Viewport::_collision_object_input_event(CollisionObject *p_object, Camera * physics_last_id = id; } -void Viewport::_test_new_mouseover(ObjectID new_collider) { -#ifndef _3D_DISABLED - if (new_collider != physics_object_over) { - - if (physics_object_over) { - - CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_over)); - if (co) { - co->_mouse_exit(); - } - } - - if (new_collider) { - - CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(new_collider)); - if (co) { - co->_mouse_enter(); - } - } - - physics_object_over = new_collider; - } -#endif -} - void Viewport::_notification(int p_what) { switch (p_what) { @@ -416,28 +391,30 @@ void Viewport::_notification(int p_what) { if (physics_object_picking && (to_screen_rect == Rect2() || Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED)) { +#ifndef _3D_DISABLED Vector2 last_pos(1e20, 1e20); CollisionObject *last_object = NULL; ObjectID last_id = 0; +#endif PhysicsDirectSpaceState::RayResult result; Physics2DDirectSpaceState *ss2d = Physics2DServer::get_singleton()->space_get_direct_state(find_world_2d()->get_space()); - bool discard_empty_motion = false; - - { // if no motion event exists, create a new one. This is necessary because objects or camera may have moved. - // while this extra event is sent, it is checked if both camera and last object and last ID did not move. If nothing changed, the event is discarded to avoid flooding with unnecesary motion events every frame - bool has_mouse_motion = false; + if (physics_has_last_mousepos) { + // if no mouse event exists, create a motion one. This is necessary because objects or camera may have moved. + // while this extra event is sent, it is checked if both camera and last object and last ID did not move. If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame + bool has_mouse_event = false; for (List<Ref<InputEvent> >::Element *E = physics_picking_events.front(); E; E = E->next()) { - Ref<InputEventMouseMotion> mm = E->get(); - if (mm.is_valid()) { - has_mouse_motion = true; + Ref<InputEventMouse> m = E->get(); + if (m.is_valid()) { + has_mouse_event = true; break; } } - if (!has_mouse_motion && physics_has_last_mousepos) { + if (!has_mouse_event) { Ref<InputEventMouseMotion> mm; mm.instance(); + mm->set_device(InputEvent::DEVICE_ID_INTERNAL); mm->set_global_position(physics_last_mousepos); mm->set_position(physics_last_mousepos); mm->set_alt(physics_last_mouse_state.alt); @@ -446,25 +423,24 @@ void Viewport::_notification(int p_what) { mm->set_metakey(physics_last_mouse_state.meta); mm->set_button_mask(physics_last_mouse_state.mouse_mask); physics_picking_events.push_back(mm); - discard_empty_motion = true; } } - bool motion_tested = false; - while (physics_picking_events.size()) { Ref<InputEvent> ev = physics_picking_events.front()->get(); physics_picking_events.pop_front(); Vector2 pos; + bool is_mouse = false; Ref<InputEventMouseMotion> mm = ev; if (mm.is_valid()) { pos = mm->get_position(); - motion_tested = true; + is_mouse = true; + physics_has_last_mousepos = true; physics_last_mousepos = pos; physics_last_mouse_state.alt = mm->get_alt(); @@ -477,7 +453,12 @@ void Viewport::_notification(int p_what) { Ref<InputEventMouseButton> mb = ev; if (mb.is_valid()) { + pos = mb->get_position(); + is_mouse = true; + + physics_has_last_mousepos = true; + physics_last_mousepos = pos; physics_last_mouse_state.alt = mb->get_alt(); physics_last_mouse_state.shift = mb->get_shift(); physics_last_mouse_state.control = mb->get_control(); @@ -487,6 +468,11 @@ void Viewport::_notification(int p_what) { physics_last_mouse_state.mouse_mask |= (1 << (mb->get_button_index() - 1)); } else { physics_last_mouse_state.mouse_mask &= ~(1 << (mb->get_button_index() - 1)); + + // If touch mouse raised, assume we don't know last mouse pos until new events come + if (mb->get_device() == InputEvent::DEVICE_ID_TOUCH_MOUSE) { + physics_has_last_mousepos = false; + } } } @@ -539,40 +525,51 @@ void Viewport::_notification(int p_what) { if (res[i].collider_id && res[i].collider) { CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider); if (co) { - - Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.find(res[i].collider_id); - if (!E) { - E = physics_2d_mouseover.insert(res[i].collider_id, frame); - co->_mouse_enter(); - } else { - E->get() = frame; + bool send_event = true; + if (is_mouse) { + Map<ObjectID, uint64_t>::Element *F = physics_2d_mouseover.find(res[i].collider_id); + + if (!F) { + physics_2d_mouseover.insert(res[i].collider_id, frame); + co->_mouse_enter(); + } else { + F->get() = frame; + // It was already hovered, so don't send the event if it's faked + if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) { + send_event = false; + } + } } - co->_input_event(this, ev, res[i].shape); + if (send_event) { + co->_input_event(this, ev, res[i].shape); + } } } } } - List<Map<ObjectID, uint64_t>::Element *> to_erase; + if (is_mouse) { + List<Map<ObjectID, uint64_t>::Element *> to_erase; - for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) { - if (E->get() != frame) { - Object *o = ObjectDB::get_instance(E->key()); - if (o) { + for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) { + if (E->get() != frame) { + Object *o = ObjectDB::get_instance(E->key()); + if (o) { - CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); - if (co) { - co->_mouse_exit(); + CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); + if (co) { + co->_mouse_exit(); + } } + to_erase.push_back(E); } - to_erase.push_back(E); } - } - while (to_erase.size()) { - physics_2d_mouseover.erase(to_erase.front()->get()); - to_erase.pop_front(); + while (to_erase.size()) { + physics_2d_mouseover.erase(to_erase.front()->get()); + to_erase.pop_front(); + } } } @@ -582,8 +579,8 @@ void Viewport::_notification(int p_what) { if (physics_object_capture != 0) { CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_capture)); - if (co) { - _collision_object_input_event(co, camera, ev, Vector3(), Vector3(), 0, discard_empty_motion); + if (co && camera) { + _collision_object_input_event(co, camera, ev, Vector3(), Vector3(), 0); captured = true; if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) { physics_object_capture = 0; @@ -601,7 +598,7 @@ void Viewport::_notification(int p_what) { if (last_id) { if (ObjectDB::get_instance(last_id) && last_object) { //good, exists - _collision_object_input_event(last_object, camera, ev, result.position, result.normal, result.shape, discard_empty_motion); + _collision_object_input_event(last_object, camera, ev, result.position, result.normal, result.shape); if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { physics_object_capture = last_id; } @@ -624,7 +621,7 @@ void Viewport::_notification(int p_what) { CollisionObject *co = Object::cast_to<CollisionObject>(result.collider); if (co) { - _collision_object_input_event(co, camera, ev, result.position, result.normal, result.shape, discard_empty_motion); + _collision_object_input_event(co, camera, ev, result.position, result.normal, result.shape); last_object = co; last_id = result.collider_id; new_collider = last_id; @@ -634,42 +631,41 @@ void Viewport::_notification(int p_what) { } } - if (mm.is_valid()) { - _test_new_mouseover(new_collider); - } - } + if (is_mouse && new_collider != physics_object_over) { - last_pos = pos; - } - } - } + if (physics_object_over) { - if (!motion_tested && camera && physics_has_last_mousepos) { + CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_over)); + if (co) { + co->_mouse_exit(); + } + } - //test anyway for mouseenter/exit because objects might move - Vector3 from = camera->project_ray_origin(physics_last_mousepos); - Vector3 dir = camera->project_ray_normal(physics_last_mousepos); + if (new_collider) { - PhysicsDirectSpaceState *space = PhysicsServer::get_singleton()->space_get_direct_state(find_world()->get_space()); - if (space) { + CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(new_collider)); + if (co) { + co->_mouse_enter(); + } + } - bool col = space->intersect_ray(from, from + dir * 10000, result, Set<RID>(), 0xFFFFFFFF, true, true, true); - ObjectID new_collider = 0; - if (col) { - CollisionObject *co = Object::cast_to<CollisionObject>(result.collider); - if (co) { - new_collider = result.collider_id; + physics_object_over = new_collider; + } } - } - _test_new_mouseover(new_collider); + last_pos = pos; + } } #endif } } } break; + case SceneTree::NOTIFICATION_WM_MOUSE_EXIT: case SceneTree::NOTIFICATION_WM_FOCUS_OUT: { + + _drop_physics_mouseover(); + if (gui.mouse_focus) { //if mouse is being pressed, send a release event _drop_mouse_focus(); @@ -890,7 +886,7 @@ void Viewport::_camera_set(Camera *p_camera) { if (camera == p_camera) return; - if (camera && find_world().is_valid()) { + if (camera) { camera->notification(Camera::NOTIFICATION_LOST_CURRENT); } camera = p_camera; @@ -899,7 +895,7 @@ void Viewport::_camera_set(Camera *p_camera) { else VisualServer::get_singleton()->viewport_attach_camera(viewport, RID()); - if (camera && find_world().is_valid()) { + if (camera) { camera->notification(Camera::NOTIFICATION_BECAME_CURRENT); } @@ -918,9 +914,7 @@ void Viewport::_camera_remove(Camera *p_camera) { cameras.erase(p_camera); if (camera == p_camera) { - if (camera && find_world().is_valid()) { - camera->notification(Camera::NOTIFICATION_LOST_CURRENT); - } + camera->notification(Camera::NOTIFICATION_LOST_CURRENT); camera = NULL; } } @@ -1017,7 +1011,7 @@ void Viewport::_propagate_enter_world(Node *p_node) { Viewport *v = Object::cast_to<Viewport>(p_node); if (v) { - if (v->world.is_valid()) + if (v->world.is_valid() || v->own_world.is_valid()) return; } } @@ -1054,7 +1048,7 @@ void Viewport::_propagate_exit_world(Node *p_node) { Viewport *v = Object::cast_to<Viewport>(p_node); if (v) { - if (v->world.is_valid()) + if (v->world.is_valid() || v->own_world.is_valid()) return; } } @@ -1074,23 +1068,11 @@ void Viewport::set_world(const Ref<World> &p_world) { if (is_inside_tree()) _propagate_exit_world(this); -#ifndef _3D_DISABLED - if (find_world().is_valid() && camera) - camera->notification(Camera::NOTIFICATION_LOST_CURRENT); -#endif - world = p_world; if (is_inside_tree()) _propagate_enter_world(this); -#ifndef _3D_DISABLED - if (find_world().is_valid() && camera) - camera->notification(Camera::NOTIFICATION_BECAME_CURRENT); -#endif - - //propagate exit - if (is_inside_tree()) { VisualServer::get_singleton()->viewport_set_scenario(viewport, find_world()->get_scenario()); } @@ -1447,6 +1429,7 @@ void Viewport::_gui_show_tooltip() { Control *which = NULL; String tooltip = _gui_get_tooltip(gui.tooltip, gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos), &which); + tooltip = tooltip.strip_edges(); if (tooltip.length() == 0) return; // bye @@ -1460,9 +1443,7 @@ void Viewport::_gui_show_tooltip() { return; } - Control *rp = which; //->get_root_parent_control(); - if (!rp) - return; + Control *rp = which; gui.tooltip_popup = which->make_custom_tooltip(tooltip); @@ -1478,7 +1459,7 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_label->set_anchor_and_margin(MARGIN_TOP, Control::ANCHOR_BEGIN, ttp->get_margin(MARGIN_TOP)); 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()); + gui.tooltip_label->set_text(tooltip); } rp->add_child(gui.tooltip_popup); @@ -1486,7 +1467,8 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_popup->set_as_toplevel(true); //gui.tooltip_popup->hide(); - Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_popup->get_minimum_size()); + Point2 tooltip_offset = ProjectSettings::get_singleton()->get("display/mouse_cursor/tooltip_position_offset"); + Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_minimum_size()); Rect2 vr = gui.tooltip_popup->get_viewport_rect(); if (r.size.x + r.position.x > vr.size.x) r.position.x = vr.size.x - r.size.x; @@ -1718,6 +1700,8 @@ bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_che void Viewport::_gui_input_event(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()) + //? /* if (!is_visible()) { @@ -1747,8 +1731,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { while (!gui.modal_stack.empty()) { Control *top = gui.modal_stack.back()->get(); - Vector2 pos = top->get_global_transform_with_canvas().affine_inverse().xform(mpos); - if (!top->has_point(pos)) { + Vector2 pos2 = top->get_global_transform_with_canvas().affine_inverse().xform(mpos); + if (!top->has_point(pos2)) { if (top->data.modal_exclusive || top->data.modal_frame == Engine::get_singleton()->get_frames_drawn()) { //cancel event, sorry, modal exclusive EATS UP ALL @@ -1783,13 +1767,15 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { */ gui.mouse_focus = _gui_find_control(pos); - gui.mouse_focus_mask = 1 << (mb->get_button_index() - 1); gui.last_mouse_focus = gui.mouse_focus; if (!gui.mouse_focus) { + gui.mouse_focus_mask = 0; return; } + gui.mouse_focus_mask = 1 << (mb->get_button_index() - 1); + if (mb->get_button_index() == BUTTON_LEFT) { gui.drag_accum = Vector2(); gui.drag_attempted = false; @@ -2303,32 +2289,32 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (from && p_event->is_pressed()) { Control *next = NULL; - if (p_event->is_action("ui_focus_next")) { + if (p_event->is_action_pressed("ui_focus_next")) { next = from->find_next_valid_focus(); } - if (p_event->is_action("ui_focus_prev")) { + if (p_event->is_action_pressed("ui_focus_prev")) { next = from->find_prev_valid_focus(); } - if (!mods && p_event->is_action("ui_up")) { + if (!mods && p_event->is_action_pressed("ui_up")) { next = from->_get_focus_neighbour(MARGIN_TOP); } - if (!mods && p_event->is_action("ui_left")) { + if (!mods && p_event->is_action_pressed("ui_left")) { next = from->_get_focus_neighbour(MARGIN_LEFT); } - if (!mods && p_event->is_action("ui_right")) { + if (!mods && p_event->is_action_pressed("ui_right")) { next = from->_get_focus_neighbour(MARGIN_RIGHT); } - if (!mods && p_event->is_action("ui_down")) { + if (!mods && p_event->is_action_pressed("ui_down")) { next = from->_get_focus_neighbour(MARGIN_BOTTOM); } @@ -2405,8 +2391,7 @@ void Viewport::_gui_remove_from_modal_stack(List<Control *>::Element *MI, Object void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control) { - ERR_EXPLAIN("Drag data must be a value"); - ERR_FAIL_COND(p_data.get_type() == Variant::NIL); + ERR_FAIL_COND_MSG(p_data.get_type() == Variant::NIL, "Drag data must be a value."); gui.dragging = true; gui.drag_data = p_data; @@ -2483,11 +2468,7 @@ void Viewport::_gui_hid_control(Control *p_control) { if (gui.mouse_over == p_control) gui.mouse_over = NULL; if (gui.tooltip == p_control) - gui.tooltip = NULL; - if (gui.tooltip == p_control) { - gui.tooltip = NULL; _gui_cancel_tooltip(); - } } void Viewport::_gui_remove_control(Control *p_control) { @@ -2496,6 +2477,9 @@ void Viewport::_gui_remove_control(Control *p_control) { gui.mouse_focus = NULL; gui.mouse_focus_mask = 0; } + if (gui.last_mouse_focus == p_control) { + gui.last_mouse_focus = NULL; + } if (gui.key_focus == p_control) gui.key_focus = NULL; if (gui.mouse_over == p_control) @@ -2565,9 +2549,34 @@ void Viewport::_drop_mouse_focus() { } } +void Viewport::_drop_physics_mouseover() { + + physics_has_last_mousepos = false; + + while (physics_2d_mouseover.size()) { + Object *o = ObjectDB::get_instance(physics_2d_mouseover.front()->key()); + if (o) { + CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o); + co->_mouse_exit(); + } + physics_2d_mouseover.erase(physics_2d_mouseover.front()); + } + +#ifndef _3D_DISABLED + if (physics_object_over) { + CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_over)); + if (co) { + co->_mouse_exit(); + } + } + + physics_object_over = physics_object_capture = 0; +#endif +} + List<Control *>::Element *Viewport::_gui_show_modal(Control *p_control) { - gui.modal_stack.push_back(p_control); + List<Control *>::Element *node = gui.modal_stack.push_back(p_control); if (gui.key_focus) p_control->_modal_set_prev_focus_owner(gui.key_focus->get_instance_id()); else @@ -2578,7 +2587,7 @@ List<Control *>::Element *Viewport::_gui_show_modal(Control *p_control) { _drop_mouse_focus(); } - return gui.modal_stack.back(); + return node; } Control *Viewport::_gui_get_focus_owner() { @@ -2699,11 +2708,6 @@ void Viewport::set_use_own_world(bool p_world) { if (is_inside_tree()) _propagate_exit_world(this); -#ifndef _3D_DISABLED - if (find_world().is_valid() && camera) - camera->notification(Camera::NOTIFICATION_LOST_CURRENT); -#endif - if (!p_world) own_world = Ref<World>(); else @@ -2712,13 +2716,6 @@ void Viewport::set_use_own_world(bool p_world) { if (is_inside_tree()) _propagate_enter_world(this); -#ifndef _3D_DISABLED - if (find_world().is_valid() && camera) - camera->notification(Camera::NOTIFICATION_BECAME_CURRENT); -#endif - - //propagate exit - if (is_inside_tree()) { VisualServer::get_singleton()->viewport_set_scenario(viewport, find_world()->get_scenario()); } @@ -2742,6 +2739,19 @@ Rect2 Viewport::get_attach_to_screen_rect() const { return to_screen_rect; } +void Viewport::set_use_render_direct_to_screen(bool p_render_direct_to_screen) { + + if (p_render_direct_to_screen == render_direct_to_screen) + return; + + render_direct_to_screen = p_render_direct_to_screen; + VS::get_singleton()->viewport_set_render_direct_to_screen(viewport, p_render_direct_to_screen); +} + +bool Viewport::is_using_render_direct_to_screen() const { + return render_direct_to_screen; +} + void Viewport::set_physics_object_picking(bool p_enable) { physics_object_picking = p_enable; @@ -2920,6 +2930,13 @@ bool Viewport::is_handling_input_locally() const { return handle_input_locally; } +void Viewport::_validate_property(PropertyInfo &property) const { + + if (VisualServer::get_singleton()->is_low_end() && property.name == "hdr") { + property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; + } +} + void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_arvr", "use"), &Viewport::set_use_arvr); @@ -3000,6 +3017,8 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d); ClassDB::bind_method(D_METHOD("is_audio_listener_2d"), &Viewport::is_audio_listener_2d); ClassDB::bind_method(D_METHOD("set_attach_to_screen_rect", "rect"), &Viewport::set_attach_to_screen_rect); + ClassDB::bind_method(D_METHOD("set_use_render_direct_to_screen", "enable"), &Viewport::set_use_render_direct_to_screen); + ClassDB::bind_method(D_METHOD("is_using_render_direct_to_screen"), &Viewport::is_using_render_direct_to_screen); ClassDB::bind_method(D_METHOD("get_mouse_position"), &Viewport::get_mouse_position); ClassDB::bind_method(D_METHOD("warp_mouse", "to_position"), &Viewport::warp_mouse); @@ -3043,6 +3062,7 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arvr"), "set_use_arvr", "use_arvr"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "size_override_stretch"), "set_size_override_stretch", "is_size_override_stretch_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world"), "set_use_own_world", "is_using_own_world"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "world", PROPERTY_HINT_RESOURCE_TYPE, "World"), "set_world", "get_world"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "world_2d", PROPERTY_HINT_RESOURCE_TYPE, "World2D", 0), "set_world_2d", "get_world_2d"); @@ -3054,6 +3074,7 @@ void Viewport::_bind_methods() { 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::BOOL, "render_direct_to_screen"), "set_use_render_direct_to_screen", "is_using_render_direct_to_screen"); 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_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "render_target_v_flip"), "set_vflip", "get_vflip"); @@ -3136,6 +3157,8 @@ Viewport::Viewport() { texture_rid = VisualServer::get_singleton()->viewport_get_texture(viewport); texture_flags = 0; + render_direct_to_screen = false; + default_texture.instance(); default_texture->vp = const_cast<Viewport *>(this); viewport_textures.insert(default_texture.ptr()); @@ -3190,7 +3213,7 @@ Viewport::Viewport() { gui.tooltip_timer = -1; //gui.tooltip_timer->force_parent_owned(); - gui.tooltip_delay = GLOBAL_DEF("gui/timers/tooltip_delay_sec", 0.7); + gui.tooltip_delay = GLOBAL_DEF("gui/timers/tooltip_delay_sec", 0.5); ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/tooltip_delay_sec", PropertyInfo(Variant::REAL, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater")); // No negative numbers gui.tooltip = NULL; @@ -3199,6 +3222,8 @@ Viewport::Viewport() { gui.drag_attempted = false; gui.canvas_sort_index = 0; gui.roots_order_dirty = false; + gui.mouse_focus = NULL; + gui.last_mouse_focus = NULL; msaa = MSAA_DISABLED; hdr = true; diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 4d0a4e8c87..6393785b22 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -36,9 +36,6 @@ #include "scene/resources/texture.h" #include "scene/resources/world_2d.h" #include "servers/visual_server.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Camera; class Camera2D; @@ -182,6 +179,7 @@ private: Size2 size; Rect2 to_screen_rect; + bool render_direct_to_screen; RID contact_2d_debug; RID contact_3d_debug_multimesh; @@ -221,12 +219,11 @@ private: } physics_last_mouse_state; - void _collision_object_input_event(CollisionObject *p_object, Camera *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape, bool p_discard_empty_motion); + void _collision_object_input_event(CollisionObject *p_object, Camera *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape); bool handle_input_locally; bool local_input_handled; - void _test_new_mouseover(ObjectID new_collider); Map<ObjectID, uint64_t> physics_2d_mouseover; Ref<World2D> world_2d; @@ -384,12 +381,14 @@ private: void _canvas_layer_remove(CanvasLayer *p_canvas_layer); void _drop_mouse_focus(); + void _drop_physics_mouseover(); void _update_canvas_items(Node *p_node); protected: void _notification(int p_what); static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const; public: Listener *get_listener() const; @@ -480,6 +479,9 @@ public: void set_attach_to_screen_rect(const Rect2 &p_rect); Rect2 get_attach_to_screen_rect() const; + void set_use_render_direct_to_screen(bool p_render_direct_to_screen); + bool is_using_render_direct_to_screen() const; + Vector2 get_mouse_position() const; void warp_mouse(const Vector2 &p_pos); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index a50a09f095..06d84302a3 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -48,7 +48,8 @@ #include "scene/2d/light_occluder_2d.h" #include "scene/2d/line_2d.h" #include "scene/2d/mesh_instance_2d.h" -#include "scene/2d/navigation2d.h" +#include "scene/2d/multimesh_instance_2d.h" +#include "scene/2d/navigation_2d.h" #include "scene/2d/parallax_background.h" #include "scene/2d/parallax_layer.h" #include "scene/2d/particles_2d.h" @@ -58,10 +59,10 @@ #include "scene/2d/position_2d.h" #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/touch_screen_button.h" #include "scene/2d/visibility_notifier_2d.h" #include "scene/2d/y_sort.h" #include "scene/animation/animation_blend_space_1d.h" @@ -73,7 +74,7 @@ #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/audio/audio_stream_player.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/center_container.h" @@ -125,12 +126,11 @@ #include "scene/main/timer.h" #include "scene/main/viewport.h" #include "scene/resources/audio_stream_sample.h" -#include "scene/resources/bit_mask.h" +#include "scene/resources/bit_map.h" #include "scene/resources/box_shape.h" #include "scene/resources/capsule_shape.h" #include "scene/resources/capsule_shape_2d.h" #include "scene/resources/circle_shape_2d.h" -#include "scene/resources/color_ramp.h" #include "scene/resources/concave_polygon_shape.h" #include "scene/resources/concave_polygon_shape_2d.h" #include "scene/resources/convex_polygon_shape.h" @@ -139,21 +139,23 @@ #include "scene/resources/default_theme/default_theme.h" #include "scene/resources/dynamic_font.h" #include "scene/resources/dynamic_font_stb.h" +#include "scene/resources/gradient.h" +#include "scene/resources/height_map_shape.h" +#include "scene/resources/line_shape_2d.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" #include "scene/resources/mesh_data_tool.h" -#include "scene/resources/mesh_library.h" #include "scene/resources/packed_scene.h" #include "scene/resources/particles_material.h" +#include "scene/resources/physics_material.h" #include "scene/resources/plane_shape.h" #include "scene/resources/polygon_path_finder.h" #include "scene/resources/primitive_meshes.h" #include "scene/resources/ray_shape.h" #include "scene/resources/rectangle_shape_2d.h" -#include "scene/resources/scene_format_text.h" +#include "scene/resources/resource_format_text.h" #include "scene/resources/segment_shape_2d.h" -#include "scene/resources/shape_line_2d.h" -#include "scene/resources/sky_box.h" +#include "scene/resources/sky.h" #include "scene/resources/sphere_shape.h" #include "scene/resources/surface_tool.h" #include "scene/resources/text_file.h" @@ -166,8 +168,8 @@ #include "scene/resources/world_2d.h" #include "scene/scene_string_names.h" -#include "scene/3d/scenario_fx.h" #include "scene/3d/spatial.h" +#include "scene/3d/world_environment.h" #ifndef _3D_DISABLED #include "scene/3d/area.h" @@ -207,7 +209,7 @@ #include "scene/3d/visibility_notifier.h" #include "scene/animation/skeleton_ik.h" #include "scene/resources/environment.h" -#include "scene/resources/physics_material.h" +#include "scene/resources/mesh_library.h" #endif static Ref<ResourceFormatSaverText> resource_saver_text; @@ -302,6 +304,7 @@ void register_scene_types() { ClassDB::register_class<TextureRect>(); ClassDB::register_class<ColorRect>(); ClassDB::register_class<NinePatchRect>(); + ClassDB::register_class<ReferenceRect>(); ClassDB::register_class<TabContainer>(); ClassDB::register_class<Tabs>(); ClassDB::register_virtual_class<Separator>(); @@ -337,7 +340,6 @@ void register_scene_types() { ClassDB::register_virtual_class<TreeItem>(); ClassDB::register_class<OptionButton>(); ClassDB::register_class<SpinBox>(); - ClassDB::register_class<ReferenceRect>(); ClassDB::register_class<ColorPicker>(); ClassDB::register_class<ColorPickerButton>(); ClassDB::register_class<RichTextLabel>(); @@ -467,15 +469,18 @@ void register_scene_types() { OS::get_singleton()->yield(); //may take time to init #endif - ClassDB::register_class<MeshLibrary>(); + AcceptDialog::set_swap_ok_cancel(GLOBAL_DEF("gui/common/swap_ok_cancel", bool(OS::get_singleton()->get_swap_ok_cancel()))); ClassDB::register_class<Shader>(); ClassDB::register_class<VisualShader>(); ClassDB::register_virtual_class<VisualShaderNode>(); + ClassDB::register_class<VisualShaderNodeCustom>(); ClassDB::register_class<VisualShaderNodeInput>(); ClassDB::register_virtual_class<VisualShaderNodeOutput>(); + ClassDB::register_class<VisualShaderNodeGroupBase>(); ClassDB::register_class<VisualShaderNodeScalarConstant>(); + ClassDB::register_class<VisualShaderNodeBooleanConstant>(); ClassDB::register_class<VisualShaderNodeColorConstant>(); ClassDB::register_class<VisualShaderNodeVec3Constant>(); ClassDB::register_class<VisualShaderNodeTransformConstant>(); @@ -486,10 +491,26 @@ void register_scene_types() { ClassDB::register_class<VisualShaderNodeTransformVecMult>(); ClassDB::register_class<VisualShaderNodeScalarFunc>(); ClassDB::register_class<VisualShaderNodeVectorFunc>(); + ClassDB::register_class<VisualShaderNodeColorFunc>(); + ClassDB::register_class<VisualShaderNodeTransformFunc>(); ClassDB::register_class<VisualShaderNodeDotProduct>(); ClassDB::register_class<VisualShaderNodeVectorLen>(); + ClassDB::register_class<VisualShaderNodeDeterminant>(); + ClassDB::register_class<VisualShaderNodeScalarDerivativeFunc>(); + ClassDB::register_class<VisualShaderNodeVectorDerivativeFunc>(); + ClassDB::register_class<VisualShaderNodeScalarClamp>(); + ClassDB::register_class<VisualShaderNodeVectorClamp>(); + ClassDB::register_class<VisualShaderNodeFaceForward>(); + ClassDB::register_class<VisualShaderNodeOuterProduct>(); + ClassDB::register_class<VisualShaderNodeVectorScalarStep>(); + ClassDB::register_class<VisualShaderNodeScalarSmoothStep>(); + ClassDB::register_class<VisualShaderNodeVectorSmoothStep>(); + ClassDB::register_class<VisualShaderNodeVectorScalarSmoothStep>(); + ClassDB::register_class<VisualShaderNodeVectorDistance>(); + ClassDB::register_class<VisualShaderNodeVectorRefract>(); ClassDB::register_class<VisualShaderNodeScalarInterp>(); ClassDB::register_class<VisualShaderNodeVectorInterp>(); + ClassDB::register_class<VisualShaderNodeVectorScalarMix>(); ClassDB::register_class<VisualShaderNodeVectorCompose>(); ClassDB::register_class<VisualShaderNodeTransformCompose>(); ClassDB::register_class<VisualShaderNodeVectorDecompose>(); @@ -498,11 +519,20 @@ void register_scene_types() { ClassDB::register_class<VisualShaderNodeCubeMap>(); ClassDB::register_virtual_class<VisualShaderNodeUniform>(); ClassDB::register_class<VisualShaderNodeScalarUniform>(); + ClassDB::register_class<VisualShaderNodeBooleanUniform>(); ClassDB::register_class<VisualShaderNodeColorUniform>(); ClassDB::register_class<VisualShaderNodeVec3Uniform>(); ClassDB::register_class<VisualShaderNodeTransformUniform>(); ClassDB::register_class<VisualShaderNodeTextureUniform>(); + ClassDB::register_class<VisualShaderNodeTextureUniformTriplanar>(); ClassDB::register_class<VisualShaderNodeCubeMapUniform>(); + ClassDB::register_class<VisualShaderNodeIf>(); + ClassDB::register_class<VisualShaderNodeSwitch>(); + ClassDB::register_class<VisualShaderNodeFresnel>(); + ClassDB::register_class<VisualShaderNodeExpression>(); + ClassDB::register_class<VisualShaderNodeGlobalExpression>(); + ClassDB::register_class<VisualShaderNodeIs>(); + ClassDB::register_class<VisualShaderNodeCompare>(); ClassDB::register_class<ShaderMaterial>(); ClassDB::register_virtual_class<CanvasItem>(); @@ -520,6 +550,7 @@ void register_scene_types() { ClassDB::register_class<Position2D>(); ClassDB::register_class<Line2D>(); ClassDB::register_class<MeshInstance2D>(); + ClassDB::register_class<MultiMeshInstance2D>(); ClassDB::register_virtual_class<CollisionObject2D>(); ClassDB::register_virtual_class<PhysicsBody2D>(); ClassDB::register_class<StaticBody2D>(); @@ -564,9 +595,13 @@ void register_scene_types() { SceneTree::add_idle_callback(ParticlesMaterial::flush_changes); ParticlesMaterial::init_shaders(); -#ifndef _3D_DISABLED ClassDB::register_virtual_class<Mesh>(); ClassDB::register_class<ArrayMesh>(); + ClassDB::register_class<MultiMesh>(); + ClassDB::register_class<SurfaceTool>(); + ClassDB::register_class<MeshDataTool>(); + +#ifndef _3D_DISABLED ClassDB::register_virtual_class<PrimitiveMesh>(); ClassDB::register_class<CapsuleMesh>(); ClassDB::register_class<CubeMesh>(); @@ -575,12 +610,12 @@ void register_scene_types() { ClassDB::register_class<PrismMesh>(); ClassDB::register_class<QuadMesh>(); ClassDB::register_class<SphereMesh>(); + ClassDB::register_class<PointMesh>(); ClassDB::register_virtual_class<Material>(); ClassDB::register_class<SpatialMaterial>(); SceneTree::add_idle_callback(SpatialMaterial::flush_changes); SpatialMaterial::init_shaders(); - ClassDB::register_class<MultiMesh>(); ClassDB::register_class<MeshLibrary>(); OS::get_singleton()->yield(); //may take time to init @@ -591,19 +626,17 @@ void register_scene_types() { ClassDB::register_class<BoxShape>(); ClassDB::register_class<CapsuleShape>(); ClassDB::register_class<CylinderShape>(); + ClassDB::register_class<HeightMapShape>(); ClassDB::register_class<PlaneShape>(); ClassDB::register_class<ConvexPolygonShape>(); ClassDB::register_class<ConcavePolygonShape>(); - ClassDB::register_class<SurfaceTool>(); - ClassDB::register_class<MeshDataTool>(); - OS::get_singleton()->yield(); //may take time to init ClassDB::register_class<SpatialVelocityTracker>(); - ClassDB::register_class<PhysicsMaterial>(); #endif + ClassDB::register_class<PhysicsMaterial>(); ClassDB::register_class<World>(); ClassDB::register_class<Environment>(); ClassDB::register_class<World2D>(); @@ -614,11 +647,13 @@ void register_scene_types() { ClassDB::register_class<StreamTexture>(); ClassDB::register_class<ImageTexture>(); ClassDB::register_class<AtlasTexture>(); + ClassDB::register_class<MeshTexture>(); ClassDB::register_class<LargeTexture>(); ClassDB::register_class<CurveTexture>(); ClassDB::register_class<GradientTexture>(); ClassDB::register_class<ProxyTexture>(); ClassDB::register_class<AnimatedTexture>(); + ClassDB::register_class<CameraTexture>(); ClassDB::register_class<CubeMap>(); ClassDB::register_virtual_class<TextureLayered>(); ClassDB::register_class<Texture3D>(); @@ -720,7 +755,7 @@ void register_scene_types() { if (theme_path != String()) { Ref<Theme> theme = ResourceLoader::load(theme_path); if (theme.is_valid()) { - Theme::set_default(theme); + Theme::set_project_default(theme); if (font.is_valid()) { Theme::set_default_font(font); } @@ -760,7 +795,11 @@ void unregister_scene_types() { ResourceLoader::remove_resource_format_loader(resource_loader_bmfont); resource_loader_bmfont.unref(); + //SpatialMaterial is not initialised when 3D is disabled, so it shouldn't be cleaned up either +#ifndef _3D_DISABLED SpatialMaterial::finish_shaders(); +#endif // _3D_DISABLED + ParticlesMaterial::finish_shaders(); CanvasItemMaterial::finish_shaders(); SceneStringNames::free(); diff --git a/scene/register_scene_types.h b/scene/register_scene_types.h index 3645f88807..b551ad2ac4 100644 --- a/scene/register_scene_types.h +++ b/scene/register_scene_types.h @@ -31,10 +31,6 @@ #ifndef REGISTER_SCENE_TYPES_H #define REGISTER_SCENE_TYPES_H -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - void register_scene_types(); void unregister_scene_types(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 3156e12c50..985b38f913 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "animation.h" +#include "scene/scene_string_names.h" #include "core/math/geometry.h" @@ -92,7 +93,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { TransformTrack *tt = static_cast<TransformTrack *>(tracks[track]); PoolVector<float> values = p_value; int vcount = values.size(); - ERR_FAIL_COND_V(vcount % 12, false); // shuld be multiple of 11 + ERR_FAIL_COND_V(vcount % 12, false); // should be multiple of 11 PoolVector<float>::Read r = values.read(); @@ -268,19 +269,19 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { for (int i = 0; i < valcount; i++) { - Dictionary d = clips[i]; - if (!d.has("start_offset")) + Dictionary d2 = clips[i]; + if (!d2.has("start_offset")) continue; - if (!d.has("end_offset")) + if (!d2.has("end_offset")) continue; - if (!d.has("stream")) + if (!d2.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"]; + ak.value.start_offset = d2["start_offset"]; + ak.value.end_offset = d2["end_offset"]; + ak.value.stream = d2["stream"]; ad->values.push_back(ak); } @@ -402,7 +403,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { w[idx++] = scale.z; } - w = PoolVector<real_t>::Write(); + w.release(); r_ret = keys; return true; @@ -437,8 +438,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { idx++; } - wti = PoolVector<float>::Write(); - wtr = PoolVector<float>::Write(); + wti.release(); + wtr.release(); d["times"] = key_times; d["transitions"] = key_transitions; @@ -477,8 +478,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { idx++; } - wti = PoolVector<float>::Write(); - wtr = PoolVector<float>::Write(); + wti.release(); + wtr.release(); d["times"] = key_times; d["transitions"] = key_transitions; @@ -522,8 +523,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { idx++; } - wti = PoolVector<float>::Write(); - wpo = PoolVector<float>::Write(); + wti.release(); + wpo.release(); d["times"] = key_times; d["points"] = key_points; @@ -561,7 +562,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { idx++; } - wti = PoolVector<float>::Write(); + wti.release(); d["times"] = key_times; d["clips"] = clips; @@ -594,8 +595,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { wcl[i] = vls[i].value; } - wti = PoolVector<float>::Write(); - wcl = PoolVector<String>::Write(); + wti.release(); + wcl.release(); d["times"] = key_times; d["clips"] = clips; @@ -668,6 +669,7 @@ int Animation::add_track(TrackType p_type, int p_at_pos) { } } emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); return p_at_pos; } @@ -719,6 +721,7 @@ void Animation::remove_track(int p_track) { memdelete(t); tracks.remove(p_track); emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); } int Animation::get_track_count() const { @@ -737,6 +740,7 @@ void Animation::track_set_path(int p_track, const NodePath &p_path) { ERR_FAIL_INDEX(p_track, tracks.size()); tracks[p_track]->path = p_path; emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); } NodePath Animation::track_get_path(int p_track) const { @@ -815,15 +819,17 @@ int Animation::_insert(float p_time, T &p_keys, const V &p_value) { while (true) { - if (idx == 0 || p_keys[idx - 1].time < p_time) { - //condition for insertion. - p_keys.insert(idx, p_value); - return idx; - } else if (p_keys[idx - 1].time == p_time) { + // Condition for replacement. + if (idx > 0 && Math::is_equal_approx(p_keys[idx - 1].time, p_time)) { - // condition for replacing. p_keys.write[idx - 1] = p_value; return idx - 1; + + // Condition for insert. + } else if (idx == 0 || p_keys[idx - 1].time < p_time) { + + p_keys.insert(idx, p_value); + return idx; } idx--; @@ -857,7 +863,7 @@ Error Animation::transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, return OK; } -int Animation::transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot, const Vector3 &p_scale) { +int Animation::transform_track_insert_key(int p_track, float p_time, const Vector3 &p_loc, const Quat &p_rot, const Vector3 &p_scale) { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -1292,6 +1298,78 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const { ERR_FAIL_V(-1); } +void Animation::track_set_key_time(int p_track, int p_key_idx, float p_time) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + + switch (t->type) { + + case TYPE_TRANSFORM: { + + TransformTrack *tt = static_cast<TransformTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, tt->transforms.size()); + TKey<TransformKey> key = tt->transforms[p_key_idx]; + key.time = p_time; + tt->transforms.remove(p_key_idx); + _insert(p_time, tt->transforms, key); + return; + } + case TYPE_VALUE: { + + ValueTrack *vt = static_cast<ValueTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, vt->values.size()); + TKey<Variant> key = vt->values[p_key_idx]; + key.time = p_time; + vt->values.remove(p_key_idx); + _insert(p_time, vt->values, key); + return; + } + case TYPE_METHOD: { + + MethodTrack *mt = static_cast<MethodTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, mt->methods.size()); + MethodKey key = mt->methods[p_key_idx]; + key.time = p_time; + mt->methods.remove(p_key_idx); + _insert(p_time, mt->methods, key); + return; + } + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, bt->values.size()); + TKey<BezierKey> key = bt->values[p_key_idx]; + key.time = p_time; + bt->values.remove(p_key_idx); + _insert(p_time, bt->values, key); + return; + } + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, at->values.size()); + TKey<AudioKey> key = at->values[p_key_idx]; + key.time = p_time; + at->values.remove(p_key_idx); + _insert(p_time, at->values, key); + return; + } + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, at->values.size()); + TKey<StringName> key = at->values[p_key_idx]; + key.time = p_time; + at->values.remove(p_key_idx); + _insert(p_time, at->values, key); + return; + } + } + + ERR_FAIL(); +} + float Animation::track_get_key_transition(int p_track, int p_key_idx) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); @@ -1347,7 +1425,9 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p TransformTrack *tt = static_cast<TransformTrack *>(t); ERR_FAIL_INDEX(p_key_idx, tt->transforms.size()); + Dictionary d = p_value; + if (d.has("location")) tt->transforms.write[p_key_idx].value.loc = d["location"]; if (d.has("rotation")) @@ -1360,6 +1440,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p ValueTrack *vt = static_cast<ValueTrack *>(t); ERR_FAIL_INDEX(p_key_idx, vt->values.size()); + vt->values.write[p_key_idx].value = p_value; } break; @@ -1367,11 +1448,14 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p MethodTrack *mt = static_cast<MethodTrack *>(t); ERR_FAIL_INDEX(p_key_idx, mt->methods.size()); + Dictionary d = p_value; + if (d.has("method")) mt->methods.write[p_key_idx].method = d["method"]; if (d.has("args")) mt->methods.write[p_key_idx].params = d["args"]; + } break; case TYPE_BEZIER: { @@ -1391,6 +1475,7 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p case TYPE_AUDIO: { AudioTrack *at = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, at->values.size()); Dictionary k = p_value; ERR_FAIL_COND(!k.has("start_offset")); @@ -1405,11 +1490,14 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p case TYPE_ANIMATION: { AnimationTrack *at = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, at->values.size()); at->values.write[p_key_idx].value = p_value; } break; } + + emit_changed(); } void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_transition) { @@ -1445,6 +1533,8 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra // they don't use transition } break; } + + emit_changed(); } template <class K> @@ -1458,7 +1548,7 @@ int Animation::_find(const Vector<K> &p_keys, float p_time) const { int high = len - 1; int middle = 0; -#if DEBUG_ENABLED +#ifdef DEBUG_ENABLED if (low > high) ERR_PRINT("low > high, this may be a bug"); #endif @@ -1469,7 +1559,7 @@ int Animation::_find(const Vector<K> &p_keys, float p_time) const { middle = (low + high) / 2; - if (Math::abs(p_time - keys[middle].time) < CMP_EPSILON) { //match + if (Math::is_equal_approx(p_time, keys[middle].time)) { //match return middle; } else if (p_time < keys[middle].time) high = middle - 1; //search low end of array @@ -1577,8 +1667,7 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a Vector2 pb = p_post_b; return a.cubic_interpolate(b, pa, pb, p_c); - - } break; + } case Variant::RECT2: { Rect2 a = p_a; @@ -1589,8 +1678,7 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a return Rect2( a.position.cubic_interpolate(b.position, pa.position, pb.position, p_c), a.size.cubic_interpolate(b.size, pa.size, pb.size, p_c)); - - } break; + } case Variant::VECTOR3: { Vector3 a = p_a; @@ -1599,8 +1687,7 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a Vector3 pb = p_post_b; return a.cubic_interpolate(b, pa, pb, p_c); - - } break; + } case Variant::QUAT: { Quat a = p_a; @@ -1609,8 +1696,7 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a Quat pb = p_post_b; return a.cubic_slerp(b, pa, pb, p_c); - - } break; + } case Variant::AABB: { AABB a = p_a; @@ -1621,14 +1707,12 @@ Variant Animation::_cubic_interpolate(const Variant &p_pre_a, const Variant &p_a return AABB( a.position.cubic_interpolate(b.position, pa.position, pb.position, p_c), a.size.cubic_interpolate(b.size, pa.size, pb.size, p_c)); - } break; + } default: { return _interpolate(p_a, p_b, p_c); } } - - return Variant(); } float Animation::_cubic_interpolate(const float &p_pre_a, const float &p_a, const float &p_b, const float &p_post_b, float p_c) const { @@ -1672,10 +1756,10 @@ T Animation::_interpolate(const Vector<TKey<T> > &p_keys, float p_time, Interpol float delta = p_keys[next].time - p_keys[idx].time; float from = p_time - p_keys[idx].time; - if (Math::absf(delta) > CMP_EPSILON) - c = from / delta; - else + if (Math::is_zero_approx(delta)) c = 0; + else + c = from / delta; } else { @@ -1683,10 +1767,10 @@ T Animation::_interpolate(const Vector<TKey<T> > &p_keys, float p_time, Interpol float delta = (length - p_keys[idx].time) + p_keys[next].time; float from = p_time - p_keys[idx].time; - if (Math::absf(delta) > CMP_EPSILON) - c = from / delta; - else + if (Math::is_zero_approx(delta)) c = 0; + else + c = from / delta; } } else { @@ -1699,10 +1783,10 @@ T Animation::_interpolate(const Vector<TKey<T> > &p_keys, float p_time, Interpol float delta = endtime + p_keys[next].time; float from = endtime + p_time; - if (Math::absf(delta) > CMP_EPSILON) - c = from / delta; - else + if (Math::is_zero_approx(delta)) c = 0; + else + c = from / delta; } } else { // no loop @@ -1715,17 +1799,17 @@ T Animation::_interpolate(const Vector<TKey<T> > &p_keys, float p_time, Interpol float delta = p_keys[next].time - p_keys[idx].time; float from = p_time - p_keys[idx].time; - if (Math::absf(delta) > CMP_EPSILON) - c = from / delta; - else + if (Math::is_zero_approx(delta)) c = 0; + else + c = from / delta; } else { next = idx; } - } else if (idx < 0) { + } else { // only allow extending first key to anim start if looping if (loop) @@ -1828,9 +1912,14 @@ Variant Animation::value_track_interpolate(int p_track, float p_time) const { void Animation::_value_track_get_key_indices_in_range(const ValueTrack *vt, 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 + to_time = length * 1.001; //include a little more if at the end int to = _find(vt->values, to_time); + if (to >= 0 && from_time == to_time && vt->values[to].time == from_time) { + //find exact (0 delta), return if found + p_indices->push_back(to); + return; + } // 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. @@ -1876,7 +1965,7 @@ void Animation::value_track_get_key_indices(int p_track, float p_time, float p_d if (from_time > to_time) { // handle loop by splitting - _value_track_get_key_indices_in_range(vt, length - from_time, length, p_indices); + _value_track_get_key_indices_in_range(vt, from_time, length, p_indices); _value_track_get_key_indices_in_range(vt, 0, to_time, p_indices); return; } @@ -2546,16 +2635,6 @@ bool Animation::has_loop() const { return loop; } -void Animation::track_move_up(int p_track) { - - if (p_track >= 0 && p_track < (tracks.size() - 1)) { - - SWAP(tracks.write[p_track], tracks.write[p_track + 1]); - } - - emit_changed(); -} - void Animation::track_set_imported(int p_track, bool p_imported) { ERR_FAIL_INDEX(p_track, tracks.size()); @@ -2581,13 +2660,42 @@ bool Animation::track_is_enabled(int p_track) const { return tracks[p_track]->enabled; } +void Animation::track_move_up(int p_track) { + + if (p_track >= 0 && p_track < (tracks.size() - 1)) { + + SWAP(tracks.write[p_track], tracks.write[p_track + 1]); + } + + emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); +} + void Animation::track_move_down(int p_track) { if (p_track > 0 && p_track < tracks.size()) { SWAP(tracks.write[p_track], tracks.write[p_track - 1]); } + + emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); +} + +void Animation::track_move_to(int p_track, int p_to_index) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + ERR_FAIL_INDEX(p_to_index, tracks.size() + 1); + if (p_track == p_to_index || p_track == p_to_index - 1) + return; + + Track *track = tracks.get(p_track); + tracks.remove(p_track); + // Take into account that the position of the tracks that come after the one removed will change. + tracks.insert(p_to_index > p_track ? p_to_index - 1 : p_to_index, track); + emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); } void Animation::track_swap(int p_track, int p_with_track) { @@ -2597,7 +2705,9 @@ void Animation::track_swap(int p_track, int p_with_track) { if (p_track == p_with_track) return; SWAP(tracks.write[p_track], tracks.write[p_with_track]); + emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); } void Animation::set_step(float p_step) { @@ -2622,6 +2732,7 @@ void Animation::copy_track(int p_track, Ref<Animation> p_to_animation) { p_to_animation->track_set_enabled(dst_track, track_is_enabled(p_track)); p_to_animation->track_set_interpolation_type(dst_track, track_get_interpolation_type(p_track)); p_to_animation->track_set_interpolation_loop_wrap(dst_track, track_get_interpolation_loop_wrap(p_track)); + p_to_animation->value_track_set_update_mode(dst_track, value_track_get_update_mode(p_track)); for (int i = 0; i < track_get_key_count(p_track); i++) { p_to_animation->track_insert_key(dst_track, track_get_key_time(p_track, i), track_get_key_value(p_track, i), track_get_key_transition(p_track, i)); } @@ -2639,6 +2750,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_move_to", "idx", "to_idx"), &Animation::track_move_to); 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); @@ -2653,6 +2765,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("track_remove_key_at_position", "idx", "position"), &Animation::track_remove_key_at_position); ClassDB::bind_method(D_METHOD("track_set_key_value", "idx", "key", "value"), &Animation::track_set_key_value); ClassDB::bind_method(D_METHOD("track_set_key_transition", "idx", "key_idx", "transition"), &Animation::track_set_key_transition); + ClassDB::bind_method(D_METHOD("track_set_key_time", "idx", "key_idx", "time"), &Animation::track_set_key_time); ClassDB::bind_method(D_METHOD("track_get_key_transition", "idx", "key_idx"), &Animation::track_get_key_transition); ClassDB::bind_method(D_METHOD("track_get_key_count", "idx"), &Animation::track_get_key_count); @@ -2716,6 +2829,8 @@ void Animation::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "step", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_step", "get_step"); + ADD_SIGNAL(MethodInfo("tracks_changed")); + BIND_ENUM_CONSTANT(TYPE_VALUE); BIND_ENUM_CONSTANT(TYPE_TRANSFORM); BIND_ENUM_CONSTANT(TYPE_METHOD); @@ -2740,6 +2855,8 @@ void Animation::clear() { tracks.clear(); loop = false; length = 1; + emit_changed(); + emit_signal(SceneStringNames::get_singleton()->tracks_changed); } bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, float p_alowed_linear_err, float p_alowed_angular_err, float p_max_optimizable_angle, const Vector3 &p_norm) { @@ -2753,9 +2870,9 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons const Vector3 &v1 = t1.value.loc; const Vector3 &v2 = t2.value.loc; - if (v0.distance_to(v2) < CMP_EPSILON) { + if (v0 == v2) { //0 and 2 are close, let's see if 1 is close - if (v0.distance_to(v1) > CMP_EPSILON) { + if (v0 != v1) { //not close, not optimizable return false; } @@ -2792,9 +2909,9 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons //localize both to rotation from q0 - if ((q0 - q2).length() < CMP_EPSILON) { + if (Math::is_zero_approx((q0 - q2).length())) { - if ((q0 - q1).length() > CMP_EPSILON) + if (!Math::is_zero_approx((q0 - q1).length())) return false; } else { @@ -2842,9 +2959,9 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons const Vector3 &v1 = t1.value.scale; const Vector3 &v2 = t2.value.scale; - if (v0.distance_to(v2) < CMP_EPSILON) { + if (v0 == v2) { //0 and 2 are close, let's see if 1 is close - if (v0.distance_to(v1) > CMP_EPSILON) { + if (v0 != v1) { //not close, not optimizable return false; } @@ -2905,7 +3022,6 @@ bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0, cons //this could be done as a second pass and would be //able to optimize more erase = false; - } else { } } } diff --git a/scene/resources/animation.h b/scene/resources/animation.h index b66ae184e9..d59dfab2c8 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -32,9 +32,7 @@ #define ANIMATION_H #include "core/resource.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Animation : public Resource { GDCLASS(Animation, Resource); @@ -93,7 +91,6 @@ private: template <class T> struct TKey : public Key { - float time; T value; }; @@ -294,6 +291,7 @@ public: void track_move_up(int p_track); void track_move_down(int p_track); + void track_move_to(int p_track, int p_to_index); void track_swap(int p_track, int p_with_track); void track_set_imported(int p_track, bool p_imported); @@ -305,6 +303,7 @@ public: 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); + void track_set_key_time(int p_track, int p_key_idx, float p_time); int track_find_key(int p_track, float p_time, bool p_exact = false) const; void track_remove_key(int p_track, int p_idx); void track_remove_key_at_position(int p_track, float p_pos); @@ -313,7 +312,7 @@ 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()); + 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; diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index a89cf108bc..5b61654c5d 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -515,10 +515,10 @@ PoolVector<uint8_t> AudioStreamSample::get_data() const { return pv; } -void AudioStreamSample::save_to_wav(String p_path) { +Error AudioStreamSample::save_to_wav(const String &p_path) { if (format == AudioStreamSample::FORMAT_IMA_ADPCM) { WARN_PRINTS("Saving IMA_ADPC samples are not supported yet"); - return; + return ERR_UNAVAILABLE; } int sub_chunk_2_size = data_bytes; //Subchunk2Size = Size of data in bytes @@ -544,8 +544,9 @@ void AudioStreamSample::save_to_wav(String p_path) { file_path += ".wav"; } - Error err; - FileAccess *file = FileAccess::open(file_path, FileAccess::WRITE, &err); //Overrides existing file if present + FileAccessRef file = FileAccess::open(file_path, FileAccess::WRITE); //Overrides existing file if present + + ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); // Create WAV Header file->store_string("RIFF"); //ChunkID @@ -563,7 +564,8 @@ void AudioStreamSample::save_to_wav(String p_path) { file->store_32(sub_chunk_2_size); //Subchunk2Size // Add data - PoolVector<uint8_t>::Read read_data = get_data().read(); + PoolVector<uint8_t> data = get_data(); + PoolVector<uint8_t>::Read read_data = data.read(); switch (format) { case AudioStreamSample::FORMAT_8_BITS: for (unsigned int i = 0; i < data_bytes; i++) { @@ -583,6 +585,8 @@ void AudioStreamSample::save_to_wav(String p_path) { } file->close(); + + return OK; } Ref<AudioStreamPlayback> AudioStreamSample::instance_playback() { diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h index bd701ddd12..bb25b60835 100644 --- a/scene/resources/audio_stream_sample.h +++ b/scene/resources/audio_stream_sample.h @@ -37,7 +37,7 @@ class AudioStreamSample; class AudioStreamPlaybackSample : public AudioStreamPlayback { - GDCLASS(AudioStreamPlaybackSample, AudioStreamPlayback) + GDCLASS(AudioStreamPlaybackSample, AudioStreamPlayback); enum { MIX_FRAC_BITS = 13, MIX_FRAC_LEN = (1 << MIX_FRAC_BITS), @@ -81,7 +81,7 @@ public: }; class AudioStreamSample : public AudioStream { - GDCLASS(AudioStreamSample, AudioStream) + GDCLASS(AudioStreamSample, AudioStream); RES_BASE_EXTENSION("sample") public: @@ -141,7 +141,7 @@ public: void set_data(const PoolVector<uint8_t> &p_data); PoolVector<uint8_t> get_data() const; - void save_to_wav(String p_path); + Error save_to_wav(const String &p_path); virtual Ref<AudioStreamPlayback> instance_playback(); virtual String get_stream_name() const; diff --git a/scene/resources/bit_mask.cpp b/scene/resources/bit_map.cpp index 0e2152f244..e4a64a1de1 100644 --- a/scene/resources/bit_mask.cpp +++ b/scene/resources/bit_map.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* bit_mask.cpp */ +/* bit_map.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "bit_mask.h" +#include "bit_map.h" #include "core/io/image_loader.h" @@ -96,7 +96,7 @@ int BitMap::get_true_bit_count() const { const uint8_t *d = bitmask.ptr(); int c = 0; - //fast, almot branchless version + //fast, almost branchless version for (int i = 0; i < ds; i++) { @@ -106,6 +106,7 @@ int BitMap::get_true_bit_count() const { c += (d[i] & (1 << 4)) >> 4; c += (d[i] & (1 << 3)) >> 3; c += (d[i] & (1 << 2)) >> 2; + c += (d[i] & (1 << 1)) >> 1; c += d[i] & 1; } @@ -332,7 +333,7 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) prevx = stepx; prevy = stepy; - ERR_FAIL_COND_V(count > width * height, _points); + ERR_FAIL_COND_V((int)count > width * height, _points); } while (curx != startx || cury != starty); return _points; } @@ -594,6 +595,62 @@ Array BitMap::_opaque_to_polygons_bind(const Rect2 &p_rect, float p_epsilon) con return result_array; } +void BitMap::resize(const Size2 &p_new_size) { + + Ref<BitMap> new_bitmap; + new_bitmap.instance(); + new_bitmap->create(p_new_size); + int lw = MIN(width, p_new_size.width); + int lh = MIN(height, p_new_size.height); + for (int x = 0; x < lw; x++) { + for (int y = 0; y < lh; y++) { + new_bitmap->set_bit(Vector2(x, y), get_bit(Vector2(x, y))); + } + } + + width = new_bitmap->width; + height = new_bitmap->height; + bitmask = new_bitmap->bitmask; +} + +Ref<Image> BitMap::convert_to_image() const { + + Ref<Image> image; + image.instance(); + image->create(width, height, false, Image::FORMAT_L8); + image->lock(); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + image->set_pixel(i, j, get_bit(Point2(i, j)) ? Color(1, 1, 1) : Color(0, 0, 0)); + } + } + + image->unlock(); + + return image; +} +void BitMap::blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap) { + + int x = p_pos.x; + int y = p_pos.y; + int w = p_bitmap->get_size().width; + int h = p_bitmap->get_size().height; + + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + int px = x + i; + int py = y + j; + if (px < 0 || px >= width) + continue; + if (py < 0 || py >= height) + continue; + if (p_bitmap->get_bit(Vector2(i, j))) { + set_bit(Vector2(x, y), true); + } + } + } +} + void BitMap::_bind_methods() { ClassDB::bind_method(D_METHOD("create", "size"), &BitMap::create); diff --git a/scene/resources/bit_mask.h b/scene/resources/bit_map.h index 4575064260..daf24affb1 100644 --- a/scene/resources/bit_mask.h +++ b/scene/resources/bit_map.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* bit_mask.h */ +/* bit_map.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BIT_MASK_H -#define BIT_MASK_H +#ifndef BIT_MAP_H +#define BIT_MAP_H #include "core/image.h" #include "core/io/resource_loader.h" @@ -64,12 +64,16 @@ public: int get_true_bit_count() const; Size2 get_size() const; + void resize(const Size2 &p_new_size); void grow_mask(int p_pixels, const Rect2 &p_rect); + void blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap); + Ref<Image> convert_to_image() const; + Vector<Vector<Vector2> > clip_opaque_to_polygons(const Rect2 &p_rect, float p_epsilon = 2.0) const; BitMap(); }; -#endif // BIT_MASK_H +#endif // BIT_MAP_H diff --git a/scene/resources/box_shape.cpp b/scene/resources/box_shape.cpp index d93754076c..d819e9f776 100644 --- a/scene/resources/box_shape.cpp +++ b/scene/resources/box_shape.cpp @@ -51,6 +51,7 @@ Vector<Vector3> BoxShape::_gen_debug_mesh_lines() { void BoxShape::_update_shape() { PhysicsServer::get_singleton()->shape_set_data(get_shape(), extents); + Shape::_update_shape(); } void BoxShape::set_extents(const Vector3 &p_extents) { diff --git a/scene/resources/capsule_shape.cpp b/scene/resources/capsule_shape.cpp index 3f7bf1e0ec..669b261bfe 100644 --- a/scene/resources/capsule_shape.cpp +++ b/scene/resources/capsule_shape.cpp @@ -75,6 +75,7 @@ void CapsuleShape::_update_shape() { d["radius"] = radius; d["height"] = height; PhysicsServer::get_singleton()->shape_set_data(get_shape(), d); + Shape::_update_shape(); } void CapsuleShape::set_radius(float p_radius) { diff --git a/scene/resources/concave_polygon_shape.cpp b/scene/resources/concave_polygon_shape.cpp index b192d088d8..b4cc38c8c0 100644 --- a/scene/resources/concave_polygon_shape.cpp +++ b/scene/resources/concave_polygon_shape.cpp @@ -65,6 +65,7 @@ Vector<Vector3> ConcavePolygonShape::_gen_debug_mesh_lines() { } void ConcavePolygonShape::_update_shape() { + Shape::_update_shape(); } void ConcavePolygonShape::set_faces(const PoolVector<Vector3> &p_faces) { diff --git a/scene/resources/concave_polygon_shape_2d.cpp b/scene/resources/concave_polygon_shape_2d.cpp index 51dd91fff5..de853f0c30 100644 --- a/scene/resources/concave_polygon_shape_2d.cpp +++ b/scene/resources/concave_polygon_shape_2d.cpp @@ -104,4 +104,6 @@ void ConcavePolygonShape2D::_bind_methods() { ConcavePolygonShape2D::ConcavePolygonShape2D() : Shape2D(Physics2DServer::get_singleton()->concave_polygon_shape_create()) { + PoolVector<Vector2> empty; + set_segments(empty); } diff --git a/scene/resources/convex_polygon_shape.cpp b/scene/resources/convex_polygon_shape.cpp index 98d3460ed2..499688a185 100644 --- a/scene/resources/convex_polygon_shape.cpp +++ b/scene/resources/convex_polygon_shape.cpp @@ -58,7 +58,7 @@ Vector<Vector3> ConvexPolygonShape::_gen_debug_mesh_lines() { void ConvexPolygonShape::_update_shape() { PhysicsServer::get_singleton()->shape_set_data(get_shape(), points); - emit_changed(); + Shape::_update_shape(); } void ConvexPolygonShape::set_points(const PoolVector<Vector3> &p_points) { @@ -83,6 +83,4 @@ void ConvexPolygonShape::_bind_methods() { ConvexPolygonShape::ConvexPolygonShape() : Shape(PhysicsServer::get_singleton()->shape_create(PhysicsServer::SHAPE_CONVEX_POLYGON)) { - - //set_points(Vector3(1,1,1)); } diff --git a/scene/resources/convex_polygon_shape_2d.cpp b/scene/resources/convex_polygon_shape_2d.cpp index f275405de2..d424fb2814 100644 --- a/scene/resources/convex_polygon_shape_2d.cpp +++ b/scene/resources/convex_polygon_shape_2d.cpp @@ -99,10 +99,4 @@ Rect2 ConvexPolygonShape2D::get_rect() const { ConvexPolygonShape2D::ConvexPolygonShape2D() : Shape2D(Physics2DServer::get_singleton()->convex_polygon_shape_create()) { - - int pcount = 3; - for (int i = 0; i < pcount; i++) - points.push_back(Vector2(Math::sin(i * Math_PI * 2 / pcount), -Math::cos(i * Math_PI * 2 / pcount)) * 10); - - _update_shape(); } diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 464ca60d31..bb14cf3ab1 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -51,6 +51,7 @@ Curve::Curve() { _baked_cache_dirty = false; _min_value = 0; _max_value = 1; + _minmax_set_once = 0b00; } int Curve::add_point(Vector2 p_pos, real_t left_tangent, real_t right_tangent, TangentMode left_mode, TangentMode right_mode) { @@ -268,7 +269,7 @@ void Curve::update_auto_tangents(int i) { } if (i + 1 < _points.size()) { - if (p.right_mode == TANGENT_LINEAR && i + 1 < _points.size()) { + if (p.right_mode == TANGENT_LINEAR) { Vector2 v = (_points[i + 1].pos - p.pos).normalized(); p.right_tangent = v.y / v.x; } @@ -282,20 +283,24 @@ void Curve::update_auto_tangents(int i) { #define MIN_Y_RANGE 0.01 void Curve::set_min_value(float p_min) { - if (p_min > _max_value - MIN_Y_RANGE) + if (_minmax_set_once & 0b11 && p_min > _max_value - MIN_Y_RANGE) { _min_value = _max_value - MIN_Y_RANGE; - else + } else { + _minmax_set_once |= 0b10; // first bit is "min set" _min_value = p_min; + } // Note: min and max are indicative values, // it's still possible that existing points are out of range at this point. emit_signal(SIGNAL_RANGE_CHANGED); } void Curve::set_max_value(float p_max) { - if (p_max < _min_value + MIN_Y_RANGE) + if (_minmax_set_once & 0b11 && p_max < _min_value + MIN_Y_RANGE) { _max_value = _min_value + MIN_Y_RANGE; - else + } else { + _minmax_set_once |= 0b01; // second bit is "max set" _max_value = p_max; + } emit_signal(SIGNAL_RANGE_CHANGED); } @@ -760,10 +765,7 @@ Vector2 Curve2D::interpolate_baked(float p_offset, bool p_cubic) const { //validate// int pc = baked_point_cache.size(); - if (pc == 0) { - ERR_EXPLAIN("No points in Curve2D."); - ERR_FAIL_V(Vector2()); - } + ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D."); if (pc == 1) return baked_point_cache.get(0); @@ -782,7 +784,8 @@ Vector2 Curve2D::interpolate_baked(float p_offset, bool p_cubic) const { if (idx >= bpc - 1) { return r[bpc - 1]; } else if (idx == bpc - 2) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + if (frac > 0) + frac /= Math::fmod(baked_max_ofs, bake_interval); } else { frac /= bake_interval; } @@ -825,10 +828,7 @@ Vector2 Curve2D::get_closest_point(const Vector2 &p_to_point) const { //validate// int pc = baked_point_cache.size(); - if (pc == 0) { - ERR_EXPLAIN("No points in Curve2D."); - ERR_FAIL_V(Vector2()); - } + ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D."); if (pc == 1) return baked_point_cache.get(0); @@ -864,10 +864,7 @@ float Curve2D::get_closest_offset(const Vector2 &p_to_point) const { //validate// int pc = baked_point_cache.size(); - if (pc == 0) { - ERR_EXPLAIN("No points in Curve2D."); - ERR_FAIL_V(0.0f); - } + ERR_FAIL_COND_V_MSG(pc == 0, 0.0f, "No points in Curve2D."); if (pc == 1) return 0.0f; @@ -1330,10 +1327,7 @@ Vector3 Curve3D::interpolate_baked(float p_offset, bool p_cubic) const { //validate// int pc = baked_point_cache.size(); - if (pc == 0) { - ERR_EXPLAIN("No points in Curve3D."); - ERR_FAIL_V(Vector3()); - } + ERR_FAIL_COND_V_MSG(pc == 0, Vector3(), "No points in Curve3D."); if (pc == 1) return baked_point_cache.get(0); @@ -1352,7 +1346,8 @@ Vector3 Curve3D::interpolate_baked(float p_offset, bool p_cubic) const { if (idx >= bpc - 1) { return r[bpc - 1]; } else if (idx == bpc - 2) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + if (frac > 0) + frac /= Math::fmod(baked_max_ofs, bake_interval); } else { frac /= bake_interval; } @@ -1374,10 +1369,7 @@ float Curve3D::interpolate_baked_tilt(float p_offset) const { //validate// int pc = baked_tilt_cache.size(); - if (pc == 0) { - ERR_EXPLAIN("No tilts in Curve3D."); - ERR_FAIL_V(0); - } + ERR_FAIL_COND_V_MSG(pc == 0, 0, "No tilts in Curve3D."); if (pc == 1) return baked_tilt_cache.get(0); @@ -1396,7 +1388,8 @@ float Curve3D::interpolate_baked_tilt(float p_offset) const { if (idx >= bpc - 1) { return r[bpc - 1]; } else if (idx == bpc - 2) { - frac /= Math::fmod(baked_max_ofs, bake_interval); + if (frac > 0) + frac /= Math::fmod(baked_max_ofs, bake_interval); } else { frac /= bake_interval; } @@ -1412,10 +1405,7 @@ Vector3 Curve3D::interpolate_baked_up_vector(float p_offset, bool p_apply_tilt) //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_V(Vector3(0, 1, 0)); - } + ERR_FAIL_COND_V_MSG(count == 0, Vector3(0, 1, 0), "No up vectors in Curve3D."); if (count == 1) return baked_up_vector_cache.get(0); @@ -1483,10 +1473,7 @@ Vector3 Curve3D::get_closest_point(const Vector3 &p_to_point) const { //validate// int pc = baked_point_cache.size(); - if (pc == 0) { - ERR_EXPLAIN("No points in Curve3D."); - ERR_FAIL_V(Vector3()); - } + ERR_FAIL_COND_V_MSG(pc == 0, Vector3(), "No points in Curve3D."); if (pc == 1) return baked_point_cache.get(0); @@ -1522,10 +1509,7 @@ float Curve3D::get_closest_offset(const Vector3 &p_to_point) const { //validate// int pc = baked_point_cache.size(); - if (pc == 0) { - ERR_EXPLAIN("No points in Curve3D."); - ERR_FAIL_V(0.0f); - } + ERR_FAIL_COND_V_MSG(pc == 0, 0.0f, "No points in Curve3D."); if (pc == 1) return 0.0f; diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 911a440567..b677097e86 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -35,7 +35,8 @@ // y(x) curve class Curve : public Resource { - GDCLASS(Curve, Resource) + GDCLASS(Curve, Resource); + public: static const int MIN_X = 0.f; static const int MAX_X = 1.f; @@ -142,6 +143,7 @@ private: int _bake_resolution; float _min_value; float _max_value; + int _minmax_set_once; // Encodes whether min and max have been set a first time, first bit for min and second for max. }; VARIANT_ENUM_CAST(Curve::TangentMode) diff --git a/scene/resources/cylinder_shape.cpp b/scene/resources/cylinder_shape.cpp index 4fd829b349..f60f7ab376 100644 --- a/scene/resources/cylinder_shape.cpp +++ b/scene/resources/cylinder_shape.cpp @@ -68,6 +68,7 @@ void CylinderShape::_update_shape() { d["radius"] = radius; d["height"] = height; PhysicsServer::get_singleton()->shape_set_data(get_shape(), d); + Shape::_update_shape(); } void CylinderShape::set_radius(float p_radius) { diff --git a/scene/resources/default_theme/background.png b/scene/resources/default_theme/background.png Binary files differdeleted file mode 100644 index 6c5f43e3ce..0000000000 --- a/scene/resources/default_theme/background.png +++ /dev/null diff --git a/scene/resources/default_theme/base_green.png b/scene/resources/default_theme/base_green.png Binary files differdeleted file mode 100644 index 03a5b313d7..0000000000 --- a/scene/resources/default_theme/base_green.png +++ /dev/null diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 7ed51a2f8f..1a5f57ce48 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -177,13 +177,13 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // Font Colors - Color control_font_color = Color::html("e0e0e0"); - Color control_font_color_lower = Color::html("a0a0a0"); - Color control_font_color_low = Color::html("b0b0b0"); - Color control_font_color_hover = Color::html("f0f0f0"); + Color control_font_color = Color(0.88, 0.88, 0.88); + Color control_font_color_lower = Color(0.63, 0.63, 0.63); + Color control_font_color_low = Color(0.69, 0.69, 0.69); + Color control_font_color_hover = Color(0.94, 0.94, 0.94); Color control_font_color_disabled = Color(0.9, 0.9, 0.9, 0.2); - Color control_font_color_pressed = Color::html("ffffff"); - Color font_color_selection = Color::html("7d7d7d"); + Color control_font_color_pressed = Color(1, 1, 1); + Color font_color_selection = Color(0.49, 0.49, 0.49); // Panel @@ -359,7 +359,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("focus", "CheckButton", focus); theme->set_icon("on", "CheckButton", make_icon(toggle_on_png)); + theme->set_icon("on_disabled", "CheckButton", make_icon(toggle_on_disabled_png)); theme->set_icon("off", "CheckButton", make_icon(toggle_off_png)); + theme->set_icon("off_disabled", "CheckButton", make_icon(toggle_off_disabled_png)); theme->set_font("font", "CheckButton", default_font); @@ -396,6 +398,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color", "LineEdit", control_font_color); theme->set_color("font_color_selected", "LineEdit", Color(0, 0, 0)); + theme->set_color("font_color_uneditable", "LineEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("cursor_color", "LineEdit", control_font_color_hover); theme->set_color("selection_color", "LineEdit", font_color_selection); theme->set_color("clear_button_color", "LineEdit", control_font_color); @@ -423,31 +426,37 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("completion", "TextEdit", make_stylebox(tree_bg_png, 3, 3, 3, 3, 0, 0, 0, 0)); theme->set_icon("tab", "TextEdit", make_icon(tab_png)); + theme->set_icon("space", "TextEdit", make_icon(space_png)); + theme->set_icon("folded", "TextEdit", make_icon(arrow_right_png)); + theme->set_icon("fold", "TextEdit", make_icon(arrow_down_png)); theme->set_font("font", "TextEdit", default_font); - theme->set_color("background_color", "TextEdit", Color(0, 0, 0, 0)); - theme->set_color("completion_background_color", "TextEdit", Color::html("2C2A32")); - theme->set_color("completion_selected_color", "TextEdit", Color::html("434244")); - theme->set_color("completion_existing_color", "TextEdit", Color::html("21dfdfdf")); + theme->set_color("background_color", "TextEdit", Color(0, 0, 0)); + theme->set_color("completion_background_color", "TextEdit", Color(0.17, 0.16, 0.2)); + theme->set_color("completion_selected_color", "TextEdit", Color(0.26, 0.26, 0.27)); + theme->set_color("completion_existing_color", "TextEdit", Color(0.87, 0.87, 0.87, 0.13)); theme->set_color("completion_scroll_color", "TextEdit", control_font_color_pressed); - theme->set_color("completion_font_color", "TextEdit", Color::html("aaaaaa")); + theme->set_color("completion_font_color", "TextEdit", Color(0.67, 0.67, 0.67)); theme->set_color("font_color", "TextEdit", control_font_color); theme->set_color("font_color_selected", "TextEdit", Color(0, 0, 0)); + theme->set_color("font_color_readonly", "TextEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("selection_color", "TextEdit", font_color_selection); theme->set_color("mark_color", "TextEdit", Color(1.0, 0.4, 0.4, 0.4)); + theme->set_color("bookmark_color", "TextEdit", Color(0.08, 0.49, 0.98)); theme->set_color("breakpoint_color", "TextEdit", Color(0.8, 0.8, 0.4, 0.2)); + theme->set_color("executing_line_color", "TextEdit", Color(0.2, 0.8, 0.2, 0.4)); theme->set_color("code_folding_color", "TextEdit", Color(0.8, 0.8, 0.8, 0.8)); theme->set_color("current_line_color", "TextEdit", Color(0.25, 0.25, 0.26, 0.8)); theme->set_color("caret_color", "TextEdit", control_font_color); - theme->set_color("caret_background_color", "TextEdit", Color::html("000000")); + theme->set_color("caret_background_color", "TextEdit", Color(0, 0, 0)); theme->set_color("symbol_color", "TextEdit", control_font_color_hover); theme->set_color("brace_mismatch_color", "TextEdit", Color(1, 0.2, 0.2)); - theme->set_color("line_number_color", "TextEdit", Color::html("66aaaaaa")); - theme->set_color("safe_line_number_color", "TextEdit", Color::html("99aac8aa")); - theme->set_color("function_color", "TextEdit", Color::html("66a2ce")); - theme->set_color("member_variable_color", "TextEdit", Color::html("e64e59")); - theme->set_color("number_color", "TextEdit", Color::html("EB9532")); + theme->set_color("line_number_color", "TextEdit", Color(0.67, 0.67, 0.67, 0.4)); + theme->set_color("safe_line_number_color", "TextEdit", Color(0.67, 0.78, 0.67, 0.6)); + theme->set_color("function_color", "TextEdit", Color(0.4, 0.64, 0.81)); + theme->set_color("member_variable_color", "TextEdit", Color(0.9, 0.31, 0.35)); + theme->set_color("number_color", "TextEdit", Color(0.92, 0.58, 0.2)); theme->set_color("word_highlighted_color", "TextEdit", Color(0.8, 0.9, 0.9, 0.15)); theme->set_constant("completion_lines", "TextEdit", 7); @@ -534,8 +543,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // File Dialog - theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png)); theme->set_icon("parent_folder", "FileDialog", make_icon(icon_parent_folder_png)); + theme->set_icon("reload", "FileDialog", make_icon(icon_reload_png)); + theme->set_icon("toggle_hidden", "FileDialog", make_icon(icon_visibility_png)); // Popup @@ -575,14 +585,14 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // GraphNode - Ref<StyleBoxTexture> graphsb = make_stylebox(graph_node_png, 6, 24, 6, 5, 16, 24, 16, 5); - Ref<StyleBoxTexture> graphsbcomment = make_stylebox(graph_node_comment_png, 6, 24, 6, 5, 16, 24, 16, 5); - Ref<StyleBoxTexture> graphsbcommentselected = make_stylebox(graph_node_comment_focus_png, 6, 24, 6, 5, 16, 24, 16, 5); - Ref<StyleBoxTexture> graphsbselected = make_stylebox(graph_node_selected_png, 6, 24, 6, 5, 16, 24, 16, 5); + Ref<StyleBoxTexture> graphsb = make_stylebox(graph_node_png, 6, 24, 6, 5, 16, 24, 16, 6); + Ref<StyleBoxTexture> graphsbcomment = make_stylebox(graph_node_comment_png, 6, 24, 6, 5, 16, 24, 16, 6); + Ref<StyleBoxTexture> graphsbcommentselected = make_stylebox(graph_node_comment_focus_png, 6, 24, 6, 5, 16, 24, 16, 6); + Ref<StyleBoxTexture> graphsbselected = make_stylebox(graph_node_selected_png, 6, 24, 6, 5, 16, 24, 16, 6); Ref<StyleBoxTexture> graphsbdefault = make_stylebox(graph_node_default_png, 4, 4, 4, 4, 6, 4, 4, 4); Ref<StyleBoxTexture> graphsbdeffocus = make_stylebox(graph_node_default_focus_png, 4, 4, 4, 4, 6, 4, 4, 4); - Ref<StyleBoxTexture> graph_bpoint = make_stylebox(graph_node_breakpoint_png, 6, 24, 6, 5, 16, 24, 16, 5); - Ref<StyleBoxTexture> graph_position = make_stylebox(graph_node_position_png, 6, 24, 6, 5, 16, 24, 16, 5); + Ref<StyleBoxTexture> graph_bpoint = make_stylebox(graph_node_breakpoint_png, 6, 24, 6, 5, 16, 24, 16, 6); + Ref<StyleBoxTexture> graph_position = make_stylebox(graph_node_position_png, 6, 24, 6, 5, 16, 24, 16, 6); //graphsb->set_expand_margin_size(MARGIN_LEFT,10); //graphsb->set_expand_margin_size(MARGIN_RIGHT,10); @@ -641,7 +651,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("cursor_color", "Tree", Color(0, 0, 0)); theme->set_color("guide_color", "Tree", Color(0, 0, 0, 0.1)); theme->set_color("drop_position_color", "Tree", Color(1, 0.3, 0.2)); - theme->set_color("relationship_line_color", "Tree", Color::html("464646")); + theme->set_color("relationship_line_color", "Tree", Color(0.27, 0.27, 0.27)); theme->set_color("custom_button_font_highlight", "Tree", control_font_color_hover); theme->set_constant("hseparation", "Tree", 4 * scale); @@ -682,6 +692,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("tab_fg", "TabContainer", sb_expand(make_stylebox(tab_current_png, 4, 4, 4, 1, 16, 4, 16, 4), 2, 2, 2, 2)); theme->set_stylebox("tab_bg", "TabContainer", sb_expand(make_stylebox(tab_behind_png, 5, 5, 5, 1, 16, 6, 16, 4), 3, 0, 3, 3)); + theme->set_stylebox("tab_disabled", "TabContainer", sb_expand(make_stylebox(tab_disabled_png, 5, 5, 5, 1, 16, 6, 16, 4), 3, 0, 3, 3)); theme->set_stylebox("panel", "TabContainer", tc_sb); theme->set_icon("increment", "TabContainer", make_icon(scroll_button_right_png)); @@ -707,6 +718,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("tab_fg", "Tabs", sb_expand(make_stylebox(tab_current_png, 4, 3, 4, 1, 16, 3, 16, 2), 2, 2, 2, 2)); theme->set_stylebox("tab_bg", "Tabs", sb_expand(make_stylebox(tab_behind_png, 5, 4, 5, 1, 16, 5, 16, 2), 3, 3, 3, 3)); + theme->set_stylebox("tab_disabled", "Tabs", sb_expand(make_stylebox(tab_disabled_png, 5, 5, 5, 1, 16, 6, 16, 4), 3, 0, 3, 3)); theme->set_stylebox("panel", "Tabs", tc_sb); theme->set_stylebox("button_pressed", "Tabs", make_stylebox(button_pressed_png, 4, 4, 4, 4)); theme->set_stylebox("button", "Tabs", make_stylebox(button_normal_png, 4, 4, 4, 4)); @@ -748,6 +760,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // FileDialog theme->set_icon("folder", "FileDialog", make_icon(icon_folder_png)); + theme->set_color("folder_icon_modulate", "FileDialog", Color(1, 1, 1)); theme->set_color("files_disabled", "FileDialog", Color(0, 0, 0, 0.7)); // colorPicker @@ -793,7 +806,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font("bold_italics_font", "RichTextLabel", default_font); theme->set_font("mono_font", "RichTextLabel", default_font); - theme->set_color("default_color", "RichTextLabel", control_font_color); + theme->set_color("default_color", "RichTextLabel", Color(1, 1, 1)); theme->set_color("font_color_selected", "RichTextLabel", font_color_selection); theme->set_color("selection_color", "RichTextLabel", Color(0.1, 0.1, 1, 0.8)); @@ -828,14 +841,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("autohide", "HSplitContainer", 1 * scale); theme->set_constant("autohide", "VSplitContainer", 1 * scale); - // ReferenceRect - - Ref<StyleBoxTexture> ttnc = make_stylebox(full_panel_bg_png, 8, 8, 8, 8); - ttnc->set_draw_center(false); - - theme->set_stylebox("panelnc", "Panel", ttnc); - theme->set_stylebox("panelf", "Panel", tc_sb); - Ref<StyleBoxTexture> sb_pc = make_stylebox(tab_container_bg_png, 4, 4, 4, 4, 7, 7, 7, 7); theme->set_stylebox("panel", "PanelContainer", sb_pc); @@ -850,8 +855,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("bezier_len_pos", "GraphEdit", 80 * scale); theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale); - theme->set_icon("logo", "Icons", make_icon(logo_png)); - // Visual Node Ports theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 48 * scale); theme->set_constant("port_grab_distance_vertical", "GraphEdit", 6 * scale); @@ -890,8 +893,9 @@ void make_default_theme(bool p_hidpi, Ref<Font> p_font) { void clear_default_theme() { - Theme::set_default(Ref<Theme>()); - Theme::set_default_icon(Ref<Texture>()); - Theme::set_default_style(Ref<StyleBox>()); - Theme::set_default_font(Ref<Font>()); + Theme::set_project_default(NULL); + Theme::set_default(NULL); + Theme::set_default_icon(NULL); + Theme::set_default_style(NULL); + Theme::set_default_font(NULL); } diff --git a/scene/resources/default_theme/default_theme.h b/scene/resources/default_theme/default_theme.h index cbf0cc1b79..e7d80ffb3d 100644 --- a/scene/resources/default_theme/default_theme.h +++ b/scene/resources/default_theme/default_theme.h @@ -32,9 +32,6 @@ #define DEFAULT_THEME_H #include "scene/resources/theme.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const Ref<Font> &large_font, Ref<Texture> &default_icon, Ref<StyleBox> &default_style, float p_scale); void make_default_theme(bool p_hidpi, Ref<Font> p_font); diff --git a/scene/resources/default_theme/dosfont.png b/scene/resources/default_theme/dosfont.png Binary files differdeleted file mode 100644 index e2739b94ea..0000000000 --- a/scene/resources/default_theme/dosfont.png +++ /dev/null diff --git a/scene/resources/default_theme/font_hidpi.inc b/scene/resources/default_theme/font_hidpi.inc index 2fc0f56c3f..4860149e6b 100644 --- a/scene/resources/default_theme/font_hidpi.inc +++ b/scene/resources/default_theme/font_hidpi.inc @@ -1,3 +1,4 @@ +/* clang-format off */ static const int _hidpi_font_height=25; static const int _hidpi_font_ascent=19; static const int _hidpi_font_charcount=191; @@ -25459,3 +25460,4 @@ static const unsigned char _hidpi_font_img_data[25255]={ 96, 130, }; +/* clang-format on */ diff --git a/scene/resources/default_theme/font_lodpi.inc b/scene/resources/default_theme/font_lodpi.inc index 8eae45a8a7..959e2c1d7b 100644 --- a/scene/resources/default_theme/font_lodpi.inc +++ b/scene/resources/default_theme/font_lodpi.inc @@ -1,3 +1,4 @@ +/* clang-format off */ static const int _lodpi_font_height=14; static const int _lodpi_font_ascent=11; static const int _lodpi_font_charcount=191; @@ -13113,3 +13114,4 @@ static const unsigned char _lodpi_font_img_data[12909]={ 96, 130, }; +/* clang-format on */ diff --git a/scene/resources/default_theme/frame_focus.png b/scene/resources/default_theme/frame_focus.png Binary files differdeleted file mode 100644 index 1b24ba47d8..0000000000 --- a/scene/resources/default_theme/frame_focus.png +++ /dev/null diff --git a/scene/resources/default_theme/full_panel_bg.png b/scene/resources/default_theme/full_panel_bg.png Binary files differdeleted file mode 100644 index 85f753cc13..0000000000 --- a/scene/resources/default_theme/full_panel_bg.png +++ /dev/null diff --git a/scene/resources/default_theme/icon_play.png b/scene/resources/default_theme/icon_play.png Binary files differdeleted file mode 100644 index b9ed6e6d5b..0000000000 --- a/scene/resources/default_theme/icon_play.png +++ /dev/null diff --git a/scene/resources/default_theme/icon_stop.png b/scene/resources/default_theme/icon_stop.png Binary files differdeleted file mode 100644 index 0c1371ceb9..0000000000 --- a/scene/resources/default_theme/icon_stop.png +++ /dev/null diff --git a/scene/resources/default_theme/icon_visibility.png b/scene/resources/default_theme/icon_visibility.png Binary files differnew file mode 100644 index 0000000000..6402571f3e --- /dev/null +++ b/scene/resources/default_theme/icon_visibility.png diff --git a/scene/resources/default_theme/line_edit_focus.png b/scene/resources/default_theme/line_edit_focus.png Binary files differdeleted file mode 100644 index 1d74b74068..0000000000 --- a/scene/resources/default_theme/line_edit_focus.png +++ /dev/null diff --git a/scene/resources/default_theme/logo.png b/scene/resources/default_theme/logo.png Binary files differdeleted file mode 100644 index d0ef9d8aa7..0000000000 --- a/scene/resources/default_theme/logo.png +++ /dev/null diff --git a/scene/resources/default_theme/make_header.py b/scene/resources/default_theme/make_header.py index 73b1ae0b0b..cf0ccf1c3a 100755 --- a/scene/resources/default_theme/make_header.py +++ b/scene/resources/default_theme/make_header.py @@ -1,11 +1,14 @@ #!/usr/bin/env python -import os import glob -import string +import os enc = "utf-8" +# Change to the directory where the script is located, +# so that the script can be run from any location +os.chdir(os.path.dirname(os.path.realpath(__file__))) + # Generate include files f = open("theme_data.h", "wb") diff --git a/scene/resources/default_theme/option_button_focus.png b/scene/resources/default_theme/option_button_focus.png Binary files differdeleted file mode 100644 index 402670f9a2..0000000000 --- a/scene/resources/default_theme/option_button_focus.png +++ /dev/null diff --git a/scene/resources/default_theme/popup_checked.png b/scene/resources/default_theme/popup_checked.png Binary files differdeleted file mode 100644 index b7b05640e1..0000000000 --- a/scene/resources/default_theme/popup_checked.png +++ /dev/null diff --git a/scene/resources/default_theme/popup_hover.png b/scene/resources/default_theme/popup_hover.png Binary files differdeleted file mode 100644 index bdb6ae8bd0..0000000000 --- a/scene/resources/default_theme/popup_hover.png +++ /dev/null diff --git a/scene/resources/default_theme/popup_unchecked.png b/scene/resources/default_theme/popup_unchecked.png Binary files differdeleted file mode 100644 index ff922335c3..0000000000 --- a/scene/resources/default_theme/popup_unchecked.png +++ /dev/null diff --git a/scene/resources/default_theme/reference_border.png b/scene/resources/default_theme/reference_border.png Binary files differdeleted file mode 100644 index 6a680f393c..0000000000 --- a/scene/resources/default_theme/reference_border.png +++ /dev/null diff --git a/scene/resources/default_theme/scroll_button_down.png b/scene/resources/default_theme/scroll_button_down.png Binary files differdeleted file mode 100644 index 1df4ef5b6b..0000000000 --- a/scene/resources/default_theme/scroll_button_down.png +++ /dev/null diff --git a/scene/resources/default_theme/scroll_button_down_hl.png b/scene/resources/default_theme/scroll_button_down_hl.png Binary files differdeleted file mode 100644 index ba79087393..0000000000 --- a/scene/resources/default_theme/scroll_button_down_hl.png +++ /dev/null diff --git a/scene/resources/default_theme/scroll_button_up.png b/scene/resources/default_theme/scroll_button_up.png Binary files differdeleted file mode 100644 index f425412f50..0000000000 --- a/scene/resources/default_theme/scroll_button_up.png +++ /dev/null diff --git a/scene/resources/default_theme/scroll_button_up_hl.png b/scene/resources/default_theme/scroll_button_up_hl.png Binary files differdeleted file mode 100644 index 615a236c52..0000000000 --- a/scene/resources/default_theme/scroll_button_up_hl.png +++ /dev/null diff --git a/scene/resources/default_theme/space.png b/scene/resources/default_theme/space.png Binary files differnew file mode 100644 index 0000000000..7e458a6c45 --- /dev/null +++ b/scene/resources/default_theme/space.png diff --git a/scene/resources/default_theme/tab_disabled.png b/scene/resources/default_theme/tab_disabled.png Binary files differnew file mode 100644 index 0000000000..97157a58dd --- /dev/null +++ b/scene/resources/default_theme/tab_disabled.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 353e7eddbe..11904b7aff 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -10,14 +10,6 @@ static const unsigned char arrow_right_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc, 0x8, 0x4, 0x0, 0x0, 0x0, 0xfc, 0x7c, 0x94, 0x6c, 0x0, 0x0, 0x0, 0x2e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0x17, 0x3c, 0xf8, 0xf, 0x82, 0xf7, 0x13, 0x70, 0x48, 0x3c, 0xf8, 0xf2, 0x50, 0x1b, 0x43, 0x2, 0xa, 0xaf, 0xbe, 0xe0, 0xc6, 0x2e, 0xf1, 0xff, 0xe1, 0x7c, 0x12, 0x24, 0x10, 0x46, 0x11, 0xb6, 0x1c, 0xe1, 0x5c, 0xa, 0x0, 0x0, 0xe0, 0x14, 0x48, 0xb1, 0x3d, 0x1b, 0x7a, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char background_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x20, 0x8, 0x3, 0x0, 0x0, 0x0, 0x44, 0xa4, 0x8a, 0xc6, 0x0, 0x0, 0x1, 0xe, 0x50, 0x4c, 0x54, 0x45, 0x91, 0xc9, 0xab, 0x90, 0xc9, 0xab, 0x90, 0xc9, 0xaa, 0x90, 0xc8, 0xab, 0x91, 0xc9, 0xaa, 0x91, 0xc8, 0xab, 0x90, 0xc8, 0xaa, 0x8f, 0xc8, 0xab, 0x8f, 0xc9, 0xab, 0x8f, 0xc8, 0xaa, 0x90, 0xc7, 0xaa, 0x90, 0xc7, 0xab, 0x8f, 0xc7, 0xaa, 0x8f, 0xc7, 0xab, 0x8e, 0xc7, 0xab, 0x8e, 0xc6, 0xab, 0x8f, 0xc6, 0xab, 0x8e, 0xc6, 0xaa, 0x8f, 0xc6, 0xaa, 0x8e, 0xc7, 0xaa, 0x8e, 0xc5, 0xaa, 0x8e, 0xc5, 0xab, 0x8d, 0xc5, 0xaa, 0x8d, 0xc5, 0xab, 0x8d, 0xc6, 0xaa, 0x8d, 0xc6, 0xab, 0x8d, 0xc4, 0xaa, 0x8e, 0xc4, 0xab, 0x8d, 0xc4, 0xab, 0x8e, 0xc4, 0xaa, 0x8c, 0xc4, 0xaa, 0x8c, 0xc5, 0xaa, 0x8d, 0xc3, 0xab, 0x8d, 0xc3, 0xaa, 0x8c, 0xc3, 0xaa, 0x8c, 0xc4, 0xab, 0x8c, 0xc3, 0xab, 0x8c, 0xc2, 0xab, 0x8b, 0xc2, 0xaa, 0x8b, 0xc3, 0xaa, 0x8b, 0xc3, 0xab, 0x8c, 0xc2, 0xaa, 0x8b, 0xc2, 0xab, 0x8b, 0xc1, 0xaa, 0x8b, 0xc1, 0xab, 0x8a, 0xc2, 0xaa, 0x8a, 0xc1, 0xaa, 0x8a, 0xc0, 0xaa, 0x8b, 0xc0, 0xaa, 0x8a, 0xc1, 0xa9, 0x8a, 0xc0, 0xa9, 0x89, 0xc0, 0xaa, 0x8a, 0xbf, 0xaa, 0x89, 0xbf, 0xaa, 0x89, 0xbf, 0xa9, 0x8a, 0xbf, 0xa9, 0x88, 0xbf, 0xaa, 0x89, 0xbe, 0xaa, 0x89, 0xbe, 0xa9, 0x88, 0xbf, 0xa9, 0x88, 0xbe, 0xa9, 0x88, 0xbe, 0xaa, 0x88, 0xbd, 0xaa, 0x88, 0xbd, 0xa9, 0x89, 0xbd, 0xaa, 0x89, 0xbd, 0xa9, 0x87, 0xbe, 0xa9, 0x87, 0xbd, 0xaa, 0x87, 0xbe, 0xaa, 0x87, 0xbd, 0xa9, 0x87, 0xbc, 0xaa, 0x88, 0xbc, 0xa9, 0x88, 0xbc, 0xaa, 0x87, 0xbc, 0xa9, 0x86, 0xbc, 0xa9, 0x87, 0xbb, 0xaa, 0x87, 0xbb, 0xa9, 0x86, 0xbb, 0xa9, 0x86, 0xbc, 0xaa, 0x86, 0xbb, 0xaa, 0x86, 0xba, 0xaa, 0x86, 0xba, 0xa9, 0x85, 0xba, 0xa9, 0x85, 0xbb, 0xaa, 0x85, 0xbb, 0xa9, 0x85, 0xba, 0xaa, 0x85, 0xb9, 0xa9, 0x86, 0xb9, 0xa9, 0x86, 0xb9, 0xaa, 0x85, 0xb9, 0xaa, 0x3e, 0xa0, 0x4f, 0x4f, 0x0, 0x0, 0x2, 0x3, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x2d, 0x4a, 0x7, 0x82, 0x4, 0x37, 0x12, 0x2, 0xa, 0x4a, 0xea, 0x9e, 0xbd, 0xff, 0x3f, 0xf5, 0x6c, 0x4f, 0x4b, 0xda, 0x58, 0x91, 0x24, 0x10, 0x20, 0xf8, 0x5d, 0x20, 0x81, 0x1f, 0xa8, 0x1f, 0xf6, 0x7, 0xf5, 0x7d, 0x0, 0x0, 0x3c, 0x0, 0x8, 0x14, 0x28, 0xb8, 0x41, 0x0, 0x50, 0x7b, 0x70, 0x63, 0xc, 0x9, 0x1c, 0xaa, 0x52, 0xeb, 0x4, 0x5b, 0xfb, 0x1d, 0x89, 0x68, 0xe1, 0x39, 0x9c, 0x7d, 0x68, 0x77, 0x60, 0x8c, 0x7a, 0x80, 0xe5, 0x6e, 0xdc, 0x41, 0x47, 0x51, 0x5f, 0xb8, 0xdd, 0xed, 0xa0, 0x9f, 0xc6, 0xad, 0xba, 0xee, 0xfb, 0xee, 0xde, 0x97, 0x74, 0x6f, 0x98, 0x82, 0x26, 0x51, 0x30, 0xa6, 0x3f, 0x10, 0xc8, 0xad, 0x13, 0xe4, 0x83, 0x0, 0x7a, 0x4, 0x4a, 0xd9, 0x5c, 0xde, 0xd2, 0x16, 0x15, 0xf2, 0xac, 0xac, 0x3e, 0x8f, 0x5a, 0x15, 0x3a, 0x5a, 0xe0, 0x6a, 0x1, 0xdc, 0xbd, 0x9b, 0x47, 0x44, 0xb6, 0x1e, 0x44, 0x5b, 0xe7, 0x84, 0x4b, 0xc3, 0xc, 0x1b, 0x51, 0xeb, 0xaa, 0x54, 0xc7, 0xb3, 0xeb, 0xca, 0x1a, 0x36, 0xbc, 0x76, 0x64, 0x4a, 0x62, 0x54, 0x3c, 0xe7, 0xf4, 0xc0, 0xd4, 0x1b, 0xda, 0x48, 0xa4, 0xc1, 0xd7, 0x98, 0xc2, 0xac, 0x19, 0x76, 0xd7, 0x44, 0x80, 0x33, 0xfe, 0xcb, 0x2b, 0x97, 0x6d, 0x8e, 0x2, 0x36, 0x4a, 0xfd, 0xc, 0xef, 0xb1, 0x35, 0x76, 0x63, 0x62, 0xc6, 0x5b, 0x38, 0xd3, 0x7e, 0x0, 0x7b, 0xfe, 0xef, 0x48, 0x93, 0xff, 0xdc, 0x3d, 0xed, 0xf4, 0xc4, 0xc4, 0x99, 0x93, 0xd3, 0x68, 0x5c, 0x9e, 0xee, 0x87, 0x16, 0xca, 0x1f, 0xbb, 0x80, 0x9e, 0xa0, 0x5, 0x47, 0x0, 0xbc, 0x5f, 0x7, 0x79, 0xa9, 0x3, 0x99, 0x80, 0x8f, 0xa7, 0x16, 0x68, 0xcd, 0x3a, 0x3d, 0x32, 0x60, 0xc0, 0x6f, 0xd8, 0x2b, 0x8a, 0xe1, 0x20, 0xf, 0xe3, 0x4, 0xcf, 0xfa, 0x1a, 0xb, 0x90, 0xfb, 0x2a, 0x57, 0x4a, 0xa8, 0xc9, 0xb7, 0x91, 0x37, 0x82, 0xf7, 0x4c, 0xd2, 0xa9, 0x9a, 0x78, 0x8f, 0x53, 0x22, 0x40, 0xfd, 0x44, 0x5c, 0xdf, 0xeb, 0x89, 0x4a, 0xc9, 0x0, 0xe7, 0x74, 0x1, 0x28, 0xff, 0xa6, 0x7e, 0xa, 0x4, 0xe9, 0x43, 0xe2, 0x7, 0x7d, 0xaf, 0x9, 0xca, 0x61, 0x3, 0x88, 0x13, 0x20, 0xab, 0x50, 0x1e, 0x41, 0xbc, 0x68, 0x3, 0xd5, 0xc2, 0x69, 0xa4, 0x5b, 0x22, 0xbb, 0x35, 0xdc, 0x45, 0x72, 0x94, 0xba, 0x85, 0xee, 0x82, 0x54, 0x0, 0xcb, 0x45, 0xa4, 0x78, 0xa, 0xc0, 0x28, 0x1c, 0x91, 0x46, 0x50, 0xa3, 0x34, 0xcb, 0x63, 0xfa, 0xeb, 0xb8, 0xd8, 0x5e, 0x9e, 0xde, 0x2b, 0xa3, 0xda, 0x35, 0xd3, 0x62, 0xc4, 0x8e, 0xca, 0x39, 0xf0, 0xb1, 0x7a, 0xd6, 0x69, 0x5f, 0x5, 0xa1, 0xa4, 0xa3, 0x3a, 0xdf, 0x8, 0xd8, 0xcf, 0x62, 0xf7, 0x14, 0x4f, 0x5a, 0x87, 0xa5, 0xc1, 0x22, 0x51, 0xe2, 0xd5, 0x9a, 0xc1, 0x1c, 0x37, 0x5e, 0xd6, 0x7f, 0xed, 0xfb, 0x41, 0x5e, 0xb7, 0xf, 0x7c, 0xe3, 0xba, 0x7b, 0xd0, 0xa5, 0x3a, 0xb3, 0x8c, 0xd7, 0x2e, 0x4e, 0xd7, 0xba, 0xbb, 0xd7, 0xc6, 0xb, 0x8d, 0x17, 0x1f, 0xe3, 0x46, 0x9, 0x49, 0xa1, 0x8c, 0x13, 0x63, 0x4c, 0xa6, 0xfa, 0x2a, 0x8c, 0x38, 0x88, 0x6a, 0xc9, 0x32, 0x4c, 0x1b, 0xa3, 0x44, 0x43, 0xd9, 0x55, 0xdb, 0xce, 0xc1, 0xe9, 0x92, 0x2f, 0x4a, 0x25, 0x59, 0x36, 0x52, 0x52, 0x41, 0xc4, 0x16, 0x2, 0x41, 0x32, 0x7a, 0x73, 0x4b, 0x21, 0xb, 0x8, 0x57, 0x89, 0xc2, 0x90, 0x65, 0xa8, 0xdc, 0x46, 0x56, 0x14, 0x15, 0x8e, 0xc1, 0x20, 0xd7, 0xcc, 0x40, 0x76, 0x42, 0x3a, 0x83, 0xf, 0x83, 0x46, 0xf5, 0x27, 0xa7, 0x80, 0x7e, 0xcf, 0xd2, 0x74, 0xd0, 0x78, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char base_green_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x20, 0x1, 0x3, 0x0, 0x0, 0x0, 0x49, 0xb4, 0xe8, 0xb7, 0x0, 0x0, 0x0, 0x6, 0x50, 0x4c, 0x54, 0x45, 0x90, 0xc9, 0xab, 0xff, 0xff, 0xff, 0xc6, 0xd0, 0x9d, 0x30, 0x0, 0x0, 0x0, 0xb, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x18, 0xe4, 0x0, 0x0, 0x0, 0xa0, 0x0, 0x1, 0xf3, 0xdb, 0xea, 0x79, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char button_disabled_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0xc7, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x6c, 0xd0, 0x81, 0x66, 0x43, 0x31, 0x14, 0x87, 0xf1, 0xf, 0x5, 0x17, 0xb8, 0x28, 0x2e, 0x8, 0x71, 0xf3, 0x6, 0x19, 0xb6, 0xb9, 0xcb, 0xac, 0x95, 0xa4, 0xb7, 0xad, 0x6a, 0xd5, 0x68, 0x5f, 0xe4, 0x3e, 0x76, 0x1e, 0xe1, 0xbf, 0x21, 0xa6, 0xab, 0xf8, 0x1, 0x7c, 0x9c, 0x73, 0xe, 0xac, 0xe8, 0xe8, 0x19, 0x30, 0x58, 0xc6, 0xca, 0x62, 0x18, 0xe8, 0xe9, 0x58, 0x41, 0xc7, 0x1a, 0x87, 0x27, 0x10, 0x49, 0xe4, 0x5f, 0x89, 0x48, 0xc0, 0xe3, 0x58, 0xd3, 0x41, 0x8f, 0xb, 0xcb, 0xbd, 0x7c, 0xeb, 0xbf, 0x7b, 0x9, 0xb, 0x8e, 0x1e, 0x6, 0xfc, 0xad, 0x64, 0x6d, 0xb5, 0x79, 0xb0, 0x55, 0xd6, 0xad, 0xe0, 0x19, 0xc0, 0x10, 0xae, 0xda, 0x34, 0x5c, 0x45, 0xc0, 0x80, 0x25, 0x5e, 0xf4, 0xd5, 0x70, 0x11, 0x11, 0xb, 0x23, 0xe9, 0xac, 0xcf, 0x86, 0xb3, 0x48, 0x8c, 0x30, 0x92, 0x4f, 0xa, 0xd, 0x27, 0x91, 0x6b, 0x70, 0xd4, 0x47, 0xc3, 0xf1, 0x2f, 0x48, 0x7, 0x4d, 0xd, 0x87, 0x3a, 0xc2, 0x12, 0x67, 0xbd, 0x37, 0xcc, 0x75, 0x49, 0x43, 0xd8, 0xe9, 0xad, 0x61, 0x57, 0xcf, 0x1c, 0xf0, 0xfb, 0x32, 0xe9, 0xf5, 0xc9, 0xa4, 0x7d, 0x7d, 0x54, 0x8f, 0x7b, 0x59, 0xe6, 0x92, 0x14, 0x1f, 0x24, 0xcd, 0x3f, 0x7b, 0x6b, 0xa, 0xe, 0x6a, 0x82, 0x91, 0x45, 0x30, 0xba, 0x1, 0x4a, 0x51, 0xc4, 0x35, 0x1f, 0xe5, 0xa1, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -62,30 +54,18 @@ static const unsigned char color_picker_sample_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x47, 0x29, 0xbc, 0x83, 0x0, 0x0, 0x0, 0x3c, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0xd5, 0x21, 0x11, 0x0, 0x30, 0xc, 0x4, 0xc1, 0xfa, 0x57, 0x53, 0x87, 0xed, 0x4, 0x45, 0xc4, 0xed, 0xa3, 0xc3, 0x4b, 0xfe, 0xbc, 0xd9, 0x9d, 0x35, 0x2b, 0xe, 0x0, 0x0, 0x0, 0x80, 0xed, 0x66, 0xc5, 0x1, 0x0, 0x0, 0x0, 0xe0, 0x6, 0x1, 0x0, 0x0, 0x90, 0x6, 0x70, 0x83, 0x0, 0x0, 0x0, 0x28, 0x3, 0x7c, 0x54, 0x93, 0xd6, 0xf1, 0xd1, 0x16, 0x8a, 0x17, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char dosfont_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x1, 0x0, 0x0, 0x0, 0x0, 0xeb, 0x45, 0x5c, 0x66, 0x0, 0x0, 0x0, 0x2, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x0, 0x76, 0x93, 0xcd, 0x38, 0x0, 0x0, 0x2, 0x64, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xbc, 0xd4, 0x81, 0x86, 0x2c, 0x47, 0x14, 0xc6, 0xf1, 0xcf, 0x45, 0x81, 0x32, 0x2, 0x38, 0x58, 0x17, 0xe4, 0x1, 0xa, 0x44, 0x8b, 0x7a, 0x98, 0xb2, 0xe2, 0xb8, 0x28, 0x2c, 0x68, 0x8d, 0x63, 0x5c, 0xb0, 0xef, 0x90, 0xb7, 0xe9, 0x6c, 0x71, 0x40, 0x9, 0x20, 0xd0, 0x63, 0x2d, 0x98, 0x0, 0xc, 0x88, 0x60, 0x54, 0xa7, 0xaa, 0xba, 0xef, 0x66, 0x67, 0x5d, 0x10, 0x37, 0xf7, 0xf, 0xc3, 0x4f, 0x39, 0x53, 0x87, 0xd2, 0xf8, 0x5a, 0x84, 0x9b, 0xb8, 0x81, 0xc3, 0x6b, 0xc4, 0x90, 0x1, 0xce, 0xee, 0xe4, 0xe1, 0x39, 0x6a, 0x84, 0x23, 0xda, 0x80, 0xe1, 0x7f, 0x8c, 0x4f, 0xf1, 0x29, 0x38, 0x8b, 0xd6, 0x87, 0x4, 0x8f, 0x32, 0xf, 0xa, 0x67, 0x99, 0x2a, 0x98, 0x4, 0x42, 0x94, 0xd1, 0x56, 0xf0, 0xd, 0xec, 0xd2, 0xc0, 0x9c, 0xa8, 0xc2, 0x7a, 0x44, 0x1, 0x6d, 0x90, 0xdd, 0x97, 0x13, 0x2e, 0x1, 0x28, 0x8f, 0x39, 0xf4, 0x19, 0x55, 0x42, 0x9f, 0xa1, 0x59, 0x41, 0x4, 0x10, 0x68, 0xe6, 0x6d, 0xe8, 0x23, 0xac, 0xeb, 0xf0, 0xd9, 0xbf, 0xdd, 0xc5, 0xdd, 0x2c, 0xf7, 0x4d, 0x23, 0x11, 0x5b, 0x86, 0x22, 0x82, 0x96, 0x0, 0x83, 0xea, 0xdd, 0x1c, 0x54, 0x15, 0x30, 0x8, 0x2a, 0x98, 0x8c, 0xf1, 0xf3, 0x6c, 0x54, 0x89, 0x2c, 0x5c, 0x75, 0xb2, 0x26, 0xee, 0x40, 0x47, 0xb2, 0x15, 0xc8, 0xe7, 0xeb, 0xd5, 0xca, 0x11, 0x70, 0xb0, 0xf4, 0xc, 0x72, 0xa6, 0x18, 0x25, 0x35, 0x40, 0x80, 0x69, 0x10, 0x8c, 0x35, 0xea, 0x1a, 0xb8, 0xa3, 0x6d, 0x30, 0xef, 0x40, 0x44, 0x20, 0x9c, 0x40, 0xaa, 0x56, 0x2b, 0xd8, 0xfe, 0x2f, 0x34, 0xe3, 0x58, 0xe4, 0xa3, 0x88, 0x93, 0x9, 0xce, 0x20, 0x90, 0xe0, 0xfb, 0xf4, 0xc3, 0xd5, 0xff, 0x5d, 0x8a, 0x57, 0xff, 0xf1, 0x7c, 0x49, 0x2a, 0x57, 0xc, 0xcc, 0x91, 0x99, 0x95, 0x2c, 0x87, 0x3f, 0xcf, 0xca, 0x88, 0xfc, 0xc4, 0xf7, 0xf7, 0x4f, 0x1d, 0xd6, 0xbf, 0x2a, 0x28, 0xcf, 0xfc, 0xe9, 0xd3, 0x5c, 0x21, 0x86, 0xb5, 0x34, 0x90, 0x99, 0xa7, 0x69, 0x2e, 0x64, 0xa7, 0xb0, 0x3c, 0xab, 0xa0, 0xf4, 0x13, 0xcf, 0xda, 0x20, 0xfd, 0xae, 0x1, 0x5a, 0x21, 0x4, 0x55, 0xca, 0x31, 0x24, 0x6d, 0xd0, 0x86, 0x76, 0xe0, 0xfb, 0x1d, 0x38, 0x72, 0xe0, 0x6, 0xbc, 0x41, 0xbb, 0xd8, 0xe5, 0x67, 0xf5, 0xd3, 0xb9, 0x24, 0x95, 0xcb, 0xff, 0xb0, 0x3a, 0xdc, 0x2d, 0xc, 0x15, 0xe4, 0x2a, 0xab, 0xa6, 0xda, 0xea, 0xe1, 0x23, 0x8, 0xca, 0xba, 0x74, 0x48, 0xee, 0xb3, 0x55, 0xa0, 0xc1, 0xaf, 0x15, 0x38, 0x3d, 0xba, 0xd9, 0xa2, 0x43, 0xa0, 0xa, 0x9e, 0xad, 0x7b, 0xd9, 0x40, 0x86, 0x6, 0xe4, 0xc9, 0x3d, 0x6c, 0x10, 0x6d, 0x85, 0x85, 0xc8, 0xb9, 0x61, 0x3, 0x36, 0xd, 0x3c, 0x5, 0x77, 0xd7, 0xe1, 0xf, 0x56, 0x52, 0x5e, 0x99, 0x7e, 0x73, 0x87, 0xe, 0xcf, 0xd, 0x3c, 0x27, 0x4a, 0xce, 0x74, 0x90, 0xb3, 0x78, 0x21, 0x4e, 0x7e, 0xf5, 0x1f, 0x7c, 0x83, 0xaa, 0xb7, 0x1d, 0xf0, 0xb6, 0x15, 0xdf, 0xa6, 0x17, 0xdc, 0x61, 0xc0, 0xb, 0x99, 0x97, 0x61, 0x83, 0x0, 0x8b, 0x88, 0x40, 0x26, 0xd0, 0xbf, 0xf0, 0xb, 0x2, 0xb0, 0xc3, 0x34, 0x89, 0x97, 0x30, 0xc6, 0xe1, 0xc0, 0x74, 0x3e, 0xc9, 0x9, 0x36, 0x6a, 0xd4, 0x4b, 0x1e, 0xc9, 0x44, 0x5a, 0x59, 0x19, 0xc2, 0x73, 0x2e, 0x21, 0x33, 0x99, 0x89, 0x96, 0x6, 0xb9, 0xc2, 0xfc, 0x75, 0x88, 0x5f, 0x40, 0xb3, 0x76, 0xe0, 0xd, 0x46, 0x91, 0x51, 0x2e, 0x72, 0xf6, 0xe6, 0xec, 0x17, 0x16, 0xc1, 0x96, 0x5, 0x18, 0xad, 0xb0, 0x43, 0x7e, 0xf, 0x13, 0xe0, 0xd1, 0xba, 0xfc, 0xe7, 0x77, 0xd3, 0x3b, 0xd0, 0xc3, 0x78, 0xf3, 0xb9, 0x22, 0xa2, 0xd3, 0x7b, 0x40, 0x6d, 0x3c, 0xc9, 0x35, 0xa7, 0x94, 0xb4, 0xec, 0x90, 0x47, 0x75, 0x15, 0x58, 0xf3, 0x3, 0xe, 0x1d, 0x38, 0x34, 0xf0, 0x4a, 0xb5, 0xe, 0x61, 0xa8, 0xb0, 0x90, 0xba, 0x57, 0xb0, 0x2e, 0xf3, 0xe2, 0x35, 0xbc, 0x82, 0xfe, 0x94, 0xfd, 0xca, 0xaa, 0x3b, 0xc8, 0x45, 0xee, 0x46, 0xe2, 0x74, 0x2c, 0xf5, 0x62, 0x6f, 0xdf, 0x87, 0xc1, 0x9e, 0x7d, 0xf7, 0xfb, 0xcf, 0x18, 0xe4, 0xa0, 0xf4, 0xf, 0x22, 0x3c, 0x3d, 0xa, 0x46, 0x1, 0x0, 0x24, 0x3a, 0x65, 0x42, 0x42, 0xc7, 0x4f, 0x7c, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char dropdown_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x4, 0x0, 0x0, 0x0, 0x6e, 0x6, 0x76, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x60, 0xf8, 0xc0, 0xcc, 0x0, 0x2, 0x60, 0x16, 0x98, 0x78, 0x67, 0x8, 0x81, 0x6f, 0x4d, 0xde, 0x9a, 0x0, 0x5, 0xde, 0x3a, 0x3d, 0xfc, 0x8f, 0x80, 0xaf, 0xba, 0x18, 0xde, 0x29, 0x2, 0x19, 0xbf, 0x61, 0x2, 0x6f, 0x62, 0x18, 0x3e, 0xb0, 0xbd, 0x97, 0x4, 0x32, 0xff, 0x80, 0xb9, 0xb1, 0x20, 0x93, 0xc0, 0x42, 0x8, 0x2e, 0x54, 0xe8, 0x9d, 0xdc, 0x9b, 0x54, 0x10, 0xb, 0x21, 0xc4, 0x4, 0x63, 0x1, 0x0, 0x86, 0x1f, 0x3b, 0x1e, 0x92, 0x22, 0x3f, 0x40, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; static const unsigned char error_icon_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x20, 0x8, 0x4, 0x0, 0x0, 0x0, 0xd9, 0x73, 0xb2, 0x7f, 0x0, 0x0, 0x0, 0x36, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x36, 0x18, 0x5, 0xa3, 0x60, 0x14, 0xfc, 0x87, 0x40, 0x38, 0xb, 0x21, 0x6, 0x6, 0x18, 0x62, 0x98, 0x6, 0xa0, 0xb1, 0xfe, 0xe3, 0x67, 0xd1, 0xc2, 0x0, 0x10, 0xc4, 0xc5, 0x82, 0x91, 0x43, 0xc0, 0xb, 0xb8, 0x15, 0x63, 0x78, 0x6, 0x5, 0x8c, 0x82, 0x51, 0x30, 0xa, 0x0, 0x35, 0xa3, 0x4c, 0xb4, 0x7c, 0x8a, 0x7, 0x6, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x2, 0x62, 0x4b, 0x47, 0x44, 0x0, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d, 0x45, 0x7, 0xe2, 0xb, 0xf, 0x0, 0x22, 0x18, 0xc, 0x35, 0xef, 0x18, 0x0, 0x0, 0x0, 0xe, 0x49, 0x44, 0x41, 0x54, 0x28, 0xcf, 0x63, 0x60, 0x18, 0x5, 0xa3, 0x0, 0x1, 0x0, 0x2, 0x10, 0x0, 0x1, 0x14, 0xc2, 0xc0, 0x92, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; static const unsigned char focus_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc, 0x4, 0x3, 0x0, 0x0, 0x0, 0xa4, 0x5b, 0x41, 0xd4, 0x0, 0x0, 0x0, 0x30, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0xff, 0xff, 0xff, 0xb9, 0xa2, 0x9b, 0xc9, 0x0, 0x0, 0x0, 0xf, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xe, 0x39, 0x68, 0x7a, 0x7b, 0x3a, 0x74, 0x10, 0x8, 0x69, 0xf, 0x6, 0x75, 0x11, 0xb8, 0x16, 0x0, 0x1, 0x0, 0x0, 0x0, 0x38, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x10, 0x32, 0x9, 0xd, 0x75, 0x56, 0x64, 0x48, 0xef, 0x9c, 0x39, 0x73, 0x46, 0x19, 0xc3, 0x6a, 0x6, 0x20, 0xd8, 0xc5, 0x10, 0x3, 0xa2, 0x8e, 0x32, 0x44, 0x82, 0xa8, 0xa9, 0xd8, 0x29, 0xa8, 0x12, 0xb0, 0x6, 0x29, 0x86, 0xdc, 0x9d, 0x33, 0x67, 0xce, 0x2b, 0x63, 0x10, 0x3, 0x1b, 0x6, 0x0, 0xdf, 0xc6, 0x11, 0x6d, 0xb8, 0xf4, 0x9c, 0xac, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char frame_focus_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc, 0x4, 0x3, 0x0, 0x0, 0x0, 0xa4, 0x5b, 0x41, 0xd4, 0x0, 0x0, 0x0, 0x30, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0x47, 0x8c, 0xbf, 0xff, 0xff, 0xff, 0xcc, 0x40, 0x27, 0xb9, 0x0, 0x0, 0x0, 0xf, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xe, 0x39, 0x68, 0x7a, 0x7b, 0x3a, 0x74, 0x10, 0x8, 0x69, 0xf, 0x6, 0x75, 0x11, 0xb8, 0x16, 0x0, 0x1, 0x0, 0x0, 0x0, 0x38, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x10, 0x32, 0x9, 0xd, 0x75, 0x56, 0x64, 0x48, 0xef, 0x9c, 0x39, 0x73, 0x46, 0x19, 0xc3, 0x6a, 0x6, 0x20, 0xd8, 0xc5, 0x10, 0x3, 0xa2, 0x8e, 0x32, 0x44, 0x82, 0xa8, 0xa9, 0xd8, 0x29, 0xa8, 0x12, 0xb0, 0x6, 0x29, 0x86, 0xdc, 0x9d, 0x33, 0x67, 0xce, 0x2b, 0x63, 0x10, 0x3, 0x1b, 0x6, 0x0, 0xdf, 0xc6, 0x11, 0x6d, 0xb8, 0xf4, 0x9c, 0xac, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char full_panel_bg_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x20, 0x4, 0x3, 0x0, 0x0, 0x0, 0x81, 0x54, 0x67, 0xc7, 0x0, 0x0, 0x0, 0x30, 0x50, 0x4c, 0x54, 0x45, 0x27, 0x27, 0x29, 0x26, 0x26, 0x28, 0x25, 0x25, 0x27, 0x24, 0x24, 0x26, 0x23, 0x23, 0x25, 0x22, 0x22, 0x24, 0x21, 0x21, 0x23, 0x1e, 0x1e, 0x20, 0x1d, 0x1d, 0x1f, 0x1c, 0x1c, 0x1e, 0x31, 0x30, 0x32, 0x50, 0x4e, 0x54, 0x4e, 0x4c, 0x50, 0x4c, 0x4a, 0x4e, 0x3d, 0x3b, 0x3f, 0x38, 0x36, 0x3a, 0xb3, 0xde, 0x6f, 0x4d, 0x0, 0x0, 0x0, 0x5a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x60, 0x14, 0x52, 0x82, 0x3, 0x45, 0x1, 0x20, 0x5f, 0xd9, 0x35, 0xd, 0xe, 0x42, 0x8c, 0x4, 0x18, 0x98, 0xcc, 0x2a, 0x66, 0xc2, 0x41, 0x7b, 0xb2, 0x2, 0x83, 0x70, 0xd6, 0x9e, 0xbb, 0x70, 0x70, 0x7a, 0x99, 0x21, 0x83, 0x48, 0xf5, 0xfb, 0xff, 0x70, 0xf0, 0x6f, 0xbb, 0x23, 0x83, 0x6a, 0xcf, 0x7f, 0x24, 0x70, 0x22, 0x88, 0x41, 0x6d, 0x2e, 0xb2, 0xc0, 0xcd, 0x24, 0x8a, 0x5, 0x46, 0x5, 0x30, 0x2, 0x19, 0x23, 0x1a, 0x30, 0x22, 0xa, 0x23, 0x2a, 0x31, 0x22, 0x1b, 0x23, 0x39, 0x0, 0x0, 0x8c, 0xb1, 0x80, 0xd2, 0x41, 0x59, 0x8c, 0x74, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char graph_node_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x40, 0x8, 0x6, 0x0, 0x0, 0x0, 0x13, 0x7d, 0xf7, 0x96, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d, 0x45, 0x7, 0xe0, 0x8, 0x17, 0xd, 0x5, 0x12, 0xa1, 0x38, 0x83, 0x9b, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x0, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57, 0x81, 0xe, 0x17, 0x0, 0x0, 0x2, 0x74, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x97, 0x3d, 0x6f, 0xd3, 0x60, 0x10, 0xc7, 0x7f, 0x17, 0x9b, 0x26, 0x25, 0x22, 0xad, 0xa, 0x8, 0xf1, 0x52, 0x75, 0x0, 0x16, 0x24, 0x90, 0x2a, 0x96, 0x7c, 0x1, 0x6, 0xc4, 0xce, 0xc4, 0x17, 0x0, 0x31, 0xb0, 0x30, 0x0, 0x23, 0x82, 0x85, 0x5, 0x9, 0x4, 0x5f, 0x80, 0x89, 0x1d, 0x31, 0xf0, 0x5, 0x58, 0x50, 0x25, 0x50, 0x59, 0x80, 0xa1, 0xe2, 0xad, 0x28, 0x34, 0x4a, 0x3, 0x25, 0x76, 0xea, 0xe7, 0x39, 0x6, 0x3f, 0x76, 0x6d, 0xc7, 0x49, 0x5f, 0xd8, 0x90, 0x6f, 0xb1, 0xf5, 0xe4, 0xb9, 0xdf, 0xdd, 0xfd, 0xef, 0x22, 0xf9, 0x84, 0x2d, 0x13, 0xa0, 0x6, 0x78, 0xee, 0x29, 0xe4, 0x4d, 0x1, 0xb, 0x18, 0xf7, 0x54, 0x32, 0x97, 0x6a, 0xc0, 0x7e, 0x60, 0xe, 0x38, 0xc, 0xb4, 0x80, 0x7d, 0x5, 0xc0, 0x26, 0xd0, 0x7, 0x3a, 0x40, 0x17, 0xf8, 0x3, 0xd8, 0x24, 0x6a, 0x13, 0x38, 0x35, 0x73, 0x60, 0xf6, 0x6a, 0xa3, 0xde, 0xb8, 0x38, 0x35, 0x55, 0x3f, 0x41, 0x89, 0xd, 0x87, 0xe1, 0x97, 0x20, 0xc, 0x5e, 0xae, 0xff, 0xea, 0x3d, 0x5, 0x3e, 0x2, 0x1b, 0xe2, 0x22, 0x2d, 0xcc, 0xcd, 0x1e, 0x7c, 0x78, 0x72, 0xe1, 0xf4, 0xa5, 0x7b, 0xb7, 0x1f, 0x7c, 0x9e, 0x6e, 0x35, 0xca, 0xfc, 0x19, 0xf4, 0x3, 0xee, 0xdc, 0xbf, 0x39, 0xff, 0x69, 0xe5, 0xc3, 0x8b, 0x6e, 0x6f, 0xed, 0x6, 0xb0, 0x22, 0x40, 0x3, 0x58, 0x3c, 0x7a, 0xe4, 0xf8, 0xab, 0x67, 0x4f, 0x9e, 0x77, 0xa3, 0xc8, 0x12, 0xd, 0xa3, 0x52, 0x80, 0x3f, 0xe5, 0xe3, 0xfb, 0x35, 0xae, 0x5c, 0xbb, 0x3c, 0xf7, 0xfd, 0xc7, 0xd7, 0xb, 0xc0, 0x92, 0xef, 0x74, 0x68, 0xfa, 0x9e, 0xdf, 0x1c, 0xfc, 0xe, 0xbb, 0x88, 0xc6, 0x47, 0xa, 0x2a, 0x59, 0x85, 0x95, 0x61, 0xb8, 0xc9, 0x30, 0x4, 0xdf, 0xf3, 0x9b, 0xae, 0x6c, 0x49, 0x0, 0x1e, 0x80, 0x51, 0x1b, 0xfb, 0xc7, 0x2, 0xc7, 0x5a, 0x3b, 0x88, 0x66, 0xcf, 0x63, 0xf3, 0x12, 0x80, 0x26, 0xbf, 0xa8, 0xb5, 0xa8, 0xa, 0x88, 0x22, 0x8, 0x8a, 0x22, 0x2a, 0x19, 0x37, 0x1d, 0xe9, 0xad, 0x9f, 0x6b, 0xb4, 0x55, 0x14, 0x5, 0x55, 0x44, 0xe2, 0x32, 0x6c, 0xe2, 0x24, 0x71, 0x18, 0x9d, 0x4, 0xb0, 0x6a, 0xd3, 0x1b, 0x2a, 0xe9, 0x4b, 0xfc, 0xae, 0xe4, 0x45, 0x19, 0x9f, 0x81, 0x2b, 0xdb, 0x15, 0xa6, 0xa2, 0x19, 0x2d, 0xec, 0x8, 0x24, 0x7, 0x30, 0xd6, 0x16, 0xf8, 0x82, 0xa8, 0xc6, 0x45, 0x68, 0x52, 0xbf, 0x4e, 0xce, 0x60, 0xeb, 0x42, 0x5c, 0xb4, 0x66, 0x9c, 0x6c, 0xc9, 0x1f, 0xa4, 0x0, 0xb0, 0x39, 0xc5, 0xc9, 0xa1, 0x28, 0x89, 0x5f, 0x14, 0xd1, 0x5a, 0x37, 0x32, 0x89, 0xf2, 0x82, 0xc6, 0x3, 0xe0, 0xda, 0xa, 0x22, 0x3a, 0xa9, 0xb, 0x9a, 0x26, 0xaa, 0xf1, 0x41, 0x9a, 0x42, 0x2, 0xb2, 0xb2, 0x6d, 0x6, 0x5b, 0xa3, 0xab, 0x23, 0xd5, 0x48, 0x32, 0x92, 0xe3, 0x33, 0x10, 0x37, 0xb7, 0x2a, 0xea, 0xee, 0x4a, 0x2a, 0x42, 0xe, 0x5a, 0x2a, 0xa2, 0x51, 0x37, 0x40, 0x85, 0x48, 0x96, 0xb4, 0x23, 0xdb, 0x88, 0x68, 0x5c, 0xc4, 0xcc, 0x3c, 0x14, 0xa7, 0x6f, 0xa2, 0x6, 0xaa, 0xb1, 0x7c, 0xd9, 0x30, 0xaa, 0x79, 0xc7, 0x49, 0x93, 0x78, 0xf7, 0xd1, 0xad, 0x79, 0x76, 0x69, 0x29, 0x20, 0xda, 0x34, 0x2c, 0x9e, 0x3d, 0xff, 0x7a, 0x27, 0x4e, 0x4b, 0xef, 0xde, 0xb4, 0x4b, 0x33, 0x58, 0xef, 0xf7, 0x76, 0x9b, 0x0, 0x35, 0xfe, 0xd1, 0x2a, 0x40, 0x5, 0xa8, 0x0, 0x15, 0xa0, 0x2, 0x54, 0x80, 0xa, 0xf0, 0x5f, 0x2, 0xa4, 0xe4, 0x13, 0x78, 0xd7, 0x19, 0xd8, 0x3d, 0xf8, 0xda, 0x4, 0x60, 0x81, 0xc0, 0x18, 0x13, 0xda, 0x68, 0x7, 0x5e, 0x11, 0x18, 0x63, 0x42, 0x20, 0x0, 0x6c, 0xcd, 0xad, 0xb2, 0x6b, 0x41, 0x38, 0x58, 0xee, 0x74, 0x57, 0xdb, 0x93, 0x20, 0x36, 0x82, 0x4e, 0x77, 0xb5, 0x1d, 0x84, 0x83, 0x65, 0x60, 0xd, 0x30, 0xc9, 0xe6, 0x3a, 0x3, 0x9c, 0x6b, 0x35, 0x67, 0x1f, 0x37, 0xea, 0xd3, 0x67, 0x3c, 0xcf, 0x2b, 0x15, 0xd7, 0x18, 0x63, 0x83, 0x70, 0xf0, 0xbe, 0xbf, 0xd1, 0xbb, 0xe, 0xbc, 0x5, 0xd6, 0x25, 0xb3, 0xc2, 0xb5, 0x80, 0x63, 0xc0, 0x21, 0xa0, 0x3e, 0x66, 0xf9, 0xe, 0x81, 0x9f, 0xc0, 0x37, 0xb7, 0x47, 0x1b, 0x29, 0x8, 0xea, 0x27, 0xfb, 0xe0, 0x98, 0x2a, 0xd4, 0x95, 0x1c, 0xed, 0x51, 0xf8, 0x51, 0xfb, 0xb, 0x1, 0xbe, 0x20, 0x9f, 0x90, 0x81, 0x17, 0xaa, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -178,10 +158,6 @@ static const unsigned char icon_parent_folder_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x68, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x33, 0xb8, 0x27, 0xfe, 0xe0, 0xfc, 0x83, 0x73, 0xf7, 0xc4, 0x71, 0x48, 0xdf, 0x11, 0x7b, 0x78, 0xe9, 0xc1, 0x3f, 0x20, 0xbc, 0xfe, 0x40, 0x12, 0x8f, 0x34, 0x4c, 0x9, 0xa6, 0xe1, 0x57, 0x80, 0x12, 0x17, 0x81, 0xf8, 0x2f, 0x58, 0xe1, 0x15, 0x34, 0x8b, 0x1e, 0x9c, 0x5, 0xa, 0x5e, 0xb8, 0x23, 0x6, 0x52, 0x70, 0x5b, 0x14, 0xac, 0xf0, 0xc, 0xaa, 0x82, 0x7d, 0xf, 0x8e, 0xde, 0x14, 0xf9, 0xcf, 0x8, 0x52, 0xc0, 0xc0, 0x70, 0x5b, 0xf4, 0xe1, 0xc9, 0x7, 0x47, 0xb1, 0xb8, 0x3, 0xaa, 0x0, 0xa, 0x48, 0x52, 0x80, 0xb0, 0xea, 0xc8, 0xc3, 0x83, 0xc, 0x83, 0xe, 0x0, 0x0, 0xb8, 0x27, 0x55, 0x4c, 0xbe, 0xc0, 0xd2, 0xac, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char icon_play_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x41, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x1b, 0x78, 0x70, 0xf8, 0xc1, 0xb5, 0x7, 0xde, 0xf8, 0x14, 0xfc, 0x7, 0xc1, 0x87, 0x3b, 0x1e, 0x6a, 0xe1, 0x54, 0x0, 0x85, 0xbf, 0x1f, 0x4c, 0x79, 0x22, 0x8c, 0x5d, 0x1, 0x2, 0xbe, 0x7f, 0x58, 0x7e, 0x9b, 0x1d, 0x43, 0x1, 0x1a, 0x3c, 0x4c, 0x91, 0x82, 0x77, 0x8, 0x2b, 0x8, 0x3b, 0x12, 0xd3, 0x9b, 0x84, 0x3, 0x8a, 0xfe, 0x0, 0x0, 0xa4, 0x15, 0x70, 0xca, 0x48, 0x40, 0x6f, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char icon_reload_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0xb1, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xad, 0x50, 0x35, 0xba, 0xc2, 0x40, 0x10, 0x7e, 0xcd, 0x33, 0x5c, 0xca, 0x1c, 0x21, 0x87, 0xc1, 0xa9, 0xd3, 0x23, 0x47, 0xca, 0x69, 0xb0, 0x6, 0x77, 0xdb, 0xfd, 0x17, 0x2f, 0x91, 0x3e, 0xee, 0xd2, 0xc1, 0x4e, 0xb5, 0xdf, 0xcc, 0xaf, 0x5f, 0x9f, 0x7c, 0xdb, 0x5f, 0xda, 0x44, 0x17, 0x2f, 0x75, 0xba, 0xa4, 0xb1, 0xfd, 0xf5, 0xad, 0x8f, 0x1c, 0x86, 0x90, 0x5c, 0x33, 0x38, 0x72, 0x1e, 0xb4, 0xbe, 0x66, 0x28, 0xad, 0xe2, 0xab, 0x38, 0xcd, 0x63, 0xa9, 0x9d, 0xb8, 0x58, 0x68, 0x53, 0x5b, 0x1f, 0x33, 0xd6, 0x9f, 0xa5, 0xc1, 0x20, 0x91, 0xba, 0x7d, 0x80, 0x1e, 0x24, 0x94, 0xdc, 0x92, 0xa4, 0x2, 0x9, 0x1d, 0xe7, 0xe0, 0x9, 0x69, 0x15, 0xf7, 0x58, 0x4e, 0x40, 0xc2, 0xc3, 0x58, 0x8a, 0xb6, 0x31, 0xd1, 0x39, 0xd8, 0x27, 0xed, 0x83, 0x5b, 0x14, 0x33, 0x7d, 0x3d, 0xbb, 0x45, 0x5d, 0x12, 0x55, 0x97, 0x4, 0xe3, 0xb5, 0xf4, 0x8c, 0x77, 0xd6, 0xa7, 0x2c, 0x3b, 0x78, 0x4c, 0x52, 0x81, 0xa, 0x8e, 0x3a, 0xa9, 0x6a, 0x6b, 0xc, 0xe6, 0x3f, 0xa1, 0x8d, 0x86, 0x16, 0xe5, 0x39, 0x78, 0xa2, 0x4d, 0xea, 0xe, 0xfa, 0xdd, 0xa7, 0x0, 0x90, 0x4f, 0x8b, 0xd0, 0xe1, 0x9e, 0x1b, 0xc2, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -190,8 +166,8 @@ static const unsigned char icon_snap_grid_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x33, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xf3, 0xf3, 0xf3, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0x85, 0x85, 0xff, 0x83, 0x83, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0x80, 0x80, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0x84, 0x84, 0xff, 0xff, 0xff, 0xa, 0xa5, 0x43, 0x1, 0x0, 0x0, 0x0, 0x10, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xff, 0x1d, 0xac, 0xf2, 0xaf, 0x27, 0xed, 0xff, 0xee, 0xb4, 0x1b, 0x1c, 0xb6, 0xaa, 0xf1, 0x50, 0xa6, 0xdd, 0x5f, 0x0, 0x0, 0x0, 0x4e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x62, 0x0, 0x2, 0x40, 0x23, 0xd3, 0x60, 0x0, 0x40, 0xc, 0x3, 0xaf, 0x76, 0x93, 0xfd, 0x97, 0x7d, 0x9b, 0x55, 0x70, 0x12, 0x62, 0xaf, 0x68, 0x0, 0xc4, 0xe9, 0x3c, 0x1, 0x67, 0xf7, 0x17, 0x20, 0x95, 0xd6, 0xc6, 0xee, 0x80, 0x74, 0xde, 0x7b, 0x1f, 0x24, 0xb0, 0x64, 0x29, 0x1f, 0x53, 0x2e, 0xbe, 0x6e, 0x80, 0xf6, 0x19, 0x90, 0x9e, 0x36, 0x8b, 0xf7, 0xc0, 0x5c, 0xdf, 0x0, 0x66, 0x60, 0xae, 0xf3, 0xb9, 0x1, 0xfb, 0xe9, 0x1, 0xa6, 0x26, 0x1, 0xcd, 0x30, 0x66, 0x63, 0x6, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char icon_stop_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0x2, 0x7c, 0x60, 0x26, 0x28, 0xf3, 0xf0, 0x3f, 0x76, 0x8, 0x94, 0xa2, 0x97, 0x82, 0x51, 0x5, 0x84, 0x23, 0x8b, 0x30, 0x0, 0x0, 0x66, 0x60, 0x11, 0xdc, 0x92, 0xb3, 0xb7, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +static const unsigned char icon_visibility_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x3, 0x73, 0x42, 0x49, 0x54, 0x8, 0x8, 0x8, 0xdb, 0xe1, 0x4f, 0xe0, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc4, 0x0, 0x0, 0xe, 0xc4, 0x1, 0x95, 0x2b, 0xe, 0x1b, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xdf, 0xdf, 0xdf, 0xe3, 0xe3, 0xe3, 0xe6, 0xe6, 0xe6, 0xd5, 0xd5, 0xd5, 0xd8, 0xd8, 0xd8, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xe1, 0xe1, 0xe1, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xde, 0xde, 0xde, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xb7, 0x7e, 0xd, 0xb6, 0x0, 0x0, 0x0, 0x32, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x8, 0x9, 0xa, 0xc, 0xd, 0xe, 0xf, 0x11, 0x12, 0x13, 0x2e, 0x2f, 0x32, 0x33, 0x36, 0x37, 0x38, 0x48, 0x49, 0x4b, 0x50, 0x53, 0x55, 0x56, 0x6c, 0x6d, 0x6e, 0x70, 0x77, 0x79, 0x7b, 0x7c, 0xc5, 0xd7, 0xd8, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe1, 0xe2, 0xe3, 0xf0, 0xf2, 0xf3, 0xf4, 0xfe, 0x5e, 0x62, 0x1a, 0x26, 0x0, 0x0, 0x0, 0x86, 0x49, 0x44, 0x41, 0x54, 0x18, 0x19, 0x8d, 0xc1, 0xb, 0x16, 0x42, 0x40, 0x0, 0x86, 0xd1, 0x2f, 0xa2, 0x77, 0x2a, 0x85, 0xde, 0x91, 0x5e, 0x33, 0x8a, 0x7f, 0xff, 0x9b, 0xcb, 0x99, 0x63, 0x1, 0xee, 0xa5, 0x9f, 0xc1, 0x3e, 0x6f, 0xea, 0xfc, 0xe0, 0xd1, 0x99, 0xdd, 0xe5, 0x94, 0xb, 0x9c, 0xf9, 0x57, 0x26, 0x9, 0x82, 0x5d, 0xa1, 0xdf, 0x92, 0x96, 0xf7, 0x94, 0x99, 0xd0, 0x1a, 0x1b, 0x7d, 0x7c, 0xe0, 0x2c, 0x25, 0x6c, 0x2b, 0x1b, 0x93, 0x4a, 0x17, 0xa0, 0x94, 0x2, 0xac, 0x64, 0x8, 0xa5, 0x12, 0x78, 0x48, 0x1, 0x56, 0x32, 0x8c, 0xa4, 0x7, 0x70, 0x93, 0x76, 0xc4, 0xd6, 0x6c, 0xc8, 0xa4, 0x2b, 0x30, 0xac, 0x54, 0x8c, 0x69, 0x4d, 0xad, 0xcc, 0x90, 0xd6, 0xba, 0x91, 0x49, 0xc3, 0x30, 0xb3, 0x6a, 0x22, 0x9c, 0xd5, 0x5b, 0xce, 0x2b, 0xa2, 0xe3, 0x9f, 0xf2, 0xba, 0xce, 0x8f, 0x3e, 0xbd, 0xfc, 0x1, 0xdb, 0xf3, 0x10, 0xc5, 0x78, 0x85, 0x14, 0x89, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; static const unsigned char icon_zoom_less_png[] = { @@ -218,14 +194,6 @@ static const unsigned char line_edit_disabled_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xa, 0x8, 0x4, 0x0, 0x0, 0x0, 0x27, 0x3b, 0x7, 0x36, 0x0, 0x0, 0x0, 0x4e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x94, 0xc8, 0x67, 0x6b, 0x60, 0xe6, 0x60, 0x64, 0x80, 0x80, 0xff, 0xc, 0x7f, 0x7f, 0xfc, 0x6a, 0x60, 0x94, 0xfb, 0xc0, 0xce, 0xcf, 0xc2, 0x80, 0x10, 0xfc, 0xc3, 0xf0, 0xf3, 0x23, 0xa3, 0xe2, 0x4f, 0xe, 0x36, 0x54, 0xc1, 0x1f, 0xbf, 0x18, 0x95, 0xbe, 0x73, 0x70, 0xb0, 0x30, 0xc0, 0x1, 0x48, 0xf0, 0x7, 0x85, 0x82, 0x58, 0x2d, 0xc2, 0xe6, 0xa4, 0x4f, 0x20, 0xc7, 0x37, 0x32, 0xb3, 0x23, 0x39, 0xfe, 0xfb, 0xaf, 0x46, 0x0, 0xee, 0x2a, 0x2f, 0xce, 0x4c, 0x47, 0x66, 0xf6, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char line_edit_focus_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0xa8, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0x17, 0x18, 0x1b, 0x17, 0x18, 0x1b, 0x17, 0x18, 0xc8, 0x68, 0x12, 0xef, 0xed, 0xe7, 0xef, 0xed, 0xe8, 0xf0, 0xed, 0xe8, 0xf0, 0xee, 0xe8, 0xf0, 0xed, 0xe7, 0xed, 0xeb, 0xe5, 0xee, 0xeb, 0xe5, 0xee, 0xeb, 0xe6, 0xec, 0xe9, 0xe3, 0xeb, 0xe9, 0xe3, 0xeb, 0xe9, 0xe2, 0xec, 0xe9, 0xe2, 0xe9, 0xe6, 0xe0, 0xea, 0xe7, 0xe0, 0xea, 0xe7, 0xe1, 0xe8, 0xe4, 0xdd, 0xe8, 0xe5, 0xde, 0xe8, 0xe5, 0xdd, 0xe8, 0xe4, 0xde, 0xe6, 0xe2, 0xdb, 0xe6, 0xe3, 0xdb, 0xe6, 0xe3, 0xdc, 0xe7, 0xe2, 0xdb, 0xe7, 0xe3, 0xdb, 0xe4, 0xe0, 0xd8, 0xe5, 0xe0, 0xd8, 0xe5, 0xe1, 0xd9, 0xe5, 0xe0, 0xd9, 0xe4, 0xe1, 0xd9, 0xe5, 0xe1, 0xd8, 0xe4, 0xe0, 0xd9, 0xe2, 0xdf, 0xd6, 0xe3, 0xdf, 0xd6, 0xe3, 0xde, 0xd6, 0xe2, 0xde, 0xd6, 0xe1, 0xdc, 0xd4, 0xe1, 0xdc, 0xd3, 0xe0, 0xdc, 0xd3, 0xe1, 0xdd, 0xd3, 0xe1, 0xdd, 0xd4, 0xdf, 0xda, 0xd0, 0xdf, 0xda, 0xd1, 0xdf, 0xdb, 0xd1, 0xe0, 0xda, 0xd1, 0xdd, 0xd8, 0xcf, 0xdd, 0xd8, 0xce, 0xde, 0xd9, 0xce, 0xde, 0xd8, 0xce, 0xdd, 0xd9, 0xce, 0xdc, 0xd6, 0xcc, 0xdb, 0xd6, 0xcc, 0xdc, 0xd6, 0xcb, 0xbd, 0x92, 0xbc, 0xa2, 0x0, 0x0, 0x0, 0x2, 0x74, 0x52, 0x4e, 0x53, 0x36, 0x61, 0xc5, 0x3a, 0xd, 0x83, 0x0, 0x0, 0x0, 0x72, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x4c, 0x8c, 0x55, 0x2, 0x4, 0x1, 0x8, 0x85, 0xc0, 0xc9, 0xfb, 0x1f, 0x76, 0x7b, 0x75, 0x3a, 0xf0, 0xc7, 0xc0, 0x27, 0xc1, 0x9d, 0x34, 0xe4, 0x4e, 0xb5, 0xa0, 0x68, 0x95, 0xf1, 0x93, 0xc4, 0xb0, 0xb7, 0xd6, 0x2, 0x7c, 0x2d, 0x46, 0xc3, 0x82, 0x45, 0xf8, 0x87, 0x16, 0x5a, 0xd7, 0x71, 0x21, 0x9e, 0x2c, 0x86, 0x52, 0x10, 0x89, 0xee, 0x86, 0x15, 0xd9, 0xf0, 0x6b, 0x7f, 0xac, 0x8b, 0xce, 0x85, 0xb4, 0x8b, 0xe1, 0xb7, 0x2e, 0x6, 0xd9, 0x53, 0x6a, 0x3c, 0x43, 0xa9, 0xd0, 0xcc, 0xd8, 0x5f, 0xd0, 0x4, 0xa2, 0xf6, 0x50, 0xac, 0x68, 0xff, 0x6d, 0xf9, 0x3f, 0xc, 0x93, 0x88, 0x59, 0x68, 0x81, 0x69, 0x18, 0x9e, 0x3, 0xba, 0x1b, 0x55, 0x0, 0x0, 0x0, 0x69, 0x26, 0x8d, 0xeb, 0x4b, 0xad, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char logo_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x8, 0x6, 0x0, 0x0, 0x0, 0xc3, 0x3e, 0x61, 0xcb, 0x0, 0x0, 0x19, 0xdb, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x99, 0x3, 0x94, 0x7b, 0x39, 0x14, 0xc6, 0xd7, 0xb6, 0xed, 0x87, 0x62, 0x6d, 0xdb, 0x33, 0xe5, 0x6b, 0xc7, 0x6b, 0xdb, 0xb6, 0x77, 0x5c, 0xb7, 0x6b, 0xdb, 0xb6, 0x6d, 0xdb, 0xb6, 0xbd, 0xdb, 0x74, 0xbf, 0xde, 0xe6, 0x35, 0xff, 0xe6, 0xcc, 0xdb, 0x1d, 0x9f, 0x37, 0xd3, 0xe4, 0x9c, 0xdf, 0x49, 0x5e, 0x9c, 0x7c, 0x37, 0x37, 0x39, 0xed, 0x54, 0x2a, 0xa8, 0xa0, 0x82, 0xa, 0x2a, 0xa8, 0xa0, 0x82, 0xa, 0x2a, 0xa8, 0xa0, 0x82, 0xa, 0xf5, 0x17, 0x8c, 0x48, 0x6a, 0xe, 0x23, 0xd4, 0x73, 0xa5, 0x16, 0xe8, 0xba, 0x5f, 0xb, 0x74, 0xdf, 0xac, 0x87, 0x7a, 0x2f, 0x34, 0xc3, 0xfd, 0x9d, 0x66, 0x24, 0x71, 0x0, 0xca, 0xa2, 0x66, 0x34, 0xb3, 0xba, 0x37, 0x9e, 0x5f, 0x78, 0x92, 0x2e, 0x5f, 0x5, 0x3d, 0xd4, 0x77, 0xe6, 0x72, 0xd, 0xa7, 0x33, 0x50, 0x2a, 0xa3, 0x35, 0x9c, 0x5e, 0xd4, 0x1a, 0xcf, 0xfc, 0x19, 0x7c, 0xae, 0x35, 0x76, 0xbe, 0xa5, 0x5, 0x3a, 0x9f, 0xd5, 0x1a, 0xbb, 0x1e, 0xd4, 0x83, 0x3d, 0xb7, 0x1b, 0xe1, 0xbe, 0x4b, 0xcd, 0x48, 0xf2, 0x0, 0x4f, 0x2c, 0xe7, 0x9f, 0x4, 0x4b, 0x57, 0xc1, 0x13, 0x3f, 0x6b, 0x16, 0x8, 0xfc, 0xe9, 0x72, 0xd, 0x67, 0x94, 0xb4, 0xc6, 0x32, 0x67, 0x56, 0x41, 0x9e, 0xc, 0x2f, 0xeb, 0xfc, 0xa, 0xc6, 0x70, 0x97, 0xc7, 0xca, 0x2c, 0x3e, 0x8e, 0x53, 0x55, 0xc1, 0xb4, 0xb2, 0x1b, 0xeb, 0x70, 0xcd, 0x38, 0xb1, 0x29, 0x23, 0x92, 0x38, 0x79, 0xa4, 0xfd, 0xad, 0xbe, 0xff, 0x63, 0x53, 0x1b, 0xd1, 0xf4, 0x36, 0x7a, 0xa0, 0xf3, 0x77, 0x12, 0xb6, 0xe1, 0xc, 0x46, 0xb1, 0x94, 0x6, 0xac, 0x36, 0x26, 0x63, 0x28, 0xc2, 0x0, 0x1a, 0xd6, 0x3c, 0xf0, 0x89, 0xa9, 0x95, 0x32, 0x63, 0x18, 0x8c, 0x68, 0x66, 0x29, 0x23, 0x92, 0xdc, 0x5, 0xc2, 0x77, 0xe1, 0x7e, 0x7e, 0xaa, 0x2a, 0x4a, 0xa0, 0xf3, 0xf, 0xe4, 0xef, 0x30, 0x62, 0xf7, 0x1f, 0xec, 0x7d, 0x0, 0xfd, 0xfd, 0x83, 0xfe, 0x4a, 0x2, 0xea, 0xdf, 0x11, 0xee, 0x5, 0x7e, 0xc7, 0x55, 0x70, 0x4, 0xd2, 0xd3, 0x28, 0x95, 0xc6, 0xe6, 0xb4, 0xaf, 0x60, 0x46, 0x53, 0xbb, 0x18, 0xa1, 0xbe, 0xab, 0xb0, 0xd9, 0x3f, 0x83, 0xaa, 0x6b, 0xd6, 0x85, 0x8, 0xdf, 0x98, 0x56, 0x66, 0xbd, 0x61, 0x8f, 0x11, 0xcb, 0x2d, 0x36, 0x45, 0x9f, 0xc, 0x94, 0x6, 0x3, 0x1f, 0xfb, 0x2f, 0x23, 0xdc, 0x7f, 0x99, 0x1e, 0x18, 0x5d, 0x3, 0x50, 0xc2, 0x47, 0xb3, 0xcb, 0x43, 0xf8, 0x3d, 0xe1, 0xe6, 0xef, 0xc4, 0x26, 0x33, 0x50, 0x2, 0xd8, 0xf8, 0xae, 0x32, 0x8c, 0xc7, 0x94, 0xbf, 0x1c, 0x84, 0xc0, 0x9, 0x7e, 0xdc, 0x13, 0xcb, 0xcf, 0x3f, 0xf4, 0x97, 0x7f, 0x72, 0x7a, 0x33, 0x9a, 0xde, 0x1b, 0xfd, 0xd0, 0xe9, 0x47, 0x9f, 0x83, 0x86, 0xe6, 0x14, 0xe8, 0x2a, 0xe2, 0x1d, 0xf0, 0xa2, 0x27, 0x5e, 0x98, 0x56, 0xa9, 0x36, 0xa, 0x1, 0xaf, 0xea, 0xc5, 0x3d, 0xb1, 0x6c, 0xb, 0x4e, 0xfc, 0xdd, 0xd8, 0xdc, 0x12, 0x87, 0x8b, 0xce, 0xd3, 0x41, 0x21, 0x2, 0x44, 0x63, 0x4, 0xd2, 0x30, 0x98, 0x33, 0xe1, 0x9, 0x66, 0x1a, 0xd2, 0x78, 0x10, 0xe, 0x2f, 0xfa, 0x47, 0xf4, 0x60, 0x37, 0x84, 0xa4, 0xfe, 0x2a, 0xfd, 0xcb, 0x4, 0x44, 0x9a, 0x8b, 0xcf, 0xec, 0x34, 0xc6, 0xfe, 0xc1, 0xd7, 0x7a, 0xe1, 0x74, 0x4a, 0xbd, 0x51, 0x8, 0x10, 0x31, 0x3, 0x31, 0x4a, 0x53, 0xc0, 0x0, 0x17, 0x5e, 0xe4, 0xf3, 0x3c, 0x2a, 0xe3, 0x14, 0xc9, 0x8, 0xac, 0xac, 0x5, 0x61, 0x1c, 0xdd, 0xb1, 0xaf, 0xf5, 0xa2, 0xe9, 0x4c, 0x2b, 0x37, 0xbf, 0x27, 0x56, 0x58, 0x6, 0xc6, 0x66, 0x80, 0xd, 0xd1, 0xf6, 0x1f, 0xd1, 0x8f, 0xe8, 0xd3, 0x19, 0x31, 0x3e, 0x28, 0x95, 0xbd, 0x7, 0xfa, 0x55, 0x6, 0x30, 0x1a, 0x1, 0xae, 0xfc, 0xb, 0x3d, 0x20, 0x84, 0x1f, 0x4, 0x4c, 0x8, 0x82, 0x38, 0xd4, 0xfb, 0x11, 0xae, 0x82, 0xd5, 0x21, 0xec, 0xac, 0x38, 0xdd, 0xcb, 0x80, 0x55, 0xbc, 0xf1, 0xc2, 0x9a, 0xde, 0xa6, 0xc2, 0x5a, 0x65, 0xb1, 0xe1, 0x21, 0x3a, 0x60, 0x64, 0xdd, 0x78, 0x4c, 0x5e, 0x8b, 0xbb, 0xfb, 0x1, 0x78, 0x9a, 0x77, 0xe5, 0xbe, 0x6, 0xd, 0x79, 0x25, 0x1a, 0xf7, 0xaf, 0xd1, 0xb8, 0x2, 0x3c, 0x4d, 0xe7, 0xcc, 0xa2, 0x5e, 0xfb, 0xe1, 0xc4, 0xc3, 0xb8, 0x53, 0x8b, 0xa0, 0x4, 0x18, 0xc5, 0x32, 0x21, 0xe9, 0x3b, 0x0, 0xe1, 0x0, 0xd2, 0x24, 0x20, 0xee, 0xf5, 0xcb, 0x71, 0xaf, 0x1f, 0xd, 0x91, 0xef, 0x45, 0xde, 0xaf, 0xbc, 0x9e, 0x83, 0x90, 0x7c, 0x1c, 0xe0, 0x3c, 0x8e, 0x9c, 0xcf, 0xc7, 0x14, 0x75, 0xfe, 0x42, 0x5f, 0x23, 0x7a, 0x4, 0x7a, 0x9b, 0xce, 0x5a, 0x1a, 0xde, 0x6b, 0x6f, 0x18, 0xef, 0xf2, 0x75, 0xfe, 0x6, 0xc8, 0x9e, 0x82, 0x53, 0xf9, 0x43, 0x79, 0x73, 0x8d, 0x50, 0x6f, 0x15, 0x5d, 0x66, 0xa0, 0x7c, 0xb4, 0x41, 0x5c, 0xe4, 0x69, 0x81, 0x28, 0x23, 0xb1, 0x45, 0x1b, 0xa4, 0xed, 0x7c, 0xb9, 0x4c, 0x8c, 0xed, 0x38, 0x9e, 0x21, 0xc6, 0xf9, 0x1b, 0x1e, 0x60, 0x35, 0x33, 0x96, 0x5b, 0xc4, 0xdb, 0x7c, 0xee, 0xec, 0x43, 0x59, 0x2f, 0xea, 0xcf, 0xe2, 0x8d, 0x9f, 0x65, 0xea, 0xe1, 0xbe, 0x77, 0x78, 0xbf, 0x5f, 0x9a, 0xb1, 0xfc, 0x46, 0xde, 0xa6, 0xb3, 0x67, 0xa8, 0x4b, 0x3, 0xc0, 0xe2, 0xb7, 0x81, 0x1, 0x7c, 0x62, 0x88, 0xd, 0x66, 0x94, 0x1e, 0x1c, 0x8c, 0xb7, 0xb1, 0xd3, 0xf2, 0x77, 0x69, 0x94, 0xa9, 0xf1, 0x1e, 0xb8, 0x52, 0xde, 0xc1, 0x15, 0x73, 0x3a, 0xae, 0x1c, 0x1d, 0xcc, 0x8a, 0xbc, 0xff, 0xf4, 0xa, 0x38, 0xed, 0xb, 0xa2, 0xfe, 0xa1, 0x7a, 0x10, 0xfd, 0x10, 0x3d, 0xd5, 0xf9, 0x22, 0xff, 0x8, 0xfc, 0xef, 0x30, 0x5f, 0xfd, 0x79, 0x80, 0xa6, 0xb3, 0x97, 0xc1, 0x46, 0xbe, 0x5b, 0x39, 0x91, 0x10, 0x2d, 0xdc, 0x87, 0x8d, 0x96, 0x8, 0x3b, 0xe0, 0x58, 0x26, 0xb5, 0x1d, 0x29, 0x21, 0x82, 0xe6, 0x27, 0xf2, 0x78, 0x9a, 0x83, 0x77, 0xc6, 0x75, 0xf0, 0xa, 0x1b, 0x7a, 0x5b, 0xce, 0x9b, 0x7d, 0xa0, 0x5f, 0x9, 0x51, 0xa6, 0xe3, 0xaa, 0xba, 0x8f, 0xd7, 0x2f, 0xf2, 0xd8, 0xf6, 0x2c, 0xd4, 0x97, 0xc7, 0xca, 0xe4, 0x51, 0x6f, 0xa9, 0xfa, 0xf3, 0x2, 0x91, 0xe4, 0x6b, 0x7a, 0x79, 0x83, 0xab, 0x9b, 0xda, 0xcf, 0x41, 0xda, 0x19, 0x51, 0x67, 0xbc, 0x9, 0xf5, 0x31, 0xc2, 0x9e, 0x27, 0xd2, 0x95, 0xf9, 0x93, 0x21, 0x3c, 0xf, 0x11, 0x1b, 0xa4, 0xfb, 0x7e, 0x53, 0x94, 0x15, 0x1, 0xd5, 0x95, 0xfb, 0xe1, 0x6b, 0x29, 0x52, 0x1c, 0x49, 0x3d, 0x8c, 0xff, 0x29, 0xd6, 0xad, 0x2f, 0x3, 0x88, 0xa6, 0x1f, 0xe2, 0x1b, 0xc8, 0x45, 0x75, 0x3b, 0x10, 0x8d, 0x10, 0x79, 0x7c, 0xfe, 0x94, 0xaf, 0x93, 0xa0, 0xc9, 0x67, 0x60, 0x8, 0xdb, 0xe2, 0x6a, 0x68, 0xa5, 0xb2, 0x8a, 0xc8, 0x4c, 0xea, 0x8b, 0x11, 0xa2, 0xf, 0x6, 0x60, 0x4, 0x89, 0xf, 0xcc, 0x58, 0xa1, 0xa5, 0x8e, 0x1e, 0x82, 0xf9, 0x5d, 0xe1, 0x1e, 0x3f, 0xb5, 0x4f, 0x13, 0xc5, 0x13, 0x9, 0x31, 0x67, 0x56, 0xf5, 0x8, 0x38, 0xd1, 0xe0, 0x4f, 0x23, 0x9c, 0x0, 0x55, 0x8f, 0xc5, 0x78, 0x2c, 0x90, 0xda, 0x89, 0xba, 0x89, 0x6f, 0x60, 0x3c, 0x7, 0xd4, 0xcb, 0x3b, 0xc0, 0x84, 0x1, 0xbc, 0x42, 0x9b, 0x10, 0x49, 0x4c, 0x48, 0x20, 0x18, 0xab, 0xc4, 0xfd, 0x22, 0x3d, 0xe5, 0x37, 0x21, 0xca, 0x64, 0x50, 0xc6, 0xe4, 0x7e, 0xf4, 0x70, 0xe2, 0x73, 0x3c, 0x92, 0xf, 0x98, 0xec, 0xff, 0xcb, 0xc7, 0x70, 0xef, 0xbd, 0x86, 0x85, 0x17, 0xcb, 0x8b, 0x36, 0xb1, 0xf8, 0x9, 0xe, 0x93, 0xd3, 0xc3, 0xc0, 0xf6, 0x28, 0x48, 0x27, 0x3f, 0x81, 0x27, 0x38, 0x74, 0x92, 0x8a, 0x5f, 0xc8, 0xe0, 0xe4, 0x7f, 0x66, 0x9f, 0x2, 0x2c, 0x96, 0xf1, 0xb8, 0x9e, 0x21, 0x2f, 0x60, 0xef, 0x5, 0xe, 0xc7, 0x77, 0x78, 0x44, 0x9e, 0x30, 0x19, 0xff, 0xef, 0xbf, 0xdf, 0x8c, 0xa4, 0x7e, 0x31, 0xb0, 0x60, 0xc0, 0xc5, 0x47, 0x1c, 0x55, 0x88, 0xfd, 0xc0, 0x61, 0xb0, 0xb2, 0xf7, 0xfb, 0x5b, 0xcf, 0x77, 0xef, 0x8f, 0x44, 0xb0, 0xce, 0x6e, 0xb8, 0xf1, 0x13, 0xf0, 0xb, 0xd7, 0xdc, 0x83, 0xa9, 0xef, 0x6f, 0xbd, 0x60, 0x66, 0x2c, 0xea, 0xc1, 0xaa, 0xf0, 0xd1, 0x54, 0x19, 0x26, 0xd2, 0xa, 0xc0, 0xf7, 0x26, 0xfd, 0x15, 0x7e, 0x21, 0x3c, 0xd8, 0xb5, 0xe2, 0x43, 0xf4, 0xd5, 0x30, 0x49, 0xb8, 0xf0, 0xd4, 0xf, 0x10, 0xf5, 0x21, 0x3c, 0x58, 0x2e, 0x87, 0x31, 0x9c, 0x8c, 0xfc, 0xe5, 0x1c, 0xfe, 0xfc, 0x58, 0x16, 0xf5, 0x1e, 0xe6, 0x8b, 0x23, 0xd0, 0x96, 0x21, 0x6, 0x3, 0x6d, 0x44, 0x5a, 0xa4, 0x1d, 0xcb, 0x44, 0x1d, 0xc7, 0x6f, 0x30, 0xf8, 0xbe, 0xd3, 0x36, 0xe, 0x79, 0xce, 0xed, 0x6, 0x3f, 0x5f, 0xe7, 0x36, 0x6, 0x3f, 0x10, 0xa0, 0x7c, 0xfa, 0x6f, 0x5d, 0xbe, 0xfd, 0xa2, 0x99, 0xdd, 0x29, 0x7e, 0xcb, 0x85, 0xb3, 0xe3, 0xdf, 0xb6, 0x87, 0xb0, 0x80, 0x62, 0xed, 0x42, 0x33, 0x3f, 0x22, 0xff, 0x61, 0x18, 0x42, 0x2, 0x86, 0xd0, 0xe2, 0x6f, 0xbf, 0x68, 0xae, 0x4a, 0xfd, 0xf3, 0x56, 0xc2, 0x82, 0x9e, 0x94, 0x36, 0x85, 0x99, 0x56, 0xba, 0xc4, 0x51, 0x60, 0x3f, 0x80, 0x2d, 0xfe, 0xfb, 0xde, 0xe6, 0x73, 0xda, 0xdc, 0xfc, 0x7a, 0x3f, 0xdb, 0xb6, 0x60, 0x8f, 0x95, 0x61, 0x9c, 0xda, 0x93, 0x62, 0xe5, 0xde, 0x41, 0xbd, 0x2b, 0x40, 0x17, 0x8c, 0xe2, 0xbe, 0x4a, 0xdd, 0x34, 0xd5, 0x3, 0x76, 0x5b, 0x90, 0x16, 0x38, 0x7e, 0x67, 0xfe, 0x87, 0xb4, 0x8c, 0x63, 0x19, 0xcf, 0x77, 0x48, 0x3b, 0xd7, 0x1d, 0xfc, 0x5c, 0x9d, 0xe7, 0x3b, 0xe0, 0xba, 0xb8, 0xf8, 0xbc, 0xe, 0xa3, 0x7d, 0x89, 0x17, 0xce, 0x5d, 0x79, 0xa7, 0xab, 0xa7, 0x73, 0xa5, 0xf8, 0xbe, 0x96, 0xf3, 0x22, 0x34, 0x51, 0x12, 0x31, 0x23, 0x26, 0x2e, 0x16, 0x4e, 0x65, 0x44, 0x94, 0xc7, 0x94, 0x9f, 0x2d, 0xe2, 0xbb, 0xc6, 0x60, 0x14, 0x2, 0x3a, 0xfd, 0xd8, 0x1f, 0x6f, 0xbc, 0xf0, 0xa4, 0xaf, 0xe5, 0xfc, 0xd, 0x5c, 0x29, 0x3e, 0x1e, 0x71, 0x8b, 0xe3, 0x34, 0xbf, 0xc5, 0x5, 0x2d, 0x3, 0xb1, 0x21, 0x66, 0xc, 0x69, 0xe, 0xff, 0xa6, 0x32, 0x29, 0x5f, 0xe4, 0x59, 0x20, 0x36, 0x78, 0x44, 0x9b, 0x11, 0x62, 0x11, 0xa3, 0xd7, 0xc6, 0x12, 0xf1, 0xb0, 0xd6, 0x21, 0xd2, 0xd5, 0x83, 0x2, 0xd7, 0x7f, 0x90, 0x3b, 0x4f, 0x7e, 0xeb, 0x85, 0x33, 0x60, 0x72, 0xbd, 0x74, 0xaa, 0x69, 0xe2, 0x5c, 0x4c, 0x97, 0x62, 0xd6, 0x1a, 0x20, 0xe3, 0x79, 0x55, 0x44, 0x3d, 0xd7, 0xcc, 0x97, 0xf0, 0x36, 0xb9, 0xd0, 0x0, 0xfc, 0xed, 0x17, 0x4f, 0xb, 0xb7, 0xb4, 0x79, 0xed, 0x6, 0xe6, 0xdc, 0x2, 0x89, 0x2d, 0xa5, 0x1, 0xa5, 0x6b, 0x90, 0xda, 0x38, 0xaf, 0x43, 0xb4, 0xa7, 0xf4, 0x78, 0xad, 0x1, 0x94, 0xef, 0xff, 0x47, 0x7c, 0xad, 0x17, 0xac, 0xef, 0x2a, 0x3, 0x58, 0xbe, 0xfd, 0xe2, 0x85, 0xf1, 0x98, 0x7b, 0xca, 0x63, 0xfd, 0xcb, 0xbd, 0x39, 0x0, 0x3b, 0x8f, 0x45, 0x71, 0xbc, 0x9f, 0x6d, 0xd, 0xd6, 0xae, 0xd7, 0xb6, 0xed, 0xdd, 0x87, 0x6a, 0x6d, 0xdb, 0xb6, 0x8d, 0xc7, 0xba, 0xfd, 0xb4, 0xb6, 0x6d, 0xdb, 0xb6, 0xed, 0xde, 0xee, 0x2f, 0xed, 0x49, 0x5e, 0xa6, 0x93, 0x4f, 0x2f, 0x49, 0x1f, 0x32, 0xf3, 0x9f, 0x73, 0x73, 0x93, 0x1e, 0xfe, 0xef, 0xe9, 0x4d, 0xa6, 0xd5, 0x93, 0x73, 0x9d, 0xcd, 0xa4, 0x5c, 0x37, 0xd7, 0x6b, 0x60, 0x7e, 0xef, 0x37, 0xfc, 0xc0, 0x37, 0x5, 0x6a, 0x3f, 0xf7, 0x23, 0x9, 0xfd, 0x0, 0xbc, 0x2, 0x9e, 0xe6, 0xbd, 0xc5, 0xc3, 0xbe, 0xc6, 0xd6, 0x7, 0x79, 0xbe, 0xbe, 0x9f, 0x78, 0x90, 0x2d, 0x8f, 0x82, 0x67, 0x19, 0xbf, 0xce, 0xbd, 0x5f, 0xf0, 0x99, 0xff, 0x2a, 0x3a, 0x80, 0xb5, 0xd, 0xfb, 0x64, 0xb0, 0x88, 0x4f, 0xf4, 0x32, 0xb7, 0xeb, 0x75, 0x25, 0xed, 0x1c, 0x9f, 0xee, 0xd, 0x44, 0x52, 0xbd, 0xe3, 0xb7, 0x1, 0xe1, 0xfd, 0x1f, 0x1a, 0x0, 0x23, 0xb7, 0x16, 0x67, 0x4b, 0x38, 0x59, 0xd6, 0xe1, 0xab, 0x82, 0x79, 0xa4, 0x35, 0x4c, 0xd7, 0xe4, 0x73, 0x48, 0x81, 0xa1, 0xc3, 0x7c, 0xaf, 0xa1, 0x5f, 0xe6, 0x2c, 0xee, 0x17, 0x2, 0x32, 0xd6, 0x24, 0x30, 0x25, 0xf4, 0x6f, 0x92, 0xf7, 0x31, 0x5, 0x7d, 0x96, 0x56, 0x7a, 0x23, 0x8f, 0x9f, 0x67, 0xfa, 0x9a, 0x3a, 0x23, 0xf8, 0xbf, 0x26, 0x5f, 0x61, 0x4b, 0xfb, 0x23, 0xe9, 0x89, 0x24, 0x76, 0x94, 0xb7, 0x29, 0x39, 0x34, 0x14, 0xcf, 0x8f, 0x61, 0x3c, 0xd, 0x78, 0xb9, 0x67, 0x43, 0x5f, 0x63, 0xc7, 0xc1, 0xbc, 0xab, 0x48, 0x43, 0x8e, 0x7, 0xf9, 0xfc, 0xab, 0x10, 0xe6, 0xfb, 0x5a, 0xb2, 0x55, 0xfc, 0xec, 0xf2, 0x4f, 0x89, 0xaf, 0x96, 0xf1, 0xc8, 0x9c, 0x39, 0x6e, 0xa4, 0xc5, 0xbc, 0x71, 0x4d, 0xf4, 0x49, 0x9e, 0x79, 0x8c, 0xbe, 0x7a, 0xc9, 0xed, 0xae, 0x18, 0xd8, 0xf3, 0xed, 0x3f, 0x92, 0xe, 0x91, 0xd4, 0xd7, 0x2a, 0x4e, 0xeb, 0x81, 0x3, 0x76, 0xab, 0x95, 0x73, 0x43, 0x2, 0x39, 0x9f, 0xcb, 0x9c, 0x71, 0xcd, 0xfa, 0x7c, 0xee, 0x73, 0xaa, 0x66, 0x5e, 0x19, 0xfa, 0x1b, 0xae, 0xff, 0xe, 0x1f, 0x5f, 0xa2, 0x80, 0x19, 0x8a, 0xdd, 0x10, 0x8e, 0xe7, 0x47, 0xd8, 0xdb, 0xef, 0xa4, 0x97, 0xa3, 0x0, 0xa7, 0xb0, 0xe7, 0xb9, 0x1b, 0xbd, 0xef, 0x51, 0x98, 0xbf, 0x88, 0xd9, 0xec, 0x7, 0xe8, 0xca, 0x47, 0x4d, 0xcc, 0xf3, 0x8c, 0x4b, 0xe6, 0x6b, 0xa5, 0xe8, 0x12, 0x42, 0x37, 0xb4, 0x7c, 0x42, 0x2c, 0xbb, 0xf7, 0x6, 0x2, 0x6c, 0xc2, 0x6a, 0x28, 0xf1, 0xa6, 0xcf, 0x8, 0xb6, 0xce, 0x30, 0xdb, 0x55, 0x66, 0x1f, 0x58, 0xa9, 0x3f, 0xb0, 0x62, 0xdf, 0x64, 0x7f, 0x72, 0x4, 0x6f, 0xcd, 0x86, 0xbb, 0xb4, 0xf9, 0x5d, 0x92, 0x47, 0xdf, 0x2c, 0x64, 0x78, 0x9b, 0x3c, 0xfc, 0x29, 0x44, 0x30, 0x16, 0x82, 0xb, 0xb1, 0x1a, 0x64, 0xa7, 0x8b, 0x3d, 0xe2, 0x8f, 0xe4, 0x86, 0xf5, 0x2c, 0x1, 0x62, 0x85, 0x61, 0xbe, 0xa6, 0x64, 0x33, 0xc9, 0xfe, 0xcf, 0x5b, 0x71, 0xaa, 0x45, 0x69, 0x90, 0xb1, 0xeb, 0xc0, 0x8e, 0xd2, 0x65, 0xd7, 0xb8, 0xe5, 0xf, 0x7e, 0x5d, 0xfb, 0x1d, 0xed, 0x7b, 0xff, 0xba, 0x3d, 0x5, 0xc5, 0x8a, 0x83, 0xe9, 0xa, 0x1d, 0xd8, 0xfe, 0x52, 0xf3, 0x43, 0x5b, 0x10, 0xc8, 0x5a, 0x3f, 0x9d, 0x8e, 0xfd, 0x3f, 0x6c, 0xe6, 0x7a, 0x7e, 0x13, 0x18, 0xcb, 0x8d, 0x60, 0x25, 0x1c, 0x42, 0xf0, 0xa, 0x48, 0xe0, 0xad, 0xba, 0xa3, 0xf3, 0x8, 0xbc, 0xd5, 0x12, 0xe6, 0x7b, 0xac, 0xe7, 0xc, 0x3b, 0xa, 0x30, 0xd6, 0x93, 0xdc, 0xf6, 0x33, 0xdf, 0xd9, 0x97, 0xfa, 0x23, 0x99, 0xa1, 0x3d, 0xb3, 0x20, 0xf2, 0x23, 0xf9, 0x11, 0xcb, 0xeb, 0xf8, 0xf2, 0x87, 0x9e, 0xb, 0xa4, 0x4e, 0xd2, 0xda, 0x58, 0xe6, 0x12, 0xab, 0xc0, 0xf2, 0x1e, 0x20, 0x8b, 0xc, 0x5b, 0x2f, 0xd1, 0x85, 0xa7, 0x59, 0xfd, 0xcd, 0x3d, 0x10, 0xcd, 0xe, 0xa, 0x26, 0x66, 0xc, 0xf6, 0x37, 0x77, 0xe, 0x61, 0x3c, 0x34, 0x10, 0xcd, 0xd, 0x72, 0x91, 0x4, 0x79, 0x48, 0x90, 0x3c, 0x87, 0x60, 0x4b, 0xac, 0xbe, 0x4a, 0x61, 0x4, 0x65, 0xce, 0x2b, 0xd2, 0x29, 0x78, 0xbb, 0xf4, 0x29, 0xfd, 0x5c, 0x83, 0xbf, 0xb1, 0xed, 0x3b, 0x1e, 0x49, 0xd7, 0xec, 0x25, 0xef, 0x45, 0x8e, 0xc6, 0xa7, 0xef, 0x29, 0x52, 0x6d, 0xfc, 0x4e, 0xe4, 0xc2, 0x4c, 0xfa, 0x32, 0x5d, 0xe0, 0xd1, 0x4a, 0xd, 0xe2, 0xc5, 0x61, 0xd8, 0x1d, 0x1e, 0x8c, 0x15, 0xc6, 0x53, 0xf0, 0x15, 0x2, 0x91, 0xcc, 0x26, 0x6c, 0x6c, 0x77, 0x86, 0x20, 0xbb, 0x33, 0x7f, 0x12, 0x73, 0x1b, 0xd0, 0x15, 0x87, 0xb8, 0x16, 0x34, 0xc, 0x1b, 0x8a, 0xa1, 0x33, 0x70, 0x8e, 0xd6, 0xd4, 0x66, 0x10, 0x80, 0xb1, 0xf3, 0x40, 0x37, 0x52, 0x27, 0xc4, 0xbf, 0x7c, 0xf, 0x3f, 0x18, 0x4a, 0x14, 0xc7, 0x7b, 0x7a, 0xd1, 0xa1, 0x25, 0x1c, 0x1f, 0x3f, 0xd1, 0xa, 0x25, 0x79, 0x50, 0x86, 0xef, 0xf6, 0xe3, 0x37, 0x24, 0x5d, 0xe0, 0x4f, 0x48, 0xf0, 0x16, 0xdd, 0xef, 0x27, 0xce, 0x4d, 0xd7, 0x6a, 0xc7, 0x6d, 0xcf, 0x43, 0x8a, 0xd, 0x5d, 0xd, 0x3a, 0xb4, 0xfb, 0xac, 0xc1, 0x30, 0xee, 0x70, 0x56, 0xe3, 0x3f, 0x3a, 0x5b, 0x19, 0x97, 0x9d, 0x86, 0x4e, 0x2e, 0xc6, 0x14, 0xbf, 0xf3, 0x26, 0x4f, 0x2f, 0x3d, 0xe8, 0x48, 0xcb, 0xf8, 0x1a, 0xda, 0x5f, 0x17, 0x5f, 0x55, 0xd5, 0x77, 0xa4, 0xfd, 0xf8, 0xd, 0x9, 0x94, 0x9, 0xc6, 0x35, 0x8b, 0x71, 0x89, 0x8e, 0xb0, 0x8f, 0xfb, 0xef, 0x6, 0x76, 0x9f, 0x3d, 0x38, 0x18, 0xcd, 0x36, 0xf2, 0xd8, 0xc5, 0x2e, 0x5c, 0x73, 0xb8, 0x7d, 0xde, 0x30, 0x7, 0x38, 0x97, 0x39, 0xa0, 0x27, 0x11, 0xe6, 0x13, 0x5c, 0x73, 0xf2, 0x4e, 0x4f, 0x2f, 0x3f, 0x2, 0xb1, 0xdc, 0x12, 0x74, 0x28, 0xde, 0x3d, 0x48, 0xf1, 0xcd, 0xf1, 0x21, 0xad, 0xcf, 0xe7, 0x7d, 0x8f, 0xe8, 0x2, 0xe6, 0xeb, 0xd5, 0x39, 0x73, 0xde, 0xb5, 0xb1, 0x7e, 0xce, 0xe2, 0x3c, 0x67, 0x89, 0xe8, 0xad, 0x3, 0x5c, 0xf, 0x3a, 0xb4, 0xdb, 0x8c, 0x61, 0xb4, 0xa5, 0xcf, 0xb4, 0xf6, 0xa3, 0x3b, 0x69, 0x17, 0xe8, 0xd2, 0xf5, 0x28, 0xc6, 0x5a, 0xf1, 0xef, 0x5d, 0x3a, 0x71, 0x87, 0xb, 0xc1, 0xb8, 0xb2, 0x51, 0xf6, 0xd2, 0xa9, 0x1e, 0xd1, 0xf3, 0x21, 0xb2, 0x5e, 0x50, 0xba, 0x4d, 0x5e, 0x7e, 0x7d, 0x10, 0x8c, 0x65, 0x1b, 0xea, 0xd0, 0xfa, 0x72, 0x61, 0x8, 0xf0, 0x35, 0x28, 0xc3, 0x7e, 0xc7, 0x80, 0xbe, 0x12, 0x80, 0xc9, 0xa9, 0x3b, 0xf8, 0xce, 0x1f, 0xe7, 0xe9, 0x43, 0x7, 0x4f, 0x26, 0xd, 0x90, 0xe0, 0x1d, 0xc9, 0x89, 0x42, 0x2a, 0x89, 0xab, 0x2e, 0xc0, 0x5e, 0x5, 0xec, 0x3, 0xdc, 0xff, 0x21, 0x29, 0xef, 0x6, 0x1e, 0xe4, 0xf5, 0xe9, 0xbf, 0xe, 0x13, 0x80, 0xa4, 0x21, 0x9b, 0x93, 0xaf, 0xf0, 0xd4, 0xb1, 0xb9, 0xa7, 0xf, 0x1e, 0x90, 0xe0, 0x78, 0x56, 0xe1, 0xaf, 0x5a, 0x1c, 0x75, 0x2e, 0xbe, 0xe4, 0xf, 0x19, 0x49, 0x5f, 0xec, 0x6e, 0xfb, 0x8f, 0x17, 0xc6, 0x12, 0xe4, 0x4f, 0x55, 0xe3, 0x9d, 0x40, 0xa4, 0x2d, 0x18, 0x3a, 0x7e, 0x66, 0x77, 0x7d, 0x8c, 0xa7, 0x8f, 0x1e, 0x10, 0x77, 0x2c, 0x5, 0x68, 0x97, 0x78, 0x94, 0xc4, 0x26, 0xd2, 0x45, 0x60, 0x43, 0x0, 0x1, 0x52, 0xf7, 0xb2, 0x2f, 0x59, 0xde, 0x95, 0x0, 0xb5, 0x7f, 0xbf, 0xd2, 0x9a, 0x37, 0x80, 0x0, 0xbf, 0x6b, 0xc6, 0x2, 0xcd, 0x4, 0xd9, 0xdc, 0x89, 0xec, 0x3e, 0x8, 0xa0, 0xaa, 0x7, 0xc9, 0xae, 0x3a, 0xcd, 0xcb, 0x96, 0x29, 0x9e, 0x3e, 0x7c, 0x4, 0xe3, 0x85, 0xb5, 0x28, 0xc2, 0xe3, 0x46, 0x61, 0x9a, 0x91, 0xb, 0x9a, 0x13, 0x8b, 0x31, 0xd2, 0x72, 0xde, 0x9c, 0x47, 0x91, 0x7f, 0xb3, 0x88, 0x4e, 0x77, 0x25, 0x38, 0x94, 0xf, 0x44, 0xf9, 0x75, 0xb4, 0xe9, 0xbf, 0xab, 0x6, 0x93, 0x8e, 0x0, 0x5d, 0x4, 0x94, 0xfa, 0x90, 0xbd, 0xc5, 0xe, 0x9e, 0x7e, 0x70, 0xb0, 0x2, 0x8f, 0xaf, 0xc6, 0x94, 0x2c, 0x49, 0x8c, 0x75, 0x1, 0x36, 0x95, 0x26, 0xd9, 0x8, 0xa6, 0x5d, 0xfc, 0x5d, 0x60, 0xf2, 0xb, 0xd8, 0x87, 0xa1, 0x94, 0x16, 0x60, 0x19, 0xd9, 0x3d, 0x44, 0xb4, 0xcf, 0x57, 0x1c, 0x56, 0x9a, 0x1e, 0x88, 0x75, 0x1, 0xab, 0x67, 0x54, 0x7f, 0x20, 0x0, 0x71, 0x4, 0xd9, 0x8c, 0xdd, 0x6b, 0xe4, 0x87, 0x18, 0xe7, 0x92, 0x7, 0xa4, 0x13, 0x90, 0x7c, 0xa2, 0xf, 0x2, 0x4c, 0x5f, 0x7a, 0xe7, 0x96, 0x81, 0xce, 0x7, 0x96, 0x98, 0xbe, 0xb8, 0x5f, 0xa, 0x86, 0xa1, 0x8a, 0x31, 0xbb, 0xa8, 0xea, 0x4a, 0x7f, 0x45, 0xd2, 0x8c, 0x8d, 0x5f, 0x3f, 0x21, 0x41, 0xdc, 0x66, 0x9e, 0xba, 0x91, 0x47, 0x91, 0xd1, 0xcc, 0x5d, 0x81, 0x78, 0x61, 0x61, 0x67, 0x5f, 0x0, 0xed, 0x71, 0xe3, 0xe0, 0x40, 0xac, 0x10, 0xa3, 0x55, 0xff, 0x7, 0x24, 0xb0, 0x74, 0xb9, 0xbb, 0x40, 0x87, 0x21, 0x79, 0x8e, 0xbe, 0x90, 0x77, 0xb, 0x53, 0xfa, 0x13, 0x1, 0x58, 0x2c, 0xeb, 0x10, 0xdf, 0xb7, 0xa0, 0xec, 0x26, 0xc8, 0x9f, 0xd4, 0x21, 0xa5, 0x18, 0x4b, 0x5e, 0x33, 0xaf, 0x53, 0xab, 0xd, 0xdd, 0xd8, 0xe5, 0x5e, 0xd, 0xc3, 0x84, 0x0, 0x69, 0xfb, 0x68, 0xc6, 0x69, 0x58, 0x1b, 0x4a, 0x4c, 0xdf, 0xd5, 0xd3, 0xcf, 0x8e, 0xf0, 0xee, 0x33, 0x27, 0xb2, 0x61, 0x3e, 0x5d, 0x62, 0x55, 0x5a, 0xac, 0xee, 0x14, 0x5e, 0x8a, 0x8f, 0xd4, 0xc0, 0xea, 0x7f, 0x9f, 0xee, 0x73, 0x56, 0x30, 0x3e, 0x7d, 0x39, 0x9b, 0xc, 0x2e, 0xe, 0xf, 0xed, 0x36, 0x73, 0x51, 0x14, 0xad, 0x1f, 0x88, 0x17, 0x23, 0xc1, 0x78, 0xf1, 0x40, 0x56, 0xea, 0x1b, 0x18, 0x2c, 0x1, 0xcd, 0x90, 0x6d, 0x88, 0x9e, 0x4f, 0x43, 0x89, 0x19, 0x6b, 0x7a, 0xfa, 0xe1, 0x11, 0xde, 0x6d, 0xe6, 0xca, 0x5a, 0x8c, 0x8e, 0xe6, 0xcb, 0xac, 0x8b, 0xc2, 0x9b, 0xe6, 0xbe, 0xd, 0xc6, 0xf3, 0xb3, 0x58, 0x4c, 0xdb, 0xfb, 0xe2, 0x33, 0x7, 0x77, 0xa3, 0xe0, 0x33, 0xc6, 0x51, 0xec, 0x55, 0x29, 0xf4, 0xe, 0xb4, 0xaf, 0x4, 0xf2, 0x68, 0x98, 0xd4, 0xc9, 0xe6, 0xec, 0x9d, 0x40, 0x4, 0x63, 0x20, 0x18, 0x35, 0xa0, 0x19, 0x56, 0xda, 0xd8, 0x6, 0xaa, 0xce, 0xc7, 0xa, 0x47, 0x62, 0x7b, 0x74, 0x7f, 0x24, 0x0, 0xb, 0x68, 0x45, 0xf2, 0xf7, 0x83, 0xe4, 0xce, 0x9, 0x28, 0x73, 0xee, 0x91, 0xac, 0xfa, 0xcc, 0xf7, 0xd8, 0xb8, 0x8f, 0x7a, 0x1d, 0x1a, 0xde, 0x7d, 0xd6, 0xc4, 0xee, 0x32, 0x75, 0x1a, 0x2b, 0xfe, 0x30, 0x1e, 0xc3, 0xde, 0x92, 0x62, 0xeb, 0xc0, 0x68, 0x56, 0x87, 0x32, 0xcf, 0xd9, 0x84, 0x2, 0x15, 0x5d, 0x7c, 0xf7, 0xaf, 0xe6, 0xe9, 0xa7, 0x7, 0xc4, 0x5e, 0x96, 0xe2, 0x3c, 0x23, 0x39, 0x53, 0x8e, 0xe4, 0x4d, 0xea, 0x80, 0xde, 0x2f, 0x59, 0xf1, 0x4f, 0x51, 0xf8, 0x13, 0x58, 0xb0, 0xa3, 0x6c, 0xb6, 0xaa, 0x19, 0xff, 0x73, 0x77, 0xce, 0x51, 0x96, 0x2b, 0x69, 0x0, 0x3f, 0xe7, 0xd9, 0xb6, 0xed, 0x77, 0xb5, 0xb6, 0x6d, 0x7b, 0xdb, 0x5a, 0xdb, 0xb6, 0x6d, 0x7b, 0x9b, 0xd7, 0xcd, 0x35, 0x9f, 0xcd, 0xb1, 0x6d, 0xab, 0xbb, 0xb2, 0xbf, 0x4a, 0xbe, 0x4c, 0x32, 0x75, 0x36, 0xe7, 0x4e, 0xcf, 0xad, 0x34, 0xf2, 0xc7, 0xef, 0x54, 0xa5, 0x92, 0xfb, 0xb9, 0xaa, 0x82, 0xc1, 0x5b, 0x49, 0xfe, 0x3a, 0x84, 0x22, 0x98, 0xa, 0x3, 0x1e, 0x25, 0x1c, 0x4d, 0xa8, 0x2f, 0xfc, 0x1e, 0x22, 0x8e, 0x27, 0x89, 0xd6, 0xc7, 0x2c, 0x79, 0x6c, 0x52, 0xb, 0x0, 0xdf, 0x4e, 0x25, 0x41, 0x9f, 0xc2, 0xcf, 0x88, 0x18, 0xfc, 0x5e, 0x30, 0x8e, 0x5, 0xf3, 0x5c, 0x38, 0x6e, 0x24, 0xfd, 0xbd, 0xd7, 0xbd, 0xb1, 0xfb, 0x70, 0x5b, 0x5f, 0xf7, 0x3e, 0x4d, 0x1, 0xf0, 0x1a, 0x36, 0x30, 0x82, 0xbe, 0x92, 0x7e, 0x6c, 0xf0, 0xc2, 0x64, 0xd7, 0x8d, 0x8d, 0x7d, 0x8f, 0x12, 0x33, 0x92, 0x5a, 0x4, 0x4f, 0x94, 0xb8, 0x5a, 0x8b, 0x27, 0xb9, 0xda, 0x74, 0x63, 0x43, 0xdf, 0xa9, 0x16, 0x97, 0xaa, 0xee, 0x9f, 0x22, 0x14, 0xe1, 0x7f, 0x70, 0xa4, 0xd, 0x61, 0x1c, 0xd7, 0x42, 0x8c, 0xac, 0x71, 0x5e, 0xc9, 0xf1, 0xd2, 0x54, 0x63, 0x7f, 0x3a, 0xc9, 0x5, 0xc0, 0xbe, 0x7c, 0x6d, 0x10, 0x57, 0x30, 0xe2, 0x74, 0x70, 0xf1, 0xa2, 0x15, 0x19, 0x1a, 0x96, 0xfe, 0xed, 0x99, 0xe6, 0xfc, 0xb9, 0x16, 0x5f, 0x5a, 0x74, 0xf7, 0x6a, 0x45, 0x8, 0xd6, 0x28, 0x69, 0xa7, 0x80, 0x3f, 0xde, 0x94, 0x6a, 0xea, 0xbf, 0x3a, 0xd1, 0x5, 0xd0, 0x9c, 0xbf, 0x6, 0x5f, 0x27, 0x2c, 0xc7, 0x6e, 0x77, 0xba, 0x69, 0xe0, 0x3c, 0x8b, 0xcb, 0x54, 0x6f, 0x51, 0xaa, 0x4c, 0xf1, 0xb5, 0xcf, 0x89, 0x1b, 0xf4, 0x78, 0x6d, 0x43, 0xcf, 0x40, 0xaa, 0x69, 0xe0, 0xe2, 0x24, 0x17, 0x0, 0x33, 0xf5, 0x4a, 0x7c, 0x5d, 0x2f, 0x3e, 0xdb, 0x62, 0x3c, 0xdd, 0x3c, 0xad, 0x5, 0xd0, 0x1d, 0x3d, 0x2e, 0x44, 0x9f, 0xb, 0x40, 0xef, 0xf7, 0xd8, 0x2, 0xce, 0x4e, 0xf8, 0xa, 0x70, 0x19, 0x2b, 0xec, 0x82, 0x20, 0x6, 0xd2, 0x46, 0xc7, 0x25, 0x32, 0xd6, 0x37, 0xa, 0x29, 0xa0, 0xb0, 0xce, 0xb7, 0x79, 0xa3, 0x52, 0x14, 0xe1, 0xea, 0x0, 0x43, 0x1a, 0xa4, 0x8d, 0xa2, 0x21, 0x7a, 0xac, 0xd6, 0xef, 0x5d, 0x7d, 0xd, 0xc9, 0x2f, 0x80, 0x54, 0x93, 0x14, 0x0, 0x3e, 0xd7, 0x4b, 0x50, 0x0, 0xdd, 0x14, 0x40, 0xc1, 0x5e, 0x1, 0xa4, 0x75, 0x1, 0x78, 0xa, 0x94, 0x4e, 0x9a, 0x1, 0xf4, 0x84, 0xe8, 0x3e, 0x34, 0x8c, 0xdf, 0x6a, 0x7d, 0x14, 0x40, 0x9e, 0x0, 0x5d, 0x9c, 0xec, 0x15, 0xa0, 0x70, 0x25, 0xbe, 0x6e, 0xa8, 0x15, 0x1b, 0xa8, 0xd1, 0x7, 0xe9, 0x23, 0x6f, 0x3c, 0xd5, 0x68, 0x75, 0xb, 0xe8, 0xeb, 0xe5, 0x8d, 0x1c, 0x82, 0x5d, 0x25, 0xa, 0x9c, 0x98, 0x11, 0x5d, 0xbd, 0x37, 0x11, 0xa0, 0x44, 0xdf, 0x4, 0x66, 0x5a, 0x8a, 0xd7, 0xe8, 0x98, 0xe2, 0xaf, 0xcd, 0xd8, 0xed, 0x4e, 0x59, 0xbd, 0x9, 0x6c, 0xea, 0xff, 0xe9, 0x8d, 0x5a, 0x30, 0xb0, 0x2f, 0x7, 0xec, 0x3f, 0xa6, 0x5, 0x12, 0x6, 0x72, 0x2c, 0xd7, 0x4, 0x63, 0x26, 0xe6, 0xb5, 0xe1, 0xeb, 0x9, 0x8, 0xb2, 0xe9, 0x2f, 0xa5, 0x0, 0x52, 0xc9, 0x2e, 0x80, 0xc2, 0xb5, 0x7e, 0xc, 0x25, 0x1e, 0x26, 0x46, 0xbc, 0x8c, 0x6b, 0x18, 0xf3, 0x5a, 0x81, 0x3e, 0xb1, 0xdb, 0x4e, 0xdc, 0xce, 0xb5, 0xf8, 0xac, 0x9a, 0xff, 0x34, 0x8a, 0xb6, 0x84, 0x92, 0x1e, 0x37, 0x52, 0xc9, 0x3d, 0xbb, 0x52, 0xcd, 0x85, 0x47, 0x25, 0x7c, 0x5, 0x78, 0x8a, 0xf8, 0xab, 0xec, 0xc5, 0xae, 0x77, 0x13, 0x5, 0x70, 0xb2, 0xc5, 0x15, 0x60, 0xe0, 0x2d, 0x6c, 0x3, 0xeb, 0x74, 0xb5, 0x71, 0x3f, 0x0, 0xbd, 0xca, 0x6d, 0x41, 0xc6, 0xac, 0xe3, 0xaf, 0x1c, 0x38, 0xf2, 0xd8, 0xc4, 0xee, 0xff, 0x2d, 0xc5, 0xd3, 0x88, 0xed, 0x67, 0x6d, 0xc7, 0x90, 0x89, 0x43, 0xdc, 0xf2, 0x5f, 0xe6, 0x25, 0xd3, 0x61, 0x76, 0xaa, 0xb4, 0xb5, 0x74, 0x1, 0x37, 0x63, 0x9d, 0xbc, 0x5c, 0xf8, 0x7b, 0xa, 0x5, 0x1, 0x18, 0x2e, 0x7d, 0xbe, 0x6f, 0xab, 0xb4, 0xd7, 0x42, 0xbf, 0x10, 0x36, 0xac, 0x5f, 0xda, 0x10, 0xe6, 0xf5, 0x72, 0xd, 0x72, 0x15, 0xc7, 0x9e, 0xdc, 0xe6, 0x62, 0x52, 0xb, 0x80, 0xb8, 0x96, 0xaf, 0x61, 0x7b, 0xbd, 0xfd, 0x46, 0xb7, 0x0, 0x24, 0x5e, 0x12, 0xb3, 0x50, 0x1f, 0xcc, 0x78, 0x1a, 0xc8, 0x78, 0x28, 0x7e, 0xd0, 0xb7, 0x89, 0x7c, 0xfd, 0x83, 0x42, 0xf8, 0x6c, 0xba, 0xa5, 0x74, 0x8a, 0x8d, 0x6a, 0x3d, 0x1e, 0x41, 0x37, 0x30, 0x23, 0x9f, 0x9b, 0x6e, 0x29, 0x34, 0xd2, 0x7e, 0x12, 0xe1, 0xbf, 0xc4, 0x81, 0x3b, 0x30, 0x6e, 0xf, 0x38, 0x3e, 0x28, 0x57, 0xb4, 0x1a, 0xa7, 0xe, 0x94, 0x57, 0x0, 0x85, 0xf, 0x64, 0xdb, 0xaa, 0x27, 0x26, 0xb3, 0x0, 0x4a, 0x39, 0x9d, 0x28, 0x10, 0x9f, 0xeb, 0x23, 0x15, 0x9e, 0x8c, 0xc1, 0xd8, 0x16, 0xa, 0xe1, 0x6f, 0x4c, 0xe0, 0xf7, 0x90, 0xc3, 0x33, 0xac, 0x19, 0x9f, 0x6b, 0x1f, 0x3c, 0x91, 0xa, 0xbe, 0x98, 0x19, 0xfa, 0x38, 0xda, 0x97, 0xd2, 0xb6, 0xa3, 0xe8, 0x7e, 0x14, 0x4e, 0xa0, 0xd8, 0x9a, 0x43, 0x14, 0xd8, 0xca, 0x6c, 0x6b, 0xf9, 0x9, 0x9, 0xdd, 0x2, 0x1e, 0xe5, 0x27, 0xdf, 0x52, 0x11, 0x28, 0x21, 0x98, 0x88, 0x22, 0x9b, 0x8f, 0x6a, 0x1b, 0xd8, 0x6e, 0xca, 0xe8, 0x7c, 0x4d, 0xae, 0xad, 0x7c, 0x64, 0x5c, 0xe, 0x7d, 0x87, 0x22, 0x18, 0x7, 0x9d, 0x38, 0x87, 0xb6, 0x2e, 0x90, 0xa1, 0xc0, 0x61, 0xe5, 0x79, 0x4d, 0xd2, 0x92, 0xcf, 0xaa, 0x76, 0x3a, 0xf1, 0xfa, 0x9c, 0xc4, 0x49, 0x4d, 0x3e, 0x3e, 0x35, 0xe3, 0xab, 0xcc, 0x38, 0x8a, 0xae, 0x25, 0xe8, 0xfd, 0x52, 0xba, 0xa5, 0x7c, 0x83, 0x55, 0x87, 0x72, 0x9d, 0x23, 0x47, 0x30, 0x53, 0xdf, 0x80, 0x61, 0xe3, 0xae, 0xa2, 0xe6, 0x1, 0x5, 0xce, 0xc1, 0x93, 0x17, 0xa4, 0xef, 0xc9, 0x70, 0xc1, 0xe0, 0x6f, 0x66, 0xdb, 0xca, 0x67, 0x27, 0xab, 0x0, 0x2a, 0x4f, 0x26, 0x19, 0xeb, 0x3, 0x3f, 0xf3, 0x66, 0x1c, 0x26, 0x43, 0xf4, 0x6f, 0x49, 0xbe, 0xf4, 0x69, 0xc1, 0xd5, 0x97, 0x7f, 0x88, 0xed, 0xe7, 0x59, 0x71, 0xdc, 0xd4, 0x5c, 0xe2, 0x57, 0x1e, 0xef, 0xa2, 0x15, 0x38, 0x36, 0xc0, 0xe0, 0xb5, 0xc8, 0x7e, 0x41, 0xc2, 0xf6, 0xff, 0x26, 0x99, 0xa1, 0x76, 0xe2, 0x24, 0xa4, 0xa3, 0xcf, 0x29, 0xf0, 0x56, 0x84, 0xe6, 0xfc, 0x70, 0xb6, 0xbd, 0x7a, 0x41, 0x5c, 0x2f, 0x36, 0x56, 0x84, 0x95, 0xd1, 0x1a, 0x14, 0x4, 0xfa, 0x46, 0xb, 0xe6, 0xb8, 0xd2, 0x20, 0x7, 0xc7, 0x8a, 0xdf, 0xc8, 0xb4, 0x55, 0x4f, 0x4e, 0xc4, 0xde, 0xdf, 0x5a, 0x4e, 0xe3, 0xcf, 0x5f, 0xd3, 0x4d, 0x92, 0x34, 0x29, 0x82, 0x38, 0x11, 0x5d, 0x12, 0xcb, 0x42, 0x8f, 0xfc, 0x27, 0x96, 0xd6, 0xbf, 0x6c, 0x1d, 0x96, 0x69, 0x29, 0xfd, 0x80, 0x76, 0xf, 0x38, 0x14, 0x83, 0x15, 0x90, 0x5, 0x85, 0xa5, 0xec, 0x5b, 0xaf, 0x4c, 0xc8, 0xe3, 0xdf, 0x47, 0x24, 0x3e, 0x4a, 0xda, 0x29, 0x1, 0x5d, 0xa2, 0xaf, 0xf4, 0xeb, 0x58, 0x1c, 0xd3, 0xff, 0x3a, 0x55, 0xa6, 0xb5, 0xf2, 0x54, 0x1e, 0x39, 0x76, 0x90, 0x30, 0x14, 0x15, 0x95, 0x28, 0x17, 0x8a, 0x61, 0x6a, 0x18, 0x5c, 0xf4, 0x8d, 0x76, 0xe5, 0x68, 0x79, 0x2c, 0x9b, 0xbf, 0x47, 0xfe, 0x39, 0xb3, 0x3b, 0xf9, 0x95, 0x27, 0xe3, 0xcf, 0xcd, 0x7e, 0x42, 0x82, 0x38, 0xd0, 0xd6, 0x8e, 0x55, 0x8d, 0x31, 0xe3, 0xd8, 0x40, 0x26, 0xd2, 0x2e, 0xee, 0x3f, 0x3e, 0x19, 0xdf, 0xcd, 0x4d, 0xfb, 0xd0, 0x89, 0xbc, 0xbe, 0xdd, 0x24, 0x5, 0xe0, 0x48, 0x5b, 0x17, 0xfb, 0x65, 0xb5, 0x14, 0xb7, 0x13, 0xc0, 0x8f, 0xcc, 0xd6, 0xe4, 0xe7, 0x3a, 0x86, 0x4e, 0x66, 0xf6, 0xff, 0x42, 0xfc, 0x91, 0xe4, 0x4f, 0x9, 0xca, 0x9f, 0x44, 0xc4, 0x70, 0x8c, 0x18, 0xa6, 0xe2, 0x5e, 0xe2, 0x46, 0x51, 0xb4, 0x17, 0xf4, 0xac, 0xb5, 0x81, 0xd2, 0xa4, 0x5d, 0x67, 0x4a, 0xf, 0xe2, 0xc0, 0x8b, 0x67, 0xe9, 0xa3, 0xdf, 0xc7, 0xf0, 0x61, 0x47, 0xfd, 0x71, 0x29, 0x4e, 0xfe, 0x3a, 0x89, 0x1f, 0xf7, 0x1f, 0x5f, 0x9d, 0x8a, 0x4a, 0xbf, 0x6, 0x65, 0xab, 0x33, 0x35, 0x1c, 0xcd, 0xa, 0xc6, 0x58, 0xe4, 0x31, 0xc9, 0x9f, 0xf0, 0xb6, 0x82, 0xf2, 0xf0, 0x23, 0x3a, 0x87, 0x4f, 0x9a, 0x55, 0xc9, 0x6f, 0x1f, 0x6c, 0xc0, 0xfe, 0x85, 0xe2, 0x8b, 0x3b, 0x23, 0xa3, 0x7c, 0xae, 0x35, 0x6e, 0x9e, 0x97, 0x36, 0x1a, 0x3f, 0xf, 0x5e, 0x1, 0x7c, 0x32, 0x76, 0x67, 0x1f, 0xd9, 0x35, 0x76, 0x34, 0x2f, 0x6f, 0x96, 0x7b, 0xc6, 0x95, 0xad, 0x40, 0xf0, 0x94, 0xb4, 0x1e, 0x6d, 0x95, 0xa1, 0x59, 0xb4, 0xf4, 0x3f, 0x86, 0x7d, 0xf7, 0x56, 0x89, 0x87, 0x2, 0x67, 0xa, 0x51, 0x1a, 0x1d, 0x33, 0x26, 0xce, 0x5c, 0x9e, 0xa4, 0x5e, 0x11, 0xab, 0xb3, 0x8f, 0xe8, 0xfa, 0xd3, 0x11, 0x54, 0x7b, 0x27, 0xca, 0x36, 0x83, 0x93, 0x6d, 0xb3, 0x87, 0xe, 0x20, 0x2d, 0x33, 0x47, 0xfa, 0xed, 0xd5, 0xea, 0xcc, 0x4f, 0xfe, 0x70, 0x8e, 0x2d, 0xeb, 0x1e, 0xdf, 0x76, 0x90, 0xb6, 0xae, 0x38, 0x44, 0xca, 0x89, 0x1a, 0xf7, 0x72, 0x31, 0xf8, 0xb9, 0x78, 0x9d, 0xed, 0x1a, 0x3b, 0x82, 0x6a, 0xff, 0x30, 0xca, 0xf6, 0x7a, 0xa, 0x2b, 0x4a, 0x5a, 0x6b, 0x84, 0xe5, 0xa6, 0x5b, 0xd0, 0xd3, 0x56, 0x2d, 0xcc, 0xdc, 0x65, 0x7f, 0x38, 0x4b, 0x91, 0xce, 0xd1, 0xb6, 0x82, 0x12, 0x1f, 0x94, 0xa5, 0x38, 0x28, 0x5f, 0xae, 0x81, 0x13, 0xc1, 0x38, 0xb9, 0x69, 0x8f, 0xd3, 0xd9, 0xa3, 0x98, 0xf9, 0x5f, 0xa2, 0xda, 0xc7, 0x41, 0x12, 0x55, 0x55, 0xf4, 0x75, 0x2b, 0x84, 0xfb, 0xe1, 0x31, 0xf3, 0x9a, 0xe8, 0xeb, 0x90, 0x17, 0xc8, 0xe5, 0x58, 0xf4, 0xdd, 0xc4, 0xd3, 0xc7, 0x69, 0x33, 0x6b, 0xe6, 0x8f, 0x3c, 0x83, 0xfd, 0x76, 0x19, 0xb6, 0x19, 0x13, 0xa1, 0xaa, 0x20, 0xc2, 0x3f, 0x21, 0x7a, 0x4c, 0xc0, 0x6f, 0x66, 0x39, 0x68, 0xff, 0x77, 0x72, 0x7c, 0x3f, 0x13, 0x61, 0x5, 0x13, 0x62, 0x27, 0x5b, 0xaf, 0x1e, 0x73, 0xf1, 0xfa, 0x65, 0xb7, 0xcf, 0xf9, 0x3b, 0xb2, 0x1d, 0xc3, 0x4f, 0x8f, 0x69, 0xd9, 0x1f, 0x3b, 0x8e, 0x4, 0x7c, 0x27, 0xdd, 0x5a, 0x99, 0xc8, 0x60, 0x1c, 0x55, 0xef, 0x3a, 0x2a, 0xfd, 0x43, 0xa3, 0xcd, 0xe8, 0xb, 0xa6, 0x5c, 0xfa, 0x8a, 0x40, 0x43, 0x65, 0x43, 0xa6, 0x7d, 0x68, 0x46, 0x7c, 0x35, 0xcc, 0xb4, 0xd, 0x7e, 0x8a, 0x80, 0x6f, 0xd4, 0x76, 0x82, 0x97, 0x74, 0xb1, 0xb5, 0xb6, 0xcf, 0xb5, 0x10, 0x59, 0x81, 0xec, 0x7f, 0x7b, 0xdf, 0x62, 0x46, 0x8f, 0xce, 0x76, 0x78, 0xf0, 0x5d, 0xe6, 0x1c, 0xa, 0xf0, 0x6, 0xe2, 0xf1, 0x64, 0x92, 0xfe, 0x12, 0x72, 0xf3, 0x56, 0xda, 0xc7, 0x30, 0x76, 0x78, 0xc, 0xc9, 0x1f, 0x3d, 0x81, 0x7d, 0xee, 0x93, 0xcc, 0xfe, 0xa0, 0xb2, 0xe9, 0x83, 0x73, 0xf0, 0x54, 0xa5, 0x9d, 0x14, 0x4a, 0x74, 0x86, 0x67, 0xd6, 0x16, 0x9c, 0xed, 0x79, 0xf4, 0xbb, 0x6e, 0x8a, 0x7e, 0xcd, 0x19, 0xef, 0xfd, 0xcf, 0xd1, 0xe8, 0x9f, 0xeb, 0xcd, 0xca, 0xfd, 0x3e, 0x29, 0xeb, 0x31, 0xc1, 0x57, 0xdd, 0xb2, 0xa4, 0xdf, 0x49, 0xe2, 0x4f, 0xfd, 0x7f, 0xb6, 0xe8, 0x18, 0x60, 0xc7, 0x61, 0xe4, 0xe7, 0x70, 0xf2, 0x73, 0x24, 0xd7, 0x1f, 0x66, 0xdd, 0x61, 0xaa, 0xea, 0x78, 0xe8, 0xe4, 0xe6, 0x62, 0x42, 0x3b, 0x99, 0xc3, 0x49, 0x8d, 0xf4, 0xe3, 0x45, 0x2, 0x21, 0x7d, 0x25, 0x38, 0xb0, 0x87, 0xc0, 0x2c, 0xcd, 0x52, 0x94, 0xa9, 0x96, 0xca, 0x91, 0x53, 0x91, 0x78, 0x66, 0x1a, 0x1, 0x1e, 0x2a, 0xa2, 0x77, 0x65, 0x78, 0xb5, 0xf2, 0x6d, 0xb2, 0x85, 0x11, 0xdb, 0x7d, 0xe8, 0xfc, 0xcd, 0x74, 0xef, 0x73, 0x4f, 0xcf, 0xba, 0xc6, 0xc, 0xb9, 0x15, 0xae, 0xdb, 0x30, 0x4, 0xc4, 0x3c, 0x6, 0xa3, 0xf, 0x91, 0xbf, 0x33, 0xae, 0x8d, 0xb8, 0x46, 0x85, 0xce, 0x2b, 0x19, 0xd3, 0xf6, 0x6c, 0xa5, 0xf2, 0xef, 0x63, 0x39, 0xfc, 0x44, 0xae, 0x73, 0xec, 0xc4, 0x98, 0xb6, 0xbe, 0x2c, 0x85, 0xf6, 0x1b, 0x96, 0xfc, 0x85, 0x2c, 0xf9, 0x7b, 0xfd, 0x19, 0xea, 0xdb, 0x22, 0x31, 0x89, 0xf0, 0x25, 0x2a, 0x46, 0x35, 0xe3, 0x25, 0x2b, 0xc9, 0xd0, 0xdf, 0x1f, 0xf5, 0xb6, 0xbf, 0x1e, 0x3d, 0xbd, 0x77, 0xb9, 0x1d, 0x23, 0xd7, 0x63, 0xc8, 0x9d, 0xbe, 0x71, 0xfe, 0xb2, 0x2c, 0xc7, 0x53, 0xc9, 0x1, 0x3a, 0x7d, 0x1b, 0x24, 0x50, 0x9b, 0x28, 0x84, 0xb9, 0x24, 0xea, 0x57, 0xd8, 0xfb, 0x4a, 0xf6, 0xc8, 0x93, 0xeb, 0xf4, 0x39, 0x45, 0xe1, 0x7f, 0x94, 0xc2, 0xfa, 0x1b, 0xb2, 0x17, 0xc1, 0x2e, 0x3f, 0xf1, 0xa2, 0xd7, 0xd7, 0x6d, 0xdd, 0xc7, 0xc0, 0xaf, 0xa1, 0xc5, 0xe8, 0x7f, 0xf3, 0xcc, 0xb8, 0xdb, 0xed, 0x1c, 0x7d, 0x3e, 0x6, 0x39, 0x30, 0x41, 0xa0, 0x9d, 0x69, 0x46, 0xb9, 0x48, 0x5f, 0xec, 0xd2, 0x7d, 0xdd, 0xee, 0xa6, 0x8, 0x16, 0xc3, 0xed, 0x30, 0x44, 0x22, 0xbf, 0xc9, 0xc, 0x7e, 0x1d, 0xab, 0x43, 0x9a, 0xfe, 0x85, 0xf4, 0x4f, 0xcd, 0x75, 0xfd, 0xe9, 0x44, 0x38, 0xee, 0x91, 0x6f, 0xfd, 0xcb, 0x89, 0x8c, 0x9d, 0x41, 0xb1, 0x5c, 0xc4, 0xf8, 0xe3, 0xd8, 0x47, 0xdf, 0xc3, 0xf1, 0x0, 0xbf, 0xbb, 0x9, 0xee, 0x67, 0xc6, 0xaf, 0xf7, 0x65, 0x83, 0xa9, 0x57, 0x8f, 0x49, 0xbf, 0x7e, 0x4c, 0x79, 0xf4, 0x27, 0xb0, 0xe5, 0x7f, 0xed, 0x9c, 0x3, 0x90, 0x2c, 0x49, 0x13, 0x80, 0xfb, 0xff, 0xcf, 0xb6, 0x6d, 0x6b, 0x7a, 0xe6, 0x6c, 0xdb, 0xb6, 0x6f, 0x7d, 0xb6, 0x6d, 0xdb, 0x76, 0xe0, 0x7c, 0xb7, 0xec, 0x99, 0xd5, 0xd9, 0xb6, 0x6d, 0x63, 0xdf, 0x54, 0xdd, 0x57, 0xbd, 0x39, 0x13, 0xf5, 0x2a, 0x5e, 0x5f, 0xaf, 0x63, 0xd0, 0x19, 0xf1, 0x45, 0x66, 0x55, 0x67, 0xd6, 0x54, 0x67, 0x66, 0xf5, 0xcc, 0xf2, 0x8a, 0xb5, 0x8f, 0x7b, 0xf9, 0x7f, 0x5e, 0x29, 0x8, 0x49, 0x9a, 0x8d, 0xd, 0x75, 0x93, 0x18, 0x4d, 0x57, 0x2, 0x9b, 0x24, 0x19, 0x46, 0x8f, 0xd, 0x4f, 0x58, 0xc8, 0x9c, 0xec, 0xc1, 0xb5, 0xd1, 0x36, 0xbf, 0x11, 0xf3, 0x1, 0x7b, 0x7f, 0x9e, 0x2f, 0x61, 0x7b, 0xfd, 0x9a, 0x27, 0x72, 0x7e, 0x4d, 0x73, 0x0, 0x1d, 0x8c, 0x3, 0x4e, 0x79, 0x27, 0xf4, 0x72, 0xfd, 0x65, 0x7c, 0xbf, 0x2, 0xed, 0xc2, 0x9a, 0xb2, 0xee, 0xf0, 0xef, 0x1, 0xed, 0xe2, 0xee, 0x3d, 0xf, 0x9a, 0x3d, 0x35, 0xaf, 0x50, 0xd3, 0x3c, 0xaf, 0x57, 0x2a, 0xc2, 0x9, 0x9a, 0x88, 0x6f, 0x0, 0xad, 0x27, 0x49, 0x51, 0x6, 0x29, 0x48, 0x29, 0x61, 0xf6, 0x55, 0xb4, 0xad, 0xf9, 0x62, 0x31, 0xa3, 0xe6, 0x42, 0x6d, 0xc7, 0x5a, 0xf1, 0x63, 0x75, 0xaf, 0x92, 0x57, 0x63, 0x77, 0xfa, 0x75, 0xad, 0xa5, 0xf7, 0xb, 0xb2, 0x3c, 0x5, 0x26, 0x81, 0x73, 0xe9, 0x4e, 0x1d, 0x52, 0xd3, 0xac, 0x38, 0x49, 0x46, 0x6b, 0xdf, 0x82, 0x31, 0xd8, 0x63, 0x57, 0x8b, 0x1d, 0xed, 0x67, 0x13, 0xbd, 0x66, 0xc4, 0x7a, 0xae, 0x2f, 0x7b, 0xb5, 0xed, 0x22, 0xe2, 0x8f, 0x4d, 0xb1, 0x5d, 0x3b, 0x6a, 0xad, 0x8, 0x7c, 0x8b, 0xb8, 0x78, 0xd7, 0x76, 0xf7, 0xc6, 0x41, 0x3b, 0xdc, 0x2b, 0x55, 0xf1, 0xeb, 0xda, 0xe6, 0xe0, 0xfd, 0xf2, 0x2d, 0xbf, 0xb6, 0x7f, 0xd3, 0xe8, 0xf0, 0xb4, 0x98, 0xb1, 0x20, 0x36, 0x14, 0x1b, 0x84, 0x64, 0xcb, 0x1c, 0x36, 0x73, 0x85, 0x58, 0x87, 0x1a, 0xc7, 0x76, 0xfd, 0xa2, 0xfd, 0x21, 0xde, 0xdf, 0xf2, 0x8b, 0x88, 0x8f, 0xb0, 0x23, 0xe7, 0x6c, 0x86, 0xb5, 0x17, 0xb0, 0x1b, 0xa0, 0xf5, 0x68, 0xaf, 0x94, 0xc5, 0xaf, 0x6d, 0xdb, 0x9a, 0xee, 0xcd, 0x83, 0xa6, 0x19, 0x14, 0x68, 0x6c, 0x25, 0xe3, 0xe2, 0x9c, 0xcc, 0x17, 0x35, 0x28, 0xeb, 0x7a, 0xc1, 0x4e, 0x0, 0x2b, 0x87, 0xe8, 0x96, 0xe7, 0x68, 0x82, 0x75, 0xbc, 0x52, 0x96, 0x15, 0x6a, 0x5a, 0xae, 0x5e, 0x81, 0x6e, 0x75, 0xb, 0x2b, 0x63, 0x80, 0xda, 0x96, 0xf7, 0x79, 0x62, 0xdc, 0x2, 0xa7, 0x63, 0x37, 0x33, 0x37, 0xc1, 0xe6, 0x18, 0x3e, 0xad, 0x15, 0x51, 0x7c, 0x3b, 0x87, 0x34, 0xc0, 0xcd, 0x2b, 0x36, 0x6, 0x93, 0x78, 0xa5, 0x2a, 0xa9, 0xba, 0xf6, 0x29, 0xd9, 0x64, 0x1b, 0xdd, 0x3a, 0xe, 0xb4, 0xc5, 0x8f, 0x34, 0x47, 0x3b, 0xfa, 0x4a, 0xa, 0xbf, 0x1b, 0xdf, 0xc4, 0x98, 0x3c, 0xf4, 0xaf, 0x6d, 0x5b, 0x6, 0xff, 0x5e, 0x8a, 0x55, 0xf4, 0x35, 0x36, 0x28, 0x40, 0x27, 0x80, 0x22, 0x2f, 0xa, 0x6d, 0xe, 0xcf, 0x47, 0x3c, 0x69, 0xf7, 0xf0, 0x4a, 0x59, 0xd2, 0xf5, 0x1d, 0x4b, 0x52, 0xe4, 0x4f, 0x29, 0xec, 0x57, 0x14, 0xbd, 0x83, 0x8d, 0x3f, 0xc8, 0xd7, 0xd6, 0x27, 0x31, 0x3f, 0x47, 0x84, 0xff, 0xbc, 0xf8, 0xe6, 0xf0, 0xd5, 0x7c, 0xca, 0xd, 0xe1, 0x46, 0x8d, 0x56, 0xa0, 0xa3, 0x69, 0xb3, 0xec, 0x61, 0xfb, 0x45, 0xf8, 0xb6, 0xb9, 0xf6, 0x28, 0xd2, 0x16, 0xf7, 0x1a, 0xca, 0xe4, 0x65, 0x85, 0x9a, 0xd6, 0x47, 0x39, 0x68, 0x53, 0x94, 0x7a, 0x13, 0x9c, 0xe5, 0xd7, 0xb5, 0xd7, 0xfb, 0xf5, 0x1d, 0x53, 0xf, 0xec, 0x43, 0x64, 0xc7, 0x64, 0x74, 0x76, 0x6f, 0x4a, 0xba, 0x5e, 0x92, 0xa1, 0x52, 0x62, 0xa7, 0x1d, 0x7c, 0x87, 0xb4, 0xad, 0x23, 0x89, 0x8f, 0x77, 0x7d, 0x5c, 0xff, 0x74, 0xd4, 0x5e, 0x22, 0xec, 0x91, 0xc6, 0xe4, 0x3, 0xbe, 0x25, 0xb7, 0x47, 0x7a, 0x15, 0x25, 0xf2, 0x6b, 0xe5, 0xdc, 0x5c, 0x2b, 0x9d, 0xfe, 0xb, 0xd0, 0x8, 0x61, 0x22, 0x55, 0x7f, 0x42, 0xdb, 0x1d, 0xda, 0x22, 0xc6, 0x68, 0x21, 0xca, 0xd7, 0xf5, 0x89, 0x5f, 0xc7, 0x9d, 0x8b, 0x7e, 0xd, 0x77, 0x7e, 0xf0, 0x7b, 0x8a, 0x88, 0x91, 0x27, 0x3, 0x5a, 0xc9, 0x1, 0x9, 0x56, 0xa8, 0x6d, 0x9b, 0xcc, 0xab, 0x44, 0x49, 0xd7, 0x7, 0x17, 0xf3, 0xd9, 0xe0, 0xf3, 0xe2, 0x69, 0xaa, 0x6f, 0x57, 0xa0, 0xab, 0x1d, 0x72, 0xa1, 0xd0, 0xa, 0xad, 0xe1, 0x47, 0xde, 0x6, 0x4e, 0xf3, 0x2a, 0x55, 0x52, 0x75, 0x1d, 0x3b, 0xd2, 0x4, 0xaf, 0xf1, 0xa8, 0xcb, 0xf3, 0x76, 0xa2, 0xcb, 0x1d, 0xee, 0x43, 0xc5, 0xce, 0xc5, 0xc7, 0x2a, 0x6c, 0x2d, 0x7c, 0x4a, 0x3, 0x1c, 0xe6, 0x55, 0xb2, 0xf8, 0x75, 0xc1, 0xec, 0x7c, 0x86, 0x78, 0x51, 0x6e, 0x1e, 0x2, 0xd1, 0x36, 0x81, 0x35, 0x3f, 0x78, 0xe2, 0xd7, 0x8, 0xe2, 0x63, 0x6d, 0x1b, 0xdc, 0x79, 0x50, 0x85, 0x42, 0x82, 0xf6, 0x43, 0x3b, 0xf4, 0x51, 0xc0, 0xb8, 0x5d, 0x45, 0xad, 0x6b, 0x62, 0xb0, 0x8d, 0xbf, 0x2, 0x6d, 0x48, 0xd5, 0xb6, 0x7f, 0x86, 0x6e, 0xf4, 0x2a, 0x5d, 0x32, 0xd, 0xd9, 0x9a, 0x74, 0x43, 0xf0, 0x85, 0x49, 0x18, 0x5a, 0x81, 0x2e, 0x17, 0xd8, 0xb3, 0xbd, 0x5f, 0x55, 0x98, 0x83, 0x71, 0xf0, 0x97, 0xc1, 0xf2, 0x53, 0x72, 0x8f, 0x2e, 0xca, 0x5e, 0x8f, 0x13, 0x6f, 0xf4, 0x97, 0xe4, 0xa5, 0xce, 0xab, 0x6, 0xf1, 0xeb, 0x83, 0xde, 0x74, 0x43, 0x56, 0xa3, 0x35, 0xda, 0x5, 0x2, 0x21, 0x1b, 0x47, 0x7c, 0xbc, 0xd8, 0xf1, 0xd7, 0x5d, 0xb2, 0x36, 0x4a, 0xd0, 0xb2, 0xef, 0xb0, 0xb0, 0x42, 0x61, 0x3e, 0xc8, 0x34, 0xe6, 0x56, 0x46, 0xef, 0xce, 0x29, 0x2e, 0x36, 0x8a, 0x1b, 0x67, 0x8f, 0xa5, 0x71, 0xcc, 0xfc, 0xab, 0xb0, 0xb9, 0x57, 0x2d, 0x42, 0xa7, 0xbf, 0x1, 0xe1, 0xa3, 0xd2, 0xe8, 0x92, 0xa4, 0x71, 0xbc, 0xb1, 0x12, 0x74, 0x61, 0xdf, 0xf2, 0xa8, 0x37, 0xcd, 0x92, 0xa3, 0x78, 0x1b, 0x52, 0xd0, 0x49, 0x3c, 0x11, 0x1a, 0x61, 0x43, 0xae, 0xfd, 0xa, 0xda, 0x8d, 0xb3, 0xc6, 0xf9, 0xfe, 0xf8, 0x6c, 0x2b, 0xfe, 0x19, 0xaf, 0x5a, 0xc4, 0xaf, 0xcf, 0x2e, 0x44, 0xc2, 0xde, 0x4f, 0x87, 0x49, 0xce, 0x29, 0xd0, 0x0, 0xe1, 0xd8, 0x21, 0x5b, 0x20, 0x62, 0xce, 0xbd, 0x1e, 0x45, 0xd6, 0xd6, 0x11, 0x36, 0x38, 0x31, 0xec, 0x51, 0x59, 0xb1, 0x8a, 0x62, 0xcb, 0x7c, 0x0, 0xd9, 0x7, 0x33, 0x8d, 0x9d, 0xab, 0xa5, 0x1b, 0x73, 0x53, 0xa4, 0x9b, 0x7a, 0xfe, 0xe7, 0x39, 0x92, 0x6e, 0xc8, 0x2d, 0xcc, 0x7d, 0x3e, 0x81, 0x9f, 0xac, 0x93, 0x1b, 0x6f, 0x2d, 0x59, 0xff, 0x46, 0xbf, 0x21, 0xbb, 0xa0, 0x57, 0x4d, 0xc2, 0xd, 0x6f, 0xc6, 0x8d, 0x7f, 0x26, 0xd, 0x60, 0x35, 0x41, 0x3c, 0xc4, 0x28, 0xdb, 0x6, 0xfb, 0x9a, 0xd1, 0x23, 0x8e, 0xfd, 0xf8, 0x67, 0xfc, 0x1, 0x45, 0x3f, 0x97, 0xa2, 0x2f, 0xa, 0x53, 0x31, 0xfe, 0xcf, 0xdf, 0xc8, 0xc5, 0x67, 0x16, 0x1a, 0xe1, 0x60, 0xeb, 0xad, 0x4e, 0x89, 0x86, 0xdc, 0xe1, 0xe9, 0xc6, 0xce, 0x99, 0xbc, 0x6a, 0x13, 0x1a, 0xe0, 0x4c, 0x6e, 0xfe, 0x67, 0x93, 0x5c, 0xb4, 0x26, 0xa1, 0x83, 0x82, 0x98, 0x3c, 0xd8, 0x63, 0x83, 0x72, 0xfc, 0x54, 0x41, 0x8b, 0xaf, 0x3b, 0x1f, 0x8b, 0xb3, 0x7e, 0x1f, 0xc5, 0x5a, 0x3c, 0xd3, 0xd4, 0x35, 0x27, 0x7a, 0x1a, 0x6f, 0x10, 0x42, 0xcc, 0x14, 0xc4, 0x2c, 0xc4, 0x1a, 0x9f, 0x81, 0xe6, 0x9, 0xf8, 0x8d, 0xdf, 0x90, 0x5b, 0xdb, 0x97, 0xb7, 0x8c, 0xaa, 0x13, 0x6e, 0xbe, 0x9b, 0x93, 0x91, 0xcf, 0x34, 0x75, 0x6a, 0x50, 0xa2, 0xe3, 0x50, 0x5, 0x88, 0xd5, 0x24, 0xf4, 0x26, 0xd8, 0x1f, 0xee, 0x66, 0xfc, 0x17, 0xe8, 0x28, 0xec, 0x78, 0x6b, 0x1c, 0xb, 0xbe, 0xca, 0xb2, 0xff, 0xf1, 0x86, 0x29, 0x34, 0xc2, 0x42, 0xec, 0x77, 0x7, 0x58, 0xd2, 0xab, 0x66, 0x21, 0x1, 0x5f, 0x83, 0x26, 0x21, 0xa, 0xb4, 0xd8, 0x91, 0x70, 0xdd, 0x1d, 0xbf, 0x97, 0x6e, 0xec, 0x4a, 0xc1, 0xc4, 0x8c, 0xe7, 0xce, 0x34, 0x75, 0xa7, 0x60, 0x4d, 0xc6, 0xeb, 0xc3, 0x26, 0xb0, 0x23, 0x1c, 0x83, 0xdf, 0xb5, 0x70, 0x3f, 0x3c, 0xb, 0xda, 0x7e, 0xcd, 0x81, 0x42, 0x8c, 0x2, 0x4d, 0xd3, 0xfe, 0x63, 0x5e, 0xcf, 0x1b, 0xa6, 0xac, 0x74, 0x48, 0x2f, 0x6b, 0x54, 0xb9, 0x64, 0x1a, 0xbb, 0x2e, 0xee, 0x2f, 0x8, 0x49, 0x86, 0x15, 0xad, 0xa2, 0x60, 0xbb, 0x28, 0x8, 0xfd, 0xf0, 0x57, 0x60, 0xfc, 0xb6, 0x49, 0xd5, 0x65, 0x23, 0xdf, 0x7b, 0xcd, 0x7, 0x32, 0x7c, 0xa6, 0x85, 0x39, 0x61, 0x3e, 0xe0, 0x4b, 0xb3, 0xce, 0x3e, 0x7b, 0x2d, 0x74, 0x2c, 0xe2, 0xab, 0x88, 0xd5, 0xd0, 0x37, 0x12, 0xd, 0x90, 0x8, 0x42, 0x22, 0xe7, 0xe2, 0xc4, 0x6e, 0x4b, 0x13, 0x3c, 0x16, 0x16, 0xfd, 0xe0, 0xee, 0x2, 0xca, 0x1a, 0x2b, 0x6b, 0xac, 0xc, 0x72, 0x8a, 0xcf, 0x26, 0x7e, 0xf2, 0x41, 0xbe, 0xde, 0xc4, 0x14, 0xb2, 0x93, 0xb5, 0xf2, 0xb2, 0x5e, 0x34, 0x4d, 0xe0, 0xec, 0x47, 0xf8, 0x91, 0x3d, 0x27, 0xd, 0x30, 0xc2, 0x8d, 0xb0, 0x14, 0x49, 0xdd, 0x1b, 0x9e, 0x0, 0xd, 0x24, 0xbd, 0xc7, 0xa0, 0x84, 0xd0, 0x66, 0xde, 0xa0, 0xe1, 0x29, 0x62, 0x66, 0xf6, 0x6, 0x29, 0xac, 0x31, 0x9, 0xd4, 0x12, 0x3b, 0x4e, 0x5e, 0x43, 0xd6, 0x8e, 0xc7, 0xf8, 0x13, 0x97, 0x47, 0xbf, 0x4, 0x13, 0x79, 0xa3, 0x22, 0x49, 0x23, 0x2c, 0xb, 0xfb, 0x92, 0xe0, 0xdb, 0xe0, 0x67, 0xd0, 0x16, 0x4a, 0x8a, 0xf0, 0x3d, 0xac, 0x31, 0xf4, 0xf, 0x60, 0x3d, 0x73, 0x11, 0xaf, 0xad, 0x6, 0x80, 0x81, 0x35, 0x0, 0xfc, 0x43, 0xec, 0x9d, 0xf0, 0x7f, 0x6f, 0xf4, 0x24, 0x91, 0x74, 0x53, 0xf7, 0x1c, 0xb0, 0x3f, 0x9c, 0x43, 0xc1, 0x3a, 0x40, 0x67, 0x28, 0x2, 0xfa, 0x77, 0xe6, 0xf6, 0x1e, 0x81, 0xf5, 0x3b, 0x56, 0x3c, 0xb8, 0xb7, 0xf, 0x58, 0x93, 0x2, 0x1f, 0xd2, 0x4b, 0x91, 0x5, 0x6c, 0x1b, 0xae, 0x2b, 0x33, 0x2f, 0xfc, 0x49, 0xec, 0xd1, 0x90, 0x34, 0xc0, 0x58, 0x9, 0x45, 0xf0, 0xe1, 0x42, 0xb8, 0x1e, 0x4e, 0x1e, 0x89, 0x5f, 0x48, 0xa1, 0x80, 0x1b, 0xd2, 0x50, 0x7f, 0x80, 0x29, 0xb2, 0x12, 0xb4, 0x8c, 0x6d, 0x94, 0xc1, 0xcc, 0xb, 0x79, 0xd8, 0x4c, 0xfe, 0x34, 0xbd, 0x5c, 0x25, 0x11, 0x9a, 0xc0, 0x7c, 0xcb, 0xf6, 0x33, 0xb4, 0xa6, 0x98, 0xe3, 0xc1, 0xdc, 0x4, 0xc1, 0xff, 0x53, 0x74, 0x2b, 0x3e, 0x73, 0x7b, 0xe5, 0x2f, 0x89, 0xf0, 0x68, 0x3f, 0x8b, 0xd3, 0xdd, 0x27, 0x8f, 0x7f, 0x85, 0xfe, 0x1b, 0x7e, 0xc6, 0xfe, 0x1a, 0xfd, 0x21, 0xbc, 0xcc, 0x69, 0xf, 0xd0, 0xf, 0x33, 0x77, 0x2b, 0xf6, 0xfe, 0x2b, 0x1d, 0xfa, 0xe4, 0xd2, 0x5e, 0x65, 0x48, 0x22, 0x9c, 0xe4, 0xa9, 0x38, 0xd1, 0x77, 0xc2, 0x43, 0xd8, 0x77, 0xc0, 0x35, 0x70, 0x16, 0xff, 0x60, 0xf9, 0x70, 0xd8, 0x3, 0x7b, 0xd, 0xae, 0xcd, 0xe2, 0x95, 0xbd, 0x24, 0x92, 0x48, 0x22, 0x89, 0x24, 0x92, 0x48, 0x22, 0x89, 0x24, 0x92, 0x48, 0x22, 0x89, 0xfc, 0xb, 0x31, 0x1b, 0x93, 0xfa, 0xac, 0xe1, 0x98, 0xc3, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char mini_checkerboard_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x0, 0x0, 0x0, 0x0, 0x3a, 0x98, 0xa0, 0xbd, 0x0, 0x0, 0x0, 0x17, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x78, 0x0, 0x5, 0xff, 0xa1, 0x60, 0xa0, 0x4, 0x60, 0xc, 0x98, 0xc4, 0x0, 0x9, 0x0, 0x0, 0x44, 0x81, 0xef, 0x81, 0xc1, 0x26, 0x8e, 0x8, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -238,10 +206,6 @@ static const unsigned char option_button_disabled_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x40, 0xde, 0x8d, 0x6b, 0x0, 0x0, 0x1, 0x2f, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x3f, 0x3f, 0x5a, 0x5a, 0x5a, 0x2b, 0x2b, 0x31, 0x2e, 0x2e, 0x34, 0x59, 0x59, 0x59, 0x2a, 0x2a, 0x30, 0x4b, 0x4b, 0x4b, 0x22, 0x22, 0x27, 0x35, 0x35, 0x35, 0x4a, 0x4a, 0x4a, 0x24, 0x24, 0x28, 0x24, 0x24, 0x29, 0x56, 0x56, 0x56, 0x62, 0x62, 0x62, 0x2a, 0x2a, 0x31, 0x2a, 0x2a, 0x30, 0x2d, 0x2d, 0x34, 0x2f, 0x2f, 0x36, 0x2e, 0x2e, 0x35, 0x2c, 0x2c, 0x32, 0x48, 0x48, 0x48, 0x44, 0x44, 0x44, 0x43, 0x43, 0x43, 0x54, 0x54, 0x54, 0x26, 0x26, 0x2b, 0x24, 0x24, 0x28, 0x27, 0x27, 0x2d, 0x29, 0x29, 0x2f, 0x28, 0x28, 0x2e, 0x25, 0x25, 0x2b, 0x23, 0x23, 0x28, 0x26, 0x26, 0x2c, 0x25, 0x25, 0x2a, 0x2a, 0x2a, 0x2f, 0x2b, 0x2b, 0x31, 0x22, 0x22, 0x26, 0x52, 0x52, 0x52, 0x42, 0x42, 0x42, 0x2d, 0x2d, 0x33, 0x22, 0x22, 0x27, 0x51, 0x51, 0x51, 0x40, 0x40, 0x40, 0x27, 0x27, 0x2b, 0x2e, 0x2e, 0x34, 0x2c, 0x2c, 0x31, 0x29, 0x29, 0x2e, 0x4f, 0x4f, 0x4f, 0x3f, 0x3f, 0x3f, 0x4d, 0x4d, 0x4d, 0x3e, 0x3e, 0x3e, 0x24, 0x24, 0x2a, 0x24, 0x24, 0x29, 0x20, 0x20, 0x25, 0x4c, 0x4c, 0x4c, 0x3d, 0x3d, 0x3d, 0x28, 0x28, 0x2d, 0x2b, 0x2b, 0x30, 0x29, 0x29, 0x2d, 0x20, 0x20, 0x23, 0x4a, 0x4a, 0x4a, 0x3b, 0x3b, 0x3b, 0x22, 0x22, 0x28, 0x27, 0x27, 0x2c, 0x1e, 0x1e, 0x22, 0x49, 0x49, 0x49, 0x3a, 0x3a, 0x3a, 0x21, 0x21, 0x26, 0x21, 0x21, 0x25, 0x23, 0x23, 0x27, 0x20, 0x20, 0x24, 0x1d, 0x1d, 0x21, 0x39, 0x39, 0x39, 0x47, 0x47, 0x47, 0x1f, 0x1f, 0x24, 0x1f, 0x1f, 0x23, 0x1e, 0x1e, 0x21, 0x46, 0x46, 0x46, 0xd3, 0xa7, 0xd4, 0x88, 0x0, 0x0, 0x0, 0x24, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x4, 0xa, 0x11, 0x19, 0x1f, 0x22, 0x24, 0x1d, 0x16, 0xd, 0x7, 0x2, 0x15, 0x25, 0x34, 0x3f, 0x46, 0x47, 0x48, 0x43, 0x3a, 0x2d, 0x1b, 0x77, 0xef, 0xe6, 0x49, 0xef, 0xe6, 0xef, 0xe7, 0x77, 0xef, 0xe4, 0x4a, 0xba, 0xea, 0xc1, 0xeb, 0x0, 0x0, 0x0, 0xec, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x6c, 0x8e, 0x5, 0x4e, 0x0, 0x31, 0x10, 0x45, 0xff, 0xef, 0x56, 0xc2, 0xe2, 0xee, 0x4e, 0x3c, 0xc8, 0xd, 0xb8, 0x38, 0xa7, 0xc0, 0xdd, 0xdd, 0x5d, 0xbb, 0x94, 0x4c, 0xd2, 0xc1, 0xdf, 0x4a, 0x47, 0x5e, 0x27, 0xc3, 0xc, 0x80, 0x64, 0x24, 0xb8, 0xc7, 0x4f, 0x2c, 0x6b, 0xd5, 0x90, 0xff, 0xdd, 0x23, 0x7e, 0xc1, 0xa2, 0xb6, 0x64, 0xad, 0x26, 0x82, 0xc6, 0xef, 0x45, 0xbc, 0xe3, 0x1, 0x2c, 0x69, 0x9b, 0xc8, 0x7f, 0x5, 0x36, 0x1e, 0x41, 0x84, 0xd6, 0x4, 0x25, 0x90, 0xb7, 0x39, 0x6c, 0x4c, 0x2f, 0xbe, 0x68, 0xdf, 0x13, 0xa1, 0x9e, 0xc8, 0xa4, 0xb7, 0xe7, 0x47, 0x93, 0x63, 0x1b, 0xf9, 0xdc, 0x8, 0x88, 0x60, 0xbf, 0x4, 0xff, 0xd2, 0x56, 0x93, 0xe3, 0xb7, 0xb2, 0xf6, 0x2c, 0x36, 0x1, 0x16, 0xf4, 0x5f, 0x42, 0xc4, 0x17, 0x8f, 0xb5, 0xc0, 0xa5, 0x8, 0x30, 0x5f, 0xc2, 0x5d, 0xcf, 0xc9, 0xd, 0x74, 0x87, 0x8b, 0x5e, 0x56, 0x22, 0x24, 0xf7, 0x6d, 0xc2, 0xd1, 0x80, 0x26, 0x27, 0x5d, 0x5b, 0x67, 0x7d, 0x2f, 0x80, 0x4d, 0xc9, 0x7f, 0x9, 0x63, 0xa7, 0x61, 0x23, 0xc7, 0x74, 0x3d, 0xf, 0xae, 0x41, 0x84, 0x6f, 0x13, 0xe6, 0x26, 0xfa, 0x36, 0x46, 0x73, 0xbc, 0xba, 0x3b, 0xd6, 0xca, 0x8, 0xd0, 0xd6, 0x36, 0x4e, 0x35, 0xeb, 0xad, 0xd7, 0xb0, 0x60, 0x72, 0xdc, 0xda, 0x9c, 0x2, 0x67, 0x76, 0x64, 0x82, 0x99, 0x9b, 0xb6, 0x80, 0xb0, 0x7e, 0x8d, 0xf6, 0x5a, 0xdd, 0xe1, 0xb5, 0xba, 0xba, 0xb1, 0x0, 0xcd, 0xc7, 0x50, 0x23, 0xeb, 0xfb, 0x7f, 0xb4, 0xc8, 0x22, 0x18, 0xdd, 0x0, 0xd5, 0xec, 0x4e, 0x53, 0xc6, 0x18, 0x44, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char option_button_focus_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0xb1, 0x50, 0x4c, 0x54, 0x45, 0x95, 0xa9, 0xb0, 0x92, 0xa7, 0xae, 0x8e, 0xa2, 0xa9, 0x8a, 0x9d, 0xa4, 0x85, 0x98, 0x9f, 0x80, 0x93, 0x9b, 0x7b, 0x8f, 0x96, 0x77, 0x8a, 0x92, 0x72, 0x86, 0x8c, 0x6e, 0x80, 0x88, 0x69, 0x7c, 0x84, 0x64, 0x77, 0x7f, 0x60, 0x72, 0x7a, 0x5b, 0x6e, 0x75, 0x56, 0x69, 0x71, 0xc8, 0xe3, 0xe7, 0xc8, 0xe2, 0xe7, 0xca, 0xe3, 0xe7, 0xce, 0xe6, 0xe9, 0xce, 0xe6, 0xea, 0xd0, 0xe6, 0xe9, 0xce, 0xe5, 0xea, 0xd0, 0xe6, 0xea, 0xce, 0xe5, 0xe9, 0xd0, 0xe5, 0xe9, 0xd3, 0xe7, 0xeb, 0xd4, 0xe7, 0xeb, 0xd9, 0xea, 0xed, 0xd7, 0xe9, 0xed, 0xd7, 0xea, 0xed, 0xdc, 0xec, 0xef, 0xdc, 0xeb, 0xef, 0xe0, 0xed, 0xf1, 0xdf, 0xee, 0xf1, 0xdf, 0xed, 0xf1, 0xe0, 0xee, 0xf1, 0xe3, 0xf0, 0xf2, 0xe2, 0xef, 0xf2, 0xe3, 0xef, 0xf2, 0xe6, 0xf1, 0xf3, 0xe8, 0xf2, 0xf5, 0xe8, 0xf3, 0xf4, 0xe8, 0xf2, 0xf4, 0xe8, 0xf3, 0xf5, 0xd6, 0x5a, 0x5b, 0xd4, 0x57, 0x58, 0xe5, 0x89, 0x89, 0xd5, 0x57, 0x59, 0xd5, 0x58, 0x59, 0xd5, 0x59, 0x5a, 0xd6, 0x59, 0x5a, 0xd6, 0x5a, 0x5c, 0xd7, 0x5b, 0x5c, 0xd7, 0x5b, 0x5d, 0xd8, 0x5c, 0x5d, 0xd8, 0x5c, 0x5e, 0xd8, 0x5d, 0x5f, 0xd9, 0x5d, 0x5f, 0xe8, 0x6c, 0x6e, 0x20, 0x6, 0x32, 0x78, 0x0, 0x0, 0x0, 0x2c, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0xac, 0x80, 0x68, 0x47, 0x0, 0x0, 0x0, 0x72, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x55, 0xc8, 0xc1, 0x8d, 0xc2, 0x30, 0x18, 0x6, 0xd1, 0x99, 0xdf, 0x8e, 0x7c, 0x59, 0x6d, 0xb, 0x14, 0x40, 0xff, 0xa5, 0x50, 0x0, 0x6d, 0x4, 0xe4, 0xf, 0x59, 0x8, 0x42, 0xde, 0xf1, 0x35, 0xce, 0xd0, 0x2b, 0xbf, 0x6e, 0x5d, 0xe5, 0x10, 0xbb, 0x95, 0x3b, 0x48, 0x2a, 0xe4, 0xd2, 0xec, 0x9a, 0xe6, 0x2, 0xcf, 0xd, 0x56, 0x30, 0x24, 0x48, 0x6, 0xb8, 0x82, 0xc2, 0x0, 0xd6, 0x3b, 0xf6, 0x6a, 0x12, 0xc0, 0xc8, 0x6e, 0x77, 0x3c, 0xa, 0xa3, 0xb3, 0x4d, 0x19, 0x76, 0xa5, 0xb, 0x12, 0x1b, 0xb8, 0x82, 0xc6, 0x22, 0x7c, 0x42, 0xc4, 0x40, 0x41, 0x59, 0x8a, 0x42, 0x80, 0x39, 0xc1, 0xae, 0x6c, 0x7c, 0xb9, 0xe2, 0x8f, 0x43, 0xf4, 0x9f, 0xb3, 0x17, 0x98, 0xa8, 0x1a, 0xb6, 0xa7, 0xd9, 0xa6, 0x4e, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char option_button_hover_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x40, 0xde, 0x8d, 0x6b, 0x0, 0x0, 0x1, 0x41, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x42, 0x40, 0x4b, 0x5f, 0x5a, 0x6c, 0x2b, 0x2b, 0x31, 0x2e, 0x2e, 0x34, 0x5f, 0x5a, 0x6b, 0x2a, 0x2a, 0x30, 0x56, 0x53, 0x64, 0x22, 0x22, 0x27, 0x3e, 0x3b, 0x46, 0x57, 0x53, 0x63, 0x24, 0x24, 0x28, 0x24, 0x24, 0x29, 0x5b, 0x57, 0x68, 0x5a, 0x56, 0x67, 0x67, 0x63, 0x76, 0x2a, 0x2a, 0x31, 0x2a, 0x2a, 0x30, 0x2d, 0x2d, 0x34, 0x2f, 0x2f, 0x36, 0x2e, 0x2e, 0x35, 0x2c, 0x2c, 0x32, 0x4d, 0x4a, 0x57, 0x49, 0x46, 0x52, 0x48, 0x45, 0x51, 0x5a, 0x56, 0x65, 0x26, 0x26, 0x2b, 0x24, 0x24, 0x28, 0x27, 0x27, 0x2d, 0x29, 0x29, 0x2f, 0x28, 0x28, 0x2e, 0x25, 0x25, 0x2b, 0x23, 0x23, 0x28, 0x5b, 0x57, 0x66, 0x26, 0x26, 0x2c, 0x25, 0x25, 0x2a, 0x2a, 0x2a, 0x2f, 0x2b, 0x2b, 0x31, 0x22, 0x22, 0x26, 0x59, 0x55, 0x64, 0x47, 0x44, 0x50, 0x2d, 0x2d, 0x33, 0x22, 0x22, 0x27, 0x58, 0x54, 0x64, 0x46, 0x43, 0x50, 0x27, 0x27, 0x2b, 0x2e, 0x2e, 0x34, 0x2c, 0x2c, 0x31, 0x29, 0x29, 0x2e, 0x56, 0x53, 0x63, 0x45, 0x42, 0x4f, 0x56, 0x53, 0x62, 0x45, 0x42, 0x4e, 0x24, 0x24, 0x2a, 0x24, 0x24, 0x29, 0x20, 0x20, 0x25, 0x55, 0x51, 0x62, 0x44, 0x41, 0x4e, 0x28, 0x28, 0x2d, 0x2b, 0x2b, 0x30, 0x29, 0x29, 0x2d, 0x20, 0x20, 0x23, 0x55, 0x51, 0x60, 0x44, 0x41, 0x4d, 0x22, 0x22, 0x28, 0x27, 0x27, 0x2c, 0x1e, 0x1e, 0x22, 0x43, 0x40, 0x4c, 0x54, 0x50, 0x5f, 0x21, 0x21, 0x26, 0x21, 0x21, 0x25, 0x23, 0x23, 0x27, 0x20, 0x20, 0x24, 0x1d, 0x1d, 0x21, 0x47, 0x43, 0x51, 0x43, 0x3f, 0x4d, 0x42, 0x3f, 0x4c, 0x53, 0x4f, 0x5f, 0x1f, 0x1f, 0x24, 0x1f, 0x1f, 0x23, 0x1e, 0x1e, 0x21, 0x53, 0x50, 0x5f, 0x53, 0x4f, 0x5e, 0x5f, 0x5a, 0x6c, 0xd3, 0x26, 0x54, 0x35, 0x0, 0x0, 0x0, 0x24, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x4, 0xa, 0x11, 0x19, 0x1f, 0x22, 0x24, 0x1d, 0x16, 0xd, 0x7, 0x2, 0x15, 0x25, 0x34, 0x3f, 0x46, 0x47, 0x48, 0x43, 0x3a, 0x2d, 0x1b, 0x77, 0xef, 0xe6, 0x49, 0xef, 0xe6, 0xef, 0xe7, 0x77, 0xef, 0xe4, 0x4a, 0xba, 0xea, 0xc1, 0xeb, 0x0, 0x0, 0x0, 0xe5, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x85, 0x91, 0x43, 0x62, 0xc5, 0x60, 0x18, 0x45, 0xef, 0x8d, 0x51, 0xdb, 0xee, 0x46, 0xca, 0x6d, 0x77, 0x58, 0xce, 0x6b, 0xdb, 0x7c, 0x8a, 0xad, 0xea, 0x44, 0x1f, 0x4e, 0x92, 0x1f, 0x4c, 0x0, 0xe0, 0x9, 0x61, 0xf0, 0x89, 0x32, 0x12, 0xcd, 0xd4, 0x8, 0xef, 0x1f, 0x35, 0x54, 0xa0, 0x68, 0x1a, 0x34, 0x89, 0x8, 0x86, 0xa4, 0xd, 0x57, 0xb4, 0xdf, 0x79, 0x5, 0x89, 0x94, 0xba, 0xf9, 0xb3, 0xc0, 0xce, 0xeb, 0xf0, 0x17, 0x1c, 0xab, 0x11, 0x9, 0x1a, 0xf9, 0x96, 0x84, 0x5d, 0x5e, 0x43, 0x15, 0x7, 0x2e, 0x42, 0x41, 0x51, 0xd3, 0xbe, 0x67, 0xd5, 0x6b, 0x42, 0x3a, 0x38, 0x9b, 0xf5, 0x2e, 0x20, 0xfa, 0x5, 0x33, 0x41, 0x69, 0xf4, 0xeb, 0x49, 0x6c, 0x19, 0xe6, 0xbd, 0xdd, 0xd, 0x48, 0xa0, 0x92, 0xb, 0x36, 0x72, 0x6a, 0x26, 0xf0, 0x14, 0xa, 0x10, 0x72, 0xe1, 0x7d, 0xf4, 0xf6, 0x35, 0x1b, 0xc3, 0xe3, 0x18, 0x9d, 0x50, 0xf0, 0xe4, 0xc2, 0x17, 0xae, 0x27, 0xd3, 0xe4, 0x6e, 0xe8, 0xf8, 0x7e, 0xbc, 0x1, 0x48, 0x9e, 0x57, 0xf8, 0xc5, 0xfc, 0xbd, 0x7a, 0x98, 0xc4, 0x94, 0x47, 0xbf, 0xe4, 0xce, 0x48, 0x8, 0x6e, 0x69, 0x91, 0x63, 0x47, 0x73, 0x49, 0xbc, 0x77, 0x3e, 0xd7, 0x4b, 0x3b, 0x12, 0x36, 0x16, 0xb3, 0x85, 0x6a, 0x68, 0xce, 0x39, 0x62, 0xc6, 0xba, 0x3d, 0x8d, 0xe7, 0x91, 0x20, 0xac, 0xad, 0xa6, 0xc2, 0xe, 0x6, 0xcc, 0x74, 0xc, 0x2d, 0xe7, 0xf9, 0x55, 0x2, 0x28, 0x94, 0x37, 0xab, 0xee, 0xa1, 0xcc, 0xbf, 0xdb, 0xed, 0x3, 0x70, 0xe6, 0x4f, 0x4a, 0xc3, 0xed, 0xed, 0xf3, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -266,18 +230,6 @@ static const unsigned char popup_bg_disabled_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x78, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x0, 0xff, 0x67, 0x7a, 0x85, 0x66, 0x7a, 0x86, 0x68, 0x7b, 0x86, 0x57, 0x51, 0x51, 0x4c, 0x42, 0x40, 0x4d, 0x43, 0x41, 0x56, 0x4c, 0x4b, 0x4d, 0x44, 0x41, 0x4e, 0x44, 0x42, 0x4f, 0x45, 0x43, 0x67, 0x7b, 0x87, 0x4f, 0x44, 0x43, 0x50, 0x45, 0x44, 0x52, 0x46, 0x44, 0x51, 0x46, 0x45, 0x4b, 0x40, 0x3f, 0x51, 0x47, 0x45, 0x52, 0x48, 0x46, 0x53, 0x48, 0x47, 0x4b, 0x41, 0x3f, 0x54, 0x49, 0x46, 0x55, 0x4a, 0x47, 0x55, 0x49, 0x47, 0x68, 0x7c, 0x88, 0x4a, 0x40, 0x3e, 0x55, 0x4b, 0x49, 0x56, 0x4d, 0x4b, 0x53, 0x49, 0x47, 0x50, 0x46, 0x44, 0x4a, 0x41, 0x3e, 0x48, 0x3e, 0x3c, 0x4b, 0x42, 0x3f, 0x49, 0x3f, 0x3d, 0x46, 0x3d, 0x3c, 0x47, 0x3d, 0x3b, 0x47, 0x3e, 0x3b, 0x49, 0x40, 0x3d, 0x45, 0x3c, 0x3b, 0x46, 0x3c, 0x3a, 0xdd, 0x63, 0x56, 0x8d, 0x0, 0x0, 0x0, 0xad, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x2d, 0x88, 0x35, 0x42, 0x4, 0x0, 0xc, 0x4, 0x77, 0x93, 0x9c, 0xbb, 0xf4, 0xc8, 0xff, 0xdf, 0x84, 0xb5, 0xb8, 0x5b, 0x84, 0x0, 0x37, 0xc5, 0xca, 0x10, 0xd, 0xc9, 0xce, 0xaa, 0xea, 0x24, 0x40, 0xca, 0x41, 0x64, 0x2b, 0x5, 0x87, 0x34, 0xa8, 0x88, 0x54, 0xef, 0x82, 0x80, 0x89, 0x34, 0x36, 0x96, 0xe0, 0x14, 0xa4, 0x12, 0xa6, 0x24, 0x18, 0xe1, 0xa8, 0x50, 0x59, 0x7c, 0x73, 0x30, 0x50, 0x55, 0x4a, 0x31, 0xd7, 0xb4, 0x89, 0xa1, 0x51, 0xb2, 0x9c, 0x1, 0x2c, 0x4, 0x83, 0x15, 0x12, 0x30, 0xab, 0xe9, 0x5a, 0x1, 0xb4, 0x40, 0xa1, 0x29, 0xbe, 0x75, 0xe, 0x5a, 0x70, 0xbe, 0x2a, 0xff, 0x12, 0xf1, 0xef, 0x1b, 0x5f, 0x8d, 0x5b, 0x68, 0xd, 0xdc, 0xe3, 0xf1, 0x71, 0x16, 0x3e, 0x5b, 0xc8, 0x33, 0xa9, 0xc7, 0xbc, 0x7f, 0xa4, 0x22, 0x6a, 0xb5, 0x90, 0xcb, 0xb2, 0x1a, 0x25, 0x67, 0x8b, 0x8f, 0x6f, 0xf8, 0x64, 0xa8, 0x35, 0x7a, 0x25, 0xa8, 0xa7, 0x1, 0x38, 0xc, 0xcc, 0xab, 0x4c, 0x5, 0xea, 0xe3, 0x76, 0x2f, 0x54, 0x93, 0xf3, 0xf, 0x4f, 0x10, 0x8d, 0x4c, 0x16, 0x9d, 0xcf, 0x1f, 0xd1, 0xf9, 0x3, 0x34, 0xc8, 0x4a, 0xb4, 0x1d, 0xb, 0xcd, 0x83, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char popup_checked_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x4, 0x0, 0x0, 0x0, 0x6e, 0x6, 0x76, 0x0, 0x0, 0x0, 0x0, 0x56, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xc0, 0x3, 0x6e, 0xf0, 0xde, 0x3f, 0xf5, 0xe0, 0x30, 0x9c, 0xfb, 0x9f, 0xf1, 0xc1, 0xda, 0x7, 0xff, 0x1f, 0x9c, 0x85, 0xb, 0x3c, 0xa8, 0x1, 0x72, 0xdf, 0x3d, 0x56, 0x61, 0x78, 0x70, 0xf8, 0xc1, 0x99, 0x3b, 0x62, 0xf, 0xbc, 0x1e, 0xfc, 0x5, 0x42, 0x2f, 0xa0, 0xcc, 0xfd, 0x53, 0x40, 0x99, 0x6b, 0xf, 0xde, 0x3, 0xc9, 0x6a, 0xb0, 0x52, 0xa0, 0xec, 0xe5, 0x7, 0xff, 0x81, 0x70, 0xed, 0x7f, 0x46, 0x20, 0x17, 0x2a, 0x74, 0xfa, 0xc1, 0xb1, 0x1b, 0xbc, 0x10, 0x1e, 0x0, 0xfd, 0x1f, 0x33, 0x9, 0xf7, 0x50, 0x16, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char popup_hover_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x2d, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x0, 0xff, 0x20, 0x2e, 0x31, 0x83, 0xae, 0xb7, 0xb3, 0xd8, 0xe1, 0xaf, 0xd5, 0xde, 0xac, 0xd2, 0xdb, 0xa9, 0xcf, 0xd8, 0xa5, 0xcc, 0xd5, 0xa2, 0xc9, 0xd2, 0x9e, 0xc6, 0xcf, 0x9b, 0xc3, 0xcc, 0x97, 0xc0, 0xc9, 0x94, 0xbd, 0xc6, 0x91, 0xba, 0xc3, 0x8d, 0xb7, 0xc0, 0x9c, 0x2c, 0x91, 0xa9, 0x0, 0x0, 0x0, 0x1f, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x62, 0x14, 0x4, 0x3, 0x1, 0x26, 0x41, 0x28, 0x60, 0x62, 0x80, 0xd0, 0xc, 0x74, 0x61, 0x98, 0x80, 0x1, 0x3, 0xd3, 0x7b, 0x28, 0x0, 0x0, 0x1a, 0x86, 0xe, 0x98, 0x2c, 0x61, 0xda, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char popup_unchecked_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x1, 0x3, 0x0, 0x0, 0x0, 0xfe, 0xc1, 0x2c, 0xc8, 0x0, 0x0, 0x0, 0x6, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x0, 0xff, 0xff, 0xff, 0xff, 0x9f, 0x18, 0x32, 0xe0, 0x0, 0x0, 0x0, 0x1, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x40, 0xe6, 0xd8, 0x66, 0x0, 0x0, 0x0, 0xa, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x40, 0x3, 0x0, 0x0, 0x10, 0x0, 0x1, 0xb3, 0xac, 0xe2, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char popup_window_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x46, 0x8, 0x3, 0x0, 0x0, 0x0, 0x8d, 0x2b, 0xf6, 0x48, 0x0, 0x0, 0x1, 0x6b, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xe8, 0xe5, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1d, 0x22, 0x0, 0x0, 0x0, 0x1a, 0x19, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1e, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1d, 0x21, 0x17, 0x16, 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x1b, 0x1a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x1e, 0x1c, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x20, 0x25, 0x20, 0x1e, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x0, 0x0, 0x0, 0x21, 0x1f, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x22, 0x20, 0x25, 0x0, 0x0, 0x0, 0x20, 0x20, 0x25, 0x20, 0x1d, 0x25, 0x20, 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1d, 0x1d, 0x20, 0x1d, 0x1a, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x30, 0x38, 0xe8, 0xe5, 0xf1, 0xe5, 0xe2, 0xeb, 0xe3, 0xe1, 0xe8, 0xe1, 0xdf, 0xe7, 0xe0, 0xde, 0xe6, 0xdf, 0xdd, 0xe5, 0xde, 0xdc, 0xe4, 0xdd, 0xdb, 0xe3, 0xdc, 0xda, 0xe2, 0xda, 0xd8, 0xe0, 0xd9, 0xd7, 0xdf, 0xd7, 0xd6, 0xdf, 0xd6, 0xd4, 0xdd, 0xd5, 0xd3, 0xdc, 0xd4, 0xd1, 0xdb, 0xd3, 0xd0, 0xda, 0xd1, 0xce, 0xd8, 0xd0, 0xcd, 0xd7, 0xcf, 0xcd, 0xd7, 0xe2, 0xdf, 0xeb, 0x48, 0x46, 0x51, 0x42, 0x40, 0x4b, 0x40, 0x3e, 0x48, 0x40, 0x3d, 0x48, 0x48, 0x45, 0x50, 0x42, 0x3f, 0x4a, 0x3f, 0x3d, 0x48, 0x47, 0x44, 0x50, 0x41, 0x3f, 0x4a, 0x3f, 0x3d, 0x47, 0x41, 0x3e, 0x49, 0x3f, 0x3c, 0x47, 0x46, 0x43, 0x4f, 0x3e, 0x3c, 0x46, 0x40, 0x3e, 0x49, 0x3d, 0x3b, 0x46, 0x45, 0x43, 0x4e, 0x3d, 0x3b, 0x45, 0x44, 0x42, 0x4d, 0x3d, 0x3a, 0x45, 0x3e, 0x3c, 0x47, 0x3c, 0x3a, 0x44, 0x43, 0x42, 0x4c, 0x43, 0x40, 0x4c, 0x3e, 0x3b, 0x46, 0x3b, 0x39, 0x43, 0x43, 0x3f, 0x4c, 0x43, 0x3f, 0x4b, 0x3a, 0x38, 0x42, 0x42, 0x3e, 0x4b, 0x42, 0x3e, 0x49, 0x3a, 0x37, 0x41, 0x39, 0x37, 0x41, 0x3f, 0x3e, 0x48, 0x39, 0x37, 0x40, 0x38, 0x36, 0x40, 0x3e, 0x3d, 0x48, 0x38, 0x36, 0x3f, 0x3e, 0x3d, 0x47, 0x3a, 0x38, 0x41, 0x38, 0x35, 0x3f, 0x37, 0x35, 0x3e, 0x39, 0x36, 0x40, 0x37, 0x34, 0x3e, 0x3d, 0x3a, 0x46, 0x36, 0x34, 0x3d, 0x3d, 0x3a, 0x44, 0x37, 0x35, 0x3f, 0x35, 0x33, 0x3c, 0x46, 0x44, 0x4f, 0xac, 0xa5, 0x1, 0x25, 0x0, 0x0, 0x0, 0x33, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xa2, 0x3, 0x9, 0x17, 0xc, 0x20, 0xf, 0x2a, 0x5e, 0x12, 0x30, 0x68, 0x46, 0x20, 0x4e, 0xa2, 0x7d, 0x3a, 0x4f, 0xa4, 0x7d, 0x3f, 0x25, 0x60, 0xc0, 0xb8, 0x57, 0x1d, 0xba, 0x59, 0xbd, 0x5b, 0x22, 0xbf, 0x5e, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xa1, 0x9f, 0x9e, 0x52, 0x92, 0x15, 0x44, 0x7e, 0xd8, 0x5, 0xc7, 0xf4, 0xac, 0x0, 0x0, 0x1, 0x98, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0xd6, 0x55, 0x9e, 0x14, 0x31, 0x10, 0x80, 0xf1, 0xb2, 0x20, 0x1d, 0xdc, 0x9d, 0x3b, 0x2c, 0xa7, 0x87, 0x4b, 0xe0, 0xee, 0xd0, 0x82, 0xcb, 0xea, 0xb4, 0x86, 0x79, 0x23, 0x93, 0xaa, 0xcc, 0xf, 0xd7, 0xfd, 0x9e, 0xff, 0xed, 0x1d, 0x21, 0xf8, 0xe2, 0xfe, 0x1c, 0x8a, 0x17, 0x32, 0xa1, 0xa2, 0x2b, 0x48, 0x66, 0xb8, 0xa2, 0x28, 0x10, 0x9a, 0xd1, 0xf7, 0x3d, 0x16, 0x66, 0xfa, 0x45, 0x74, 0x9b, 0xd2, 0x97, 0x52, 0xe2, 0x2f, 0xa6, 0x40, 0x5f, 0x4c, 0x1d, 0xf3, 0x97, 0x52, 0x5e, 0x46, 0xf7, 0xee, 0xe3, 0x88, 0x8a, 0x48, 0x9e, 0x8a, 0xec, 0x8c, 0x28, 0x2c, 0xa7, 0xf0, 0x99, 0xd2, 0x6e, 0xe7, 0x8e, 0x10, 0x9b, 0xd1, 0x11, 0xe7, 0xe, 0xd1, 0x17, 0x8e, 0x2, 0xe6, 0x55, 0xf8, 0x42, 0x4a, 0x74, 0x18, 0xbe, 0xf8, 0xac, 0x9b, 0x5f, 0x4a, 0xb7, 0x36, 0x20, 0xa5, 0xf9, 0x2f, 0x90, 0x50, 0x11, 0x36, 0x13, 0x49, 0xa9, 0xe7, 0x6c, 0x5e, 0xcf, 0x2e, 0x99, 0xf4, 0xd, 0xb8, 0x8c, 0x5, 0xa7, 0x28, 0x8, 0x98, 0x9, 0x68, 0xba, 0x41, 0x66, 0x1b, 0x8a, 0xa2, 0xb0, 0x4d, 0x59, 0x30, 0xa5, 0x94, 0xa3, 0x94, 0x52, 0x0, 0x6f, 0x53, 0x6f, 0x7c, 0x82, 0x16, 0xcc, 0x5a, 0x51, 0x14, 0xbd, 0x98, 0x79, 0x4c, 0x29, 0x72, 0xee, 0xac, 0x8c, 0xea, 0xac, 0xb9, 0x51, 0xa0, 0xce, 0xa, 0x44, 0x60, 0x46, 0xa4, 0x28, 0x2, 0x9b, 0x1, 0x2a, 0x5a, 0xa, 0x9a, 0x49, 0xa9, 0xe8, 0x79, 0x20, 0x33, 0x38, 0xaf, 0x68, 0xc5, 0x68, 0xc6, 0x95, 0xa2, 0xe7, 0x72, 0x67, 0x3d, 0x97, 0x52, 0x24, 0xcf, 0x66, 0x9e, 0x30, 0xa5, 0xef, 0x5a, 0x34, 0x6b, 0xdf, 0xa5, 0x14, 0xa4, 0x0, 0xb3, 0x42, 0x8c, 0xe5, 0x38, 0x37, 0xb4, 0x14, 0x3d, 0xd2, 0xda, 0xb4, 0x3d, 0xa2, 0x68, 0xe3, 0xc0, 0xcc, 0x35, 0x8a, 0x9e, 0x86, 0x4c, 0xa7, 0x15, 0x85, 0x9d, 0x6c, 0xb6, 0x13, 0x16, 0xe8, 0x54, 0x78, 0xbc, 0x8e, 0x60, 0x86, 0xd7, 0xd1, 0x17, 0xd3, 0x37, 0x6e, 0x48, 0x30, 0x4f, 0x70, 0x81, 0xbe, 0xed, 0x97, 0xd1, 0xfe, 0x6d, 0x44, 0xf7, 0xed, 0xa7, 0x63, 0x39, 0x79, 0x8c, 0xf6, 0xef, 0x8b, 0xe8, 0x61, 0xe6, 0x8d, 0x55, 0x5b, 0xae, 0x9e, 0x62, 0x3e, 0x1c, 0xd1, 0x3b, 0xf7, 0x9b, 0xc7, 0xfb, 0x9b, 0xab, 0x46, 0xcd, 0xab, 0x4b, 0xcd, 0xfd, 0x3b, 0x11, 0x1d, 0xe, 0x76, 0xf5, 0xc5, 0x2b, 0xe5, 0xf3, 0x67, 0x49, 0xcf, 0xcb, 0x2b, 0x8f, 0xea, 0xee, 0xe0, 0x10, 0xd1, 0xf0, 0xb2, 0x58, 0xb, 0x61, 0x75, 0xd6, 0x26, 0xcd, 0x56, 0x43, 0x58, 0x2b, 0x5e, 0x86, 0x88, 0x4e, 0x63, 0x33, 0x84, 0xf7, 0x1f, 0x26, 0xd5, 0x87, 0xf7, 0x61, 0x68, 0xc6, 0x29, 0xa2, 0x73, 0xdb, 0x5e, 0x2d, 0x57, 0x1f, 0xab, 0x56, 0xcb, 0xab, 0xed, 0x5c, 0xfe, 0xc4, 0x5d, 0xf1, 0x27, 0x1a, 0x8f, 0xba, 0x8d, 0xd7, 0xa0, 0x9a, 0x40, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -298,22 +250,10 @@ static const unsigned char radio_unchecked_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x2a, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x4, 0x3, 0x4, 0x9, 0x9, 0x9, 0x6, 0x6, 0x6, 0xa, 0xa, 0xb, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x7f, 0x7f, 0x82, 0xd9, 0xd9, 0xd9, 0x47, 0x47, 0x48, 0x2b, 0x6e, 0xf2, 0xbf, 0x0, 0x0, 0x0, 0xb, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x88, 0xd1, 0xf7, 0x64, 0xf6, 0x2, 0xb3, 0xed, 0xd7, 0x0, 0x0, 0x0, 0x49, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x40, 0x2, 0x61, 0x15, 0xed, 0xa9, 0x20, 0x5a, 0x72, 0xf5, 0x99, 0x33, 0xbb, 0x26, 0x1, 0x19, 0x73, 0xcf, 0x0, 0xc1, 0x4d, 0x6, 0x6, 0xd6, 0x35, 0x20, 0xc6, 0xa9, 0x0, 0x6, 0xb6, 0x3d, 0x20, 0xc6, 0xe9, 0x4, 0x6, 0xf6, 0x33, 0x60, 0x50, 0xc0, 0xc0, 0x1, 0x61, 0x34, 0xc0, 0x19, 0x70, 0x29, 0xb8, 0x62, 0xb8, 0x76, 0x84, 0x81, 0xc, 0x96, 0x20, 0x2b, 0xa6, 0xc0, 0x2d, 0x45, 0x0, 0x0, 0x37, 0xca, 0x3d, 0x81, 0xb4, 0x84, 0xb6, 0x80, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char reference_border_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x2, 0x3, 0x0, 0x0, 0x0, 0x62, 0x9d, 0x17, 0xf2, 0x0, 0x0, 0x0, 0xc, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xff, 0xd1, 0xd1, 0xff, 0xb7, 0xb7, 0xff, 0x41, 0x41, 0x2b, 0x2, 0x77, 0xea, 0x0, 0x0, 0x0, 0x2, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x0, 0x76, 0x93, 0xcd, 0x38, 0x0, 0x0, 0x0, 0x25, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x58, 0xff, 0xff, 0xff, 0x2f, 0x86, 0x6, 0x6, 0x6, 0x26, 0x86, 0xa3, 0xa1, 0xa1, 0xc1, 0xc, 0x47, 0x18, 0x18, 0x84, 0x49, 0x22, 0xc0, 0xda, 0xc0, 0x6, 0x80, 0x8d, 0x2, 0x0, 0x36, 0x2b, 0x14, 0x3d, 0x85, 0x39, 0x85, 0x31, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char scroll_bg_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc, 0x8, 0x3, 0x0, 0x0, 0x0, 0x61, 0xab, 0xac, 0xd5, 0x0, 0x0, 0x0, 0x45, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x40, 0x3e, 0x4a, 0x2a, 0x29, 0x2f, 0x20, 0x20, 0x24, 0x3f, 0x3e, 0x49, 0x1f, 0x1f, 0x24, 0x20, 0x20, 0x24, 0x4d, 0x4b, 0x59, 0x4d, 0x4b, 0x59, 0x3f, 0x3e, 0x49, 0x3f, 0x3e, 0x49, 0x1e, 0x1e, 0x23, 0x20, 0x20, 0x25, 0x22, 0x22, 0x27, 0x23, 0x23, 0x27, 0x23, 0x23, 0x28, 0x25, 0x25, 0x2a, 0x14, 0xee, 0x69, 0x20, 0x0, 0x0, 0x0, 0x11, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x4, 0x19, 0x40, 0x5d, 0x66, 0x28, 0x93, 0xf0, 0xfc, 0x94, 0xfc, 0xfd, 0x67, 0x1a, 0x96, 0x95, 0x1c, 0xf0, 0x43, 0x52, 0x0, 0x0, 0x0, 0x55, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x55, 0x8e, 0x45, 0x2, 0x80, 0x50, 0x10, 0x42, 0xc1, 0xee, 0xfb, 0x5f, 0xd4, 0xd6, 0xdf, 0xfd, 0x36, 0xd3, 0x3, 0x4, 0xd, 0x90, 0x6, 0xb2, 0x25, 0x39, 0xe0, 0xd2, 0xf9, 0xcb, 0x6a, 0x60, 0x6f, 0x27, 0xb7, 0xbc, 0x58, 0xb7, 0x53, 0x4d, 0x0, 0xf2, 0x3f, 0x5e, 0x36, 0x43, 0x5f, 0xc3, 0xf0, 0xdf, 0x17, 0xd7, 0xa6, 0xae, 0x60, 0x10, 0xff, 0x57, 0x16, 0xc5, 0x5a, 0xf1, 0x60, 0xe3, 0xe7, 0x5f, 0x37, 0x46, 0x74, 0xba, 0x9a, 0x16, 0xef, 0x37, 0x1c, 0x6f, 0x61, 0x47, 0x1, 0xa5, 0xc7, 0x32, 0x47, 0x38, 0x12, 0x92, 0xb1, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char scroll_button_down_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x21, 0x50, 0x4c, 0x54, 0x45, 0x2d, 0x2c, 0x2f, 0x48, 0x46, 0x4a, 0x2d, 0x2c, 0x2f, 0x2d, 0x2c, 0x2f, 0x2d, 0x2c, 0x2f, 0x4c, 0x4a, 0x4e, 0x48, 0x46, 0x4a, 0x40, 0x3e, 0x42, 0x38, 0x36, 0x3a, 0xc3, 0xc3, 0xc3, 0x59, 0x59, 0x59, 0xb3, 0x52, 0xf2, 0x5, 0x0, 0x0, 0x0, 0x5, 0x74, 0x52, 0x4e, 0x53, 0x8, 0xfe, 0x9, 0xd, 0x19, 0x4a, 0xb6, 0xc1, 0xe6, 0x0, 0x0, 0x0, 0x33, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0xc, 0x5, 0x3, 0x21, 0x86, 0xf4, 0xe, 0x30, 0x28, 0x63, 0x88, 0x80, 0x30, 0x5a, 0xf1, 0x33, 0x66, 0x2, 0x1, 0x2a, 0xa3, 0x73, 0xe6, 0xcc, 0x19, 0x10, 0x35, 0x40, 0x1, 0x8, 0xa3, 0x73, 0x6, 0x1, 0x73, 0xe0, 0x96, 0x1a, 0x42, 0x9c, 0x21, 0x2, 0x0, 0x5a, 0xfa, 0x3d, 0xf9, 0xfa, 0xe2, 0x64, 0xe2, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char scroll_button_down_hl_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x21, 0x50, 0x4c, 0x54, 0x45, 0x3d, 0x3b, 0x3f, 0x60, 0x5d, 0x62, 0x3d, 0x3b, 0x3f, 0x3d, 0x3b, 0x3f, 0x3d, 0x3b, 0x3f, 0x65, 0x62, 0x67, 0x60, 0x5d, 0x62, 0x56, 0x53, 0x58, 0x4b, 0x49, 0x4e, 0xce, 0xce, 0xce, 0x59, 0x59, 0x59, 0xb8, 0xf5, 0x6d, 0x48, 0x0, 0x0, 0x0, 0x5, 0x74, 0x52, 0x4e, 0x53, 0x7, 0xfe, 0xc, 0x9, 0x1c, 0xda, 0x2b, 0xa5, 0x57, 0x0, 0x0, 0x0, 0x33, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0xc, 0x5, 0x3, 0x21, 0x86, 0xf4, 0xe, 0x30, 0x28, 0x63, 0x88, 0x80, 0x30, 0x5a, 0xf1, 0x33, 0x66, 0x2, 0x1, 0x2a, 0xa3, 0x73, 0xe6, 0xcc, 0x19, 0x10, 0x35, 0x40, 0x1, 0x8, 0xa3, 0x73, 0x6, 0x1, 0x73, 0xe0, 0x96, 0x1a, 0x42, 0x9c, 0x21, 0x2, 0x0, 0x5a, 0xfa, 0x3d, 0xf9, 0xfa, 0xe2, 0x64, 0xe2, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char scroll_button_left_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x30, 0x50, 0x4c, 0x54, 0x45, 0x2d, 0x2c, 0x2f, 0x48, 0x46, 0x4a, 0x2d, 0x2c, 0x2f, 0x2d, 0x2c, 0x2f, 0x2d, 0x2c, 0x2f, 0x4c, 0x4a, 0x4e, 0x48, 0x46, 0x4a, 0x40, 0x3e, 0x42, 0x38, 0x36, 0x3a, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0x59, 0x59, 0x59, 0x8e, 0x47, 0x76, 0xf1, 0x0, 0x0, 0x0, 0x5, 0x74, 0x52, 0x4e, 0x53, 0x8, 0xfe, 0x9, 0xd, 0x19, 0x4a, 0xb6, 0xc1, 0xe6, 0x0, 0x0, 0x0, 0x3e, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0xc, 0x5, 0x3, 0x21, 0x86, 0xf4, 0xe, 0x30, 0x28, 0x63, 0x88, 0x80, 0x30, 0x5a, 0x51, 0x19, 0x33, 0xa1, 0x8c, 0xae, 0x55, 0x50, 0xc6, 0x2e, 0x28, 0xa3, 0x7b, 0xf7, 0x6e, 0x8, 0xa3, 0xe7, 0xcc, 0x19, 0xa8, 0x14, 0x9c, 0xd1, 0x7b, 0x17, 0xa6, 0xfd, 0x1d, 0x86, 0x81, 0x60, 0x6, 0xdc, 0x52, 0x43, 0x88, 0x33, 0x44, 0x0, 0xcc, 0x4e, 0x3f, 0xd1, 0x4, 0x90, 0xbf, 0x60, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -330,14 +270,6 @@ static const unsigned char scroll_button_right_hl_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x33, 0x50, 0x4c, 0x54, 0x45, 0x3d, 0x3b, 0x3f, 0x60, 0x5d, 0x62, 0x3d, 0x3b, 0x3f, 0x3d, 0x3b, 0x3f, 0x3d, 0x3b, 0x3f, 0x65, 0x62, 0x67, 0x60, 0x5d, 0x62, 0x56, 0x53, 0x58, 0x4b, 0x49, 0x4e, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0x2e, 0x3d, 0xb1, 0x1e, 0x0, 0x0, 0x0, 0x5, 0x74, 0x52, 0x4e, 0x53, 0x7, 0xfe, 0xc, 0x9, 0x1c, 0xda, 0x2b, 0xa5, 0x57, 0x0, 0x0, 0x0, 0x49, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x65, 0xc8, 0x31, 0x16, 0x2, 0x20, 0x10, 0x43, 0xc1, 0xe4, 0x83, 0xdc, 0xff, 0xb8, 0x88, 0xf, 0x57, 0xb, 0x8b, 0x80, 0x53, 0xe, 0xf2, 0x23, 0x18, 0xc6, 0x68, 0x61, 0x74, 0xca, 0xa, 0x2e, 0x74, 0xf9, 0x85, 0xfd, 0x17, 0x5c, 0x81, 0xfb, 0x11, 0x2a, 0xaa, 0x65, 0x80, 0x20, 0xc3, 0x5f, 0xaf, 0x2b, 0x96, 0xce, 0x78, 0xea, 0x88, 0x39, 0x95, 0x91, 0x70, 0x29, 0x94, 0xd9, 0x6b, 0x87, 0xf5, 0xfe, 0x0, 0xc6, 0xa7, 0x1b, 0x66, 0x7b, 0x42, 0xf1, 0x14, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char scroll_button_up_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x21, 0x50, 0x4c, 0x54, 0x45, 0x2d, 0x2c, 0x2f, 0x48, 0x46, 0x4a, 0x2d, 0x2c, 0x2f, 0x2d, 0x2c, 0x2f, 0x2d, 0x2c, 0x2f, 0x4c, 0x4a, 0x4e, 0x48, 0x46, 0x4a, 0x40, 0x3e, 0x42, 0x38, 0x36, 0x3a, 0xc3, 0xc3, 0xc3, 0x59, 0x59, 0x59, 0xb3, 0x52, 0xf2, 0x5, 0x0, 0x0, 0x0, 0x5, 0x74, 0x52, 0x4e, 0x53, 0x8, 0xfe, 0x9, 0xd, 0x19, 0x4a, 0xb6, 0xc1, 0xe6, 0x0, 0x0, 0x0, 0x36, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0xc, 0x5, 0x3, 0x21, 0x86, 0xf4, 0xe, 0x30, 0x28, 0x63, 0x88, 0x80, 0x30, 0x5a, 0xb1, 0x33, 0x3a, 0x67, 0x40, 0x19, 0x33, 0x67, 0x42, 0x18, 0x9d, 0x33, 0x67, 0xce, 0x0, 0x33, 0x66, 0x2, 0x1, 0x2a, 0x3, 0x9f, 0x39, 0x10, 0x6, 0xdc, 0x52, 0x43, 0x88, 0x33, 0x44, 0x0, 0x59, 0xc8, 0x3d, 0xf9, 0xf, 0x68, 0xc5, 0xa9, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char scroll_button_up_hl_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x21, 0x50, 0x4c, 0x54, 0x45, 0x3d, 0x3b, 0x3f, 0x60, 0x5d, 0x62, 0x3d, 0x3b, 0x3f, 0x3d, 0x3b, 0x3f, 0x3d, 0x3b, 0x3f, 0x65, 0x62, 0x67, 0x60, 0x5d, 0x62, 0x56, 0x53, 0x58, 0x4b, 0x49, 0x4e, 0xce, 0xce, 0xce, 0x59, 0x59, 0x59, 0xb8, 0xf5, 0x6d, 0x48, 0x0, 0x0, 0x0, 0x5, 0x74, 0x52, 0x4e, 0x53, 0x7, 0xfe, 0xc, 0x9, 0x1c, 0xda, 0x2b, 0xa5, 0x57, 0x0, 0x0, 0x0, 0x36, 0x49, 0x44, 0x41, 0x54, 0x8, 0xd7, 0x63, 0x60, 0xc, 0x5, 0x3, 0x21, 0x86, 0xf4, 0xe, 0x30, 0x28, 0x63, 0x88, 0x80, 0x30, 0x5a, 0xb1, 0x33, 0x3a, 0x67, 0x40, 0x19, 0x33, 0x67, 0x42, 0x18, 0x9d, 0x33, 0x67, 0xce, 0x0, 0x33, 0x66, 0x2, 0x1, 0x2a, 0x3, 0x9f, 0x39, 0x10, 0x6, 0xdc, 0x52, 0x43, 0x88, 0x33, 0x44, 0x0, 0x59, 0xc8, 0x3d, 0xf9, 0xf, 0x68, 0xc5, 0xa9, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char scroll_grabber_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc, 0x8, 0x3, 0x0, 0x0, 0x0, 0x61, 0xab, 0xac, 0xd5, 0x0, 0x0, 0x0, 0x5d, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x5b, 0x59, 0x61, 0x5b, 0x59, 0x61, 0x5a, 0x58, 0x60, 0x59, 0x57, 0x5f, 0x5a, 0x58, 0x60, 0x5a, 0x58, 0x60, 0x57, 0x56, 0x5e, 0x58, 0x56, 0x5e, 0x56, 0x55, 0x5d, 0x57, 0x55, 0x5d, 0x57, 0x55, 0x5d, 0x55, 0x53, 0x5b, 0x55, 0x53, 0x5b, 0x54, 0x53, 0x5b, 0x55, 0x54, 0x5c, 0x54, 0x52, 0x5a, 0x55, 0x53, 0x5b, 0x5a, 0x58, 0x60, 0x56, 0x54, 0x5c, 0x54, 0x53, 0x5a, 0x55, 0x53, 0x5b, 0x53, 0x51, 0x59, 0x52, 0x51, 0x59, 0x52, 0x50, 0x58, 0x51, 0x50, 0x58, 0x51, 0x4f, 0x57, 0x50, 0x4e, 0x56, 0x4f, 0x4d, 0x55, 0x50, 0x4f, 0x57, 0x54, 0x52, 0x5a, 0xae, 0x55, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x12, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x2c, 0xb8, 0xf4, 0x2e, 0xf2, 0xb8, 0xf4, 0xf5, 0xf4, 0xf5, 0xb8, 0x2f, 0xf2, 0x2e, 0xb8, 0xf4, 0xb8, 0x66, 0xf6, 0xf7, 0x12, 0x0, 0x0, 0x0, 0x48, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x64, 0xc7, 0xb1, 0x11, 0x80, 0x40, 0x8, 0x45, 0x41, 0xff, 0x83, 0x2, 0xe, 0xfb, 0x2f, 0x13, 0xe2, 0xf3, 0x6, 0x12, 0x1d, 0x37, 0x5b, 0xae, 0x97, 0x5f, 0x84, 0xdd, 0x68, 0xe2, 0x1e, 0x41, 0xb8, 0x77, 0x58, 0x6, 0xb6, 0xe8, 0x88, 0xd1, 0xe1, 0x93, 0xad, 0x63, 0xab, 0xa3, 0x3a, 0xa3, 0x26, 0xa9, 0x4a, 0x52, 0xf9, 0xc, 0x62, 0xcf, 0xa7, 0x8f, 0x1f, 0x1e, 0xbd, 0xff, 0x84, 0xee, 0x2, 0x0, 0x54, 0x76, 0x10, 0x19, 0x1e, 0xd7, 0x1d, 0x9b, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -358,6 +290,10 @@ static const unsigned char selection_oof_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x4, 0x3, 0x0, 0x0, 0x0, 0xed, 0xdd, 0xe2, 0x52, 0x0, 0x0, 0x0, 0x2d, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2c, 0x2, 0xfd, 0xfb, 0xff, 0xfd, 0xfb, 0xff, 0xfd, 0xfb, 0xff, 0xfd, 0xfb, 0xff, 0xaf, 0xdf, 0x90, 0xa5, 0x0, 0x0, 0x0, 0xf, 0x74, 0x52, 0x4e, 0x53, 0xa, 0x1a, 0x26, 0x29, 0x2a, 0x48, 0x65, 0x6d, 0x6e, 0x66, 0x3, 0x20, 0x25, 0x16, 0xc, 0x1f, 0x74, 0xbf, 0x74, 0x0, 0x0, 0x0, 0x37, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x64, 0x54, 0x52, 0x64, 0x60, 0x60, 0x78, 0x77, 0x8f, 0x51, 0x34, 0x8, 0xcc, 0xb8, 0xcd, 0xa8, 0xd9, 0x4, 0x66, 0xdc, 0x60, 0x74, 0x2f, 0x33, 0x4, 0x32, 0xde, 0xce, 0x64, 0xf4, 0x68, 0x53, 0x0, 0x32, 0xfe, 0xcd, 0xa0, 0x90, 0x1, 0x37, 0x10, 0x6e, 0x5, 0xdc, 0x52, 0xb8, 0x33, 0x0, 0xcc, 0x7, 0x26, 0xff, 0x1f, 0x38, 0x23, 0x97, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char space_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x6, 0x0, 0x0, 0x0, 0xc4, 0xf, 0xbe, 0x8b, 0x0, 0x0, 0x0, 0x6, 0x62, 0x4b, 0x47, 0x44, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0xa0, 0xbd, 0xa7, 0x93, 0x0, 0x0, 0x0, 0x2f, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x63, 0x60, 0xa0, 0x2a, 0xf8, 0xff, 0xff, 0xbf, 0xe0, 0xff, 0xff, 0xff, 0x5, 0x91, 0xc5, 0x58, 0x90, 0x24, 0x85, 0x18, 0x18, 0x18, 0x14, 0xa0, 0x6c, 0x6, 0x46, 0x46, 0xc6, 0xf7, 0xc, 0xc, 0xc, 0xc, 0x4c, 0x14, 0x5b, 0x41, 0x39, 0x0, 0x0, 0x71, 0x1a, 0x13, 0x5d, 0x87, 0xfe, 0x9e, 0x7d, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char spinbox_updown_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x59, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0x11, 0xdc, 0x4f, 0x7f, 0x98, 0x86, 0x47, 0xfa, 0x81, 0xe5, 0x83, 0x1f, 0xf, 0x7e, 0x3d, 0xb2, 0xc5, 0xa5, 0x5b, 0xe2, 0xc1, 0x93, 0x7, 0xff, 0x81, 0xf0, 0xf9, 0x63, 0x69, 0x2c, 0xd2, 0x67, 0x58, 0xef, 0x1f, 0x2, 0x4a, 0x42, 0xe0, 0xf1, 0xdb, 0xec, 0x98, 0xfa, 0x67, 0x2, 0x25, 0xe0, 0xf0, 0xe1, 0x2, 0x86, 0x41, 0x7, 0x30, 0x1d, 0x39, 0x3, 0xbf, 0x37, 0x8f, 0xdd, 0x66, 0x27, 0x29, 0xa0, 0x10, 0x4a, 0x2c, 0xa0, 0x41, 0x8d, 0x1b, 0x3c, 0x4c, 0x3, 0x46, 0x16, 0x69, 0x0, 0x0, 0x87, 0x2a, 0x58, 0xb5, 0x18, 0xe9, 0x80, 0x2c, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -386,6 +322,10 @@ static const unsigned char tab_current_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x0, 0x99, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x3d, 0x48, 0x5b, 0x58, 0x66, 0x5b, 0x57, 0x65, 0x57, 0x54, 0x62, 0x55, 0x53, 0x62, 0x4a, 0x46, 0x52, 0x46, 0x41, 0x4e, 0x45, 0x41, 0x4d, 0x55, 0x52, 0x60, 0x44, 0x41, 0x4c, 0x53, 0x50, 0x5e, 0x43, 0x40, 0x4b, 0x52, 0x4e, 0x5d, 0x41, 0x3e, 0x4a, 0x4f, 0x4d, 0x5a, 0x3f, 0x3d, 0x48, 0x4e, 0x4b, 0x59, 0x3e, 0x3c, 0x47, 0x4d, 0x4a, 0x58, 0x3d, 0x3b, 0x46, 0x4b, 0x49, 0x54, 0x3c, 0x3a, 0x44, 0x4b, 0x47, 0x54, 0x3b, 0x39, 0x43, 0x3b, 0x39, 0x42, 0x3b, 0x38, 0x43, 0x3b, 0x38, 0x42, 0x3a, 0x37, 0x41, 0x39, 0x37, 0x41, 0x3a, 0x38, 0x41, 0x39, 0x36, 0x3f, 0x38, 0x36, 0x3f, 0x39, 0x36, 0x40, 0x38, 0x36, 0x40, 0x37, 0x35, 0x3e, 0x37, 0x34, 0x3e, 0x36, 0x35, 0x3d, 0xd7, 0x41, 0xa4, 0x19, 0x0, 0x0, 0x0, 0x11, 0x74, 0x52, 0x4e, 0x53, 0x4, 0xa, 0x11, 0x19, 0x1f, 0x22, 0x24, 0x15, 0x25, 0x34, 0x3f, 0x46, 0x47, 0x48, 0x77, 0xef, 0xef, 0xa3, 0x31, 0x6b, 0xc2, 0x0, 0x0, 0x0, 0x60, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x55, 0xca, 0x85, 0xd, 0xc0, 0x40, 0x14, 0xc3, 0x50, 0x27, 0xf7, 0xd5, 0xfd, 0xd7, 0x2d, 0xa6, 0x4c, 0x16, 0x3f, 0xb9, 0xd0, 0x11, 0x90, 0xa3, 0x52, 0x77, 0x49, 0x8e, 0x86, 0xd2, 0x26, 0x16, 0x7b, 0x59, 0x32, 0x68, 0x3, 0x37, 0x5d, 0xe0, 0x59, 0x3b, 0x74, 0x31, 0x67, 0x4b, 0x3b, 0xf, 0x71, 0xe5, 0xe8, 0xf, 0xec, 0xc0, 0x1f, 0x28, 0xf8, 0x2, 0x14, 0xf9, 0x42, 0xa8, 0xfc, 0x21, 0x3b, 0xe4, 0x1, 0x6f, 0x0, 0x18, 0x11, 0xac, 0x99, 0xc0, 0xe, 0x25, 0x22, 0x2d, 0x76, 0xc6, 0x13, 0x1a, 0x8, 0xac, 0x78, 0xfc, 0x1c, 0x70, 0x30, 0x2b, 0xba, 0xe9, 0x31, 0x70, 0xc1, 0x7f, 0x3b, 0x77, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char tab_disabled_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x3, 0xa7, 0x7a, 0x54, 0x58, 0x74, 0x52, 0x61, 0x77, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x78, 0x69, 0x66, 0x0, 0x0, 0x78, 0xda, 0xcd, 0x96, 0x59, 0x92, 0xe3, 0x2a, 0x10, 0x45, 0xff, 0x59, 0x45, 0x2f, 0x81, 0xcc, 0x24, 0x19, 0x96, 0x83, 0x18, 0x22, 0x7a, 0x7, 0x6f, 0xf9, 0x7d, 0xc1, 0x48, 0x1e, 0xab, 0x5c, 0x65, 0xfb, 0xe3, 0x89, 0xb0, 0x40, 0x29, 0x48, 0xae, 0xee, 0x41, 0xc8, 0xa6, 0xfd, 0xf7, 0xb7, 0x9b, 0x3f, 0x38, 0x28, 0x45, 0x6f, 0x9c, 0x86, 0xe8, 0x93, 0xf7, 0x16, 0x87, 0x4b, 0x2e, 0x71, 0x46, 0x23, 0xda, 0xd3, 0x71, 0xaa, 0xc9, 0xba, 0x79, 0x5e, 0x17, 0x76, 0x6f, 0x5c, 0xc5, 0xcd, 0x71, 0x83, 0x11, 0x12, 0xd4, 0x72, 0xba, 0xf4, 0x6d, 0xf5, 0xcf, 0x88, 0xeb, 0x79, 0x40, 0x70, 0x2b, 0xbe, 0x5d, 0xc7, 0x4d, 0x28, 0x2b, 0x4f, 0x5c, 0x89, 0xe8, 0x48, 0x3c, 0xf, 0x19, 0x33, 0x8f, 0xf6, 0xea, 0x17, 0x57, 0x22, 0xe1, 0x53, 0x9c, 0xd6, 0xb5, 0x49, 0x6b, 0x5c, 0x76, 0x17, 0x8f, 0xb3, 0x7e, 0x5c, 0x56, 0xda, 0x95, 0xfc, 0xf6, 0xda, 0x5, 0x98, 0x51, 0x15, 0xf9, 0x84, 0xd, 0x37, 0x21, 0xb1, 0x38, 0xc7, 0x31, 0x8b, 0x40, 0x81, 0x24, 0xc9, 0x32, 0xda, 0x79, 0xde, 0x19, 0x91, 0x80, 0xb6, 0x43, 0x9d, 0x45, 0x84, 0x1e, 0x7b, 0x67, 0x8e, 0xe6, 0x8d, 0x79, 0x47, 0xeb, 0xc6, 0x3b, 0x9b, 0x57, 0x5c, 0xae, 0xad, 0x30, 0xd6, 0xaf, 0xe, 0xfe, 0xc6, 0xa3, 0x15, 0x27, 0xbd, 0x89, 0xcb, 0x31, 0xd, 0xdf, 0x52, 0xdb, 0x67, 0xbe, 0xba, 0x91, 0xcb, 0x31, 0xc5, 0x9d, 0x77, 0xbd, 0xd7, 0xd8, 0x7b, 0x3b, 0x3d, 0x5d, 0x76, 0x1e, 0x4e, 0x79, 0xb3, 0x1e, 0x6a, 0x7f, 0x94, 0xd9, 0x42, 0xc7, 0xd, 0x56, 0xca, 0x1c, 0xe6, 0x51, 0x2, 0x7e, 0x8a, 0x76, 0x98, 0x25, 0xa1, 0x44, 0x3c, 0x62, 0x1, 0xb1, 0xa, 0x9a, 0x1b, 0x4a, 0x31, 0x94, 0x88, 0xe1, 0x69, 0x27, 0x47, 0x95, 0x32, 0x75, 0x6a, 0xb3, 0x2e, 0x54, 0x20, 0xd1, 0x71, 0xe3, 0x80, 0x9a, 0xb9, 0xb0, 0xcc, 0x58, 0x94, 0xc0, 0x89, 0xcb, 0x84, 0xe2, 0x46, 0xa1, 0xce, 0x1, 0x78, 0xaa, 0x91, 0x8, 0x3e, 0x5, 0xd4, 0x4, 0x61, 0x3e, 0xb4, 0xd0, 0x9c, 0x37, 0xcd, 0xf9, 0xa, 0x45, 0xcc, 0x5c, 0x9, 0x3d, 0x99, 0x90, 0x8c, 0x30, 0xe2, 0xae, 0x98, 0x47, 0xc1, 0x57, 0xca, 0x91, 0xa8, 0xf7, 0xb1, 0x74, 0x89, 0x6c, 0x3c, 0xbc, 0x82, 0x2e, 0x1e, 0x6b, 0x1a, 0x32, 0x6, 0xb9, 0x71, 0x46, 0x2f, 0x0, 0xa1, 0xbe, 0x3c, 0xd5, 0xe9, 0xef, 0x2c, 0xe6, 0x62, 0xdd, 0xd8, 0xb, 0xb0, 0x2, 0x82, 0x3a, 0x6d, 0x8e, 0x78, 0xc0, 0x6c, 0xb7, 0x53, 0x8a, 0x4d, 0xe9, 0xbc, 0xb6, 0x64, 0x72, 0x16, 0xf4, 0x53, 0xeb, 0x8c, 0x3d, 0xbd, 0x1a, 0x14, 0xea, 0x4a, 0x0, 0x8b, 0x30, 0xb7, 0x42, 0xc, 0x9, 0x8, 0x58, 0x4f, 0xa2, 0xe4, 0xc9, 0x6, 0xe6, 0x40, 0x4, 0x1f, 0x23, 0xf8, 0x64, 0x28, 0x67, 0x71, 0xbc, 0x81, 0x0, 0xa9, 0x72, 0x25, 0xd3, 0xc1, 0x46, 0xc4, 0x3, 0x4e, 0xe4, 0x31, 0x37, 0xc6, 0x4, 0x9a, 0x7d, 0x59, 0xf9, 0x14, 0xc6, 0xd6, 0x2, 0x10, 0x2a, 0x1e, 0xaf, 0x4a, 0x1c, 0x2f, 0x10, 0x60, 0x39, 0xa7, 0x58, 0x3f, 0xc1, 0x45, 0xac, 0xa1, 0xac, 0xa2, 0xce, 0xa8, 0xaa, 0xd7, 0xa0, 0x51, 0x93, 0x66, 0x2f, 0xde, 0x79, 0xf5, 0xde, 0x7, 0x3f, 0xf6, 0xa8, 0x1c, 0x24, 0xb8, 0xa0, 0xc1, 0x87, 0x10, 0x62, 0x48, 0x21, 0x47, 0x89, 0x2e, 0x6a, 0xf4, 0x31, 0xc4, 0x18, 0x53, 0xcc, 0x89, 0x93, 0x60, 0xb, 0xd3, 0xe4, 0x53, 0x30, 0x29, 0xa6, 0x94, 0x72, 0xc6, 0xa4, 0x19, 0xa9, 0x33, 0x46, 0x67, 0xf4, 0xc8, 0x79, 0xe3, 0x4d, 0x36, 0xb7, 0xe9, 0xe6, 0xb7, 0xb0, 0xc5, 0x2d, 0x6d, 0xb9, 0x60, 0xf9, 0x14, 0x57, 0xb4, 0xf8, 0x12, 0x4a, 0x2c, 0xa9, 0xe4, 0xca, 0x55, 0x2a, 0x5e, 0xff, 0xea, 0x6b, 0x30, 0x35, 0xd6, 0x54, 0x73, 0xa3, 0x86, 0xa5, 0xd4, 0x5c, 0xd3, 0xe6, 0x5b, 0x68, 0xb1, 0xa5, 0x96, 0x3b, 0xd6, 0x5a, 0x97, 0xee, 0xba, 0x76, 0xdf, 0x43, 0x8f, 0x3d, 0xf5, 0x7c, 0x50, 0x5b, 0x54, 0xaf, 0xa9, 0xd1, 0xd, 0xb9, 0xef, 0xa9, 0xd1, 0xa2, 0x36, 0x88, 0xb9, 0xd9, 0x2f, 0x9c, 0xa9, 0x21, 0x1c, 0xc2, 0x9e, 0x82, 0xc6, 0x76, 0xa2, 0x83, 0x19, 0x88, 0xb1, 0x23, 0x10, 0xf, 0x83, 0x0, 0x16, 0x34, 0xf, 0x66, 0x36, 0x92, 0x73, 0x3c, 0xc8, 0xd, 0x66, 0x36, 0x31, 0x5e, 0xa, 0x65, 0x50, 0x23, 0x1d, 0x70, 0x2a, 0xd, 0x62, 0x20, 0xe8, 0x1a, 0xb1, 0x76, 0x3a, 0xd8, 0x9d, 0xc9, 0x7d, 0xcb, 0xcd, 0xa8, 0xfb, 0x15, 0x37, 0xfe, 0x8a, 0x9c, 0x19, 0xe8, 0x3e, 0x41, 0xce, 0xc, 0x74, 0x8b, 0xdc, 0x3d, 0xb7, 0x7, 0xd4, 0x6a, 0x9e, 0x5f, 0x14, 0x99, 0x80, 0xc6, 0x5b, 0x38, 0x3c, 0xb5, 0xd2, 0xb1, 0xb1, 0xa1, 0x43, 0x8b, 0x99, 0x63, 0x1e, 0xdf, 0xa4, 0x97, 0x6b, 0xf3, 0x6e, 0x82, 0xff, 0x59, 0xa2, 0x76, 0x98, 0x52, 0xcd, 0x96, 0x67, 0xd4, 0xe2, 0x7b, 0x78, 0x6a, 0xd8, 0xd7, 0x6a, 0x83, 0x5d, 0x64, 0x45, 0x84, 0x8e, 0x89, 0x9e, 0x4a, 0xe9, 0x9a, 0xf6, 0x51, 0x92, 0x67, 0xc, 0xd4, 0xe2, 0x2e, 0xcf, 0xfd, 0x52, 0xb, 0xf9, 0x35, 0x30, 0x1d, 0x1e, 0xe1, 0x3, 0xb4, 0xf2, 0x97, 0x94, 0x9f, 0x8a, 0x71, 0x37, 0x62, 0x3e, 0x48, 0xd, 0x42, 0xcc, 0xae, 0x24, 0xb7, 0xa7, 0x4a, 0xf8, 0x50, 0xd2, 0x3e, 0x8d, 0xbf, 0xda, 0xc3, 0x12, 0xb3, 0xf9, 0xa7, 0x4a, 0xe8, 0x91, 0x27, 0x9f, 0x51, 0x74, 0x1, 0x67, 0x5b, 0xc9, 0xcd, 0xd7, 0x4a, 0x5c, 0xff, 0xce, 0x93, 0xf7, 0x14, 0x5d, 0x78, 0x52, 0x6e, 0xee, 0x99, 0x7, 0x4a, 0xea, 0xfe, 0xd6, 0x44, 0x25, 0xa7, 0xe3, 0x6f, 0xeb, 0xf, 0x6a, 0xf3, 0x93, 0x8e, 0x14, 0xf6, 0x67, 0xeb, 0xc2, 0x5f, 0x2c, 0x73, 0x73, 0x88, 0xad, 0xb2, 0x6b, 0xca, 0x73, 0xb8, 0x8e, 0xff, 0xd0, 0x3f, 0xaf, 0xcd, 0x77, 0x1d, 0xb4, 0x9f, 0x2d, 0x79, 0xe6, 0xb7, 0xe9, 0x2e, 0xbf, 0x62, 0xc9, 0x5d, 0x6d, 0x9e, 0x58, 0xa2, 0x4f, 0x76, 0x0, 0xa9, 0xec, 0xf7, 0x6d, 0xe4, 0x25, 0x4b, 0xee, 0x6a, 0xf3, 0xd8, 0x92, 0xfa, 0x74, 0x33, 0x71, 0xe9, 0x7a, 0xb9, 0x9a, 0xcf, 0x6c, 0xfd, 0xa7, 0x44, 0x17, 0xeb, 0xf5, 0xf7, 0x4a, 0x2e, 0x13, 0xbd, 0xbd, 0xad, 0xd9, 0x99, 0xe8, 0x4d, 0x25, 0x9f, 0x50, 0x74, 0x65, 0x89, 0x79, 0x57, 0xc9, 0x3b, 0x8a, 0x1e, 0xc2, 0xd9, 0x15, 0x75, 0xfc, 0x61, 0x49, 0xe6, 0x1f, 0xbf, 0x1c, 0xa8, 0x52, 0x2b, 0xb1, 0xc9, 0xd4, 0x0, 0x0, 0x0, 0x2, 0x62, 0x4b, 0x47, 0x44, 0x0, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0x2e, 0x23, 0x0, 0x0, 0x2e, 0x23, 0x1, 0x78, 0xa5, 0x3f, 0x76, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d, 0x45, 0x7, 0xe3, 0x2, 0xa, 0x13, 0x29, 0x21, 0x5f, 0x36, 0xe2, 0x14, 0x0, 0x0, 0x0, 0xaf, 0x49, 0x44, 0x41, 0x54, 0x28, 0xcf, 0x7d, 0x91, 0x41, 0xa, 0xc2, 0x40, 0x10, 0x4, 0x6b, 0x92, 0x61, 0x37, 0xb0, 0x87, 0xec, 0x39, 0x3e, 0xc6, 0x3f, 0xf8, 0x9a, 0x3c, 0xc1, 0xd7, 0xf8, 0x3, 0xf, 0x3e, 0xc6, 0xab, 0x44, 0x50, 0x30, 0x26, 0x66, 0x3c, 0x2c, 0x6, 0x3, 0xd9, 0xf4, 0x69, 0xa0, 0xab, 0x9b, 0x86, 0x11, 0x14, 0x87, 0xa7, 0xc2, 0x51, 0x52, 0x90, 0x34, 0xf1, 0xe1, 0xcd, 0x8b, 0x9e, 0xb7, 0xe2, 0x8, 0x44, 0x6a, 0x2, 0x1e, 0x41, 0x0, 0xc3, 0xe8, 0x79, 0x72, 0xa7, 0x3, 0xc5, 0x13, 0xf7, 0x87, 0x5d, 0x3b, 0x46, 0x16, 0xd2, 0xee, 0x7a, 0xbc, 0x9c, 0x18, 0x95, 0x8a, 0xba, 0x69, 0x6f, 0x71, 0xc4, 0xfe, 0x6c, 0x41, 0x63, 0xd3, 0x72, 0xe6, 0xa1, 0x38, 0xc2, 0x10, 0x87, 0x65, 0x1c, 0x63, 0x60, 0x88, 0x4, 0x9c, 0x52, 0xe2, 0xa7, 0x45, 0x9a, 0x79, 0x29, 0x9e, 0x52, 0x29, 0x10, 0x63, 0x5a, 0x1, 0xc, 0x84, 0x42, 0x1, 0xc9, 0x36, 0x8, 0x68, 0x3a, 0x33, 0x0, 0x9, 0x30, 0x5b, 0x5, 0xc, 0x6c, 0x6e, 0x98, 0x36, 0x1b, 0x24, 0xdb, 0xf0, 0xdb, 0xc0, 0x2a, 0x90, 0x54, 0x6c, 0xba, 0x96, 0x0, 0xc9, 0x2, 0xc2, 0xfc, 0xe0, 0xac, 0xbe, 0xe, 0x38, 0x38, 0x83, 0xbf, 0x35, 0x30, 0x94, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char tab_menu_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x36, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x40, 0x5, 0xa3, 0xe0, 0xc1, 0x7f, 0x54, 0x48, 0x3, 0x5, 0xf, 0xe3, 0x1e, 0x7c, 0x81, 0x4b, 0x7f, 0x7b, 0x98, 0x86, 0xc5, 0x15, 0xf7, 0x35, 0xee, 0x5f, 0x2, 0x4b, 0x5f, 0x7f, 0xac, 0x8b, 0xc3, 0xa1, 0x2f, 0xb8, 0x1f, 0xce, 0x7f, 0x38, 0xff, 0x5, 0x37, 0x75, 0xbd, 0xf, 0x0, 0x52, 0xd4, 0x48, 0xb8, 0x2d, 0x78, 0x5a, 0x91, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; @@ -398,12 +338,16 @@ static const unsigned char toggle_off_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x20, 0x8, 0x3, 0x0, 0x0, 0x0, 0x95, 0x43, 0x8e, 0xb6, 0x0, 0x0, 0x1, 0x7a, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x14, 0x17, 0x20, 0x20, 0x25, 0x24, 0x24, 0x28, 0x24, 0x24, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x10, 0x13, 0x22, 0x22, 0x27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x19, 0x19, 0x1c, 0x1a, 0x1a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11, 0x14, 0x12, 0x12, 0x14, 0x23, 0x23, 0x27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0x15, 0x18, 0x20, 0x20, 0x25, 0x20, 0x20, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x24, 0x28, 0x0, 0x0, 0x0, 0x24, 0x24, 0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x24, 0x27, 0x15, 0x15, 0x18, 0x23, 0x23, 0x28, 0x12, 0x12, 0x14, 0x0, 0x0, 0x0, 0x1a, 0x1a, 0x1e, 0x0, 0x0, 0x0, 0x11, 0x11, 0x13, 0x22, 0x22, 0x26, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x24, 0x29, 0x25, 0x25, 0x2a, 0x24, 0x24, 0x28, 0x25, 0x25, 0x28, 0x25, 0x25, 0x29, 0x25, 0x25, 0x27, 0x2d, 0x26, 0x2c, 0x4d, 0x2b, 0x37, 0x63, 0x2f, 0x3f, 0x6e, 0x31, 0x43, 0x71, 0x32, 0x44, 0x6c, 0x31, 0x42, 0x51, 0x2c, 0x39, 0x47, 0x2a, 0x35, 0x66, 0x30, 0x40, 0x4d, 0x2b, 0x38, 0x32, 0x26, 0x2e, 0x26, 0x25, 0x2a, 0x2e, 0x25, 0x2c, 0x3c, 0x28, 0x31, 0x52, 0x2c, 0x39, 0x68, 0x30, 0x40, 0x27, 0x25, 0x2a, 0x50, 0x2c, 0x38, 0x5f, 0x2e, 0x3d, 0x35, 0x27, 0x2f, 0x38, 0x27, 0x30, 0x5e, 0x2e, 0x3d, 0x43, 0x2a, 0x34, 0x5f, 0x2f, 0x3e, 0x2f, 0x25, 0x2c, 0x44, 0x2a, 0x34, 0x2b, 0x26, 0x2c, 0x64, 0x2f, 0x3f, 0x36, 0x27, 0x30, 0x37, 0x27, 0x30, 0x66, 0x2f, 0x40, 0x2c, 0x26, 0x2c, 0x46, 0x2a, 0x35, 0x53, 0x2c, 0x39, 0x40, 0x40, 0x44, 0xad, 0xad, 0xaf, 0xff, 0xff, 0xff, 0xf2, 0xf2, 0xf2, 0x77, 0x77, 0x7a, 0x5b, 0x5b, 0x5f, 0x32, 0x32, 0x37, 0x5d, 0x2e, 0x3d, 0x3e, 0x29, 0x32, 0xc9, 0xc9, 0xca, 0xbb, 0xbb, 0xbd, 0x69, 0x69, 0x6c, 0x69, 0x30, 0x41, 0x2f, 0x26, 0x2d, 0x84, 0x84, 0x87, 0xd6, 0xd6, 0xd7, 0x92, 0x92, 0x94, 0xa0, 0xa0, 0xa2, 0x4e, 0x4e, 0x52, 0x48, 0x2b, 0x36, 0x2c, 0x26, 0x2b, 0x97, 0xb0, 0x86, 0xb4, 0x0, 0x0, 0x0, 0x41, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x1, 0x2, 0x3, 0x4, 0x9, 0xe, 0x13, 0x16, 0x18, 0x19, 0xa, 0x26, 0x36, 0x44, 0x4d, 0x52, 0x54, 0x55, 0x6, 0x12, 0x27, 0x43, 0x98, 0xe5, 0xfa, 0xfe, 0x8, 0x17, 0x35, 0x86, 0xf3, 0x7, 0x3a, 0xb4, 0xb9, 0xb, 0x28, 0x8a, 0x8b, 0xf6, 0x45, 0x5, 0x9b, 0xe6, 0xe6, 0x37, 0xf, 0xfb, 0x4c, 0xfe, 0x4e, 0x4f, 0x50, 0xfb, 0x9c, 0xf6, 0x8c, 0x3b, 0xbb, 0x3c, 0x87, 0xf3, 0x53, 0x14, 0xe5, 0x7c, 0xf3, 0x66, 0x0, 0x0, 0x2, 0x29, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xdd, 0x95, 0x3, 0x93, 0x24, 0x4b, 0x14, 0x85, 0x5f, 0xb9, 0xaa, 0x6d, 0x8e, 0x6d, 0xdb, 0x73, 0x2b, 0xb3, 0xe7, 0xad, 0x6d, 0xab, 0xdd, 0x63, 0xe3, 0xbf, 0x6f, 0x67, 0x65, 0xbb, 0xb4, 0xbb, 0x81, 0xc5, 0x97, 0x11, 0x27, 0x78, 0x4f, 0xea, 0xe2, 0xbf, 0x3f, 0x9, 0x86, 0xe5, 0x38, 0xde, 0x16, 0x8e, 0x63, 0x19, 0xc3, 0x70, 0x96, 0x17, 0x44, 0x49, 0x56, 0x1c, 0x36, 0x28, 0xb2, 0x24, 0xa, 0x3c, 0xab, 0xdf, 0x9d, 0x77, 0xca, 0x2e, 0xb7, 0xc7, 0xeb, 0xf3, 0x7, 0x2c, 0xf1, 0xfb, 0xbc, 0x1e, 0xb7, 0x4b, 0x76, 0xf2, 0x2d, 0xa7, 0x60, 0x83, 0xa1, 0x70, 0x24, 0x1a, 0x8b, 0x27, 0xb6, 0xc1, 0x86, 0xed, 0x44, 0x3c, 0x16, 0x8d, 0x84, 0x43, 0x41, 0xb6, 0x29, 0x3e, 0xd9, 0xd6, 0xde, 0xd1, 0xa9, 0x22, 0x84, 0x31, 0x6, 0xd, 0x4c, 0x40, 0x38, 0x85, 0xd, 0x4d, 0x3a, 0x3b, 0xda, 0xdb, 0x92, 0xd, 0xe, 0x4c, 0x97, 0xd2, 0xdd, 0xa3, 0xa2, 0x9d, 0xff, 0x6f, 0xdd, 0xbe, 0x43, 0xb9, 0x7b, 0x6f, 0x47, 0x73, 0x49, 0x21, 0x93, 0x73, 0xf4, 0x74, 0x2b, 0x5d, 0x4c, 0xfd, 0xfe, 0x92, 0xbb, 0x57, 0xc5, 0xf7, 0x1f, 0x3c, 0x7c, 0xf4, 0x18, 0x8, 0x8f, 0x9f, 0x3c, 0x7d, 0xf6, 0xfc, 0xfe, 0xb, 0xed, 0x24, 0x66, 0x37, 0xe9, 0x75, 0x4b, 0xb5, 0x77, 0x60, 0xfb, 0xfa, 0x7, 0x54, 0xfc, 0xf2, 0xd5, 0x6b, 0x68, 0xe0, 0xcd, 0xdb, 0x97, 0x2f, 0x2c, 0xdf, 0x62, 0xb0, 0xbf, 0xaf, 0x7a, 0x9, 0xbe, 0xcd, 0x33, 0x84, 0xde, 0xbd, 0x7f, 0x2, 0x4d, 0x7c, 0x78, 0xff, 0xf1, 0x31, 0x58, 0x30, 0x34, 0xdc, 0x36, 0x42, 0x8f, 0xc0, 0x8, 0xae, 0x51, 0xf4, 0xe9, 0xf3, 0x17, 0x68, 0xe1, 0xeb, 0xb7, 0x34, 0x58, 0x31, 0xea, 0x12, 0xa8, 0x1, 0x2b, 0xba, 0xc7, 0x50, 0xe6, 0x19, 0xe8, 0xc8, 0x66, 0x80, 0x92, 0xcb, 0x17, 0x8a, 0x25, 0xd8, 0x2d, 0x94, 0xd1, 0x64, 0xf, 0x8, 0xe3, 0x13, 0x93, 0xf4, 0xe, 0x9c, 0xe4, 0x89, 0xe3, 0xfd, 0x3, 0xd0, 0x71, 0xb0, 0xf, 0x94, 0xc3, 0xa3, 0xdc, 0x71, 0x21, 0xb7, 0x4b, 0x2, 0x35, 0xa1, 0x4c, 0xd, 0x4b, 0x1c, 0x35, 0x90, 0xa7, 0x67, 0xf0, 0xc9, 0x29, 0xe8, 0x38, 0x3d, 0xa9, 0x1c, 0x80, 0x4, 0x1d, 0x9d, 0xed, 0x9e, 0x13, 0x3, 0x22, 0x94, 0xed, 0x59, 0x99, 0xa7, 0x6, 0x6d, 0x73, 0x2a, 0xbe, 0x6d, 0xf0, 0x60, 0x8f, 0x6f, 0x13, 0x25, 0x41, 0x65, 0xb9, 0xc8, 0xef, 0x1e, 0x1e, 0x1e, 0xee, 0x69, 0x2, 0x94, 0xf9, 0xb6, 0x8a, 0x81, 0xec, 0x55, 0x2d, 0x4f, 0xb0, 0x47, 0x4e, 0x90, 0xbf, 0xdc, 0x25, 0x91, 0x44, 0x74, 0x27, 0x90, 0x3c, 0xb, 0xc8, 0xf2, 0xd, 0xce, 0xcf, 0x60, 0xaf, 0x58, 0xaa, 0x18, 0xe8, 0xdf, 0x80, 0x15, 0x27, 0x6c, 0x7e, 0x61, 0xb7, 0x78, 0x5e, 0xcc, 0x43, 0xab, 0x1, 0xfd, 0x5, 0x9a, 0x7, 0x8b, 0x36, 0x79, 0xb0, 0x77, 0x76, 0x5, 0x90, 0x2b, 0xed, 0x55, 0x84, 0xb2, 0x18, 0x16, 0x98, 0x5a, 0x26, 0x2e, 0xe1, 0x5f, 0xce, 0x44, 0x5a, 0xb, 0x83, 0x3f, 0x5f, 0xb, 0xcb, 0xb4, 0x16, 0xcc, 0xab, 0xf1, 0x9a, 0xc6, 0xdb, 0x57, 0x23, 0xed, 0x7, 0x2b, 0xab, 0x2a, 0xba, 0x69, 0xed, 0x7, 0xe6, 0x6c, 0xaf, 0xae, 0xd5, 0xfa, 0x1, 0xed, 0x48, 0x8a, 0x7b, 0x7d, 0x3, 0xa5, 0x10, 0x6d, 0x48, 0xb8, 0x2, 0x2a, 0x2f, 0x30, 0xa2, 0x73, 0xdd, 0xad, 0x24, 0x9b, 0x7b, 0x5a, 0x97, 0x14, 0xf6, 0x44, 0x63, 0x53, 0xdb, 0x60, 0xcb, 0xf6, 0x54, 0x2c, 0xea, 0x9, 0x4b, 0x5d, 0x6c, 0x6b, 0x57, 0xee, 0x93, 0x5d, 0x13, 0xc3, 0xb3, 0x9b, 0x1, 0x1b, 0x36, 0x67, 0x87, 0x27, 0x5c, 0x72, 0x1f, 0xcf, 0xe8, 0xa7, 0xca, 0x88, 0x30, 0xb9, 0xd5, 0x66, 0x3f, 0x17, 0xda, 0xb6, 0x26, 0x85, 0x11, 0x96, 0x31, 0x99, 0x4c, 0xfc, 0xf, 0x40, 0x27, 0xd3, 0xbf, 0xc4, 0x77, 0x82, 0xde, 0x40, 0xde, 0x4b, 0x3f, 0xe2, 0x98, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char toggle_off_disabled_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x20, 0x8, 0x3, 0x0, 0x0, 0x0, 0x95, 0x43, 0x8e, 0xb6, 0x0, 0x0, 0x0, 0xfc, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x14, 0x17, 0x20, 0x20, 0x25, 0x24, 0x24, 0x28, 0x24, 0x24, 0x29, 0x24, 0x24, 0x29, 0x25, 0x25, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x10, 0x13, 0x22, 0x22, 0x27, 0x24, 0x24, 0x28, 0x25, 0x25, 0x28, 0x25, 0x25, 0x29, 0x25, 0x25, 0x27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x19, 0x19, 0x1c, 0x2b, 0x26, 0x2c, 0x40, 0x40, 0x44, 0x4e, 0x4e, 0x52, 0x1a, 0x1a, 0x1d, 0x32, 0x32, 0x37, 0x2c, 0x26, 0x2c, 0x26, 0x25, 0x2a, 0x27, 0x25, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11, 0x14, 0x2f, 0x26, 0x2d, 0x12, 0x12, 0x14, 0x23, 0x23, 0x27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0x15, 0x18, 0x20, 0x20, 0x25, 0x20, 0x20, 0x24, 0x5b, 0x5b, 0x5f, 0x84, 0x84, 0x87, 0x77, 0x77, 0x7a, 0x20, 0x20, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x69, 0x69, 0x6c, 0x24, 0x24, 0x28, 0x0, 0x0, 0x0, 0x24, 0x24, 0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x24, 0x27, 0x15, 0x15, 0x18, 0x23, 0x23, 0x28, 0x12, 0x12, 0x14, 0x0, 0x0, 0x0, 0x1a, 0x1a, 0x1e, 0x0, 0x0, 0x0, 0x11, 0x11, 0x13, 0x22, 0x22, 0x26, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71, 0xb, 0x1b, 0xbb, 0x0, 0x0, 0x0, 0x54, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x1, 0x2, 0x3, 0x4, 0x9, 0xe, 0x13, 0x16, 0x18, 0x19, 0xa, 0x26, 0x36, 0x44, 0x4d, 0x52, 0x54, 0x55, 0x6, 0x12, 0x27, 0x43, 0x98, 0xe5, 0xfa, 0xfe, 0xff, 0xff, 0x8, 0x17, 0x35, 0x86, 0xf3, 0xff, 0xff, 0xff, 0xff, 0x7, 0x3a, 0xb4, 0xff, 0xff, 0xff, 0xb9, 0xff, 0xff, 0xff, 0xff, 0xb, 0x28, 0x8a, 0xff, 0x8b, 0xf6, 0x45, 0x5, 0x9b, 0xe6, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x37, 0xf, 0xff, 0xfb, 0x4c, 0xfe, 0x4e, 0x4f, 0x50, 0xfb, 0x9c, 0xf6, 0x8c, 0x3b, 0xbb, 0x3c, 0x87, 0xf3, 0x53, 0x14, 0xd4, 0x6d, 0x6c, 0xf9, 0x0, 0x0, 0x2, 0x3, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xdd, 0x55, 0x85, 0x9a, 0xe2, 0x30, 0x18, 0xbc, 0x7a, 0x8b, 0xbb, 0x7b, 0x2, 0xbb, 0x4d, 0x36, 0xb8, 0x3b, 0xeb, 0xae, 0xef, 0xff, 0x2e, 0x47, 0x48, 0x3f, 0xa0, 0x7a, 0xae, 0x83, 0x56, 0xfe, 0xe9, 0xfc, 0xfe, 0xe9, 0x6f, 0x2, 0xc7, 0xb, 0x82, 0xf8, 0x45, 0x8, 0x2, 0xcf, 0x39, 0x9a, 0xf3, 0xa2, 0x24, 0x2b, 0xaa, 0xe6, 0xfb, 0x2, 0x34, 0x55, 0x91, 0x25, 0x91, 0xb7, 0x3f, 0x5d, 0xf4, 0xab, 0x81, 0x60, 0x28, 0x1c, 0x89, 0xc6, 0x3c, 0x11, 0x8d, 0x84, 0x43, 0xc1, 0x80, 0xea, 0x17, 0x2d, 0x2a, 0xf8, 0x78, 0x22, 0x99, 0x4a, 0x67, 0xb2, 0xb9, 0x7c, 0xe1, 0xb, 0xc8, 0xe7, 0xb2, 0x99, 0x74, 0x2a, 0x99, 0x88, 0xf3, 0x26, 0xfb, 0x62, 0xa9, 0x5c, 0xa9, 0xd6, 0x0, 0x80, 0x10, 0xb2, 0xfb, 0x20, 0x5, 0x80, 0x75, 0xe8, 0x48, 0x52, 0xad, 0x94, 0x4b, 0xc5, 0x23, 0x6, 0xae, 0xa1, 0x9d, 0x9c, 0xd6, 0x80, 0x8e, 0xf0, 0x1e, 0x48, 0xdf, 0xb1, 0xd4, 0x81, 0x8b, 0x8e, 0xd3, 0x13, 0xad, 0xc1, 0x1d, 0xfc, 0x57, 0x82, 0x67, 0x35, 0x48, 0x30, 0x6a, 0xb6, 0x76, 0x97, 0x5b, 0x3a, 0x41, 0x98, 0xb4, 0x77, 0x4a, 0xdc, 0x3c, 0x39, 0xb, 0x2a, 0xfb, 0x38, 0xf0, 0x9d, 0x6e, 0xaf, 0x6, 0x11, 0xea, 0x1f, 0xdf, 0x41, 0x10, 0x6a, 0x7b, 0xc6, 0x62, 0xd0, 0xed, 0xf0, 0x6, 0x81, 0x58, 0xa, 0xd, 0x1, 0xa1, 0xa2, 0x8f, 0xa1, 0x23, 0xd2, 0xf2, 0x62, 0x18, 0x8e, 0x4a, 0x63, 0x26, 0x81, 0x93, 0x2, 0x13, 0xa0, 0xe3, 0xbe, 0xf5, 0xe, 0x82, 0x3d, 0x25, 0x14, 0x26, 0x1, 0x89, 0x11, 0xf0, 0x72, 0x70, 0xba, 0x15, 0x60, 0xbf, 0x3, 0x11, 0xe3, 0xcf, 0x6c, 0xbe, 0x58, 0xa2, 0x42, 0x7f, 0xb1, 0xc5, 0xee, 0x8b, 0x9d, 0x5f, 0xad, 0x37, 0xcc, 0x7, 0x41, 0x9, 0x65, 0x21, 0xbd, 0xd9, 0x8a, 0x3d, 0xe9, 0xf9, 0x7c, 0x46, 0x16, 0xb3, 0x3e, 0x35, 0xa4, 0x5f, 0x6, 0x2e, 0x46, 0x8a, 0xc0, 0x8, 0xd4, 0xcb, 0x2b, 0x88, 0x75, 0x3b, 0x81, 0x8e, 0xd, 0x1, 0xd4, 0x68, 0x8e, 0xfa, 0xe7, 0x94, 0xe0, 0x7c, 0x4f, 0x90, 0xbf, 0x56, 0x45, 0x46, 0x50, 0xba, 0xa9, 0x41, 0xec, 0x10, 0xb0, 0x96, 0x41, 0xd0, 0x3f, 0xdf, 0x7e, 0xe1, 0x79, 0xff, 0xfc, 0xfc, 0x9c, 0x6c, 0xbf, 0xf6, 0x14, 0xb7, 0x25, 0x83, 0x40, 0xd, 0xd7, 0x3c, 0x15, 0x90, 0x9d, 0x2, 0xec, 0xae, 0x40, 0x9, 0xdd, 0x1, 0xef, 0x18, 0xa0, 0x2, 0x59, 0xda, 0x5c, 0xd8, 0xc7, 0x80, 0x97, 0xd7, 0x5f, 0xc8, 0x42, 0x7f, 0x79, 0xbe, 0x9c, 0x17, 0x2c, 0x4, 0xfb, 0x2c, 0xd0, 0x3a, 0xb8, 0xff, 0x42, 0x1d, 0x10, 0x5a, 0x95, 0x33, 0x44, 0xd8, 0x97, 0x81, 0xfb, 0xa4, 0xc4, 0xed, 0x2b, 0xf1, 0x1, 0xfe, 0x40, 0x25, 0xd2, 0x5e, 0x18, 0x7c, 0x7b, 0x2f, 0x3c, 0xd2, 0x5e, 0xf8, 0x72, 0x37, 0x16, 0xbe, 0xdc, 0x8d, 0x6c, 0x1e, 0x3c, 0x3d, 0xd7, 0x40, 0xdb, 0x32, 0xf, 0xbc, 0xec, 0x9f, 0x5f, 0xf6, 0xf3, 0x80, 0x4d, 0x24, 0x2d, 0xf8, 0xfa, 0x6, 0xea, 0x80, 0xd, 0x24, 0x68, 0x0, 0x6c, 0x5f, 0x8e, 0xf6, 0xd5, 0xd7, 0xa0, 0x56, 0x34, 0xcf, 0xb4, 0x86, 0x92, 0xc, 0xa5, 0x33, 0x17, 0xf9, 0xc2, 0x17, 0x91, 0xbf, 0xc8, 0xa4, 0x43, 0x49, 0xa5, 0xc1, 0x5b, 0xa7, 0x72, 0x47, 0xd, 0xac, 0x47, 0xd7, 0xef, 0xb1, 0x2f, 0xe0, 0xfd, 0x7a, 0xb4, 0xe, 0xa8, 0x1d, 0x91, 0xb3, 0x6f, 0x95, 0xb1, 0xb4, 0xf9, 0x28, 0x7d, 0x79, 0x2f, 0x94, 0x3e, 0x36, 0xd2, 0x98, 0xe7, 0x5c, 0x36, 0x93, 0xf8, 0x15, 0xa0, 0x9b, 0xe9, 0xff, 0xc2, 0x67, 0x14, 0xf4, 0xa5, 0xb3, 0x35, 0x5e, 0x63, 0x97, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char toggle_on_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x20, 0x8, 0x3, 0x0, 0x0, 0x0, 0x95, 0x43, 0x8e, 0xb6, 0x0, 0x0, 0x1, 0x74, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0xd, 0xf, 0x1a, 0x1a, 0x1e, 0x20, 0x20, 0x24, 0x22, 0x22, 0x27, 0x24, 0x24, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xa, 0xc, 0x1d, 0x1d, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11, 0x14, 0x23, 0x23, 0x28, 0x12, 0x12, 0x15, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xb, 0xd, 0x23, 0x23, 0x28, 0xb, 0xb, 0xd, 0x1e, 0x1e, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, 0xe, 0x10, 0x1a, 0x1a, 0x1e, 0x1a, 0x1a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x20, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, 0xe, 0x10, 0xb, 0xb, 0xd, 0x0, 0x0, 0x0, 0x13, 0x13, 0x15, 0x0, 0x0, 0x0, 0xb, 0xb, 0xc, 0x1d, 0x1d, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x25, 0x25, 0x2a, 0x24, 0x24, 0x29, 0x25, 0x2c, 0x36, 0x27, 0x49, 0x65, 0x29, 0x5d, 0x85, 0x2a, 0x66, 0x95, 0x2a, 0x68, 0x99, 0x29, 0x64, 0x92, 0x28, 0x4c, 0x6b, 0x25, 0x27, 0x2d, 0x27, 0x43, 0x5c, 0x29, 0x5f, 0x89, 0x27, 0x49, 0x66, 0x25, 0x30, 0x3e, 0x25, 0x26, 0x2d, 0x25, 0x25, 0x2b, 0x25, 0x26, 0x2c, 0x25, 0x2d, 0x38, 0x25, 0x3a, 0x4c, 0x27, 0x4d, 0x6b, 0x29, 0x60, 0x8c, 0x27, 0x44, 0x5c, 0x27, 0x4b, 0x69, 0x28, 0x59, 0x7f, 0x25, 0x34, 0x43, 0x25, 0x35, 0x45, 0x28, 0x58, 0x7f, 0x25, 0x26, 0x2b, 0x27, 0x40, 0x57, 0x27, 0x41, 0x57, 0x25, 0x2a, 0x33, 0x29, 0x5d, 0x87, 0x25, 0x34, 0x44, 0x25, 0x2b, 0x34, 0x40, 0x40, 0x44, 0xad, 0xad, 0xaf, 0xff, 0xff, 0xff, 0xf2, 0xf2, 0xf2, 0x77, 0x77, 0x7a, 0x5b, 0x5b, 0x5f, 0x4e, 0x4e, 0x52, 0xc9, 0xc9, 0xca, 0x27, 0x43, 0x5b, 0x27, 0x4d, 0x6c, 0x27, 0x4e, 0x6d, 0xbb, 0xbb, 0xbd, 0x69, 0x69, 0x6c, 0x28, 0x56, 0x7b, 0x26, 0x3b, 0x4e, 0x26, 0x3a, 0x4e, 0x32, 0x32, 0x37, 0x84, 0x84, 0x87, 0xd6, 0xd6, 0xd7, 0x29, 0x61, 0x8d, 0x25, 0x2e, 0x39, 0x92, 0x92, 0x94, 0xa0, 0xa0, 0xa2, 0xe4, 0xe4, 0xe5, 0x27, 0x44, 0x5d, 0xdd, 0xc9, 0xf2, 0x7e, 0x0, 0x0, 0x0, 0x41, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x1, 0x2, 0x3, 0x4, 0x9, 0xe, 0x13, 0x16, 0x18, 0x19, 0xa, 0x26, 0x36, 0x44, 0x4d, 0x52, 0x54, 0x55, 0x6, 0x12, 0x27, 0x43, 0x80, 0xc5, 0xe7, 0xf5, 0xfe, 0x8, 0x17, 0x35, 0x73, 0xd9, 0x7, 0x3a, 0x96, 0xf9, 0x9a, 0xb, 0x28, 0x76, 0xfb, 0x77, 0xde, 0x45, 0x5, 0x82, 0xc6, 0xc6, 0x37, 0xf, 0xe9, 0x4c, 0x4e, 0x4f, 0x50, 0x83, 0x78, 0x3b, 0x9c, 0x3c, 0x74, 0xda, 0x53, 0x14, 0x37, 0x21, 0x5a, 0x6c, 0x0, 0x0, 0x2, 0x4, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xdd, 0x95, 0x63, 0x83, 0xdc, 0x60, 0x10, 0xc7, 0x1b, 0x3c, 0xc1, 0xda, 0x3e, 0xdb, 0x36, 0xe7, 0x6c, 0xdb, 0xa, 0xcf, 0xc6, 0x7e, 0xf8, 0x2a, 0x6d, 0xb8, 0xac, 0x7b, 0xbf, 0xf7, 0xf3, 0x1f, 0xcf, 0x7c, 0xf8, 0x97, 0xc0, 0x70, 0x82, 0x20, 0xb3, 0x42, 0x10, 0x38, 0x96, 0xd2, 0x1c, 0x27, 0x11, 0x45, 0x33, 0xac, 0x2d, 0xb, 0x2c, 0x43, 0x53, 0x88, 0xc4, 0xad, 0xde, 0x49, 0x3b, 0xe3, 0x70, 0xba, 0xdc, 0x1e, 0xaf, 0x2f, 0x23, 0x5e, 0x8f, 0xdb, 0xe5, 0x74, 0x30, 0x76, 0xd2, 0x14, 0x5, 0xee, 0xf, 0x4, 0x43, 0xe1, 0x48, 0x34, 0x16, 0x87, 0x2c, 0xc4, 0x63, 0xd1, 0x48, 0x38, 0x14, 0xc, 0xf8, 0x71, 0x83, 0x7d, 0xa2, 0xa0, 0xb0, 0xa8, 0x78, 0x4, 0x72, 0x64, 0xa4, 0xb8, 0xa8, 0xb0, 0x20, 0xa1, 0x53, 0xc0, 0x4a, 0xd8, 0xd2, 0xb2, 0x72, 0xc8, 0xc4, 0xe8, 0xd8, 0xf8, 0xc4, 0xa4, 0xc2, 0xd4, 0xf4, 0x28, 0x94, 0x97, 0x95, 0xb2, 0x25, 0x98, 0x96, 0x3f, 0xed, 0xac, 0xc8, 0x18, 0xfb, 0xcc, 0xec, 0xdc, 0xfc, 0xc2, 0xe2, 0xd2, 0x17, 0x96, 0x57, 0x56, 0xd7, 0xd6, 0x37, 0x66, 0xe2, 0x15, 0x4e, 0x5a, 0xad, 0x3, 0x5e, 0x59, 0x55, 0x5d, 0x93, 0xd1, 0x7e, 0x73, 0x6b, 0x1b, 0x74, 0xec, 0xec, 0x6e, 0xce, 0xd4, 0xd4, 0x56, 0x55, 0x7e, 0x4f, 0x82, 0x2c, 0x70, 0xd5, 0x41, 0x6, 0xf6, 0xf6, 0xb7, 0x56, 0xc0, 0xc0, 0xca, 0xd6, 0xc1, 0x5e, 0x5d, 0x7d, 0x41, 0x83, 0x12, 0x2, 0x86, 0x1c, 0x8d, 0x90, 0x89, 0xc3, 0xa3, 0x63, 0x30, 0xb1, 0x33, 0x77, 0x2, 0x8d, 0xe, 0xa4, 0x8, 0xe0, 0x94, 0xb3, 0x9, 0x54, 0x4e, 0xcf, 0x38, 0x5e, 0x0, 0x91, 0x93, 0x40, 0x94, 0x41, 0xe1, 0xfc, 0x2, 0x2c, 0x5c, 0x9e, 0x43, 0x73, 0x4b, 0xab, 0x92, 0x3, 0x41, 0xbb, 0xa2, 0xa0, 0x22, 0x5f, 0x9d, 0x5e, 0x73, 0xa7, 0x22, 0x27, 0x6b, 0x2, 0x37, 0xb7, 0x60, 0xe1, 0xee, 0x6, 0xda, 0xea, 0x69, 0x42, 0x11, 0x60, 0xda, 0x63, 0x5a, 0x0, 0xdc, 0x3d, 0xc0, 0xd5, 0x83, 0xf8, 0xc8, 0x8b, 0xaa, 0xc0, 0xd3, 0x33, 0x58, 0x78, 0x7e, 0x82, 0xf2, 0xe, 0x86, 0x54, 0x4, 0xa, 0x3a, 0xb5, 0x1e, 0x8a, 0x8f, 0x0, 0xf0, 0x72, 0x26, 0xca, 0x2f, 0x8f, 0xaa, 0xc0, 0xc4, 0x22, 0x58, 0x58, 0x9c, 0x0, 0xe8, 0x2a, 0xf8, 0x26, 0xc0, 0xb8, 0xb5, 0x21, 0xba, 0xff, 0x12, 0xc1, 0xd9, 0xeb, 0x67, 0xe3, 0xb7, 0xb3, 0x9c, 0x23, 0xa0, 0x5d, 0x6d, 0xa0, 0xf2, 0xf8, 0x0, 0xf7, 0xbc, 0xf0, 0x59, 0x40, 0xe0, 0x72, 0xad, 0x1, 0x4e, 0xb5, 0xe8, 0xba, 0x20, 0xf2, 0x8f, 0xfc, 0xd9, 0xd7, 0x2, 0x3e, 0xe6, 0xda, 0x5, 0xc, 0x39, 0xba, 0x41, 0xe3, 0xfe, 0x41, 0x2, 0x38, 0x15, 0x0, 0x24, 0x21, 0xf3, 0x1c, 0x74, 0x7, 0x11, 0xf6, 0xd3, 0x93, 0xa8, 0xee, 0x42, 0x6d, 0xfe, 0xbb, 0xd0, 0xa3, 0xed, 0x42, 0xe, 0xdb, 0xb8, 0x61, 0xdc, 0xc6, 0xa4, 0xba, 0x8d, 0xea, 0x3d, 0xe8, 0xed, 0xab, 0xc9, 0xe7, 0x1e, 0xd4, 0xf4, 0xf5, 0xab, 0xf7, 0x40, 0xb9, 0x48, 0xac, 0x73, 0x60, 0x10, 0x72, 0x66, 0x70, 0xc0, 0xc9, 0x26, 0x8c, 0x37, 0xad, 0x84, 0xe, 0xba, 0xc2, 0x91, 0xb6, 0x72, 0xc8, 0x4a, 0x79, 0x5b, 0x24, 0xec, 0xa, 0xd2, 0x25, 0xb8, 0xf9, 0x2a, 0x57, 0x32, 0x8e, 0x96, 0xfa, 0x8e, 0x21, 0x5f, 0x16, 0x86, 0x3a, 0xea, 0x5b, 0x1c, 0x4c, 0x25, 0x89, 0x59, 0xbf, 0x4a, 0x3, 0x6a, 0x1d, 0x2e, 0xc8, 0xfe, 0x17, 0xa, 0x86, 0x5b, 0x51, 0x3, 0x8e, 0xa5, 0xf9, 0x4c, 0x64, 0xe, 0x68, 0x9f, 0xe9, 0xbd, 0xf0, 0x9, 0xb7, 0x71, 0x36, 0xc6, 0x9b, 0x3d, 0x7f, 0x21, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char tool_button_pressed_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x2d, 0xf, 0x53, 0x0, 0x0, 0x1, 0xfe, 0x50, 0x4c, 0x54, 0x45, 0x29, 0x3a, 0x40, 0x2d, 0x3e, 0x44, 0x26, 0x34, 0x3b, 0x24, 0x34, 0x39, 0x23, 0x31, 0x38, 0x22, 0x31, 0x37, 0x22, 0x31, 0x37, 0x22, 0x30, 0x36, 0x22, 0x31, 0x36, 0x26, 0x34, 0x3c, 0x32, 0x44, 0x4c, 0x26, 0x34, 0x39, 0x23, 0x31, 0x36, 0x21, 0x2e, 0x34, 0x1f, 0x2c, 0x30, 0x1f, 0x2b, 0x2f, 0x1f, 0x2a, 0x2e, 0x1e, 0x2b, 0x2f, 0x1f, 0x2b, 0x2e, 0x36, 0x4b, 0x52, 0x25, 0x33, 0x38, 0x20, 0x2f, 0x32, 0x1c, 0x29, 0x2e, 0x1b, 0x26, 0x2a, 0x1a, 0x23, 0x26, 0x18, 0x22, 0x26, 0x19, 0x22, 0x26, 0x19, 0x23, 0x26, 0x19, 0x26, 0x29, 0x20, 0x2d, 0x32, 0x25, 0x31, 0x38, 0x3c, 0x51, 0x59, 0x23, 0x31, 0x37, 0x1f, 0x2b, 0x31, 0x1a, 0x25, 0x2b, 0x17, 0x20, 0x24, 0x15, 0x1c, 0x21, 0x14, 0x1b, 0x21, 0x14, 0x1c, 0x21, 0x13, 0x1b, 0x21, 0x15, 0x1d, 0x21, 0x1a, 0x25, 0x2a, 0x40, 0x57, 0x60, 0x23, 0x31, 0x36, 0x1f, 0x2b, 0x31, 0x1b, 0x25, 0x29, 0x16, 0x1e, 0x23, 0x14, 0x1b, 0x1d, 0x12, 0x19, 0x1d, 0x12, 0x1b, 0x1d, 0x14, 0x1a, 0x1d, 0x45, 0x5e, 0x67, 0x22, 0x32, 0x37, 0x20, 0x2d, 0x31, 0x1a, 0x26, 0x2a, 0x15, 0x1f, 0x25, 0x14, 0x1c, 0x1f, 0x12, 0x1b, 0x1f, 0x12, 0x1b, 0x20, 0x14, 0x1b, 0x1f, 0x15, 0x1e, 0x24, 0x1a, 0x25, 0x29, 0x4b, 0x64, 0x6d, 0x23, 0x32, 0x38, 0x20, 0x2e, 0x32, 0x1b, 0x27, 0x2b, 0x17, 0x22, 0x27, 0x16, 0x1e, 0x23, 0x14, 0x1e, 0x23, 0x16, 0x20, 0x24, 0x14, 0x1e, 0x22, 0x15, 0x1e, 0x22, 0x17, 0x21, 0x27, 0x1c, 0x27, 0x2c, 0x4f, 0x6a, 0x75, 0x21, 0x2f, 0x33, 0x1d, 0x29, 0x2d, 0x19, 0x23, 0x2a, 0x18, 0x22, 0x27, 0x16, 0x21, 0x27, 0x18, 0x23, 0x29, 0x17, 0x21, 0x26, 0x19, 0x23, 0x29, 0x1c, 0x28, 0x2d, 0x21, 0x2e, 0x33, 0x54, 0x70, 0x7c, 0x23, 0x33, 0x38, 0x22, 0x30, 0x34, 0x1e, 0x2a, 0x2f, 0x1a, 0x26, 0x2d, 0x1a, 0x25, 0x2b, 0x19, 0x25, 0x2b, 0x1a, 0x26, 0x2d, 0x1a, 0x26, 0x2c, 0x18, 0x25, 0x2a, 0x1a, 0x24, 0x2a, 0x1a, 0x25, 0x2c, 0x1d, 0x2a, 0x2f, 0x22, 0x2f, 0x34, 0x59, 0x77, 0x82, 0x23, 0x33, 0x39, 0x22, 0x30, 0x35, 0x1f, 0x2c, 0x31, 0x1c, 0x28, 0x30, 0x1c, 0x28, 0x2e, 0x1b, 0x29, 0x2f, 0x1c, 0x2a, 0x31, 0x1b, 0x28, 0x2f, 0x1c, 0x28, 0x2d, 0x1b, 0x27, 0x2f, 0x1f, 0x2b, 0x31, 0x5e, 0x7d, 0x8a, 0x24, 0x34, 0x39, 0x21, 0x2f, 0x37, 0x20, 0x2d, 0x34, 0x1d, 0x2b, 0x33, 0x1d, 0x2b, 0x32, 0x1d, 0x2d, 0x35, 0x1e, 0x2e, 0x36, 0x1f, 0x2e, 0x36, 0x1d, 0x2b, 0x34, 0x1d, 0x2b, 0x31, 0x1d, 0x2b, 0x32, 0x20, 0x2d, 0x32, 0x21, 0x2f, 0x36, 0x63, 0x83, 0x90, 0x25, 0x34, 0x39, 0x21, 0x31, 0x36, 0x1f, 0x2e, 0x34, 0x1f, 0x2e, 0x34, 0x1f, 0x2e, 0x36, 0x20, 0x31, 0x39, 0x21, 0x33, 0x3b, 0x21, 0x32, 0x3b, 0x1f, 0x30, 0x37, 0x1f, 0x2e, 0x35, 0x1e, 0x2d, 0x33, 0x1f, 0x2d, 0x33, 0x21, 0x30, 0x36, 0x67, 0x8a, 0x97, 0x24, 0x33, 0x39, 0x20, 0x30, 0x36, 0x1f, 0x2f, 0x35, 0x21, 0x30, 0x37, 0x22, 0x32, 0x39, 0x21, 0x35, 0x3e, 0x24, 0x37, 0x41, 0x24, 0x36, 0x41, 0x21, 0x33, 0x3c, 0x21, 0x31, 0x38, 0x1e, 0x2f, 0x35, 0x1e, 0x2e, 0x35, 0x20, 0x2e, 0x35, 0x24, 0x31, 0x39, 0x6c, 0x90, 0x9e, 0x22, 0x30, 0x36, 0x1f, 0x2e, 0x36, 0x20, 0x30, 0x36, 0x20, 0x31, 0x39, 0x23, 0x34, 0x3d, 0x23, 0x37, 0x41, 0x26, 0x3c, 0x47, 0x26, 0x3b, 0x46, 0x22, 0x35, 0x3f, 0x22, 0x32, 0x3b, 0x1f, 0x30, 0x37, 0x1f, 0x2e, 0x35, 0x1f, 0x2d, 0x35, 0x21, 0x30, 0x36, 0x72, 0x96, 0xa5, 0x7e, 0x8c, 0xc3, 0xb0, 0x0, 0x0, 0x0, 0xaa, 0x74, 0x52, 0x4e, 0x53, 0xc3, 0xc3, 0xe6, 0xd7, 0xcb, 0xc3, 0xbf, 0xbe, 0xbd, 0xe5, 0xc3, 0xd7, 0xc0, 0xac, 0xa0, 0x9a, 0x98, 0x98, 0x98, 0xc3, 0xcb, 0xac, 0x92, 0x82, 0x7b, 0x78, 0x78, 0x7b, 0x82, 0xac, 0xcb, 0xc3, 0xc3, 0xa0, 0x82, 0x6f, 0x67, 0x64, 0x63, 0x64, 0x67, 0x82, 0xc3, 0xbf, 0x9a, 0x7a, 0x67, 0x5e, 0x5b, 0x5a, 0x5e, 0xc3, 0xbd, 0x98, 0x78, 0x64, 0x5b, 0x57, 0x57, 0x5b, 0x64, 0x78, 0xc3, 0xbd, 0x98, 0x78, 0x63, 0x5a, 0x57, 0x56, 0x57, 0x5a, 0x63, 0x77, 0xc3, 0x98, 0x78, 0x63, 0x5a, 0x57, 0x56, 0x5a, 0x63, 0x77, 0x98, 0xc3, 0xbd, 0x98, 0x78, 0x63, 0x5a, 0x57, 0x56, 0x56, 0x57, 0x5a, 0x63, 0x77, 0x98, 0xc3, 0xbd, 0x98, 0x77, 0x63, 0x5a, 0x57, 0x56, 0x57, 0x5a, 0x63, 0x77, 0xc3, 0xbb, 0x96, 0x76, 0x63, 0x5a, 0x57, 0x56, 0x56, 0x57, 0x5a, 0x63, 0x76, 0x96, 0xc3, 0xb5, 0x92, 0x75, 0x62, 0x5a, 0x57, 0x56, 0x56, 0x57, 0x59, 0x62, 0x74, 0x92, 0xc3, 0xa9, 0x8b, 0x71, 0x61, 0x59, 0x57, 0x56, 0x56, 0x57, 0x59, 0x61, 0x71, 0x8b, 0xa9, 0xc3, 0x95, 0x7e, 0x6b, 0x5e, 0x59, 0x57, 0x56, 0x56, 0x57, 0x59, 0x5e, 0x6b, 0x7e, 0x95, 0xc3, 0x4f, 0x78, 0x99, 0x30, 0x0, 0x0, 0x0, 0x67, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x5c, 0x8b, 0x5, 0x2, 0xc3, 0x30, 0xc, 0xc4, 0x72, 0x5d, 0x39, 0xf4, 0xff, 0x67, 0x8e, 0x79, 0x1a, 0x37, 0xa0, 0xa0, 0x6d, 0x5d, 0x6b, 0x2a, 0x5a, 0xfd, 0x30, 0xe6, 0x1, 0xf4, 0xa7, 0x76, 0xfa, 0x37, 0x74, 0x7, 0x98, 0x52, 0x83, 0x46, 0x97, 0x41, 0xfb, 0xd6, 0x2d, 0x86, 0xae, 0xfe, 0x84, 0x6b, 0x6d, 0x32, 0x6e, 0x58, 0x28, 0x22, 0x3a, 0x41, 0x32, 0xde, 0xd7, 0x6a, 0x67, 0x5b, 0xb7, 0xb7, 0xc9, 0xb0, 0xd8, 0xd6, 0x3a, 0x65, 0x34, 0xb4, 0x21, 0x8f, 0x1c, 0x3, 0x6d, 0x21, 0x84, 0x5d, 0x32, 0x8a, 0x48, 0x22, 0x6e, 0xda, 0xa8, 0x92, 0x36, 0x3e, 0x87, 0xea, 0x7b, 0x7e, 0x0, 0x62, 0xa8, 0x25, 0xad, 0x68, 0x1d, 0x7d, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +static const unsigned char toggle_on_disabled_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x20, 0x8, 0x3, 0x0, 0x0, 0x0, 0x95, 0x43, 0x8e, 0xb6, 0x0, 0x0, 0x1, 0x53, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0xd, 0xf, 0x1a, 0x1a, 0x1e, 0x20, 0x20, 0x24, 0x22, 0x22, 0x27, 0x24, 0x24, 0x29, 0x25, 0x25, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xa, 0xc, 0x1d, 0x1d, 0x21, 0x24, 0x24, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11, 0x14, 0x23, 0x23, 0x28, 0x2e, 0x2e, 0x2e, 0x46, 0x46, 0x46, 0x57, 0x57, 0x57, 0x60, 0x60, 0x60, 0x62, 0x62, 0x62, 0x5e, 0x5e, 0x5e, 0x4a, 0x4a, 0x4a, 0x12, 0x12, 0x15, 0x25, 0x27, 0x2d, 0x42, 0x42, 0x42, 0x59, 0x59, 0x59, 0x32, 0x32, 0x32, 0x25, 0x26, 0x2d, 0x25, 0x25, 0x2b, 0x25, 0x26, 0x2c, 0x39, 0x39, 0x39, 0x49, 0x49, 0x49, 0x5a, 0x5a, 0x5a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xb, 0xd, 0x23, 0x23, 0x28, 0x48, 0x48, 0x48, 0x54, 0x54, 0x54, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0xb, 0xb, 0xd, 0x1e, 0x1e, 0x22, 0x25, 0x26, 0x2b, 0x3f, 0x3f, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, 0xe, 0x10, 0x2c, 0x2c, 0x2c, 0x58, 0x58, 0x58, 0x1a, 0x1a, 0x1e, 0x40, 0x40, 0x44, 0x56, 0x56, 0x58, 0x80, 0x80, 0x80, 0x79, 0x79, 0x79, 0x3c, 0x3c, 0x3d, 0x2e, 0x2e, 0x30, 0x27, 0x27, 0x29, 0x64, 0x64, 0x66, 0x41, 0x41, 0x41, 0x1a, 0x1a, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5d, 0x5d, 0x5f, 0x34, 0x34, 0x36, 0x52, 0x52, 0x52, 0x3a, 0x3a, 0x3a, 0x20, 0x20, 0x24, 0x0, 0x0, 0x0, 0x32, 0x32, 0x37, 0x42, 0x42, 0x44, 0x6a, 0x6a, 0x6d, 0x5b, 0x5b, 0x5b, 0x2f, 0x2f, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x49, 0x4a, 0x0, 0x0, 0x0, 0x50, 0x50, 0x51, 0x70, 0x70, 0x74, 0xe, 0xe, 0x10, 0xb, 0xb, 0xd, 0x0, 0x0, 0x0, 0x13, 0x13, 0x15, 0x0, 0x0, 0x0, 0xb, 0xb, 0xc, 0x1d, 0x1d, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbd, 0xb, 0x85, 0x35, 0x0, 0x0, 0x0, 0x71, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x1, 0x2, 0x3, 0x4, 0x9, 0xe, 0x13, 0x16, 0x18, 0x19, 0xa, 0x26, 0x36, 0x44, 0x4d, 0x52, 0x54, 0x55, 0x6, 0x12, 0x27, 0x43, 0x80, 0xc5, 0xe7, 0xf5, 0xfe, 0xff, 0x8, 0x17, 0x35, 0x73, 0xd9, 0xff, 0x7, 0x3a, 0x96, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb, 0x28, 0x76, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x77, 0xde, 0xff, 0xff, 0x45, 0x5, 0x82, 0xff, 0xff, 0xc6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc6, 0x37, 0xf, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x4c, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4e, 0x4f, 0xff, 0x50, 0xff, 0xff, 0x83, 0x78, 0x3b, 0x9c, 0x3c, 0x74, 0xda, 0x53, 0x14, 0x49, 0x96, 0x6e, 0xf, 0x0, 0x0, 0x1, 0xfa, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x62, 0x18, 0x5e, 0x0, 0xd0, 0x5c, 0x39, 0x28, 0x49, 0x12, 0xc0, 0x60, 0xb8, 0xda, 0xdd, 0xcb, 0x31, 0x33, 0xb6, 0x6d, 0xdb, 0x5e, 0xdb, 0xd6, 0xfb, 0x17, 0x4e, 0x7d, 0x37, 0xe6, 0xf9, 0x2b, 0x23, 0x7f, 0x9c, 0x20, 0x28, 0x86, 0xe1, 0xb, 0xc1, 0x30, 0x14, 0x99, 0x6a, 0x8e, 0xe2, 0x4, 0x49, 0xd1, 0xcc, 0xda, 0x2, 0x18, 0x9a, 0x22, 0x9, 0x1c, 0x9d, 0xf4, 0x8e, 0xaf, 0xd3, 0x1b, 0x9b, 0x5b, 0xdb, 0x1c, 0x2e, 0x6f, 0x2e, 0x5c, 0xce, 0xf6, 0xd6, 0xe6, 0x6, 0xbd, 0x8e, 0x8f, 0x45, 0x81, 0xf2, 0x5, 0x42, 0x91, 0x58, 0x22, 0x95, 0xc9, 0x61, 0x1, 0x72, 0x99, 0x54, 0x22, 0x16, 0x9, 0x5, 0x7c, 0x74, 0xc4, 0x5e, 0xa1, 0x54, 0xa9, 0x35, 0x5a, 0x58, 0x12, 0xad, 0x46, 0xad, 0x52, 0x2a, 0x86, 0x14, 0x10, 0x1d, 0xa3, 0x37, 0x18, 0x61, 0x1e, 0x26, 0xb3, 0xc5, 0x6a, 0x63, 0xb1, 0x3b, 0x4c, 0x60, 0x34, 0xe8, 0x19, 0x1d, 0x32, 0xc8, 0x9f, 0xda, 0x74, 0xce, 0x8d, 0xdd, 0xe5, 0xf6, 0x98, 0xbd, 0x3e, 0xff, 0x57, 0x2, 0xa6, 0x60, 0x28, 0xec, 0x76, 0xc9, 0x9d, 0x9b, 0x54, 0xbf, 0xe, 0x68, 0x24, 0x1a, 0x8b, 0xcf, 0xb5, 0x4f, 0x24, 0x53, 0x30, 0x44, 0x3a, 0x99, 0x70, 0xc5, 0x33, 0xd1, 0xc8, 0x8f, 0x24, 0x70, 0xe5, 0x56, 0x16, 0xe6, 0x90, 0xcb, 0x27, 0x4d, 0x63, 0x9, 0x25, 0xf3, 0xb9, 0x6c, 0x41, 0x59, 0x64, 0x43, 0x40, 0x88, 0x8d, 0x12, 0xcc, 0xa3, 0x5c, 0x49, 0xc3, 0x18, 0x69, 0x4f, 0x19, 0x4a, 0x1b, 0x4, 0x2b, 0x80, 0x92, 0x9b, 0x55, 0xe8, 0x53, 0xab, 0x37, 0x9a, 0x2d, 0x68, 0x37, 0x3a, 0xd0, 0xee, 0x2, 0x4b, 0xcf, 0x1, 0x13, 0x38, 0x7a, 0xb0, 0xb3, 0xbb, 0xc7, 0xe6, 0x80, 0x51, 0x5b, 0x52, 0xe8, 0xd3, 0xdd, 0xaf, 0x1d, 0x34, 0x6a, 0xed, 0x46, 0x77, 0x20, 0x70, 0x78, 0x4, 0x13, 0x1c, 0x1d, 0xc2, 0x71, 0x81, 0xc2, 0x58, 0x1, 0xfa, 0x44, 0x36, 0x8, 0xa0, 0x71, 0xa, 0xb0, 0x7f, 0xd6, 0x3e, 0x6f, 0xb6, 0xfb, 0x2, 0x17, 0x97, 0x30, 0xc1, 0xe5, 0x5, 0x18, 0xaf, 0x68, 0x9c, 0x15, 0x50, 0x5e, 0xf, 0x7a, 0xd8, 0x3e, 0x7, 0x80, 0x9b, 0x7a, 0xbb, 0x7b, 0x73, 0xde, 0x17, 0xb0, 0xfa, 0x60, 0x2, 0x9f, 0x15, 0xe0, 0x56, 0xf9, 0x5d, 0x80, 0xde, 0x1e, 0xc, 0xd1, 0xe9, 0xd7, 0x8, 0xea, 0x77, 0xed, 0x2e, 0xdc, 0xd7, 0x97, 0x8e, 0x80, 0xda, 0x3a, 0x86, 0x3e, 0xe7, 0x67, 0x70, 0xda, 0x6c, 0xb5, 0xbb, 0xd0, 0x6a, 0x2c, 0x5b, 0x3, 0x94, 0xdc, 0xad, 0x42, 0x9f, 0x76, 0xf3, 0xbc, 0x59, 0x87, 0xaf, 0xe1, 0x9f, 0x2f, 0xd5, 0x5, 0x76, 0xe, 0x1e, 0x60, 0xc0, 0xe9, 0x59, 0x7, 0xa0, 0xd6, 0x2, 0xe8, 0xb4, 0xe6, 0xcf, 0xc1, 0x83, 0x90, 0x40, 0x7e, 0x79, 0x12, 0xfb, 0xbb, 0x90, 0x59, 0x7d, 0x17, 0x1e, 0xd9, 0x5d, 0xf8, 0xd5, 0x6d, 0xec, 0xdf, 0x83, 0xa7, 0xe7, 0xf8, 0x2a, 0xf7, 0x20, 0xfe, 0xfc, 0xc2, 0xde, 0x83, 0xfe, 0x45, 0x62, 0x36, 0x5f, 0xdf, 0x60, 0x69, 0xde, 0x5e, 0x37, 0x19, 0xc5, 0xe8, 0x4d, 0xd3, 0x51, 0xc2, 0x2d, 0xb1, 0xe4, 0xd8, 0x8, 0xb, 0x31, 0x1e, 0x4b, 0xc4, 0x5b, 0x42, 0x4a, 0x87, 0x8e, 0x5f, 0xe5, 0x8, 0xbd, 0xb1, 0x5b, 0xb8, 0x7a, 0xe7, 0x2d, 0xe0, 0xfd, 0xaa, 0xb0, 0xbb, 0x41, 0x47, 0x70, 0x64, 0xf2, 0xab, 0x14, 0x89, 0xbd, 0xf, 0xe5, 0xe2, 0xbf, 0xa0, 0xfc, 0xd8, 0x23, 0x8a, 0x28, 0x32, 0xe3, 0x33, 0xe1, 0x4b, 0xc0, 0x7e, 0xa6, 0xff, 0x87, 0xcf, 0xb, 0x94, 0xb9, 0x37, 0x3c, 0xc6, 0xd8, 0xcd, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; static const unsigned char tooltip_bg_png[] = { @@ -418,18 +362,6 @@ static const unsigned char tree_bg_disabled_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0xa, 0x8, 0x4, 0x0, 0x0, 0x0, 0x27, 0x3b, 0x7, 0x36, 0x0, 0x0, 0x0, 0x4e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x94, 0xc8, 0x67, 0x6b, 0x60, 0xe6, 0x60, 0x64, 0x80, 0x80, 0xff, 0xc, 0x7f, 0x7f, 0xfc, 0x6a, 0x60, 0x94, 0xfb, 0xc0, 0xce, 0xcf, 0xc2, 0x80, 0x10, 0xfc, 0xc3, 0xf0, 0xf3, 0x23, 0xa3, 0xe2, 0x4f, 0xe, 0x36, 0x54, 0xc1, 0x1f, 0xbf, 0x18, 0x95, 0xbe, 0x73, 0x70, 0xb0, 0x30, 0xc0, 0x1, 0x48, 0xf0, 0x7, 0x85, 0x82, 0x58, 0x2d, 0xc2, 0xe6, 0xa4, 0x4f, 0x20, 0xc7, 0x37, 0x32, 0xb3, 0x23, 0x39, 0xfe, 0xfb, 0xaf, 0x46, 0x0, 0xee, 0x2a, 0x2f, 0xce, 0x4c, 0x47, 0x66, 0xf6, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; -static const unsigned char tree_bg_focus_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x20, 0x8, 0x6, 0x0, 0x0, 0x0, 0x73, 0x7a, 0x7a, 0xf4, 0x0, 0x0, 0x2, 0x7f, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x62, 0x91, 0x16, 0x97, 0x30, 0x63, 0x60, 0x60, 0x38, 0xc9, 0x30, 0x30, 0xc0, 0x9c, 0x11, 0xe8, 0x80, 0xff, 0x6b, 0x3, 0x7f, 0xd, 0x88, 0xed, 0xc1, 0xeb, 0xd9, 0x18, 0x58, 0x60, 0x1c, 0x9e, 0xac, 0xdd, 0x0, 0x5a, 0xcb, 0x6, 0xc5, 0x99, 0x23, 0x6, 0xa2, 0xaf, 0xe4, 0xb3, 0xe5, 0x8a, 0x39, 0x69, 0xf8, 0xcf, 0xa8, 0x82, 0x2d, 0x52, 0xe0, 0x1e, 0x61, 0x63, 0xe0, 0x93, 0x29, 0x7a, 0x66, 0xdb, 0xdb, 0x7a, 0x5d, 0x92, 0x96, 0x45, 0x12, 0x55, 0xf, 0x24, 0xbd, 0x4, 0x50, 0x22, 0x81, 0x98, 0x70, 0x96, 0x63, 0x2b, 0x91, 0x3d, 0xdb, 0x80, 0xb0, 0xfd, 0x52, 0xf7, 0x85, 0x6d, 0xfe, 0xf9, 0xfb, 0x2f, 0x0, 0x2, 0x80, 0x18, 0x80, 0xa7, 0x4a, 0xa8, 0x6a, 0x40, 0xf8, 0x15, 0xc0, 0x9c, 0x61, 0x43, 0x77, 0xd3, 0xd, 0x48, 0x34, 0xce, 0x5e, 0x0, 0x84, 0xd0, 0x24, 0xe7, 0xf1, 0xa8, 0x17, 0x0, 0x3, 0x94, 0x7d, 0xef, 0x1c, 0xec, 0xe, 0x98, 0x38, 0x60, 0x33, 0xc7, 0x34, 0xb6, 0x90, 0xb5, 0x1, 0xf0, 0x76, 0xfb, 0x47, 0x3d, 0xe6, 0x59, 0x62, 0x89, 0x5, 0x60, 0x8f, 0xb1, 0x7d, 0x70, 0x6a, 0x9c, 0x20, 0xf1, 0x5e, 0x82, 0x49, 0x5c, 0xff, 0x27, 0x7f, 0xcc, 0x73, 0x1c, 0xd0, 0x6f, 0xe, 0xb4, 0x3b, 0x0, 0x52, 0x3, 0x35, 0x4e, 0x94, 0xa0, 0x37, 0x0, 0x9b, 0xa4, 0x18, 0x37, 0x5e, 0xd2, 0xe8, 0xd3, 0x2d, 0xd7, 0xbd, 0x52, 0x61, 0x3b, 0x97, 0x3b, 0x5c, 0xb9, 0x3, 0x98, 0xb1, 0xa, 0x8c, 0x34, 0xeb, 0x68, 0xef, 0xc2, 0x9f, 0x22, 0xc, 0x4e, 0xf2, 0x80, 0x42, 0xa8, 0x4e, 0xdd, 0x89, 0x8f, 0x50, 0xb4, 0x84, 0x9d, 0xbd, 0xb7, 0x33, 0x6d, 0xc0, 0x7b, 0x9, 0xc8, 0x17, 0xdf, 0x13, 0x4b, 0xca, 0xf3, 0x2f, 0xe, 0x24, 0x69, 0x8e, 0xf7, 0x68, 0x2d, 0x81, 0xfb, 0xa9, 0xfc, 0xe2, 0xbc, 0x73, 0xdc, 0x51, 0x7c, 0x1d, 0x9, 0xdd, 0x1, 0x72, 0xb6, 0x59, 0x1, 0x26, 0xe, 0xc2, 0xb8, 0x85, 0xf7, 0xe, 0xd8, 0x71, 0xe2, 0x9e, 0x6e, 0xae, 0x8e, 0x18, 0x2d, 0x0, 0x9, 0x1f, 0xd2, 0xbd, 0x17, 0xb4, 0x14, 0xc2, 0xc7, 0x29, 0x2, 0x33, 0x9f, 0x1c, 0xc5, 0xbc, 0x3, 0x5b, 0x9, 0x8c, 0xf9, 0xe2, 0x80, 0xff, 0xa0, 0x3, 0x53, 0x27, 0xe1, 0x6e, 0xac, 0x26, 0x3d, 0x60, 0x2d, 0x37, 0x36, 0xb9, 0x26, 0xc7, 0xa3, 0xd9, 0x9a, 0x2e, 0xea, 0xa7, 0x7a, 0x3, 0x38, 0x8, 0x23, 0xb8, 0x25, 0x51, 0x3a, 0xfb, 0xc3, 0x5c, 0x2c, 0x0, 0xf0, 0x69, 0xa, 0xce, 0x8f, 0x47, 0x5b, 0x18, 0x7f, 0x9f, 0x46, 0x3, 0x6f, 0x73, 0xff, 0xb5, 0x4, 0x26, 0xf5, 0x62, 0x1f, 0x43, 0xbc, 0x81, 0xa4, 0x3f, 0x12, 0xeb, 0x18, 0xd2, 0x74, 0x37, 0xf6, 0xe8, 0xcb, 0x1f, 0xa2, 0x88, 0x3d, 0x16, 0x1e, 0xed, 0x25, 0x38, 0xc5, 0x5e, 0x82, 0xd3, 0x2d, 0x13, 0x89, 0x25, 0x74, 0x47, 0xf0, 0xfb, 0x9e, 0xf1, 0x32, 0x86, 0x29, 0xc6, 0xe2, 0x0, 0xd, 0xae, 0x1f, 0x1d, 0x30, 0x9, 0x2d, 0xf6, 0x47, 0xcd, 0xc8, 0x23, 0x3e, 0x4c, 0x41, 0x77, 0x53, 0xae, 0x2f, 0x0, 0xbe, 0x97, 0xc0, 0xb7, 0xbd, 0xfb, 0x8, 0x76, 0x20, 0x56, 0x80, 0x50, 0x32, 0x5f, 0x46, 0x12, 0x68, 0xf6, 0x24, 0xa1, 0xb5, 0xf9, 0xd7, 0x9f, 0x25, 0xc9, 0x9c, 0x75, 0x81, 0x1b, 0x3e, 0x4f, 0xc1, 0xe8, 0x24, 0x16, 0x42, 0xd2, 0x40, 0x1c, 0xd9, 0xb6, 0x26, 0xc, 0xa4, 0x58, 0x1b, 0x70, 0x3e, 0x2c, 0x0, 0xdd, 0xb8, 0x8a, 0x76, 0x73, 0x5d, 0x57, 0x32, 0xc9, 0x45, 0xfe, 0x23, 0x3a, 0x2c, 0x8, 0x52, 0x32, 0xef, 0x0, 0xdd, 0x7e, 0x9d, 0xd9, 0xbe, 0xc6, 0xe5, 0xbe, 0xd6, 0x31, 0x1c, 0x88, 0x16, 0xad, 0xa2, 0x74, 0xd1, 0xae, 0x24, 0x95, 0xc4, 0xad, 0x6, 0xb, 0x0, 0x59, 0x1c, 0x67, 0xe7, 0x5c, 0x33, 0x6b, 0xef, 0x25, 0x68, 0x37, 0x58, 0xa8, 0x5, 0xfa, 0x17, 0x28, 0xaa, 0x1a, 0xa9, 0x96, 0x46, 0x77, 0x1c, 0xf8, 0x16, 0x71, 0xa0, 0x2f, 0xfc, 0x5a, 0xe7, 0x5d, 0x6b, 0x13, 0x76, 0x73, 0x89, 0x38, 0x72, 0x35, 0x1, 0x10, 0xfa, 0x9, 0x20, 0xe, 0xa4, 0xa9, 0x9f, 0xba, 0xc6, 0x5, 0x2f, 0x0, 0x6d, 0x43, 0x77, 0x6c, 0xab, 0x66, 0x6c, 0xa7, 0x7f, 0x73, 0x60, 0x77, 0x21, 0xe7, 0xf7, 0x53, 0x36, 0x75, 0x2, 0xfc, 0x37, 0x96, 0x15, 0xd1, 0x28, 0xc6, 0xff, 0x65, 0xa0, 0xd, 0x60, 0x2, 0x63, 0x4, 0x0, 0xf5, 0x8e, 0x13, 0x81, 0xf4, 0x3c, 0x86, 0x81, 0x1, 0x49, 0x0, 0x33, 0xd4, 0x35, 0xaa, 0x8d, 0x7e, 0xfe, 0xa5, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char tree_cursor_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0xe, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x96, 0xdd, 0xe3, 0x0, 0x0, 0x0, 0xc3, 0x50, 0x4c, 0x54, 0x45, 0xd4, 0xab, 0x9e, 0xd3, 0xaa, 0x9d, 0xd4, 0xab, 0x9e, 0xd3, 0xaa, 0x9d, 0xd4, 0xab, 0x9c, 0xd4, 0xac, 0x9e, 0xd5, 0xaf, 0xa3, 0xd5, 0xb0, 0xa3, 0xd5, 0xaf, 0xa3, 0xd5, 0xad, 0xa1, 0xd5, 0xaf, 0xa3, 0xd5, 0xad, 0xa1, 0xd5, 0xb0, 0xa3, 0xd6, 0xad, 0xa0, 0xd6, 0xad, 0xa0, 0xd5, 0xb0, 0xa3, 0xd7, 0xb1, 0xa5, 0xd7, 0xb1, 0xa7, 0xd7, 0xb1, 0xa7, 0xd7, 0xb1, 0xa7, 0xd7, 0xb1, 0xa7, 0xd7, 0xb1, 0xa5, 0xd7, 0xb1, 0xa5, 0xd8, 0xb3, 0xa8, 0xd8, 0xb5, 0xaa, 0xda, 0xb3, 0xa8, 0xda, 0xb3, 0xa8, 0xd8, 0xb3, 0xa8, 0xd8, 0xb5, 0xaa, 0xda, 0xb3, 0xa8, 0xd8, 0xb3, 0xa8, 0xdb, 0xb7, 0xad, 0xda, 0xb8, 0xae, 0xdb, 0xb9, 0xad, 0xdb, 0xb9, 0xad, 0xdb, 0xb7, 0xad, 0xdd, 0xba, 0xb1, 0xdd, 0xbb, 0xb1, 0xdd, 0xbd, 0xb1, 0xdd, 0xbf, 0xb3, 0xdd, 0xbd, 0xb3, 0xdf, 0xc1, 0xb7, 0xdf, 0xc0, 0xb5, 0xdf, 0xbf, 0xb5, 0xe1, 0xc3, 0xb9, 0xe1, 0xc3, 0xbb, 0xe1, 0xc5, 0xbb, 0xe2, 0xc7, 0xbe, 0xe1, 0xc5, 0xbd, 0xe4, 0xcb, 0xc2, 0xe3, 0xca, 0xc1, 0xe3, 0xcb, 0xc3, 0xe6, 0xce, 0xc6, 0xe6, 0xd0, 0xc6, 0xe8, 0xd1, 0xca, 0xe8, 0xd1, 0xca, 0xe8, 0xd0, 0xca, 0xe8, 0xcf, 0xca, 0xb7, 0x7d, 0x69, 0xb1, 0x77, 0x63, 0xac, 0x73, 0x5c, 0xa6, 0x69, 0x56, 0x9c, 0x67, 0x54, 0x93, 0x62, 0x51, 0x88, 0x60, 0x50, 0x9e, 0xe4, 0xa3, 0x87, 0x0, 0x0, 0x0, 0x3a, 0x74, 0x52, 0x4e, 0x53, 0x32, 0x2f, 0x30, 0x33, 0x38, 0x40, 0x32, 0x2f, 0x2f, 0x2f, 0x31, 0x33, 0x33, 0x33, 0x36, 0x38, 0x31, 0x2f, 0x30, 0x31, 0x33, 0x33, 0x35, 0x2f, 0x2f, 0x2f, 0x30, 0x32, 0x33, 0x33, 0x33, 0x2f, 0x2f, 0x2f, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x32, 0x2f, 0x2f, 0x2f, 0xb8, 0xf, 0x95, 0x41, 0x0, 0x0, 0x0, 0x64, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x4d, 0xc9, 0x1, 0xa, 0x2, 0x31, 0xc, 0x44, 0xd1, 0xff, 0xdb, 0xec, 0x2a, 0x1e, 0x48, 0xc0, 0xfb, 0x7b, 0x2c, 0x3b, 0x96, 0x2c, 0x88, 0x3, 0x19, 0x78, 0x19, 0x5f, 0xfc, 0xa7, 0x7c, 0xb, 0x78, 0xd5, 0xb3, 0x3c, 0x45, 0xf5, 0xea, 0xf2, 0x21, 0xb4, 0xc2, 0xd8, 0xbc, 0x19, 0xcd, 0xb0, 0xab, 0xbc, 0x83, 0xa4, 0x1f, 0x6c, 0x1e, 0x82, 0xca, 0x67, 0x2c, 0x2d, 0xe, 0x25, 0x31, 0x67, 0x6a, 0x51, 0x99, 0xc0, 0xc8, 0xbe, 0x35, 0xcc, 0x45, 0xc, 0xca, 0xdc, 0x1c, 0x6, 0x70, 0x8f, 0xa1, 0xd7, 0x36, 0x13, 0xe8, 0xb5, 0x6d, 0x18, 0x6b, 0xb3, 0xf8, 0x65, 0xe6, 0xb, 0x36, 0x5c, 0x24, 0xde, 0x86, 0x96, 0x3a, 0xaf, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -static const unsigned char tree_cursor_unfocus_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0xe, 0x8, 0x3, 0x0, 0x0, 0x0, 0x28, 0x96, 0xdd, 0xe3, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0xc0, 0xb5, 0xb2, 0xbf, 0xb4, 0xb1, 0xc0, 0xb5, 0xb2, 0xbf, 0xb4, 0xb1, 0xbf, 0xb5, 0xb1, 0xc0, 0xb5, 0xb2, 0xc3, 0xb8, 0xb5, 0xc3, 0xb8, 0xb5, 0xc2, 0xb7, 0xb4, 0xc3, 0xb8, 0xb5, 0xc2, 0xb7, 0xb4, 0xc3, 0xb8, 0xb5, 0xc2, 0xb7, 0xb4, 0xc3, 0xb8, 0xb5, 0xc5, 0xba, 0xb7, 0xc5, 0xbc, 0xb9, 0xc5, 0xbc, 0xb9, 0xc5, 0xbc, 0xb9, 0xc5, 0xbc, 0xb9, 0xc5, 0xba, 0xb7, 0xc5, 0xba, 0xb7, 0xc6, 0xbd, 0xba, 0xc7, 0xbe, 0xbb, 0xc7, 0xbe, 0xbb, 0xc6, 0xbd, 0xba, 0xc7, 0xbe, 0xbb, 0xc6, 0xbd, 0xba, 0xca, 0xc1, 0xbe, 0xca, 0xc1, 0xbe, 0xcd, 0xc4, 0xc1, 0xcd, 0xc6, 0xc3, 0xd0, 0xc9, 0xc6, 0xcf, 0xc8, 0xc5, 0xd2, 0xcb, 0xc8, 0xd3, 0xcb, 0xc9, 0xd3, 0xcc, 0xc9, 0xd5, 0xcd, 0xcb, 0xd4, 0xcc, 0xca, 0xd7, 0xd1, 0xcf, 0xd6, 0xd0, 0xce, 0xda, 0xd4, 0xd2, 0xdd, 0xd7, 0xd5, 0xdd, 0xd7, 0xd5, 0x9a, 0x8b, 0x86, 0x94, 0x85, 0x80, 0x8e, 0x80, 0x7a, 0x88, 0x79, 0x74, 0x81, 0x75, 0x6f, 0x7a, 0x6f, 0x6a, 0x73, 0x69, 0x65, 0x9a, 0x51, 0xd2, 0xe3, 0x0, 0x0, 0x0, 0x2b, 0x74, 0x52, 0x4e, 0x53, 0x32, 0x2f, 0x30, 0x33, 0x38, 0x40, 0x32, 0x2f, 0x2f, 0x31, 0x33, 0x33, 0x36, 0x38, 0x31, 0x2f, 0x30, 0x31, 0x33, 0x33, 0x35, 0x2f, 0x2f, 0x30, 0x32, 0x33, 0x33, 0x2f, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x32, 0x2f, 0x82, 0xd8, 0x8a, 0x2f, 0x0, 0x0, 0x0, 0x5f, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x62, 0xd1, 0x66, 0x40, 0x6, 0x2c, 0x8c, 0x57, 0x0, 0x2d, 0xc9, 0x87, 0x15, 0x4, 0x51, 0x8, 0x42, 0xd1, 0x7, 0x13, 0x3b, 0xd9, 0xfe, 0x1b, 0x1c, 0x16, 0xcf, 0xf, 0xa6, 0xab, 0x6a, 0xd3, 0x2a, 0xbf, 0x53, 0xb7, 0x66, 0xa8, 0x63, 0xe9, 0xd4, 0xbb, 0x88, 0x82, 0xcb, 0x47, 0xd9, 0xb, 0x54, 0xde, 0xec, 0x57, 0x97, 0xde, 0x63, 0x94, 0x12, 0xab, 0xec, 0xec, 0x56, 0xce, 0x69, 0x28, 0xc8, 0x9f, 0xbf, 0x9c, 0x39, 0xd8, 0x16, 0x47, 0x69, 0x65, 0xc, 0xe, 0xc3, 0x8e, 0xeb, 0x69, 0x28, 0x1, 0x0, 0xb0, 0xae, 0x0, 0x0, 0x90, 0x3f, 0xa6, 0x5b, 0x1b, 0xad, 0x12, 0x69, 0xd7, 0x5c, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - static const unsigned char tree_title_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x1, 0x3, 0x0, 0x0, 0x0, 0x25, 0x3d, 0x6d, 0x22, 0x0, 0x0, 0x0, 0x6, 0x50, 0x4c, 0x54, 0x45, 0x25, 0x23, 0x25, 0x4c, 0x4a, 0x4e, 0x1, 0xf9, 0x98, 0x2e, 0x0, 0x0, 0x0, 0xb, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x20, 0x11, 0x0, 0x0, 0x0, 0x30, 0x0, 0x1, 0x6e, 0xa6, 0xf, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; diff --git a/scene/resources/default_theme/toggle_off_disabled.png b/scene/resources/default_theme/toggle_off_disabled.png Binary files differnew file mode 100644 index 0000000000..250cd29b66 --- /dev/null +++ b/scene/resources/default_theme/toggle_off_disabled.png diff --git a/scene/resources/default_theme/toggle_on_disabled.png b/scene/resources/default_theme/toggle_on_disabled.png Binary files differnew file mode 100644 index 0000000000..b1dacbaf32 --- /dev/null +++ b/scene/resources/default_theme/toggle_on_disabled.png diff --git a/scene/resources/default_theme/tool_button_pressed.png b/scene/resources/default_theme/tool_button_pressed.png Binary files differdeleted file mode 100644 index 5494475792..0000000000 --- a/scene/resources/default_theme/tool_button_pressed.png +++ /dev/null diff --git a/scene/resources/default_theme/tree_bg_focus.png b/scene/resources/default_theme/tree_bg_focus.png Binary files differdeleted file mode 100644 index aadc6b0db4..0000000000 --- a/scene/resources/default_theme/tree_bg_focus.png +++ /dev/null diff --git a/scene/resources/default_theme/tree_cursor.png b/scene/resources/default_theme/tree_cursor.png Binary files differdeleted file mode 100644 index 2b8722d066..0000000000 --- a/scene/resources/default_theme/tree_cursor.png +++ /dev/null diff --git a/scene/resources/default_theme/tree_cursor_unfocus.png b/scene/resources/default_theme/tree_cursor_unfocus.png Binary files differdeleted file mode 100644 index bfaebbea85..0000000000 --- a/scene/resources/default_theme/tree_cursor_unfocus.png +++ /dev/null diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index fd7b67a218..2364a4a8a3 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -118,8 +118,7 @@ Error DynamicFontAtSize::_load() { int error = FT_Init_FreeType(&library); - ERR_EXPLAIN(TTR("Error initializing FreeType.")); - ERR_FAIL_COND_V(error != 0, ERR_CANT_CREATE); + ERR_FAIL_COND_V_MSG(error != 0, ERR_CANT_CREATE, "Error initializing FreeType."); // FT_OPEN_STREAM is extremely slow only on Android. if (OS::get_singleton()->get_name() == "Android" && font->font_mem == NULL && font->font_path != String()) { @@ -177,32 +176,23 @@ Error DynamicFontAtSize::_load() { error = FT_Open_Face(library, &fargs, 0, &face); } else { - ERR_EXPLAIN("DynamicFont uninitialized"); - ERR_FAIL_V(ERR_UNCONFIGURED); + ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "DynamicFont uninitialized."); } //error = FT_New_Face( library, src_path.utf8().get_data(),0,&face ); if (error == FT_Err_Unknown_File_Format) { - ERR_EXPLAIN(TTR("Unknown font format.")); + ERR_EXPLAIN("Unknown font format."); FT_Done_FreeType(library); } else if (error) { - ERR_EXPLAIN(TTR("Error loading font.")); + ERR_EXPLAIN("Error loading font."); FT_Done_FreeType(library); } ERR_FAIL_COND_V(error, ERR_FILE_CANT_OPEN); - /*error = FT_Set_Char_Size(face,0,64*size,512,512); - - if ( error ) { - FT_Done_FreeType( library ); - ERR_EXPLAIN(TTR("Invalid font size.")); - ERR_FAIL_COND_V( error, ERR_INVALID_PARAMETER ); - }*/ - if (FT_HAS_COLOR(face)) { int best_match = 0; int diff = ABS(id.size - ((int64_t)face->available_sizes[0].width)); @@ -494,7 +484,7 @@ DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap b int byte = i * bitmap.pitch + (j >> 3); int bit = 1 << (7 - (j % 8)); wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = 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 @@ -509,8 +499,7 @@ DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap b } break; // TODO: FT_PIXEL_MODE_LCD default: - ERR_EXPLAIN("Font uses unsupported pixel format: " + itos(bitmap.pixel_mode)); - ERR_FAIL_V(Character::not_found()); + ERR_FAIL_V_MSG(Character::not_found(), "Font uses unsupported pixel format: " + itos(bitmap.pixel_mode) + "."); break; } } @@ -1008,7 +997,7 @@ void DynamicFont::_bind_methods() { ClassDB::bind_method(D_METHOD("get_fallback_count"), &DynamicFont::get_fallback_count); ADD_GROUP("Settings", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "size", PROPERTY_HINT_RANGE, "1,255,1"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_size", PROPERTY_HINT_RANGE, "0,255,1"), "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"); diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h index 39e81ca43d..d0a709a2d3 100644 --- a/scene/resources/dynamic_font.h +++ b/scene/resources/dynamic_font.h @@ -54,8 +54,9 @@ public: struct { uint32_t size : 16; uint32_t outline_size : 8; - bool mipmaps : 1; - bool filter : 1; + uint32_t mipmaps : 1; + uint32_t filter : 1; + uint32_t unused : 6; }; uint32_t key; }; @@ -109,7 +110,7 @@ VARIANT_ENUM_CAST(DynamicFontData::Hinting); class DynamicFontAtSize : public Reference { - GDCLASS(DynamicFontAtSize, Reference) + GDCLASS(DynamicFontAtSize, Reference); _THREAD_SAFE_CLASS_ @@ -302,7 +303,6 @@ VARIANT_ENUM_CAST(DynamicFont::SpacingType); ///////////// class ResourceFormatLoaderDynamicFont : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderDynamicFont, ResourceFormatLoader) public: virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List<String> *p_extensions) const; diff --git a/scene/resources/dynamic_font_stb.cpp b/scene/resources/dynamic_font_stb.cpp index 3b44f05b94..ccff617a16 100644 --- a/scene/resources/dynamic_font_stb.cpp +++ b/scene/resources/dynamic_font_stb.cpp @@ -55,7 +55,7 @@ void DynamicFontData::lock() { void DynamicFontData::unlock() { - fr = PoolVector<uint8_t>::Read(); + fr.release(); } void DynamicFontData::set_font_data(const PoolVector<uint8_t> &p_font) { diff --git a/scene/resources/dynamic_font_stb.h b/scene/resources/dynamic_font_stb.h index 4c98487600..caee6e7e32 100644 --- a/scene/resources/dynamic_font_stb.h +++ b/scene/resources/dynamic_font_stb.h @@ -180,7 +180,6 @@ public: ///////////// class ResourceFormatLoaderDynamicFont : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderDynamicFont, ResourceFormatLoader) public: virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List<String> *p_extensions) const; diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index a57b7bbb42..afb7f1102b 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -111,6 +111,11 @@ void Environment::set_ambient_light_sky_contribution(float p_energy) { VS::get_singleton()->environment_set_ambient_light(environment, ambient_color, ambient_energy, ambient_sky_contribution); } +void Environment::set_camera_feed_id(int p_camera_feed_id) { + camera_feed_id = p_camera_feed_id; + VS::get_singleton()->environment_set_camera_feed_id(environment, camera_feed_id); +}; + Environment::BGMode Environment::get_background() const { return bg_mode; @@ -165,6 +170,10 @@ float Environment::get_ambient_light_sky_contribution() const { return ambient_sky_contribution; } +int Environment::get_camera_feed_id(void) const { + + return camera_feed_id; +} void Environment::set_tonemapper(ToneMapper p_tone_mapper) { @@ -303,7 +312,7 @@ Ref<Texture> Environment::get_adjustment_color_correction() const { void Environment::_validate_property(PropertyInfo &property) const { - if (property.name == "background_sky" || property.name == "background_sky_custom_fov" || property.name == "background_sky_orientation" || property.name == "ambient_light/sky_contribution") { + if (property.name == "background_sky" || property.name == "background_sky_custom_fov" || property.name == "background_sky_orientation" || property.name == "background_sky_rotation" || property.name == "background_sky_rotation_degrees" || property.name == "ambient_light/sky_contribution") { if (bg_mode != BG_SKY && bg_mode != BG_COLOR_SKY) { property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL; } @@ -321,6 +330,12 @@ void Environment::_validate_property(PropertyInfo &property) const { } } + if (property.name == "background_camera_feed_id") { + if (bg_mode != BG_CAMERA_FEED) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } + } + static const char *hide_prefixes[] = { "fog_", "auto_exposure_", @@ -946,6 +961,7 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_ambient_light_color", "color"), &Environment::set_ambient_light_color); ClassDB::bind_method(D_METHOD("set_ambient_light_energy", "energy"), &Environment::set_ambient_light_energy); ClassDB::bind_method(D_METHOD("set_ambient_light_sky_contribution", "energy"), &Environment::set_ambient_light_sky_contribution); + ClassDB::bind_method(D_METHOD("set_camera_feed_id", "camera_feed_id"), &Environment::set_camera_feed_id); ClassDB::bind_method(D_METHOD("get_background"), &Environment::get_background); ClassDB::bind_method(D_METHOD("get_sky"), &Environment::get_sky); @@ -959,9 +975,10 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("get_ambient_light_color"), &Environment::get_ambient_light_color); ClassDB::bind_method(D_METHOD("get_ambient_light_energy"), &Environment::get_ambient_light_energy); ClassDB::bind_method(D_METHOD("get_ambient_light_sky_contribution"), &Environment::get_ambient_light_sky_contribution); + ClassDB::bind_method(D_METHOD("get_camera_feed_id"), &Environment::get_camera_feed_id); ADD_GROUP("Background", "background_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "background_mode", PROPERTY_HINT_ENUM, "Clear Color,Custom Color,Sky,Color+Sky,Canvas,Keep"), "set_background", "get_background"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "background_mode", PROPERTY_HINT_ENUM, "Clear Color,Custom Color,Sky,Color+Sky,Canvas,Keep,Camera Feed"), "set_background", "get_background"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "background_sky", PROPERTY_HINT_RESOURCE_TYPE, "Sky"), "set_sky", "get_sky"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "background_sky_custom_fov", PROPERTY_HINT_RANGE, "0,180,0.1"), "set_sky_custom_fov", "get_sky_custom_fov"); ADD_PROPERTY(PropertyInfo(Variant::BASIS, "background_sky_orientation"), "set_sky_orientation", "get_sky_orientation"); @@ -970,6 +987,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "background_color"), "set_bg_color", "get_bg_color"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "background_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_bg_energy", "get_bg_energy"); ADD_PROPERTY(PropertyInfo(Variant::INT, "background_canvas_max_layer", PROPERTY_HINT_RANGE, "-1000,1000,1"), "set_canvas_max_layer", "get_canvas_max_layer"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "background_camera_feed_id", PROPERTY_HINT_RANGE, "1,10,1"), "set_camera_feed_id", "get_camera_feed_id"); ADD_GROUP("Ambient Light", "ambient_light_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ambient_light_color"), "set_ambient_light_color", "get_ambient_light_color"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "ambient_light_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_ambient_light_energy", "get_ambient_light_energy"); @@ -1029,8 +1047,8 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fog_transmit_enabled"), "set_fog_transmit_enabled", "is_fog_transmit_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "fog_transmit_curve", PROPERTY_HINT_EXP_EASING), "set_fog_transmit_curve", "get_fog_transmit_curve"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fog_height_enabled"), "set_fog_height_enabled", "is_fog_height_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "fog_height_min", PROPERTY_HINT_RANGE, "-4000,4000,0.1"), "set_fog_height_min", "get_fog_height_min"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "fog_height_max", PROPERTY_HINT_RANGE, "-4000,4000,0.1"), "set_fog_height_max", "get_fog_height_max"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fog_height_min", PROPERTY_HINT_RANGE, "-4000,4000,0.1,or_lesser,or_greater"), "set_fog_height_min", "get_fog_height_min"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fog_height_max", PROPERTY_HINT_RANGE, "-4000,4000,0.1,or_lesser,or_greater"), "set_fog_height_max", "get_fog_height_max"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "fog_height_curve", PROPERTY_HINT_EXP_EASING), "set_fog_height_curve", "get_fog_height_curve"); ClassDB::bind_method(D_METHOD("set_tonemapper", "mode"), &Environment::set_tonemapper); @@ -1265,6 +1283,7 @@ void Environment::_bind_methods() { BIND_ENUM_CONSTANT(BG_SKY); BIND_ENUM_CONSTANT(BG_COLOR_SKY); BIND_ENUM_CONSTANT(BG_CANVAS); + BIND_ENUM_CONSTANT(BG_CAMERA_FEED); BIND_ENUM_CONSTANT(BG_MAX); BIND_ENUM_CONSTANT(GLOW_BLEND_MODE_ADDITIVE); @@ -1291,7 +1310,14 @@ void Environment::_bind_methods() { BIND_ENUM_CONSTANT(SSAO_QUALITY_HIGH); } -Environment::Environment() { +Environment::Environment() : + bg_mode(BG_CLEAR_COLOR), + tone_mapper(TONE_MAPPER_LINEAR), + ssao_blur(SSAO_BLUR_3x3), + ssao_quality(SSAO_QUALITY_MEDIUM), + glow_blend_mode(GLOW_BLEND_MODE_ADDITIVE), + dof_blur_far_quality(DOF_BLUR_QUALITY_LOW), + dof_blur_near_quality(DOF_BLUR_QUALITY_LOW) { environment = VS::get_singleton()->environment_create(); @@ -1303,6 +1329,7 @@ Environment::Environment() { ambient_energy = 1.0; //ambient_sky_contribution = 1.0; set_ambient_light_sky_contribution(1.0); + set_camera_feed_id(1); tone_mapper = TONE_MAPPER_LINEAR; tonemap_exposure = 1.0; @@ -1339,7 +1366,7 @@ Environment::Environment() { ssao_ao_channel_affect = 0.0; ssao_blur = SSAO_BLUR_3x3; set_ssao_edge_sharpness(4); - set_ssao_quality(SSAO_QUALITY_LOW); + set_ssao_quality(SSAO_QUALITY_MEDIUM); glow_enabled = false; glow_levels = (1 << 2) | (1 << 4); @@ -1372,15 +1399,15 @@ Environment::Environment() { fog_depth_enabled = true; fog_depth_begin = 10; - fog_depth_end = 0; + fog_depth_end = 100; fog_depth_curve = 1; fog_transmit_enabled = false; fog_transmit_curve = 1; fog_height_enabled = false; - fog_height_min = 0; - fog_height_max = 100; + fog_height_min = 10; + fog_height_max = 0; fog_height_curve = 1; set_fog_color(Color(0.5, 0.6, 0.7)); diff --git a/scene/resources/environment.h b/scene/resources/environment.h index 666112b473..acce9c09a2 100644 --- a/scene/resources/environment.h +++ b/scene/resources/environment.h @@ -32,7 +32,7 @@ #define ENVIRONMENT_H #include "core/resource.h" -#include "scene/resources/sky_box.h" +#include "scene/resources/sky.h" #include "scene/resources/texture.h" #include "servers/visual_server.h" @@ -49,6 +49,7 @@ public: BG_COLOR_SKY, BG_CANVAS, BG_KEEP, + BG_CAMERA_FEED, BG_MAX }; @@ -98,6 +99,7 @@ private: Color ambient_color; float ambient_energy; float ambient_sky_contribution; + int camera_feed_id; ToneMapper tone_mapper; float tonemap_exposure; @@ -192,6 +194,7 @@ public: void set_ambient_light_color(const Color &p_color); void set_ambient_light_energy(float p_energy); void set_ambient_light_sky_contribution(float p_energy); + void set_camera_feed_id(int p_camera_feed_id); BGMode get_background() const; Ref<Sky> get_sky() const; @@ -205,6 +208,7 @@ public: Color get_ambient_light_color() const; float get_ambient_light_energy() const; float get_ambient_light_sky_contribution() const; + int get_camera_feed_id(void) const; void set_tonemapper(ToneMapper p_tone_mapper); ToneMapper get_tonemapper() const; diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 7ecf9a2b16..cff77acdd7 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -96,6 +96,7 @@ void Font::_bind_methods() { 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("get_wordwrap_string_size", "string", "p_width"), &Font::get_wordwrap_string_size); 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); @@ -132,6 +133,7 @@ PoolVector<int> BitmapFont::_get_chars() const { while ((key = char_map.next(key))) { const Character *c = char_map.getptr(*key); + ERR_FAIL_COND_V(!c, PoolVector<int>()); chars.push_back(*key); chars.push_back(c->texture_idx); chars.push_back(c->rect.position.x); @@ -200,10 +202,7 @@ Error BitmapFont::create_from_fnt(const String &p_file) { FileAccess *f = FileAccess::open(p_file, FileAccess::READ); - if (!f) { - ERR_EXPLAIN("Can't open font: " + p_file); - ERR_FAIL_V(ERR_FILE_NOT_FOUND); - } + ERR_FAIL_COND_V_MSG(!f, ERR_FILE_NOT_FOUND, "Can't open font: " + p_file + "."); clear(); @@ -495,8 +494,44 @@ Size2 Font::get_string_size(const String &p_string) const { return Size2(w, get_height()); } + +Size2 Font::get_wordwrap_string_size(const String &p_string, float p_width) const { + + ERR_FAIL_COND_V(p_width <= 0, Vector2(0, get_height())); + + int l = p_string.length(); + if (l == 0) + return Size2(p_width, get_height()); + + float line_w = 0; + float h = 0; + float space_w = get_char_size(' ').width; + Vector<String> lines = p_string.split("\n"); + for (int i = 0; i < lines.size(); i++) { + h += get_height(); + String t = lines[i]; + line_w = 0; + Vector<String> words = t.split(" "); + for (int j = 0; j < words.size(); j++) { + line_w += get_string_size(words[j]).x; + if (line_w > p_width) { + h += get_height(); + line_w = get_string_size(words[j]).x; + } else { + line_w += space_w; + } + } + } + + return Size2(p_width, h); +} + void BitmapFont::set_fallback(const Ref<BitmapFont> &p_fallback) { + for (Ref<BitmapFont> fallback_child = p_fallback; fallback_child != NULL; fallback_child = fallback_child->get_fallback()) { + ERR_FAIL_COND_MSG(fallback_child == this, "Can't set as fallback one of its parents to prevent crashes due to recursive loop."); + } + fallback = p_fallback; } diff --git a/scene/resources/font.h b/scene/resources/font.h index 6bc8d9b792..9b99b85d22 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -34,9 +34,6 @@ #include "core/map.h" #include "core/resource.h" #include "scene/resources/texture.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Font : public Resource { @@ -53,6 +50,7 @@ public: virtual Size2 get_char_size(CharType p_char, CharType p_next = 0) const = 0; Size2 get_string_size(const String &p_string) const; + Size2 get_wordwrap_string_size(const String &p_string, float p_width) const; virtual bool is_distance_field_hint() const = 0; @@ -201,7 +199,6 @@ public: }; class ResourceFormatLoaderBMFont : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderBMFont, ResourceFormatLoader) public: virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List<String> *p_extensions) const; diff --git a/scene/resources/color_ramp.cpp b/scene/resources/gradient.cpp index 845a1474a4..1ff02c2f82 100644 --- a/scene/resources/color_ramp.cpp +++ b/scene/resources/gradient.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* color_ramp.cpp */ +/* gradient.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "color_ramp.h" +#include "gradient.h" + #include "core/core_string_names.h" //setter and getter names for property serialization @@ -142,6 +143,8 @@ void Gradient::set_points(Vector<Gradient::Point> &p_points) { } void Gradient::set_offset(int pos, const float offset) { + + ERR_FAIL_COND(pos < 0); if (points.size() <= pos) points.resize(pos + 1); points.write[pos].offset = offset; @@ -150,12 +153,12 @@ void Gradient::set_offset(int pos, const float offset) { } float Gradient::get_offset(int pos) const { - if (points.size() && points.size() > pos) - return points[pos].offset; - return 0; //TODO: Maybe throw some error instead? + ERR_FAIL_INDEX_V(pos, points.size(), 0.0); + return points[pos].offset; } void Gradient::set_color(int pos, const Color &color) { + ERR_FAIL_COND(pos < 0); if (points.size() <= pos) { points.resize(pos + 1); is_sorted = false; @@ -165,9 +168,8 @@ void Gradient::set_color(int pos, const Color &color) { } Color Gradient::get_color(int pos) const { - if (points.size() && points.size() > pos) - return points[pos].color; - return Color(0, 0, 0, 1); //TODO: Maybe throw some error instead? + ERR_FAIL_INDEX_V(pos, points.size(), Color()); + return points[pos].color; } int Gradient::get_points_count() const { diff --git a/scene/resources/color_ramp.h b/scene/resources/gradient.h index 7a96bb429b..7e7fc99a5d 100644 --- a/scene/resources/color_ramp.h +++ b/scene/resources/gradient.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* color_ramp.h */ +/* gradient.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SCENE_RESOURCES_COLOR_RAMP_H_ -#define SCENE_RESOURCES_COLOR_RAMP_H_ +#ifndef GRADIENT_H +#define GRADIENT_H #include "core/resource.h" @@ -91,7 +91,7 @@ public: int high = points.size() - 1; int middle = 0; -#if DEBUG_ENABLED +#ifdef DEBUG_ENABLED if (low > high) ERR_PRINT("low > high, this may be a bug"); #endif @@ -126,4 +126,4 @@ public: int get_points_count() const; }; -#endif /* SCENE_RESOURCES_COLOR_RAMP_H_ */ +#endif // GRADIENT_H diff --git a/scene/resources/height_map_shape.cpp b/scene/resources/height_map_shape.cpp new file mode 100644 index 0000000000..f763700d52 --- /dev/null +++ b/scene/resources/height_map_shape.cpp @@ -0,0 +1,205 @@ +/*************************************************************************/ +/* height_map_shape.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 "height_map_shape.h" +#include "servers/physics_server.h" + +Vector<Vector3> HeightMapShape::_gen_debug_mesh_lines() { + Vector<Vector3> points; + + if ((map_width != 0) && (map_depth != 0)) { + + // This will be slow for large maps... + // also we'll have to figure out how well bullet centers this shape... + + Vector2 size(map_width - 1, map_depth - 1); + Vector2 start = size * -0.5; + + PoolRealArray::Read r = map_data.read(); + + // reserve some memory for our points.. + points.resize(((map_width - 1) * map_depth * 2) + (map_width * (map_depth - 1) * 2)); + + // now set our points + int r_offset = 0; + int w_offset = 0; + for (int d = 0; d < map_depth; d++) { + Vector3 height(start.x, 0.0, start.y); + + for (int w = 0; w < map_width; w++) { + height.y = r[r_offset++]; + + if (w != map_width - 1) { + points.write[w_offset++] = height; + points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z); + } + + if (d != map_depth - 1) { + points.write[w_offset++] = height; + points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0); + } + + height.x += 1.0; + } + + start.y += 1.0; + } + } + + return points; +} + +void HeightMapShape::_update_shape() { + + Dictionary d; + d["width"] = map_width; + d["depth"] = map_depth; + d["heights"] = map_data; + d["min_height"] = min_height; + d["max_height"] = max_height; + PhysicsServer::get_singleton()->shape_set_data(get_shape(), d); + Shape::_update_shape(); +} + +void HeightMapShape::set_map_width(int p_new) { + if (p_new < 1) { + // ignore + } else if (map_width != p_new) { + int was_size = map_width * map_depth; + map_width = p_new; + + int new_size = map_width * map_depth; + map_data.resize(map_width * map_depth); + + PoolRealArray::Write w = map_data.write(); + while (was_size < new_size) { + w[was_size++] = 0.0; + } + + _update_shape(); + notify_change_to_owners(); + _change_notify("map_width"); + _change_notify("map_data"); + } +} + +int HeightMapShape::get_map_width() const { + return map_width; +} + +void HeightMapShape::set_map_depth(int p_new) { + if (p_new < 1) { + // ignore + } else if (map_depth != p_new) { + int was_size = map_width * map_depth; + map_depth = p_new; + + int new_size = map_width * map_depth; + map_data.resize(new_size); + + PoolRealArray::Write w = map_data.write(); + while (was_size < new_size) { + w[was_size++] = 0.0; + } + + _update_shape(); + notify_change_to_owners(); + _change_notify("map_depth"); + _change_notify("map_data"); + } +} + +int HeightMapShape::get_map_depth() const { + return map_depth; +} + +void HeightMapShape::set_map_data(PoolRealArray p_new) { + int size = (map_width * map_depth); + if (p_new.size() != size) { + // fail + return; + } + + // copy + PoolRealArray::Write w = map_data.write(); + PoolRealArray::Read r = p_new.read(); + for (int i = 0; i < size; i++) { + float val = r[i]; + w[i] = val; + if (i == 0) { + min_height = val; + max_height = val; + } else { + if (min_height > val) + min_height = val; + + if (max_height < val) + max_height = val; + } + } + + _update_shape(); + notify_change_to_owners(); + _change_notify("map_data"); +} + +PoolRealArray HeightMapShape::get_map_data() const { + return map_data; +} + +void HeightMapShape::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_map_width", "width"), &HeightMapShape::set_map_width); + ClassDB::bind_method(D_METHOD("get_map_width"), &HeightMapShape::get_map_width); + ClassDB::bind_method(D_METHOD("set_map_depth", "height"), &HeightMapShape::set_map_depth); + ClassDB::bind_method(D_METHOD("get_map_depth"), &HeightMapShape::get_map_depth); + ClassDB::bind_method(D_METHOD("set_map_data", "data"), &HeightMapShape::set_map_data); + ClassDB::bind_method(D_METHOD("get_map_data"), &HeightMapShape::get_map_data); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "map_width", PROPERTY_HINT_RANGE, "1,4096,1"), "set_map_width", "get_map_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "map_depth", PROPERTY_HINT_RANGE, "1,4096,1"), "set_map_depth", "get_map_depth"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "map_data"), "set_map_data", "get_map_data"); +} + +HeightMapShape::HeightMapShape() : + Shape(PhysicsServer::get_singleton()->shape_create(PhysicsServer::SHAPE_HEIGHTMAP)) { + + map_width = 2; + map_depth = 2; + map_data.resize(map_width * map_depth); + PoolRealArray::Write w = map_data.write(); + w[0] = 0.0; + w[1] = 0.0; + w[2] = 0.0; + w[3] = 0.0; + min_height = 0.0; + max_height = 0.0; + + _update_shape(); +} diff --git a/scene/resources/bounds.h b/scene/resources/height_map_shape.h index 9a1801f23d..b062f4e893 100644 --- a/scene/resources/bounds.h +++ b/scene/resources/height_map_shape.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* bounds.h */ +/* height_map_shape.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,35 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BOUNDS_H -#define BOUNDS_H +#ifndef HEIGHT_MAP_SHAPE_H +#define HEIGHT_MAP_SHAPE_H -#include "core/math/bsp_tree.h" -#include "core/resource.h" +#include "scene/resources/shape.h" -class Bounds : public Resource { +class HeightMapShape : public Shape { + GDCLASS(HeightMapShape, Shape); - GDCLASS(Bounds, Resource); - BSP_Tree bsp_tree; + int map_width; + int map_depth; + PoolRealArray map_data; + float min_height; + float max_height; protected: static void _bind_methods(); + virtual void _update_shape(); -public: - void set_bsp_tree(const BSP_Tree &p_bsp_tree); - BSP_Tree get_bsp_tree() const; + virtual Vector<Vector3> _gen_debug_mesh_lines(); - Bounds(); +public: + void set_map_width(int p_new); + int get_map_width() const; + void set_map_depth(int p_new); + int get_map_depth() const; + void set_map_data(PoolRealArray p_new); + PoolRealArray get_map_data() const; + + HeightMapShape(); }; -#endif // BOUNDS_H +#endif /* !HEIGHT_MAP_SHAPE_H */ diff --git a/scene/resources/shape_line_2d.cpp b/scene/resources/line_shape_2d.cpp index 4ca535658f..f5d5fb561a 100644 --- a/scene/resources/shape_line_2d.cpp +++ b/scene/resources/line_shape_2d.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* shape_line_2d.cpp */ +/* line_shape_2d.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "shape_line_2d.h" +#include "line_shape_2d.h" + #include "servers/physics_2d_server.h" #include "servers/visual_server.h" diff --git a/scene/resources/shape_line_2d.h b/scene/resources/line_shape_2d.h index dd3dbe3a43..f684862025 100644 --- a/scene/resources/shape_line_2d.h +++ b/scene/resources/line_shape_2d.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* shape_line_2d.h */ +/* line_shape_2d.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SHAPE_LINE_2D_H -#define SHAPE_LINE_2D_H +#ifndef LINE_SHAPE_2D_H +#define LINE_SHAPE_2D_H #include "scene/resources/shape_2d.h" @@ -59,4 +59,4 @@ public: LineShape2D(); }; -#endif // SHAPE_LINE_2D_H +#endif // LINE_SHAPE_2D_H diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 5ffaf41581..0de462d616 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -30,11 +30,17 @@ #include "material.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + #include "scene/scene_string_names.h" void Material::set_next_pass(const Ref<Material> &p_pass) { - ERR_FAIL_COND(p_pass == this); + for (Ref<Material> pass_child = p_pass; pass_child != NULL; pass_child = pass_child->get_next_pass()) { + ERR_FAIL_COND_MSG(pass_child == this, "Can't set as next_pass one of its parents to prevent crashes due to recursive loop."); + } if (next_pass == p_pass) return; @@ -113,6 +119,9 @@ bool ShaderMaterial::_set(const StringName &p_name, const Variant &p_value) { if (n.find("param/") == 0) { //backwards compatibility pr = n.substr(6, n.length()); } + if (n.find("shader_param/") == 0) { //backwards compatibility + pr = n.replace_first("shader_param/", ""); + } } if (pr) { VisualServer::get_singleton()->material_set_param(_get_material(), pr, p_value); @@ -128,6 +137,16 @@ bool ShaderMaterial::_get(const StringName &p_name, Variant &r_ret) const { if (shader.is_valid()) { StringName pr = shader->remap_param(p_name); + if (!pr) { + String n = p_name; + if (n.find("param/") == 0) { //backwards compatibility + pr = n.substr(6, n.length()); + } + if (n.find("shader_param/") == 0) { //backwards compatibility + pr = n.replace_first("shader_param/", ""); + } + } + if (pr) { r_ret = VisualServer::get_singleton()->material_get_param(_get_material(), pr); return true; @@ -223,6 +242,12 @@ void ShaderMaterial::_bind_methods() { void ShaderMaterial::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { +#ifdef TOOLS_ENABLED + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; +#else + const String quote_style = "\""; +#endif + String f = p_function.operator String(); if ((f == "get_shader_param" || f == "set_shader_param") && p_idx == 0) { @@ -230,7 +255,7 @@ void ShaderMaterial::get_argument_options(const StringName &p_function, int p_id List<PropertyInfo> pl; shader->get_param_list(&pl); for (List<PropertyInfo>::Element *E = pl.front(); E; E = E->next()) { - r_options->push_back("\"" + E->get().name.replace_first("shader_param/", "") + "\""); + r_options->push_back(quote_style + E->get().name.replace_first("shader_param/", "") + quote_style); } } } @@ -445,6 +470,9 @@ void SpatialMaterial::_update_shader() { if (flags[FLAG_ENSURE_CORRECT_NORMALS]) { code += ",ensure_correct_normals"; } + if (flags[FLAG_USE_SHADOW_TO_OPACITY]) { + code += ",shadow_to_opacity"; + } code += ";\n"; code += "uniform vec4 albedo : hint_color;\n"; @@ -594,17 +622,17 @@ void SpatialMaterial::_update_shader() { code += "\tMODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);\n"; if (flags[FLAG_BILLBOARD_KEEP_SCALE]) { - code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz),0,0,0),vec4(0,length(WORLD_MATRIX[1].xyz),0,0),vec4(0,0,length(WORLD_MATRIX[2].xyz),0),vec4(0,0,0,1));\n"; + code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n"; } } break; case BILLBOARD_FIXED_Y: { - code += "\tMODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)),0.0),WORLD_MATRIX[3]);\n"; + code += "\tMODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)), 0.0),WORLD_MATRIX[3]);\n"; if (flags[FLAG_BILLBOARD_KEEP_SCALE]) { - code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz),0,0,0),vec4(0,1,0,0),vec4(0,0,length(WORLD_MATRIX[2].xyz),0),vec4(0,0,0,1));\n"; + code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n"; } else { - code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1,0,0,0),vec4(0,1.0/length(WORLD_MATRIX[1].xyz),0,0),vec4(0,0,1,0),vec4(0,0,0,1));\n"; + code += "\tMODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1.0, 0.0, 0.0, 0.0),vec4(0.0, 1.0/length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0 ,1.0));\n"; } } break; case BILLBOARD_PARTICLES: { @@ -612,16 +640,22 @@ void SpatialMaterial::_update_shader() { //make billboard code += "\tmat4 mat_world = mat4(normalize(CAMERA_MATRIX[0])*length(WORLD_MATRIX[0]),normalize(CAMERA_MATRIX[1])*length(WORLD_MATRIX[0]),normalize(CAMERA_MATRIX[2])*length(WORLD_MATRIX[2]),WORLD_MATRIX[3]);\n"; //rotate by rotation - code += "\tmat_world = mat_world * mat4( vec4(cos(INSTANCE_CUSTOM.x),-sin(INSTANCE_CUSTOM.x),0.0,0.0), vec4(sin(INSTANCE_CUSTOM.x),cos(INSTANCE_CUSTOM.x),0.0,0.0),vec4(0.0,0.0,1.0,0.0),vec4(0.0,0.0,0.0,1.0));\n"; + code += "\tmat_world = mat_world * mat4( vec4(cos(INSTANCE_CUSTOM.x),-sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0),vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n"; //set modelview code += "\tMODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat_world;\n"; //handle animation + code += "\tfloat h_frames = float(particles_anim_h_frames);\n"; + code += "\tfloat v_frames = float(particles_anim_v_frames);\n"; code += "\tfloat particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n"; code += "\tfloat particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n"; - code += "\tif (!particles_anim_loop) particle_frame=clamp(particle_frame,0.0,particle_total_frames-1.0); else particle_frame=mod(particle_frame,float(particle_total_frames));\n"; - code += "\tUV /= vec2(float(particles_anim_h_frames),float(particles_anim_v_frames));\n"; - code += "\tUV += vec2(mod(particle_frame,float(particles_anim_h_frames)) / float(particles_anim_h_frames), floor(particle_frame / float(particles_anim_h_frames)) / float(particles_anim_v_frames));\n"; + code += "\tif (!particles_anim_loop) {\n"; + code += "\t\tparticle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n"; + code += "\t} else {\n"; + code += "\t\tparticle_frame = mod(particle_frame, particle_total_frames);\n"; + code += "\t}"; + code += "\tUV /= vec2(h_frames, v_frames);\n"; + code += "\tUV += vec2(mod(particle_frame, h_frames) / h_frames, floor(particle_frame / h_frames) / v_frames);\n"; } break; } @@ -654,9 +688,9 @@ void SpatialMaterial::_update_shader() { code += "\tTANGENT+= vec3(1.0,0.0,0.0) * abs(NORMAL.z);\n"; code += "\tTANGENT = normalize(TANGENT);\n"; - code += "\tBINORMAL = vec3(0.0,1.0,0.0) * abs(NORMAL.x);\n"; - code += "\tBINORMAL+= vec3(0.0,0.0,-1.0) * abs(NORMAL.y);\n"; - code += "\tBINORMAL+= vec3(0.0,1.0,0.0) * abs(NORMAL.z);\n"; + code += "\tBINORMAL = vec3(0.0,-1.0,0.0) * abs(NORMAL.x);\n"; + code += "\tBINORMAL+= vec3(0.0,0.0,1.0) * abs(NORMAL.y);\n"; + code += "\tBINORMAL+= vec3(0.0,-1.0,0.0) * abs(NORMAL.z);\n"; code += "\tBINORMAL = normalize(BINORMAL);\n"; } @@ -702,7 +736,7 @@ void SpatialMaterial::_update_shader() { code += "\tvec2 base_uv2 = UV2;\n"; } - if (features[FEATURE_DEPTH_MAPPING] && !flags[FLAG_UV1_USE_TRIPLANAR]) { //depthmap not supported with triplanar + if (!VisualServer::get_singleton()->is_low_end() && features[FEATURE_DEPTH_MAPPING] && !flags[FLAG_UV1_USE_TRIPLANAR]) { //depthmap not supported with triplanar code += "\t{\n"; code += "\t\tvec3 view_dir = normalize(normalize(-VERTEX)*mat3(TANGENT*depth_flip.x,-BINORMAL*depth_flip.y,NORMAL));\n"; // binormal is negative due to mikktspace, flip 'unflips' it ;-) @@ -820,11 +854,11 @@ void SpatialMaterial::_update_shader() { code += "\tALBEDO *= 1.0 - ref_amount;\n"; code += "\tALPHA = 1.0;\n"; - } else if (features[FEATURE_TRANSPARENT] || flags[FLAG_USE_ALPHA_SCISSOR] || (distance_fade == DISTANCE_FADE_PIXEL_ALPHA) || proximity_fade_enabled) { + } else if (features[FEATURE_TRANSPARENT] || flags[FLAG_USE_ALPHA_SCISSOR] || flags[FLAG_USE_SHADOW_TO_OPACITY] || (distance_fade == DISTANCE_FADE_PIXEL_ALPHA) || proximity_fade_enabled) { code += "\tALPHA = albedo.a * albedo_tex.a;\n"; } - if (!VisualServer::get_singleton()->is_low_end() && proximity_fade_enabled) { + if (proximity_fade_enabled) { code += "\tfloat depth_tex = textureLod(DEPTH_TEXTURE,SCREEN_UV,0.0).r;\n"; code += "\tvec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV*2.0-1.0,depth_tex*2.0-1.0,1.0);\n"; code += "\tworld_pos.xyz/=world_pos.w;\n"; @@ -1320,7 +1354,7 @@ void SpatialMaterial::set_flag(Flags p_flag, bool p_enabled) { return; flags[p_flag] = p_enabled; - if (p_flag == FLAG_USE_ALPHA_SCISSOR || p_flag == FLAG_UNSHADED) { + if ((p_flag == FLAG_USE_ALPHA_SCISSOR) || (p_flag == FLAG_UNSHADED) || (p_flag == FLAG_USE_SHADOW_TO_OPACITY)) { _change_notify(); } _queue_shader_change(); @@ -1738,7 +1772,7 @@ SpatialMaterial::TextureChannel SpatialMaterial::get_refraction_texture_channel( return refraction_texture_channel; } -RID SpatialMaterial::get_material_rid_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass) { +RID SpatialMaterial::get_material_rid_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard, bool p_billboard_y) { int version = 0; if (p_shaded) @@ -1751,6 +1785,10 @@ RID SpatialMaterial::get_material_rid_for_2d(bool p_shaded, bool p_transparent, version |= 8; if (p_double_sided) version |= 16; + if (p_billboard) + version |= 32; + if (p_billboard_y) + version |= 64; if (materials_for_2d[version].is_valid()) { return materials_for_2d[version]->get_rid(); @@ -1766,6 +1804,11 @@ RID SpatialMaterial::get_material_rid_for_2d(bool p_shaded, bool p_transparent, material->set_flag(FLAG_SRGB_VERTEX_COLOR, true); material->set_flag(FLAG_ALBEDO_FROM_VERTEX_COLOR, true); material->set_flag(FLAG_USE_ALPHA_SCISSOR, p_cut_alpha); + if (p_billboard) { + material->set_billboard_mode(BILLBOARD_ENABLED); + } else if (p_billboard_y) { + material->set_billboard_mode(BILLBOARD_FIXED_Y); + } materials_for_2d[version] = material; @@ -2031,6 +2074,7 @@ void SpatialMaterial::_bind_methods() { ADD_GROUP("Flags", "flags_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_transparent"), "set_feature", "get_feature", FEATURE_TRANSPARENT); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_use_shadow_to_opacity"), "set_flag", "get_flag", FLAG_USE_SHADOW_TO_OPACITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_unshaded"), "set_flag", "get_flag", FLAG_UNSHADED); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_vertex_lighting"), "set_flag", "get_flag", FLAG_USE_VERTEX_LIGHTING); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flags_no_depth_test"), "set_flag", "get_flag", FLAG_DISABLE_DEPTH_TEST); @@ -2056,7 +2100,7 @@ void SpatialMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "params_billboard_mode", PROPERTY_HINT_ENUM, "Disabled,Enabled,Y-Billboard,Particle Billboard"), "set_billboard_mode", "get_billboard_mode"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "params_billboard_keep_scale"), "set_flag", "get_flag", FLAG_BILLBOARD_KEEP_SCALE); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "params_grow"), "set_grow_enabled", "is_grow_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "params_grow_amount", PROPERTY_HINT_RANGE, "-16,10,0.01"), "set_grow", "get_grow"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "params_grow_amount", PROPERTY_HINT_RANGE, "-16,16,0.001"), "set_grow", "get_grow"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "params_use_alpha_scissor"), "set_flag", "get_flag", FLAG_USE_ALPHA_SCISSOR); ADD_PROPERTY(PropertyInfo(Variant::REAL, "params_alpha_scissor_threshold", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_alpha_scissor_threshold", "get_alpha_scissor_threshold"); ADD_GROUP("Particles Anim", "particles_anim_"); @@ -2082,7 +2126,7 @@ void SpatialMaterial::_bind_methods() { ADD_GROUP("Emission", "emission_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "emission_enabled"), "set_feature", "get_feature", FEATURE_EMISSION); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_emission_energy", "get_emission_energy"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_emission_energy", "get_emission_energy"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_operator", PROPERTY_HINT_ENUM, "Add,Multiply"), "set_emission_operator", "get_emission_operator"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "emission_on_uv2"), "set_flag", "get_flag", FLAG_EMISSION_ON_UV2); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "emission_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture", TEXTURE_EMISSION); @@ -2164,11 +2208,11 @@ void SpatialMaterial::_bind_methods() { ADD_GROUP("Proximity Fade", "proximity_fade_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "proximity_fade_enable"), "set_proximity_fade", "is_proximity_fade_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "proximity_fade_distance", PROPERTY_HINT_RANGE, "0,4096,0.1"), "set_proximity_fade_distance", "get_proximity_fade_distance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "proximity_fade_distance", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_proximity_fade_distance", "get_proximity_fade_distance"); ADD_GROUP("Distance Fade", "distance_fade_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "distance_fade_mode", PROPERTY_HINT_ENUM, "Disabled,PixelAlpha,PixelDither,ObjectDither"), "set_distance_fade", "get_distance_fade"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "distance_fade_min_distance", PROPERTY_HINT_RANGE, "0,4096,0.1"), "set_distance_fade_min_distance", "get_distance_fade_min_distance"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "distance_fade_max_distance", PROPERTY_HINT_RANGE, "0,4096,0.1"), "set_distance_fade_max_distance", "get_distance_fade_max_distance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "distance_fade_min_distance", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_distance_fade_min_distance", "get_distance_fade_min_distance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "distance_fade_max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_distance_fade_max_distance", "get_distance_fade_max_distance"); BIND_ENUM_CONSTANT(TEXTURE_ALBEDO); BIND_ENUM_CONSTANT(TEXTURE_METALLIC); @@ -2237,6 +2281,7 @@ void SpatialMaterial::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_DONT_RECEIVE_SHADOWS); BIND_ENUM_CONSTANT(FLAG_DISABLE_AMBIENT_LIGHT); BIND_ENUM_CONSTANT(FLAG_ENSURE_CORRECT_NORMALS); + BIND_ENUM_CONSTANT(FLAG_USE_SHADOW_TO_OPACITY); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(DIFFUSE_BURLEY); diff --git a/scene/resources/material.h b/scene/resources/material.h index 1f7fc267af..2423d6d48b 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -37,13 +37,10 @@ #include "scene/resources/texture.h" #include "servers/visual/shader_language.h" #include "servers/visual_server.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Material : public Resource { - GDCLASS(Material, Resource) + GDCLASS(Material, Resource); RES_BASE_EXTENSION("material") OBJ_SAVE_TYPE(Material) @@ -111,7 +108,7 @@ public: class SpatialMaterial : public Material { - GDCLASS(SpatialMaterial, Material) + GDCLASS(SpatialMaterial, Material); public: enum TextureParam { @@ -196,6 +193,7 @@ public: FLAG_DONT_RECEIVE_SHADOWS, FLAG_ENSURE_CORRECT_NORMALS, FLAG_DISABLE_AMBIENT_LIGHT, + FLAG_USE_SHADOW_TO_OPACITY, FLAG_MAX }; @@ -439,9 +437,7 @@ private: _FORCE_INLINE_ void _validate_feature(const String &text, Feature feature, PropertyInfo &property) const; - enum { - MAX_MATERIALS_FOR_2D = 32 - }; + static const int MAX_MATERIALS_FOR_2D = 128; static Ref<SpatialMaterial> materials_for_2d[MAX_MATERIALS_FOR_2D]; //used by Sprite3D and other stuff @@ -628,7 +624,7 @@ public: static void finish_shaders(); static void flush_changes(); - static RID get_material_rid_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass); + static RID get_material_rid_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard = false, bool p_billboard_y = false); RID get_shader_rid() const; diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 805d30245a..4afb07cb6f 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -37,6 +37,8 @@ #include <stdlib.h> +Mesh::ConvexDecompositionFunc Mesh::convex_composition_function = NULL; + Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { if (triangle_mesh.is_valid()) @@ -84,19 +86,19 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { PoolVector<int> indices = a[ARRAY_INDEX]; PoolVector<int>::Read ir = indices.read(); - for (int i = 0; i < ic; i++) { - int index = ir[i]; + for (int j = 0; j < ic; j++) { + int index = ir[j]; facesw[widx++] = vr[index]; } } else { - for (int i = 0; i < vc; i++) - facesw[widx++] = vr[i]; + for (int j = 0; j < vc; j++) + facesw[widx++] = vr[j]; } } - facesw = PoolVector<Vector3>::Write(); + facesw.release(); triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); triangle_mesh->create(faces); @@ -434,7 +436,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { r[i] = t; } - r = PoolVector<Vector3>::Write(); + r.release(); arrays[ARRAY_VERTEX] = vertices; if (!has_indices) { @@ -450,7 +452,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { iw[j + 2] = j + 1; } - iw = PoolVector<int>::Write(); + iw.release(); arrays[ARRAY_INDEX] = new_indices; } else { @@ -459,7 +461,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { SWAP(ir[j + 1], ir[j + 2]); } - ir = PoolVector<int>::Write(); + ir.release(); arrays[ARRAY_INDEX] = indices; } } @@ -487,6 +489,7 @@ void Mesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_surface_count"), &Mesh::get_surface_count); ClassDB::bind_method(D_METHOD("surface_get_arrays", "surf_idx"), &Mesh::surface_get_arrays); ClassDB::bind_method(D_METHOD("surface_get_blend_shape_arrays", "surf_idx"), &Mesh::surface_get_blend_shape_arrays); + ClassDB::bind_method(D_METHOD("surface_set_material", "surf_idx", "material"), &Mesh::surface_set_material); ClassDB::bind_method(D_METHOD("surface_get_material", "surf_idx"), &Mesh::surface_get_material); BIND_ENUM_CONSTANT(PRIMITIVE_POINTS); @@ -543,6 +546,49 @@ void Mesh::clear_cache() const { debug_lines.clear(); } +Vector<Ref<Shape> > Mesh::convex_decompose() const { + + ERR_FAIL_COND_V(!convex_composition_function, Vector<Ref<Shape> >()); + + PoolVector<Face3> faces = get_faces(); + Vector<Face3> f3; + f3.resize(faces.size()); + PoolVector<Face3>::Read f = faces.read(); + for (int i = 0; i < f3.size(); i++) { + f3.write[i] = f[i]; + } + + Vector<Vector<Face3> > decomposed = convex_composition_function(f3); + + Vector<Ref<Shape> > ret; + + for (int i = 0; i < decomposed.size(); i++) { + Set<Vector3> points; + for (int j = 0; j < decomposed[i].size(); j++) { + points.insert(decomposed[i][j].vertex[0]); + points.insert(decomposed[i][j].vertex[1]); + points.insert(decomposed[i][j].vertex[2]); + } + + PoolVector<Vector3> convex_points; + convex_points.resize(points.size()); + { + PoolVector<Vector3>::Write w = convex_points.write(); + int idx = 0; + for (Set<Vector3>::Element *E = points.front(); E; E = E->next()) { + w[idx++] = E->get(); + } + } + + Ref<ConvexPolygonShape> shape; + shape.instance(); + shape->set_points(convex_points); + ret.push_back(shape); + } + + return ret; +} + Mesh::Mesh() { } @@ -787,7 +833,6 @@ void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array & Surface s; VisualServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (VisualServer::PrimitiveType)p_primitive, p_arrays, p_blend_shapes, p_flags); - surfaces.push_back(s); /* make aABB? */ { @@ -808,8 +853,9 @@ void ArrayMesh::add_surface_from_arrays(PrimitiveType p_primitive, const Array & aabb.expand_to(vtx[i]); } - surfaces.write[surfaces.size() - 1].aabb = aabb; - surfaces.write[surfaces.size() - 1].is_2d = arr.get_type() == Variant::POOL_VECTOR2_ARRAY; + s.aabb = aabb; + s.is_2d = arr.get_type() == Variant::POOL_VECTOR2_ARRAY; + surfaces.push_back(s); _recompute_aabb(); } @@ -837,10 +883,7 @@ int ArrayMesh::get_surface_count() const { void ArrayMesh::add_blend_shape(const StringName &p_name) { - if (surfaces.size()) { - ERR_EXPLAIN("Can't add a shape key count if surfaces are already created."); - ERR_FAIL_COND(surfaces.size()); - } + ERR_FAIL_COND_MSG(surfaces.size(), "Can't add a shape key count if surfaces are already created."); StringName name = p_name; @@ -868,10 +911,7 @@ StringName ArrayMesh::get_blend_shape_name(int p_index) const { } void ArrayMesh::clear_blend_shapes() { - if (surfaces.size()) { - ERR_EXPLAIN("Can't set shape key count if surfaces are already created."); - ERR_FAIL_COND(surfaces.size()); - } + ERR_FAIL_COND_MSG(surfaces.size(), "Can't set shape key count if surfaces are already created."); blend_shapes.clear(); } @@ -1028,34 +1068,6 @@ AABB ArrayMesh::get_custom_aabb() const { return custom_aabb; } -void ArrayMesh::center_geometry() { - - /* - Vector3 ofs = aabb.pos+aabb.size*0.5; - - for(int i=0;i<get_surface_count();i++) { - - PoolVector<Vector3> geom = surface_get_array(i,ARRAY_VERTEX); - int gc =geom.size(); - PoolVector<Vector3>::Write w = geom.write(); - surfaces[i].aabb.pos-=ofs; - - for(int i=0;i<gc;i++) { - - w[i]-=ofs; - } - - w = PoolVector<Vector3>::Write(); - - surface_set_array(i,ARRAY_VERTEX,geom); - - } - - aabb.pos-=ofs; - -*/ -} - void ArrayMesh::regen_normalmaps() { Vector<Ref<SurfaceTool> > surfs; @@ -1091,8 +1103,7 @@ struct ArrayMeshLightmapSurface { Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texel_size) { ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); - ERR_EXPLAIN("Can't unwrap mesh with blend shapes"); - ERR_FAIL_COND_V(blend_shapes.size() != 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); Vector<float> vertices; Vector<float> normals; @@ -1106,15 +1117,9 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe ArrayMeshLightmapSurface s; s.primitive = surface_get_primitive_type(i); - if (s.primitive != Mesh::PRIMITIVE_TRIANGLES) { - ERR_EXPLAIN("Only triangles are supported for lightmap unwrap"); - ERR_FAIL_V(ERR_UNAVAILABLE); - } + ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap."); s.format = surface_get_format(i); - if (!(s.format & ARRAY_FORMAT_NORMAL)) { - ERR_EXPLAIN("Normals are required for lightmap unwrap"); - ERR_FAIL_V(ERR_UNAVAILABLE); - } + ERR_FAIL_COND_V_MSG(!(s.format & ARRAY_FORMAT_NORMAL), ERR_UNAVAILABLE, "Normals are required for lightmap unwrap."); Array arrays = surface_get_arrays(i); s.material = surface_get_material(i); @@ -1288,15 +1293,12 @@ void ArrayMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("surface_get_array_index_len", "surf_idx"), &ArrayMesh::surface_get_array_index_len); ClassDB::bind_method(D_METHOD("surface_get_format", "surf_idx"), &ArrayMesh::surface_get_format); ClassDB::bind_method(D_METHOD("surface_get_primitive_type", "surf_idx"), &ArrayMesh::surface_get_primitive_type); - ClassDB::bind_method(D_METHOD("surface_set_material", "surf_idx", "material"), &ArrayMesh::surface_set_material); ClassDB::bind_method(D_METHOD("surface_find_by_name", "name"), &ArrayMesh::surface_find_by_name); ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name); ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name); ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &ArrayMesh::create_trimesh_shape); ClassDB::bind_method(D_METHOD("create_convex_shape"), &ArrayMesh::create_convex_shape); ClassDB::bind_method(D_METHOD("create_outline", "margin"), &ArrayMesh::create_outline); - ClassDB::bind_method(D_METHOD("center_geometry"), &ArrayMesh::center_geometry); - ClassDB::set_method_flags(get_class_static(), _scs_create("center_geometry"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); ClassDB::bind_method(D_METHOD("regen_normalmaps"), &ArrayMesh::regen_normalmaps); ClassDB::set_method_flags(get_class_static(), _scs_create("regen_normalmaps"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); ClassDB::bind_method(D_METHOD("lightmap_unwrap", "transform", "texel_size"), &ArrayMesh::lightmap_unwrap); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 2d0aef8ab0..6b6ee8a209 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -31,14 +31,12 @@ #ifndef MESH_H #define MESH_H +#include "core/math/face3.h" #include "core/math/triangle_mesh.h" #include "core/resource.h" #include "scene/resources/material.h" #include "scene/resources/shape.h" #include "servers/visual_server.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Mesh : public Resource { GDCLASS(Mesh, Resource); @@ -127,6 +125,7 @@ public: virtual Array surface_get_blend_shape_arrays(int p_surface) const = 0; virtual uint32_t surface_get_format(int p_idx) const = 0; virtual PrimitiveType surface_get_primitive_type(int p_idx) const = 0; + virtual void surface_set_material(int p_idx, const Ref<Material> &p_material) = 0; virtual Ref<Material> surface_get_material(int p_idx) const = 0; virtual int get_blend_shape_count() const = 0; virtual StringName get_blend_shape_name(int p_index) const = 0; @@ -147,6 +146,12 @@ public: Size2 get_lightmap_size_hint() const; void clear_cache() const; + typedef Vector<Vector<Face3> > (*ConvexDecompositionFunc)(const Vector<Face3> &); + + static ConvexDecompositionFunc convex_composition_function; + + Vector<Ref<Shape> > convex_decompose() const; + Mesh(); }; @@ -208,8 +213,8 @@ public: PrimitiveType surface_get_primitive_type(int p_idx) const; bool surface_is_alpha_sorting_enabled(int p_idx) const; - void surface_set_material(int p_idx, const Ref<Material> &p_material); - Ref<Material> surface_get_material(int p_idx) const; + virtual void surface_set_material(int p_idx, const Ref<Material> &p_material); + virtual Ref<Material> surface_get_material(int p_idx) const; int surface_find_by_name(const String &p_name) const; void surface_set_name(int p_idx, const String &p_name); @@ -223,7 +228,6 @@ public: AABB get_aabb() const; virtual RID get_rid() const; - void center_geometry(); void regen_normalmaps(); Error lightmap_unwrap(const Transform &p_base_transform = Transform(), float p_texel_size = 0.05); diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index c5125d576d..d40a5dee2e 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -56,6 +56,8 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { set_item_preview(idx, p_value); else if (what == "navmesh") set_item_navmesh(idx, p_value); + else if (what == "navmesh_transform") + set_item_navmesh_transform(idx, p_value); else return false; @@ -80,6 +82,8 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { r_ret = _get_item_shapes(idx); else if (what == "navmesh") r_ret = get_item_navmesh(idx); + else if (what == "navmesh_transform") + r_ret = get_item_navmesh_transform(idx); else if (what == "preview") r_ret = get_item_preview(idx); else @@ -95,8 +99,10 @@ void MeshLibrary::_get_property_list(List<PropertyInfo> *p_list) const { String name = "item/" + itos(E->key()) + "/"; p_list->push_back(PropertyInfo(Variant::STRING, name + "name")); p_list->push_back(PropertyInfo(Variant::OBJECT, name + "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM, name + "mesh_transform")); p_list->push_back(PropertyInfo(Variant::ARRAY, name + "shapes")); p_list->push_back(PropertyInfo(Variant::OBJECT, name + "navmesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM, name + "navmesh_transform")); p_list->push_back(PropertyInfo(Variant::OBJECT, name + "preview", PROPERTY_HINT_RESOURCE_TYPE, "Texture", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_HELPER)); } } @@ -116,6 +122,7 @@ void MeshLibrary::set_item_name(int p_item, const String &p_name) { emit_changed(); _change_notify(); } + void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) { ERR_FAIL_COND(!item_map.has(p_item)); @@ -145,6 +152,15 @@ void MeshLibrary::set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navm _change_notify(); } +void MeshLibrary::set_item_navmesh_transform(int p_item, const Transform &p_transform) { + + ERR_FAIL_COND(!item_map.has(p_item)); + item_map[p_item].navmesh_transform = p_transform; + notify_change_to_owners(); + emit_changed(); + _change_notify(); +} + void MeshLibrary::set_item_preview(int p_item, const Ref<Texture> &p_preview) { ERR_FAIL_COND(!item_map.has(p_item)); @@ -152,11 +168,13 @@ void MeshLibrary::set_item_preview(int p_item, const Ref<Texture> &p_preview) { emit_changed(); _change_notify(); } + String MeshLibrary::get_item_name(int p_item) const { ERR_FAIL_COND_V(!item_map.has(p_item), ""); return item_map[p_item].name; } + Ref<Mesh> MeshLibrary::get_item_mesh(int p_item) const { ERR_FAIL_COND_V(!item_map.has(p_item), Ref<Mesh>()); @@ -175,6 +193,12 @@ Ref<NavigationMesh> MeshLibrary::get_item_navmesh(int p_item) const { return item_map[p_item].navmesh; } +Transform MeshLibrary::get_item_navmesh_transform(int p_item) const { + + ERR_FAIL_COND_V(!item_map.has(p_item), Transform()); + return item_map[p_item].navmesh_transform; +} + Ref<Texture> MeshLibrary::get_item_preview(int p_item) const { ERR_FAIL_COND_V(!item_map.has(p_item), Ref<Texture>()); @@ -268,11 +292,13 @@ void MeshLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); ClassDB::bind_method(D_METHOD("set_item_navmesh", "id", "navmesh"), &MeshLibrary::set_item_navmesh); + ClassDB::bind_method(D_METHOD("set_item_navmesh_transform", "id", "navmesh"), &MeshLibrary::set_item_navmesh_transform); ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); ClassDB::bind_method(D_METHOD("get_item_navmesh", "id"), &MeshLibrary::get_item_navmesh); + ClassDB::bind_method(D_METHOD("get_item_navmesh_transform", "id"), &MeshLibrary::get_item_navmesh_transform); ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); ClassDB::bind_method(D_METHOD("get_item_preview", "id"), &MeshLibrary::get_item_preview); ClassDB::bind_method(D_METHOD("remove_item", "id"), &MeshLibrary::remove_item); diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index 849a0577eb..4ae4fc6483 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -52,6 +52,7 @@ public: Ref<Mesh> mesh; Vector<ShapeData> shapes; Ref<Texture> preview; + Transform navmesh_transform; Ref<NavigationMesh> navmesh; }; @@ -72,11 +73,13 @@ public: void set_item_name(int p_item, const String &p_name); void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh); void set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navmesh); + void set_item_navmesh_transform(int p_item, const Transform &p_transform); void set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes); void set_item_preview(int p_item, const Ref<Texture> &p_preview); String get_item_name(int p_item) const; Ref<Mesh> get_item_mesh(int p_item) const; Ref<NavigationMesh> get_item_navmesh(int p_item) const; + Transform get_item_navmesh_transform(int p_item) const; Vector<ShapeData> get_item_shapes(int p_item) const; Ref<Texture> get_item_preview(int p_item) const; diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index 7084c3b4e1..99a17fb5b9 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -32,8 +32,10 @@ #include "servers/visual_server.h" void MultiMesh::_set_transform_array(const PoolVector<Vector3> &p_array) { + if (transform_format != TRANSFORM_3D) + return; - PoolVector<Vector3> xforms = p_array; + const PoolVector<Vector3> &xforms = p_array; int len = xforms.size(); ERR_FAIL_COND((len / 4) != instance_count); if (len == 0) @@ -55,6 +57,9 @@ void MultiMesh::_set_transform_array(const PoolVector<Vector3> &p_array) { PoolVector<Vector3> MultiMesh::_get_transform_array() const { + if (transform_format != TRANSFORM_3D) + return PoolVector<Vector3>(); + if (instance_count == 0) return PoolVector<Vector3>(); @@ -75,9 +80,57 @@ PoolVector<Vector3> MultiMesh::_get_transform_array() const { return xforms; } +void MultiMesh::_set_transform_2d_array(const PoolVector<Vector2> &p_array) { + + if (transform_format != TRANSFORM_2D) + return; + + const PoolVector<Vector2> &xforms = p_array; + int len = xforms.size(); + ERR_FAIL_COND((len / 3) != instance_count); + if (len == 0) + return; + + PoolVector<Vector2>::Read r = xforms.read(); + + for (int i = 0; i < len / 3; i++) { + + Transform2D t; + t.elements[0] = r[i * 3 + 0]; + t.elements[1] = r[i * 3 + 1]; + t.elements[2] = r[i * 3 + 2]; + + set_instance_transform_2d(i, t); + } +} + +PoolVector<Vector2> MultiMesh::_get_transform_2d_array() const { + + if (transform_format != TRANSFORM_2D) + return PoolVector<Vector2>(); + + if (instance_count == 0) + return PoolVector<Vector2>(); + + PoolVector<Vector2> xforms; + xforms.resize(instance_count * 3); + + PoolVector<Vector2>::Write w = xforms.write(); + + for (int i = 0; i < instance_count; i++) { + + Transform2D t = get_instance_transform_2d(i); + w[i * 3 + 0] = t.elements[0]; + w[i * 3 + 1] = t.elements[1]; + w[i * 3 + 2] = t.elements[2]; + } + + return xforms; +} + void MultiMesh::_set_color_array(const PoolVector<Color> &p_array) { - PoolVector<Color> colors = p_array; + const PoolVector<Color> &colors = p_array; int len = colors.size(); if (len == 0) return; @@ -109,7 +162,7 @@ PoolVector<Color> MultiMesh::_get_color_array() const { void MultiMesh::_set_custom_data_array(const PoolVector<Color> &p_array) { - PoolVector<Color> custom_datas = p_array; + const PoolVector<Color> &custom_datas = p_array; int len = custom_datas.size(); if (len == 0) return; @@ -162,15 +215,36 @@ int MultiMesh::get_instance_count() const { return instance_count; } +void MultiMesh::set_visible_instance_count(int p_count) { + ERR_FAIL_COND(p_count < -1); + VisualServer::get_singleton()->multimesh_set_visible_instances(multimesh, p_count); + visible_instance_count = p_count; +} +int MultiMesh::get_visible_instance_count() const { + + return visible_instance_count; +} + void MultiMesh::set_instance_transform(int p_instance, const Transform &p_transform) { VisualServer::get_singleton()->multimesh_instance_set_transform(multimesh, p_instance, p_transform); } + +void MultiMesh::set_instance_transform_2d(int p_instance, const Transform2D &p_transform) { + + VisualServer::get_singleton()->multimesh_instance_set_transform_2d(multimesh, p_instance, p_transform); +} + Transform MultiMesh::get_instance_transform(int p_instance) const { return VisualServer::get_singleton()->multimesh_instance_get_transform(multimesh, p_instance); } +Transform2D MultiMesh::get_instance_transform_2d(int p_instance) const { + + return VisualServer::get_singleton()->multimesh_instance_get_transform_2d(multimesh, p_instance); +} + void MultiMesh::set_instance_color(int p_instance, const Color &p_color) { VisualServer::get_singleton()->multimesh_instance_set_color(multimesh, p_instance, p_color); @@ -189,6 +263,11 @@ Color MultiMesh::get_instance_custom_data(int p_instance) const { return VisualServer::get_singleton()->multimesh_instance_get_custom_data(multimesh, p_instance); } +void MultiMesh::set_as_bulk_array(const PoolVector<float> &p_array) { + + VisualServer::get_singleton()->multimesh_set_as_bulk_array(multimesh, p_array); +} + AABB MultiMesh::get_aabb() const { return VisualServer::get_singleton()->multimesh_get_aabb(multimesh); @@ -244,16 +323,23 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_instance_count", "count"), &MultiMesh::set_instance_count); ClassDB::bind_method(D_METHOD("get_instance_count"), &MultiMesh::get_instance_count); + ClassDB::bind_method(D_METHOD("set_visible_instance_count", "count"), &MultiMesh::set_visible_instance_count); + ClassDB::bind_method(D_METHOD("get_visible_instance_count"), &MultiMesh::get_visible_instance_count); ClassDB::bind_method(D_METHOD("set_instance_transform", "instance", "transform"), &MultiMesh::set_instance_transform); + ClassDB::bind_method(D_METHOD("set_instance_transform_2d", "instance", "transform"), &MultiMesh::set_instance_transform_2d); ClassDB::bind_method(D_METHOD("get_instance_transform", "instance"), &MultiMesh::get_instance_transform); + ClassDB::bind_method(D_METHOD("get_instance_transform_2d", "instance"), &MultiMesh::get_instance_transform_2d); ClassDB::bind_method(D_METHOD("set_instance_color", "instance", "color"), &MultiMesh::set_instance_color); ClassDB::bind_method(D_METHOD("get_instance_color", "instance"), &MultiMesh::get_instance_color); ClassDB::bind_method(D_METHOD("set_instance_custom_data", "instance", "custom_data"), &MultiMesh::set_instance_custom_data); ClassDB::bind_method(D_METHOD("get_instance_custom_data", "instance"), &MultiMesh::get_instance_custom_data); + ClassDB::bind_method(D_METHOD("set_as_bulk_array", "array"), &MultiMesh::set_as_bulk_array); ClassDB::bind_method(D_METHOD("get_aabb"), &MultiMesh::get_aabb); ClassDB::bind_method(D_METHOD("_set_transform_array"), &MultiMesh::_set_transform_array); ClassDB::bind_method(D_METHOD("_get_transform_array"), &MultiMesh::_get_transform_array); + ClassDB::bind_method(D_METHOD("_set_transform_2d_array"), &MultiMesh::_set_transform_2d_array); + ClassDB::bind_method(D_METHOD("_get_transform_2d_array"), &MultiMesh::_get_transform_2d_array); ClassDB::bind_method(D_METHOD("_set_color_array"), &MultiMesh::_set_color_array); ClassDB::bind_method(D_METHOD("_get_color_array"), &MultiMesh::_get_color_array); ClassDB::bind_method(D_METHOD("_set_custom_data_array"), &MultiMesh::_set_custom_data_array); @@ -263,8 +349,10 @@ void MultiMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_format", PROPERTY_HINT_ENUM, "2D,3D"), "set_transform_format", "get_transform_format"); ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_data_format", PROPERTY_HINT_ENUM, "None,Byte,Float"), "set_custom_data_format", "get_custom_data_format"); ADD_PROPERTY(PropertyInfo(Variant::INT, "instance_count", PROPERTY_HINT_RANGE, "0,16384,1,or_greater"), "set_instance_count", "get_instance_count"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_instance_count", PROPERTY_HINT_RANGE, "-1,16384,1,or_greater"), "set_visible_instance_count", "get_visible_instance_count"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "transform_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_transform_array", "_get_transform_array"); + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "transform_2d_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_transform_2d_array", "_get_transform_2d_array"); ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "color_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_color_array", "_get_color_array"); ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "custom_data_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_custom_data_array", "_get_custom_data_array"); @@ -286,6 +374,7 @@ MultiMesh::MultiMesh() { color_format = COLOR_NONE; custom_data_format = CUSTOM_DATA_NONE; transform_format = TRANSFORM_2D; + visible_instance_count = -1; instance_count = 0; } diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h index 4e70907399..6b17e9278d 100644 --- a/scene/resources/multimesh.h +++ b/scene/resources/multimesh.h @@ -64,6 +64,7 @@ private: ColorFormat color_format; CustomDataFormat custom_data_format; int instance_count; + int visible_instance_count; protected: static void _bind_methods(); @@ -71,6 +72,9 @@ protected: void _set_transform_array(const PoolVector<Vector3> &p_array); PoolVector<Vector3> _get_transform_array() const; + void _set_transform_2d_array(const PoolVector<Vector2> &p_array); + PoolVector<Vector2> _get_transform_2d_array() const; + void _set_color_array(const PoolVector<Color> &p_array); PoolVector<Color> _get_color_array() const; @@ -93,8 +97,13 @@ public: void set_instance_count(int p_count); int get_instance_count() const; + void set_visible_instance_count(int p_count); + int get_visible_instance_count() const; + void set_instance_transform(int p_instance, const Transform &p_transform); + void set_instance_transform_2d(int p_instance, const Transform2D &p_transform); Transform get_instance_transform(int p_instance) const; + Transform2D get_instance_transform_2d(int p_instance) const; void set_instance_color(int p_instance, const Color &p_color); Color get_instance_color(int p_instance) const; @@ -102,6 +111,8 @@ public: void set_instance_custom_data(int p_instance, const Color &p_custom_data); Color get_instance_custom_data(int p_instance) const; + void set_as_bulk_array(const PoolVector<float> &p_array); + virtual AABB get_aabb() const; virtual RID get_rid() const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index ea810edf8c..7d62873bbd 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -55,7 +55,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { Node *p_name; \ if (p_id & FLAG_ID_IS_PATH) { \ NodePath np = node_paths[p_id & FLAG_MASK]; \ - p_name = ret_nodes[0]->_get_node(np); \ + p_name = ret_nodes[0]->get_node_or_null(np); \ } else { \ ERR_FAIL_INDEX_V(p_id &FLAG_MASK, nc, NULL); \ p_name = ret_nodes[p_id & FLAG_MASK]; \ @@ -92,6 +92,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { if (i > 0) { + ERR_FAIL_COND_V_MSG(n.parent == -1, NULL, vformat("Invalid scene: node %s does not specify its parent node.", snames[n.name])); NODE_FROM_ID(nparent, n.parent); #ifdef DEBUG_ENABLED if (!nparent && (n.parent & FLAG_ID_IS_PATH)) { @@ -175,8 +176,8 @@ Node *SceneState::instance(GenEditState p_edit_state) const { node = Object::cast_to<Node>(obj); } else { - print_line("Class is disabled for: " + itos(n.type)); - print_line("name: " + String(snames[n.type])); + //print_line("Class is disabled for: " + itos(n.type)); + //print_line("name: " + String(snames[n.type])); } if (node) { @@ -236,8 +237,8 @@ Node *SceneState::instance(GenEditState p_edit_state) const { } else { //for instances, a copy must be made - Node *base = i == 0 ? node : ret_nodes[0]; - Ref<Resource> local_dupe = res->duplicate_for_local_scene(base, resources_local_to_scene); + Node *base2 = i == 0 ? node : ret_nodes[0]; + Ref<Resource> local_dupe = res->duplicate_for_local_scene(base2, resources_local_to_scene); resources_local_to_scene[res] = local_dupe; res = local_dupe; value = local_dupe; @@ -296,8 +297,8 @@ Node *SceneState::instance(GenEditState p_edit_state) const { ret_nodes[i] = node; if (node && gen_node_path_cache && ret_nodes[0]) { - NodePath n = ret_nodes[0]->get_path_to(node); - node_path_cache[n] = i; + NodePath n2 = ret_nodes[0]->get_path_to(node); + node_path_cache[n2] = i; } } @@ -342,7 +343,7 @@ Node *SceneState::instance(GenEditState p_edit_state) const { } for (int i = 0; i < editable_instances.size(); i++) { - Node *ei = ret_nodes[0]->_get_node(editable_instances[i]); + Node *ei = ret_nodes[0]->get_node_or_null(editable_instances[i]); if (ei) { ret_nodes[0]->set_editable_instance(ei, true); } @@ -533,7 +534,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map float a = value; float b = original; - if (Math::abs(a - b) < CMP_EPSILON) + if (Math::is_equal_approx(a, b)) continue; } else if (bool(Variant::evaluate(Variant::OP_EQUAL, value, original))) { @@ -749,7 +750,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName { Node *nl = p_node; - bool exists = false; + bool exists2 = false; while (nl) { @@ -763,7 +764,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName if (from_node >= 0 && to_node >= 0) { //this one has state for this node, save if (state->is_connection(from_node, c.signal, to_node, c.method)) { - exists = true; + exists2 = true; break; } } @@ -781,7 +782,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName if (from_node >= 0 && to_node >= 0) { //this one has state for this node, save if (state->is_connection(from_node, c.signal, to_node, c.method)) { - exists = true; + exists2 = true; break; } } @@ -791,7 +792,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName } } - if (exists) { + if (exists2) { continue; } } @@ -1091,10 +1092,7 @@ void SceneState::set_bundled_scene(const Dictionary &p_dictionary) { if (p_dictionary.has("version")) version = p_dictionary["version"]; - if (version > PACK_VERSION) { - ERR_EXPLAIN("Save format version too new!"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(version > PACK_VERSION, "Save format version too new."); PoolVector<String> snames = p_dictionary["names"]; if (snames.size()) { @@ -1688,10 +1686,7 @@ bool PackedScene::can_instance() const { Node *PackedScene::instance(GenEditState p_edit_state) const { #ifndef TOOLS_ENABLED - if (p_edit_state != GEN_EDIT_STATE_DISABLED) { - ERR_EXPLAIN("Edit state is only for editors, does not work without tools compiled"); - ERR_FAIL_COND_V(p_edit_state != GEN_EDIT_STATE_DISABLED, NULL); - } + ERR_FAIL_COND_V_MSG(p_edit_state != GEN_EDIT_STATE_DISABLED, NULL, "Edit state is only for editors, does not work without tools compiled."); #endif Node *s = state->instance((SceneState::GenEditState)p_edit_state); diff --git a/scene/resources/particles_material.cpp b/scene/resources/particles_material.cpp index 59fde7787e..dc6ef2b49c 100644 --- a/scene/resources/particles_material.cpp +++ b/scene/resources/particles_material.cpp @@ -45,6 +45,7 @@ void ParticlesMaterial::init_shaders() { shader_names = memnew(ShaderNames); + shader_names->direction = "direction"; shader_names->spread = "spread"; shader_names->flatness = "flatness"; shader_names->initial_linear_velocity = "initial_linear_velocity"; @@ -100,6 +101,8 @@ void ParticlesMaterial::init_shaders() { shader_names->trail_color_modifier = "trail_color_modifier"; shader_names->gravity = "gravity"; + + shader_names->lifetime_randomness = "lifetime_randomness"; } void ParticlesMaterial::finish_shaders() { @@ -144,6 +147,7 @@ void ParticlesMaterial::_update_shader() { String code = "shader_type particles;\n"; + code += "uniform vec3 direction;\n"; code += "uniform float spread;\n"; code += "uniform float flatness;\n"; code += "uniform float initial_linear_velocity;\n"; @@ -171,6 +175,7 @@ void ParticlesMaterial::_update_shader() { code += "uniform float hue_variation_random;\n"; code += "uniform float anim_speed_random;\n"; code += "uniform float anim_offset_random;\n"; + code += "uniform float lifetime_randomness;\n"; switch (emission_shape) { case EMISSION_SHAPE_POINT: { @@ -184,7 +189,8 @@ void ParticlesMaterial::_update_shader() { } break; case EMISSION_SHAPE_DIRECTED_POINTS: { code += "uniform sampler2D emission_texture_normal : hint_black;\n"; - } //fallthrough + FALLTHROUGH; + } case EMISSION_SHAPE_POINTS: { code += "uniform sampler2D emission_texture_points : hint_black;\n"; code += "uniform int emission_texture_point_count;\n"; @@ -282,7 +288,11 @@ void ParticlesMaterial::_update_shader() { code += " ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n"; code += " ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n"; } - code += " if (RESTART) {\n"; + code += " bool restart = false;\n"; + code += " if (CUSTOM.y > CUSTOM.w) {\n"; + code += " restart = true;\n"; + code += " }\n\n"; + code += " if (RESTART || restart) {\n"; if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n"; @@ -303,25 +313,26 @@ void ParticlesMaterial::_update_shader() { if (flags[FLAG_DISABLE_Z]) { - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " float angle1_rad = atan(direction.y, direction.x) + rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n"; code += " VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; } else { //initiate velocity spread in 3D - code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; - code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; - code += " vec3 direction_xz = vec3(sin(angle1_rad), 0, cos(angle1_rad));\n"; - code += " vec3 direction_yz = vec3(0, sin(angle2_rad), cos(angle2_rad));\n"; + code += " float angle1_rad = atan(direction.x, direction.z) + rand_from_seed_m1_p1(alt_seed) * spread_rad;\n"; + code += " float angle2_rad = atan(direction.y, abs(direction.z)) + rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n"; + code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n"; + code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n"; code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n"; - code += " vec3 direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; - code += " direction = normalize(direction);\n"; - code += " VELOCITY = direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; + code += " vec3 vec_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n"; + code += " vec_direction = normalize(vec_direction);\n"; + code += " VELOCITY = vec_direction * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random);\n"; } code += " float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random);\n"; code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle code += " CUSTOM.y = 0.0;\n"; // phase + code += " CUSTOM.w = LIFETIME * (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n"; code += " CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random);\n"; // animation offset (0-1) switch (emission_shape) { @@ -329,7 +340,10 @@ void ParticlesMaterial::_update_shader() { //do none } break; case EMISSION_SHAPE_SPHERE: { - code += " TRANSFORM[3].xyz = normalize(vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0)) * emission_sphere_radius;\n"; + code += " float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n"; + code += " float t = rand_from_seed(alt_seed) * 2.0 * pi;\n"; + code += " float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n"; + code += " TRANSFORM[3].xyz = vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s);\n"; } break; case EMISSION_SHAPE_BOX: { code += " TRANSFORM[3].xyz = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents;\n"; @@ -347,7 +361,7 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY.xy = rotm * VELOCITY.xy;\n"; } else { code += " vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n"; - code += " vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0, 1.0, 0.0);\n"; + code += " vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n"; code += " vec3 tangent = normalize(cross(v0, normal));\n"; code += " vec3 bitangent = normalize(cross(tangent, normal));\n"; code += " VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n"; @@ -570,6 +584,9 @@ void ParticlesMaterial::_update_shader() { code += " VELOCITY.z = 0.0;\n"; code += " TRANSFORM[3].z = 0.0;\n"; } + code += " if (CUSTOM.y > CUSTOM.w) {"; + code += " ACTIVE = false;\n"; + code += " }\n"; code += "}\n"; code += "\n"; @@ -626,6 +643,17 @@ bool ParticlesMaterial::_is_shader_dirty() const { return dirty; } +void ParticlesMaterial::set_direction(Vector3 p_direction) { + + direction = p_direction; + VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->direction, direction); +} + +Vector3 ParticlesMaterial::get_direction() const { + + return direction; +} + void ParticlesMaterial::set_spread(float p_spread) { spread = p_spread; @@ -997,6 +1025,17 @@ Vector3 ParticlesMaterial::get_gravity() const { return gravity; } +void ParticlesMaterial::set_lifetime_randomness(float p_lifetime) { + + lifetime_randomness = p_lifetime; + VisualServer::get_singleton()->material_set_param(_get_material(), shader_names->lifetime_randomness, lifetime_randomness); +} + +float ParticlesMaterial::get_lifetime_randomness() const { + + return lifetime_randomness; +} + RID ParticlesMaterial::get_shader_rid() const { ERR_FAIL_COND_V(!shader_map.has(current_key), RID()); @@ -1041,6 +1080,9 @@ Shader::Mode ParticlesMaterial::get_shader_mode() const { void ParticlesMaterial::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_direction", "degrees"), &ParticlesMaterial::set_direction); + ClassDB::bind_method(D_METHOD("get_direction"), &ParticlesMaterial::get_direction); + ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &ParticlesMaterial::set_spread); ClassDB::bind_method(D_METHOD("get_spread"), &ParticlesMaterial::get_spread); @@ -1098,6 +1140,11 @@ void ParticlesMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("get_gravity"), &ParticlesMaterial::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &ParticlesMaterial::set_gravity); + ClassDB::bind_method(D_METHOD("set_lifetime_randomness", "randomness"), &ParticlesMaterial::set_lifetime_randomness); + ClassDB::bind_method(D_METHOD("get_lifetime_randomness"), &ParticlesMaterial::get_lifetime_randomness); + + ADD_GROUP("Time", ""); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); ADD_GROUP("Trail", "trail_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_divisor", PROPERTY_HINT_RANGE, "1,1000000,1"), "set_trail_divisor", "get_trail_divisor"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trail_size_modifier", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_trail_size_modifier", "get_trail_size_modifier"); @@ -1114,7 +1161,8 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_align_y"), "set_flag", "get_flag", FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_rotate_y"), "set_flag", "get_flag", FLAG_ROTATE_Y); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "flag_disable_z"), "set_flag", "get_flag", FLAG_DISABLE_Z); - ADD_GROUP("Spread", ""); + ADD_GROUP("Direction", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "direction"), "set_direction", "get_direction"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "flatness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_flatness", "get_flatness"); ADD_GROUP("Gravity", ""); @@ -1186,6 +1234,7 @@ void ParticlesMaterial::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_ALIGN_Y_TO_VELOCITY); BIND_ENUM_CONSTANT(FLAG_ROTATE_Y); + BIND_ENUM_CONSTANT(FLAG_DISABLE_Z); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT); @@ -1198,9 +1247,11 @@ void ParticlesMaterial::_bind_methods() { ParticlesMaterial::ParticlesMaterial() : element(this) { + set_direction(Vector3(1, 0, 0)); set_spread(45); set_flatness(0); set_param(PARAM_INITIAL_LINEAR_VELOCITY, 0); + set_param(PARAM_ANGULAR_VELOCITY, 0); set_param(PARAM_ORBIT_VELOCITY, 0); set_param(PARAM_LINEAR_ACCEL, 0); set_param(PARAM_RADIAL_ACCEL, 0); @@ -1216,6 +1267,7 @@ ParticlesMaterial::ParticlesMaterial() : set_emission_box_extents(Vector3(1, 1, 1)); set_trail_divisor(1); set_gravity(Vector3(0, -9.8, 0)); + set_lifetime_randomness(0); emission_point_count = 1; for (int i = 0; i < PARAM_MAX; i++) { diff --git a/scene/resources/particles_material.h b/scene/resources/particles_material.h index 8b3248a9cb..6fe381db0a 100644 --- a/scene/resources/particles_material.h +++ b/scene/resources/particles_material.h @@ -36,7 +36,7 @@ class ParticlesMaterial : public Material { - GDCLASS(ParticlesMaterial, Material) + GDCLASS(ParticlesMaterial, Material); public: enum Parameter { @@ -129,6 +129,7 @@ private: static SelfList<ParticlesMaterial>::List *dirty_materials; struct ShaderNames { + StringName direction; StringName spread; StringName flatness; StringName initial_linear_velocity; @@ -184,6 +185,8 @@ private: StringName trail_color_modifier; StringName gravity; + + StringName lifetime_randomness; }; static ShaderNames *shader_names; @@ -194,6 +197,7 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; + Vector3 direction; float spread; float flatness; @@ -223,6 +227,8 @@ private: Vector3 gravity; + float lifetime_randomness; + //do not save emission points here protected: @@ -230,6 +236,9 @@ protected: virtual void _validate_property(PropertyInfo &property) const; public: + void set_direction(Vector3 p_direction); + Vector3 get_direction() const; + void set_spread(float p_spread); float get_spread() const; @@ -282,6 +291,9 @@ public: void set_gravity(const Vector3 &p_gravity); Vector3 get_gravity() const; + void set_lifetime_randomness(float p_lifetime); + float get_lifetime_randomness() const; + static void init_shaders(); static void finish_shaders(); static void flush_changes(); diff --git a/scene/resources/plane_shape.cpp b/scene/resources/plane_shape.cpp index 419ff8f972..08f6ccd764 100644 --- a/scene/resources/plane_shape.cpp +++ b/scene/resources/plane_shape.cpp @@ -64,6 +64,7 @@ Vector<Vector3> PlaneShape::_gen_debug_mesh_lines() { void PlaneShape::_update_shape() { PhysicsServer::get_singleton()->shape_set_data(get_shape(), plane); + Shape::_update_shape(); } void PlaneShape::set_plane(Plane p_plane) { diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp index a45a55a258..52fc21ac11 100644 --- a/scene/resources/polygon_path_finder.cpp +++ b/scene/resources/polygon_path_finder.cpp @@ -441,9 +441,9 @@ void PolygonPathFinder::_set_data(const Dictionary &p_data) { PoolVector<float> penalties = p_data["penalties"]; if (penalties.size() == pc) { - PoolVector<float>::Read pr = penalties.read(); + PoolVector<float>::Read pr2 = penalties.read(); for (int i = 0; i < pc; i++) { - points.write[i].penalty = pr[i]; + points.write[i].penalty = pr2[i]; } } } diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 9ef12aa4e6..24fdaafbe1 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -157,6 +157,12 @@ Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { return primitive_type; } +void PrimitiveMesh::surface_set_material(int p_idx, const Ref<Material> &p_material) { + ERR_FAIL_INDEX(p_idx, 1); + + set_material(p_material); +} + Ref<Material> PrimitiveMesh::surface_get_material(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, 1, NULL); @@ -376,17 +382,17 @@ void CapsuleMesh::_create_mesh_array(Array &p_arr) const { z = radius * cos(0.5 * Math_PI * v); for (i = 0; i <= radial_segments; i++) { - float u = i; - u /= radial_segments; + float u2 = i; + u2 /= radial_segments; - x = sin(u * (Math_PI * 2.0)); - y = -cos(u * (Math_PI * 2.0)); + x = sin(u2 * (Math_PI * 2.0)); + y = -cos(u2 * (Math_PI * 2.0)); Vector3 p = Vector3(x * radius * w, y * radius * w, z); points.push_back(p + Vector3(0.0, 0.0, -0.5 * mid_height)); normals.push_back(p.normalized()); ADD_TANGENT(-y, x, 0.0, 1.0) - uvs.push_back(Vector2(u, twothirds + ((v - 1.0) * onethird))); + uvs.push_back(Vector2(u2, twothirds + ((v - 1.0) * onethird))); point++; if (i > 0 && j > 0) { @@ -737,8 +743,6 @@ void CylinderMesh::_create_mesh_array(Array &p_arr) const { int i, j, prevrow, thisrow, point; float x, y, z, u, v, radius; - radius = bottom_radius > top_radius ? bottom_radius : top_radius; - PoolVector<Vector3> points; PoolVector<Vector3> normals; PoolVector<float> tangents; @@ -1568,3 +1572,19 @@ SphereMesh::SphereMesh() { rings = 32; is_hemisphere = false; } + +/** + PointMesh +*/ + +void PointMesh::_create_mesh_array(Array &p_arr) const { + PoolVector<Vector3> faces; + faces.resize(1); + faces.set(0, Vector3(0.0, 0.0, 0.0)); + + p_arr[VS::ARRAY_VERTEX] = faces; +} + +PointMesh::PointMesh() { + primitive_type = PRIMITIVE_POINTS; +} diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 88a26801b7..fad49f9642 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -72,6 +72,7 @@ public: virtual Array surface_get_blend_shape_arrays(int p_surface) const; virtual uint32_t surface_get_format(int p_idx) const; virtual Mesh::PrimitiveType surface_get_primitive_type(int p_idx) const; + virtual void surface_set_material(int p_idx, const Ref<Material> &p_material); virtual Ref<Material> surface_get_material(int p_idx) const; virtual int get_blend_shape_count() const; virtual StringName get_blend_shape_name(int p_index) const; @@ -268,7 +269,7 @@ public: class QuadMesh : public PrimitiveMesh { - GDCLASS(QuadMesh, PrimitiveMesh) + GDCLASS(QuadMesh, PrimitiveMesh); private: Size2 size; @@ -321,4 +322,19 @@ public: SphereMesh(); }; +/** + A single point for use in particle systems +*/ + +class PointMesh : public PrimitiveMesh { + + GDCLASS(PointMesh, PrimitiveMesh) + +protected: + virtual void _create_mesh_array(Array &p_arr) const; + +public: + PointMesh(); +}; + #endif diff --git a/scene/resources/ray_shape.cpp b/scene/resources/ray_shape.cpp index b7925d8a58..0acfffdc06 100644 --- a/scene/resources/ray_shape.cpp +++ b/scene/resources/ray_shape.cpp @@ -47,7 +47,7 @@ void RayShape::_update_shape() { d["length"] = length; d["slips_on_slope"] = slips_on_slope; PhysicsServer::get_singleton()->shape_set_data(get_shape(), d); - emit_changed(); + Shape::_update_shape(); } void RayShape::set_length(float p_length) { diff --git a/scene/resources/scene_format_text.cpp b/scene/resources/resource_format_text.cpp index 558810d92a..cd229732ba 100644 --- a/scene/resources/scene_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* scene_format_text.cpp */ +/* resource_format_text.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "scene_format_text.h" +#include "resource_format_text.h" + #include "core/io/resource_format_binary.h" #include "core/os/dir_access.h" #include "core/project_settings.h" @@ -444,6 +445,10 @@ Error ResourceInteractiveLoaderText::poll() { } else { resource_cache.push_back(res); +#ifdef TOOLS_ENABLED + //remember ID for saving + res->set_id_for_path(local_path, index); +#endif } ExtResource er; @@ -1220,10 +1225,7 @@ Ref<ResourceInteractiveLoader> ResourceFormatLoaderText::load_interactive(const Error err; FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); - if (err != OK) { - - ERR_FAIL_COND_V(err != OK, Ref<ResourceInteractiveLoader>()); - } + ERR_FAIL_COND_V(err != OK, Ref<ResourceInteractiveLoader>()); Ref<ResourceInteractiveLoaderText> ria = memnew(ResourceInteractiveLoaderText); String path = p_original_path != "" ? p_original_path : p_path; @@ -1319,13 +1321,10 @@ Error ResourceFormatLoaderText::convert_file_to_binary(const String &p_src_path, Error err; FileAccess *f = FileAccess::open(p_src_path, FileAccess::READ, &err); - if (err != OK) { - - ERR_FAIL_COND_V(err != OK, ERR_CANT_OPEN); - } + ERR_FAIL_COND_V(err != OK, ERR_CANT_OPEN); Ref<ResourceInteractiveLoaderText> ria = memnew(ResourceInteractiveLoaderText); - String path = p_src_path; + const String &path = p_src_path; ria->local_path = ProjectSettings::get_singleton()->localize_path(path); ria->res_path = ria->local_path; //ria->set_local_path( ProjectSettings::get_singleton()->localize_path(p_path) ); @@ -1354,24 +1353,23 @@ String ResourceFormatSaverTextInstance::_write_resource(const RES &res) { if (external_resources.has(res)) { - return "ExtResource( " + itos(external_resources[res] + 1) + " )"; + return "ExtResource( " + itos(external_resources[res]) + " )"; } else { if (internal_resources.has(res)) { return "SubResource( " + itos(internal_resources[res]) + " )"; } else if (res->get_path().length() && res->get_path().find("::") == -1) { - + if (res->get_path() == local_path) { //circular reference attempt + return "null"; + } //external resource String path = relative_paths ? local_path.path_to_file(res->get_path()) : res->get_path(); return "Resource( \"" + path + "\" )"; } else { - ERR_EXPLAIN("Resource was not pre cached for the resource section, bug?"); - ERR_FAIL_V("null"); + ERR_FAIL_V_MSG("null", "Resource was not pre cached for the resource section, bug?"); //internal resource } } - - return "null"; } void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, bool p_main) { @@ -1385,6 +1383,10 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, return; if (!p_main && (!bundle_resources) && res->get_path().length() && res->get_path().find("::") == -1) { + if (res->get_path() == local_path) { + ERR_PRINTS("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded."); + return; + } int index = external_resources.size(); external_resources[res] = index; return; @@ -1407,7 +1409,20 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, if (pi.usage & PROPERTY_USAGE_STORAGE) { Variant v = res->get(I->get().name); - _find_resources(v); + + if (pi.usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { + RES sres = v; + if (sres.is_valid()) { + NonPersistentKey npk; + npk.base = res; + npk.property = pi.name; + non_persistent_map[npk] = sres; + resource_set.insert(sres); + saved_resources.push_back(sres); + } + } else { + _find_resources(v); + } } I = I->next(); @@ -1423,7 +1438,7 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, int len = varray.size(); for (int i = 0; i < len; i++) { - Variant v = varray.get(i); + const Variant &v = varray.get(i); _find_resources(v); } @@ -1439,7 +1454,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, _find_resources(v); } } break; - default: {} + default: { + } } } @@ -1495,8 +1511,6 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } } - ERR_FAIL_COND_V(err != OK, err); - { String title = packed_scene.is_valid() ? "[gd_scene " : "[gd_resource "; if (packed_scene.is_null()) @@ -1518,18 +1532,58 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r f->store_line("]\n"); //one empty line } - Vector<RES> sorted_er; - sorted_er.resize(external_resources.size()); +#ifdef TOOLS_ENABLED + //keep order from cached ids + Set<int> cached_ids_found; + for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { + int cached_id = E->key()->get_id_for_path(local_path); + if (cached_id < 0 || cached_ids_found.has(cached_id)) { + E->get() = -1; //reset + } else { + E->get() = cached_id; + cached_ids_found.insert(cached_id); + } + } + //create IDs for non cached resources + for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { + if (cached_ids_found.has(E->get())) { //already cached, go on + continue; + } + + int attempt = 1; //start from one, more readable format + while (cached_ids_found.has(attempt)) { + attempt++; + } + cached_ids_found.insert(attempt); + E->get() = attempt; + //update also in resource + Ref<Resource> res = E->key(); + res->set_id_for_path(local_path, attempt); + } +#else + //make sure to start from one, as it makes format more readable for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { + E->get() = E->get() + 1; + } +#endif + + Vector<ResourceSort> sorted_er; - sorted_er.write[E->get()] = E->key(); + for (Map<RES, int>::Element *E = external_resources.front(); E; E = E->next()) { + + ResourceSort rs; + rs.resource = E->key(); + rs.index = E->get(); + sorted_er.push_back(rs); } + sorted_er.sort(); + for (int i = 0; i < sorted_er.size(); i++) { - String p = sorted_er[i]->get_path(); + String p = sorted_er[i].resource->get_path(); - f->store_string("[ext_resource path=\"" + p + "\" type=\"" + sorted_er[i]->get_save_class() + "\" id=" + itos(i + 1) + "]\n"); //bundled + f->store_string("[ext_resource path=\"" + p + "\" type=\"" + sorted_er[i].resource->get_save_class() + "\" id=" + itos(sorted_er[i].index) + "]\n"); //bundled } if (external_resources.size()) @@ -1562,7 +1616,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r break; //save as a scene if (main) { - f->store_line("[resource]\n"); + f->store_line("[resource]"); } else { String line = "[sub_resource "; if (res->get_subindex() == 0) { @@ -1577,7 +1631,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r int idx = res->get_subindex(); line += "type=\"" + res->get_class() + "\" id=" + itos(idx); - f->store_line(line + "]\n"); + f->store_line(line + "]"); if (takeover_paths) { res->set_path(p_path + "::" + itos(idx), true); } @@ -1599,7 +1653,17 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r if (PE->get().usage & PROPERTY_USAGE_STORAGE) { String name = PE->get().name; - Variant value = res->get(name); + Variant value; + if (PE->get().usage & PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT) { + NonPersistentKey npk; + npk.base = res; + npk.property = name; + if (non_persistent_map.has(npk)) { + value = non_persistent_map[npk]; + } + } else { + value = res->get(name); + } Variant default_value = ClassDB::class_get_default_property_value(res->get_class(), name); if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { @@ -1615,7 +1679,8 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r } } - f->store_string("\n"); + if (E->next()) + f->store_line(String()); } if (packed_scene.is_valid()) { @@ -1633,15 +1698,15 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r Vector<StringName> groups = state->get_node_groups(i); String header = "[node"; - header += " name=\"" + String(name) + "\""; + header += " name=\"" + String(name).c_escape() + "\""; if (type != StringName()) { header += " type=\"" + String(type) + "\""; } if (path != NodePath()) { - header += " parent=\"" + String(path.simplified()) + "\""; + header += " parent=\"" + String(path.simplified()).c_escape() + "\""; } if (owner != NodePath() && owner != NodePath(".")) { - header += " owner=\"" + String(owner.simplified()) + "\""; + header += " owner=\"" + String(owner.simplified()).c_escape() + "\""; } if (index >= 0) { header += " index=\"" + itos(index) + "\""; @@ -1684,7 +1749,8 @@ 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"); } - f->store_line(String()); + if (i < state->get_node_count() - 1) + f->store_line(String()); } for (int i = 0; i < state->get_connection_count(); i++) { diff --git a/scene/resources/scene_format_text.h b/scene/resources/resource_format_text.h index 8fedbd0dd6..0041989f09 100644 --- a/scene/resources/scene_format_text.h +++ b/scene/resources/resource_format_text.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* scene_format_text.h */ +/* resource_format_text.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SCENE_FORMAT_TEXT_H -#define SCENE_FORMAT_TEXT_H +#ifndef RESOURCE_FORMAT_TEXT_H +#define RESOURCE_FORMAT_TEXT_H #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" @@ -128,7 +128,6 @@ public: }; class ResourceFormatLoaderText : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderText, ResourceFormatLoader) public: static ResourceFormatLoaderText *singleton; virtual Ref<ResourceInteractiveLoader> load_interactive(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); @@ -155,11 +154,28 @@ class ResourceFormatSaverTextInstance { bool bundle_resources; bool skip_editor; FileAccess *f; + + struct NonPersistentKey { //for resource properties generated on the fly + RES base; + StringName property; + bool operator<(const NonPersistentKey &p_key) const { return base == p_key.base ? property < p_key.property : base < p_key.base; } + }; + + Map<NonPersistentKey, RES> non_persistent_map; + Set<RES> resource_set; List<RES> saved_resources; Map<RES, int> external_resources; Map<RES, int> internal_resources; + struct ResourceSort { + RES resource; + int index; + bool operator<(const ResourceSort &p_right) const { + return index < p_right.index; + } + }; + void _find_resources(const Variant &p_variant, bool p_main = false); static String _write_resources(void *ud, const RES &p_resource); @@ -170,7 +186,6 @@ public: }; class ResourceFormatSaverText : public ResourceFormatSaver { - GDCLASS(ResourceFormatSaverText, ResourceFormatSaver) public: static ResourceFormatSaverText *singleton; virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); @@ -180,4 +195,4 @@ public: ResourceFormatSaverText(); }; -#endif // SCENE_FORMAT_TEXT_H +#endif // RESOURCE_FORMAT_TEXT_H diff --git a/scene/resources/room.h b/scene/resources/room.h index 8990056f46..2c53ea1aed 100644 --- a/scene/resources/room.h +++ b/scene/resources/room.h @@ -33,9 +33,6 @@ #include "core/math/bsp_tree.h" #include "core/resource.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ // FIXME: left for reference but will be removed when portals are reimplemented using Area #if 0 diff --git a/scene/resources/shader.h b/scene/resources/shader.h index fb493046fc..6e7447839f 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -100,7 +100,6 @@ public: VARIANT_ENUM_CAST(Shader::Mode); class ResourceFormatLoaderShader : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderShader, ResourceFormatLoader) public: virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List<String> *p_extensions) const; @@ -109,7 +108,6 @@ public: }; class ResourceFormatSaverShader : public ResourceFormatSaver { - GDCLASS(ResourceFormatSaverShader, ResourceFormatSaver) public: virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const; diff --git a/scene/resources/shape.cpp b/scene/resources/shape.cpp index 825b7f4c8b..6ba46f066c 100644 --- a/scene/resources/shape.cpp +++ b/scene/resources/shape.cpp @@ -96,6 +96,11 @@ Ref<ArrayMesh> Shape::get_debug_mesh() { return debug_mesh_cache; } +void Shape::_update_shape() { + emit_changed(); + debug_mesh_cache.unref(); +} + void Shape::_bind_methods() { ClassDB::bind_method(D_METHOD("set_margin", "margin"), &Shape::set_margin); diff --git a/scene/resources/shape.h b/scene/resources/shape.h index de99a967a8..ba763eaab1 100644 --- a/scene/resources/shape.h +++ b/scene/resources/shape.h @@ -51,6 +51,8 @@ protected: Shape(RID p_shape); virtual Vector<Vector3> _gen_debug_mesh_lines() = 0; // { return Vector<Vector3>(); } + virtual void _update_shape(); + public: virtual RID get_rid() const { return shape; } diff --git a/scene/resources/sky_box.cpp b/scene/resources/sky.cpp index d9da85310e..292fdcdbd2 100644 --- a/scene/resources/sky_box.cpp +++ b/scene/resources/sky.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* sky_box.cpp */ +/* sky.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "sky_box.h" +#include "sky.h" + #include "core/io/image_loader.h" void Sky::set_radiance_size(RadianceSize p_size) { @@ -61,7 +62,7 @@ void Sky::_bind_methods() { } Sky::Sky() { - radiance_size = RADIANCE_SIZE_512; + radiance_size = RADIANCE_SIZE_128; } ///////////////////////////////////////// @@ -155,7 +156,10 @@ Ref<Image> ProceduralSky::_generate_sky() { Color ground_bottom_linear = ground_bottom_color.to_linear(); Color ground_horizon_linear = ground_horizon_color.to_linear(); - //Color sun_linear = sun_color.to_linear(); + Color sun_linear; + sun_linear.r = sun_color.r * sun_energy; + sun_linear.g = sun_color.g * sun_energy; + sun_linear.b = sun_color.b * sun_energy; Vector3 sun(0, 0, -1); @@ -203,13 +207,13 @@ Ref<Image> ProceduralSky::_generate_sky() { float sun_angle = Math::rad2deg(Math::acos(CLAMP(sun.dot(normal), -1.0, 1.0))); if (sun_angle < sun_angle_min) { - color = color.blend(sun_color); + color = color.blend(sun_linear); } else if (sun_angle < sun_angle_max) { float c2 = (sun_angle - sun_angle_min) / (sun_angle_max - sun_angle_min); c2 = Math::ease(c2, sun_curve); - color = color.blend(sun_color).linear_interpolate(color, c2); + color = color.blend(sun_linear).linear_interpolate(color, c2); } } @@ -532,7 +536,7 @@ void ProceduralSky::_bind_methods() { BIND_ENUM_CONSTANT(TEXTURE_SIZE_MAX); } -ProceduralSky::ProceduralSky() { +ProceduralSky::ProceduralSky(bool p_desaturate) { sky = VS::get_singleton()->sky_create(); texture = VS::get_singleton()->texture_create(); @@ -548,13 +552,19 @@ ProceduralSky::ProceduralSky() { ground_curve = 0.02; ground_energy = 1; + if (p_desaturate) { + sky_top_color.set_hsv(sky_top_color.get_h(), 0, sky_top_color.get_v()); + sky_horizon_color.set_hsv(sky_horizon_color.get_h(), 0, sky_horizon_color.get_v()); + ground_bottom_color.set_hsv(ground_bottom_color.get_h(), 0, ground_bottom_color.get_v()); + ground_horizon_color.set_hsv(ground_horizon_color.get_h(), 0, ground_horizon_color.get_v()); + } sun_color = Color(1, 1, 1); sun_latitude = 35; sun_longitude = 0; sun_angle_min = 1; sun_angle_max = 100; sun_curve = 0.05; - sun_energy = 16; + sun_energy = 1; texture_size = TEXTURE_SIZE_1024; sky_thread = NULL; diff --git a/scene/resources/sky_box.h b/scene/resources/sky.h index af10803b36..3a6f44e11b 100644 --- a/scene/resources/sky_box.h +++ b/scene/resources/sky.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* sky_box.h */ +/* sky.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,11 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SKY_BOX_H -#define SKY_BOX_H +#ifndef SKY_H +#define SKY_H #include "core/os/thread.h" #include "scene/resources/texture.h" + class Sky : public Resource { GDCLASS(Sky, Resource); @@ -190,10 +191,10 @@ public: virtual RID get_rid() const; - ProceduralSky(); + ProceduralSky(bool p_desaturate = false); ~ProceduralSky(); }; VARIANT_ENUM_CAST(ProceduralSky::TextureSize) -#endif // SKY_BOX_H +#endif // SKY_H diff --git a/scene/resources/sphere_shape.cpp b/scene/resources/sphere_shape.cpp index 492cf3959d..af89413ced 100644 --- a/scene/resources/sphere_shape.cpp +++ b/scene/resources/sphere_shape.cpp @@ -58,6 +58,7 @@ Vector<Vector3> SphereShape::_gen_debug_mesh_lines() { void SphereShape::_update_shape() { PhysicsServer::get_singleton()->shape_set_data(get_shape(), radius); + Shape::_update_shape(); } void SphereShape::set_radius(float p_radius) { diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index affe9a3e24..5dd429fa75 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -368,25 +368,14 @@ Color StyleBoxFlat::get_bg_color() const { return bg_color; } -void StyleBoxFlat::set_border_color_all(const Color &p_color) { - for (int i = 0; i < 4; i++) { +void StyleBoxFlat::set_border_color(const Color &p_color) { - border_color.write()[i] = p_color; - } + border_color = p_color; emit_changed(); } -Color StyleBoxFlat::get_border_color_all() const { +Color StyleBoxFlat::get_border_color() const { - return border_color[MARGIN_TOP]; -} -void StyleBoxFlat::set_border_color(Margin p_border, const Color &p_color) { - - border_color.write()[p_border] = p_color; - emit_changed(); -} -Color StyleBoxFlat::get_border_color(Margin p_border) const { - - return border_color[p_border]; + return border_color; } void StyleBoxFlat::set_border_width_all(int p_size) { @@ -511,6 +500,16 @@ int StyleBoxFlat::get_shadow_size() const { return shadow_size; } +void StyleBoxFlat::set_shadow_offset(const Point2 &p_offset) { + + shadow_offset = p_offset; + emit_changed(); +} +Point2 StyleBoxFlat::get_shadow_offset() const { + + return shadow_offset; +} + void StyleBoxFlat::set_anti_aliased(const bool &p_anti_aliased) { anti_aliased = p_anti_aliased; emit_changed(); @@ -552,7 +551,7 @@ inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_re inner_corner_radius[0] = MAX(corner_radius[0] - rad, 0); //tr - rad = MIN(border_top, border_bottom); + rad = MIN(border_top, border_right); inner_corner_radius[1] = MAX(corner_radius[1] - rad, 0); //br @@ -565,12 +564,13 @@ inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_re } inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 style_rect, const int corner_radius[4], - const Rect2 ring_rect, const int border_width[4], const Color inner_color[4], const Color outer_color[4], const int corner_detail) { + const Rect2 ring_rect, const int border_width[4], const Color &inner_color, const Color &outer_color, const int corner_detail, const bool fill_center = false) { int vert_offset = verts.size(); if (!vert_offset) { vert_offset = 0; } + int adapted_corner_detail = (corner_radius[0] == 0 && corner_radius[1] == 0 && corner_radius[2] == 0 && corner_radius[3] == 0) ? 1 : corner_detail; int ring_corner_radius[4]; @@ -585,10 +585,11 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color Rect2 inner_rect; inner_rect = ring_rect.grow_individual(-border_width[MARGIN_LEFT], -border_width[MARGIN_TOP], -border_width[MARGIN_RIGHT], -border_width[MARGIN_BOTTOM]); + int inner_corner_radius[4]; + set_inner_corner_radius(style_rect, inner_rect, corner_radius, inner_corner_radius); Vector<Point2> inner_points; - set_inner_corner_radius(style_rect, inner_rect, corner_radius, inner_corner_radius); inner_points.push_back(inner_rect.position + Vector2(inner_corner_radius[0], inner_corner_radius[0])); //tl inner_points.push_back(Point2(inner_rect.position.x + inner_rect.size.x - inner_corner_radius[1], inner_rect.position.y + inner_corner_radius[1])); //tr inner_points.push_back(inner_rect.position + inner_rect.size - Vector2(inner_corner_radius[2], inner_corner_radius[2])); //br @@ -603,11 +604,11 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color Point2 corner_point; if (inner_outer == 0) { radius = inner_corner_radius[corner_index]; - color = *inner_color; + color = inner_color; corner_point = inner_points[corner_index]; } else { radius = ring_corner_radius[corner_index]; - color = *outer_color; + color = outer_color; corner_point = outer_points[corner_index]; } float x = radius * (float)cos((double)corner_index * Math_PI / 2.0 + (double)detail / (double)adapted_corner_detail * Math_PI / 2.0 + Math_PI) + corner_point.x; @@ -618,17 +619,28 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color } } - int vert_count = (adapted_corner_detail + 1) * 4 * 2; + int ring_vert_count = verts.size() - vert_offset; + //fill the indices and the colors for the border - for (int i = 0; i < vert_count; i++) { - //poly 1 - indices.push_back(vert_offset + ((i + 0) % vert_count)); - indices.push_back(vert_offset + ((i + 2) % vert_count)); - indices.push_back(vert_offset + ((i + 1) % vert_count)); - //poly 2 - indices.push_back(vert_offset + ((i + 1) % vert_count)); - indices.push_back(vert_offset + ((i + 2) % vert_count)); - indices.push_back(vert_offset + ((i + 3) % vert_count)); + for (int i = 0; i < ring_vert_count; i++) { + indices.push_back(vert_offset + ((i + 0) % ring_vert_count)); + indices.push_back(vert_offset + ((i + 2) % ring_vert_count)); + indices.push_back(vert_offset + ((i + 1) % ring_vert_count)); + } + + if (fill_center) { + //fill the indices and the colors for the center + for (int index = 0; index < ring_vert_count / 2; index += 2) { + int i = index; + //poly 1 + indices.push_back(vert_offset + i); + indices.push_back(vert_offset + ring_vert_count - 4 - i); + indices.push_back(vert_offset + i + 2); + //poly 2 + indices.push_back(vert_offset + i); + indices.push_back(vert_offset + ring_vert_count - 2 - i); + indices.push_back(vert_offset + ring_vert_count - 4 - i); + } } } @@ -657,13 +669,29 @@ inline void adapt_values(int p_index_a, int p_index_b, int *adapted_values, cons void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { //PREPARATIONS + bool draw_border = (border_width[0] > 0) || (border_width[1] > 0) || (border_width[2] > 0) || (border_width[3] > 0); + bool draw_shadow = (shadow_size > 0); + if (!draw_border && !draw_center && !draw_shadow) { + return; + } bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); bool aa_on = rounded_corners && anti_aliased; + Color border_color_alpha = Color(border_color.r, border_color.g, border_color.b, 0); + + bool blend_on = blend_border && draw_border; + Rect2 style_rect = p_rect.grow_individual(expand_margin[MARGIN_LEFT], expand_margin[MARGIN_TOP], expand_margin[MARGIN_RIGHT], expand_margin[MARGIN_BOTTOM]); - if (aa_on) { - style_rect = style_rect.grow(-((aa_size + 1) / 2)); + Rect2 border_style_rect = style_rect; + if (aa_on && !blend_on) { + float aa_size_grow = 0.5 * ((aa_size + 1) / 2); + style_rect = style_rect.grow(-aa_size_grow); + for (int i = 0; i < 4; i++) { + if (border_width[i] > 0) { + border_style_rect = border_style_rect.grow_margin((Margin)i, -aa_size_grow); + } + } } //adapt borders (prevent weird overlapping/glitchy drawings) @@ -686,79 +714,93 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Vector<int> indices; Vector<Color> colors; - //DRAWING - VisualServer *vs = VisualServer::get_singleton(); - //DRAW SHADOW - if (shadow_size > 0) { + if (draw_shadow) { int shadow_width[4] = { shadow_size, shadow_size, shadow_size, shadow_size }; - Color shadow_colors[4] = { shadow_color, shadow_color, shadow_color, shadow_color }; - Color shadow_colors_transparent[4]; - for (int i = 0; i < 4; i++) { - shadow_colors_transparent[i] = Color(shadow_color.r, shadow_color.g, shadow_color.b, 0); + + Rect2 shadow_inner_rect = style_rect; + shadow_inner_rect.position += shadow_offset; + + Rect2 shadow_rect = style_rect.grow(shadow_size); + shadow_rect.position += shadow_offset; + + Color shadow_color_transparent = Color(shadow_color.r, shadow_color.g, shadow_color.b, 0); + + draw_ring(verts, indices, colors, shadow_inner_rect, adapted_corner, + shadow_rect, shadow_width, shadow_color, shadow_color_transparent, corner_detail); + + if (draw_center) { + int no_border[4] = { 0, 0, 0, 0 }; + draw_ring(verts, indices, colors, shadow_inner_rect, adapted_corner, + shadow_inner_rect, no_border, shadow_color, shadow_color, corner_detail, true); } - draw_ring(verts, indices, colors, style_rect, adapted_corner, - style_rect.grow(shadow_size), shadow_width, shadow_colors, shadow_colors_transparent, corner_detail); } //DRAW border - Color bg_color_array[4] = { bg_color, bg_color, bg_color, bg_color }; - const Color *inner_color = ((blend_border) ? bg_color_array : border_color.read().ptr()); - draw_ring(verts, indices, colors, style_rect, adapted_corner, - style_rect, adapted_border, inner_color, border_color.read().ptr(), corner_detail); + if (draw_border) { + draw_ring(verts, indices, colors, border_style_rect, adapted_corner, + border_style_rect, adapted_border, blend_on ? (draw_center ? bg_color : border_color_alpha) : border_color, border_color, corner_detail); + } //DRAW INFILL if (draw_center) { - int temp_vert_offset = verts.size(); int no_border[4] = { 0, 0, 0, 0 }; draw_ring(verts, indices, colors, style_rect, adapted_corner, - infill_rect, no_border, &bg_color, &bg_color, corner_detail); - int added_vert_count = verts.size() - temp_vert_offset; - //fill the indices and the colors for the center - for (int index = 0; index <= added_vert_count / 2; index += 2) { - int i = index; - //poly 1 - indices.push_back(temp_vert_offset + i); - indices.push_back(temp_vert_offset + added_vert_count - 4 - i); - indices.push_back(temp_vert_offset + i + 2); - //poly 1 - indices.push_back(temp_vert_offset + i); - indices.push_back(temp_vert_offset + added_vert_count - 2 - i); - indices.push_back(temp_vert_offset + added_vert_count - 4 - i); - } + infill_rect, no_border, bg_color, bg_color, corner_detail, true); } if (aa_on) { - - //HELPER ARRAYS - Color border_color_alpha[4]; - for (int i = 0; i < 4; i++) { - Color c = border_color.read().ptr()[i]; - border_color_alpha[i] = Color(c.r, c.g, c.b, 0); + Rect2 border_inner_rect = infill_rect; + int aa_border_width[4]; + int aa_fill_width[4]; + if (draw_border) { + border_inner_rect = border_style_rect.grow_individual(-adapted_border[MARGIN_LEFT], -adapted_border[MARGIN_TOP], -adapted_border[MARGIN_RIGHT], -adapted_border[MARGIN_BOTTOM]); + for (int i = 0; i < 4; i++) { + if (border_width[i] > 0) { + aa_border_width[i] = aa_size; + aa_fill_width[i] = 0; + } else { + aa_border_width[i] = 0; + aa_fill_width[i] = aa_size; + } + } + } else { + for (int i = 0; i < 4; i++) { + aa_fill_width[i] = aa_size; + } } - Color alpha_bg = Color(bg_color.r, bg_color.g, bg_color.b, 0); - Color bg_color_array_alpha[4] = { alpha_bg, alpha_bg, alpha_bg, alpha_bg }; - - int aa_border_width[4] = { aa_size, aa_size, aa_size, aa_size }; if (draw_center) { - if (!blend_border) { + if (!draw_border || !blend_on) { + Rect2 aa_rect = infill_rect.grow_individual(aa_fill_width[MARGIN_LEFT], aa_fill_width[MARGIN_TOP], + aa_fill_width[MARGIN_RIGHT], aa_fill_width[MARGIN_BOTTOM]); + + Color alpha_bg = Color(bg_color.r, bg_color.g, bg_color.b, 0); + //INFILL AA draw_ring(verts, indices, colors, style_rect, adapted_corner, - infill_rect.grow(aa_size), aa_border_width, bg_color_array, bg_color_array_alpha, corner_detail); + aa_rect, aa_fill_width, bg_color, alpha_bg, corner_detail); } - } else if (!(border_width[0] == 0 && border_width[1] == 0 && border_width[2] == 0 && border_width[3] == 0)) { - //DRAW INNER BORDER AA - draw_ring(verts, indices, colors, style_rect, adapted_corner, - infill_rect, aa_border_width, border_color_alpha, border_color.read().ptr(), corner_detail); } - //DRAW OUTER BORDER AA - if (!(border_width[0] == 0 && border_width[1] == 0 && border_width[2] == 0 && border_width[3] == 0)) { - draw_ring(verts, indices, colors, style_rect, adapted_corner, - style_rect.grow(aa_size), aa_border_width, border_color.read().ptr(), border_color_alpha, corner_detail); + + if (draw_border) { + if (!blend_on) { + //DRAW INNER BORDER AA + draw_ring(verts, indices, colors, border_style_rect, adapted_corner, + border_inner_rect, aa_border_width, border_color_alpha, border_color, corner_detail); + } + + Rect2 aa_rect = border_style_rect.grow_individual(aa_border_width[MARGIN_LEFT], aa_border_width[MARGIN_TOP], + aa_border_width[MARGIN_RIGHT], aa_border_width[MARGIN_BOTTOM]); + + //DRAW OUTER BORDER AA + draw_ring(verts, indices, colors, border_style_rect, adapted_corner, + aa_rect, aa_border_width, border_color, border_color_alpha, corner_detail); } } + //DRAWING + VisualServer *vs = VisualServer::get_singleton(); vs->canvas_item_add_triangle_array(p_canvas_item, indices, verts, colors); } @@ -770,8 +812,8 @@ void StyleBoxFlat::_bind_methods() { ClassDB::bind_method(D_METHOD("set_bg_color", "color"), &StyleBoxFlat::set_bg_color); ClassDB::bind_method(D_METHOD("get_bg_color"), &StyleBoxFlat::get_bg_color); - ClassDB::bind_method(D_METHOD("set_border_color", "color"), &StyleBoxFlat::set_border_color_all); - ClassDB::bind_method(D_METHOD("get_border_color"), &StyleBoxFlat::get_border_color_all); + ClassDB::bind_method(D_METHOD("set_border_color", "color"), &StyleBoxFlat::set_border_color); + ClassDB::bind_method(D_METHOD("get_border_color"), &StyleBoxFlat::get_border_color); ClassDB::bind_method(D_METHOD("set_border_width_all", "width"), &StyleBoxFlat::set_border_width_all); ClassDB::bind_method(D_METHOD("get_border_width_min"), &StyleBoxFlat::get_border_width_min); @@ -802,6 +844,9 @@ void StyleBoxFlat::_bind_methods() { ClassDB::bind_method(D_METHOD("set_shadow_size", "size"), &StyleBoxFlat::set_shadow_size); ClassDB::bind_method(D_METHOD("get_shadow_size"), &StyleBoxFlat::get_shadow_size); + ClassDB::bind_method(D_METHOD("set_shadow_offset", "offset"), &StyleBoxFlat::set_shadow_offset); + ClassDB::bind_method(D_METHOD("get_shadow_offset"), &StyleBoxFlat::get_shadow_offset); + ClassDB::bind_method(D_METHOD("set_anti_aliased", "anti_aliased"), &StyleBoxFlat::set_anti_aliased); ClassDB::bind_method(D_METHOD("is_anti_aliased"), &StyleBoxFlat::is_anti_aliased); @@ -843,6 +888,7 @@ void StyleBoxFlat::_bind_methods() { ADD_GROUP("Shadow", "shadow_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color"); ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_size"), "set_shadow_size", "get_shadow_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "shadow_offset"), "set_shadow_offset", "get_shadow_offset"); ADD_GROUP("Anti Aliasing", "anti_aliasing_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "anti_aliasing"), "set_anti_aliased", "is_anti_aliased"); @@ -853,17 +899,14 @@ StyleBoxFlat::StyleBoxFlat() { bg_color = Color(0.6, 0.6, 0.6); shadow_color = Color(0, 0, 0, 0.6); - - border_color.append(Color(0.8, 0.8, 0.8)); - border_color.append(Color(0.8, 0.8, 0.8)); - border_color.append(Color(0.8, 0.8, 0.8)); - border_color.append(Color(0.8, 0.8, 0.8)); + border_color = Color(0.8, 0.8, 0.8); blend_border = false; draw_center = true; anti_aliased = true; shadow_size = 0; + shadow_offset = Point2(0, 0); corner_detail = 8; aa_size = 1; @@ -903,6 +946,7 @@ int StyleBoxLine::get_thickness() const { void StyleBoxLine::set_vertical(bool p_vertical) { vertical = p_vertical; + emit_changed(); } bool StyleBoxLine::is_vertical() const { return vertical; diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index 9062270765..d02e107480 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -34,9 +34,7 @@ #include "core/resource.h" #include "scene/resources/texture.h" #include "servers/visual_server.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class CanvasItem; class StyleBox : public Resource { @@ -149,7 +147,7 @@ class StyleBoxFlat : public StyleBox { Color bg_color; Color shadow_color; - PoolVector<Color> border_color; + Color border_color; int border_width[4]; int expand_margin[4]; @@ -161,6 +159,7 @@ class StyleBoxFlat : public StyleBox { int corner_detail; int shadow_size; + Point2 shadow_offset; int aa_size; protected: @@ -173,10 +172,8 @@ public: Color get_bg_color() const; //Border Color - void set_border_color_all(const Color &p_color); - Color get_border_color_all() const; - void set_border_color(Margin p_border, const Color &p_color); - Color get_border_color(Margin p_border) const; + void set_border_color(const Color &p_color); + Color get_border_color() const; //BORDER //width @@ -218,6 +215,9 @@ public: void set_shadow_size(const int &p_size); int get_shadow_size() const; + void set_shadow_offset(const Point2 &p_offset); + Point2 get_shadow_offset() const; + //ANTI_ALIASING void set_anti_aliased(const bool &p_anti_aliased); bool is_anti_aliased() const; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 2116dd0b1e..b294991248 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -108,8 +108,55 @@ void SurfaceTool::add_vertex(const Vector3 &p_vertex) { vtx.bones = last_bones; vtx.tangent = last_tangent.normal; vtx.binormal = last_normal.cross(last_tangent.normal).normalized() * last_tangent.d; + + const int expected_vertices = 4; + + if ((format & Mesh::ARRAY_FORMAT_WEIGHTS || format & Mesh::ARRAY_FORMAT_BONES) && (vtx.weights.size() != expected_vertices || vtx.bones.size() != expected_vertices)) { + //ensure vertices are the expected amount + ERR_FAIL_COND(vtx.weights.size() != vtx.bones.size()); + if (vtx.weights.size() < expected_vertices) { + //less than required, fill + for (int i = vtx.weights.size(); i < expected_vertices; i++) { + vtx.weights.push_back(0); + vtx.bones.push_back(0); + } + } else if (vtx.weights.size() > expected_vertices) { + //more than required, sort, cap and normalize. + Vector<WeightSort> weights; + for (int i = 0; i < vtx.weights.size(); i++) { + WeightSort ws; + ws.index = vtx.bones[i]; + ws.weight = vtx.weights[i]; + weights.push_back(ws); + } + + //sort + weights.sort(); + //cap + weights.resize(expected_vertices); + //renormalize + float total = 0; + for (int i = 0; i < expected_vertices; i++) { + total += weights[i].weight; + } + + vtx.weights.resize(expected_vertices); + vtx.bones.resize(expected_vertices); + + for (int i = 0; i < expected_vertices; i++) { + if (total > 0) { + vtx.weights.write[i] = weights[i].weight / total; + } else { + vtx.weights.write[i] = 0; + } + vtx.bones.write[i] = weights[i].index; + } + } + } + vertex_array.push_back(vtx); first = false; + format |= Mesh::ARRAY_FORMAT_VERTEX; } void SurfaceTool::add_color(Color p_color) { @@ -161,7 +208,6 @@ void SurfaceTool::add_uv2(const Vector2 &p_uv2) { void SurfaceTool::add_bones(const Vector<int> &p_bones) { ERR_FAIL_COND(!begun); - ERR_FAIL_COND(p_bones.size() != 4); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_BONES)); format |= Mesh::ARRAY_FORMAT_BONES; @@ -171,8 +217,6 @@ void SurfaceTool::add_bones(const Vector<int> &p_bones) { void SurfaceTool::add_weights(const Vector<float> &p_weights) { ERR_FAIL_COND(!begun); - - ERR_FAIL_COND(p_weights.size() != 4); ERR_FAIL_COND(!first && !(format & Mesh::ARRAY_FORMAT_WEIGHTS)); format |= Mesh::ARRAY_FORMAT_WEIGHTS; @@ -190,10 +234,10 @@ void SurfaceTool::add_smooth_group(bool p_smooth) { } } -void SurfaceTool::add_triangle_fan(const Vector<Vector3> &p_vertexes, const Vector<Vector2> &p_uvs, const Vector<Color> &p_colors, const Vector<Vector2> &p_uv2s, const Vector<Vector3> &p_normals, const Vector<Plane> &p_tangents) { +void SurfaceTool::add_triangle_fan(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs, const Vector<Color> &p_colors, const Vector<Vector2> &p_uv2s, const Vector<Vector3> &p_normals, const Vector<Plane> &p_tangents) { ERR_FAIL_COND(!begun); ERR_FAIL_COND(primitive != Mesh::PRIMITIVE_TRIANGLES); - ERR_FAIL_COND(p_vertexes.size() < 3); + ERR_FAIL_COND(p_vertices.size() < 3); #define ADD_POINT(n) \ { \ @@ -207,10 +251,10 @@ void SurfaceTool::add_triangle_fan(const Vector<Vector3> &p_vertexes, const Vect add_normal(p_normals[n]); \ if (p_tangents.size() > n) \ add_tangent(p_tangents[n]); \ - add_vertex(p_vertexes[n]); \ + add_vertex(p_vertices[n]); \ } - for (int i = 0; i < p_vertexes.size() - 2; i++) { + for (int i = 0; i < p_vertices.size() - 2; i++) { ADD_POINT(0); ADD_POINT(i + 1); ADD_POINT(i + 2); @@ -263,7 +307,7 @@ Array SurfaceTool::commit_to_arrays() { } } - w = PoolVector<Vector3>::Write(); + w.release(); a[i] = array; } break; @@ -291,7 +335,7 @@ Array SurfaceTool::commit_to_arrays() { } } - w = PoolVector<Vector2>::Write(); + w.release(); a[i] = array; } break; case Mesh::ARRAY_TANGENT: { @@ -314,7 +358,7 @@ Array SurfaceTool::commit_to_arrays() { w[idx + 3] = d < 0 ? -1 : 1; } - w = PoolVector<float>::Write(); + w.release(); a[i] = array; } break; @@ -331,7 +375,7 @@ Array SurfaceTool::commit_to_arrays() { w[idx] = v.color; } - w = PoolVector<Color>::Write(); + w.release(); a[i] = array; } break; case Mesh::ARRAY_BONES: { @@ -352,7 +396,7 @@ Array SurfaceTool::commit_to_arrays() { } } - w = PoolVector<int>::Write(); + w.release(); a[i] = array; } break; @@ -374,7 +418,7 @@ Array SurfaceTool::commit_to_arrays() { } } - w = PoolVector<float>::Write(); + w.release(); a[i] = array; } break; @@ -392,12 +436,13 @@ Array SurfaceTool::commit_to_arrays() { w[idx] = E->get(); } - w = PoolVector<int>::Write(); + w.release(); a[i] = array; } break; - default: {} + default: { + } } } @@ -724,6 +769,26 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { material = p_existing->surface_get_material(p_surface); } +void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) { + clear(); + primitive = p_existing->surface_get_primitive_type(p_surface); + Array arr = p_existing->surface_get_blend_shape_arrays(p_surface); + Array blend_shape_names; + int32_t shape_idx = -1; + for (int32_t i = 0; i < p_existing->get_blend_shape_count(); i++) { + String name = p_existing->get_blend_shape_name(i); + if (name == p_blend_shape_name) { + shape_idx = i; + break; + } + } + ERR_FAIL_COND(shape_idx == -1); + ERR_FAIL_COND(shape_idx >= arr.size()); + Array mesh = arr[shape_idx]; + ERR_FAIL_COND(mesh.size() != VS::ARRAY_MAX); + _create_list_from_arrays(arr[shape_idx], &vertex_array, &index_array, format); +} + void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform &p_xform) { if (vertex_array.size() == 0) { @@ -1012,7 +1077,7 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("add_weights", "weights"), &SurfaceTool::add_weights); ClassDB::bind_method(D_METHOD("add_smooth_group", "smooth"), &SurfaceTool::add_smooth_group); - ClassDB::bind_method(D_METHOD("add_triangle_fan", "vertexes", "uvs", "colors", "uv2s", "normals", "tangents"), &SurfaceTool::add_triangle_fan, DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Color>()), DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Vector3>()), DEFVAL(Vector<Plane>())); + ClassDB::bind_method(D_METHOD("add_triangle_fan", "vertices", "uvs", "colors", "uv2s", "normals", "tangents"), &SurfaceTool::add_triangle_fan, DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Color>()), DEFVAL(Vector<Vector2>()), DEFVAL(Vector<Vector3>()), DEFVAL(Vector<Plane>())); ClassDB::bind_method(D_METHOD("add_index", "index"), &SurfaceTool::add_index); @@ -1026,8 +1091,10 @@ void SurfaceTool::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &SurfaceTool::clear); ClassDB::bind_method(D_METHOD("create_from", "existing", "surface"), &SurfaceTool::create_from); + ClassDB::bind_method(D_METHOD("create_from_blend_shape", "existing", "surface", "blend_shape"), &SurfaceTool::create_from_blend_shape); ClassDB::bind_method(D_METHOD("append_from", "existing", "surface", "transform"), &SurfaceTool::append_from); ClassDB::bind_method(D_METHOD("commit", "existing", "flags"), &SurfaceTool::commit, DEFVAL(Variant()), DEFVAL(Mesh::ARRAY_COMPRESS_DEFAULT)); + ClassDB::bind_method(D_METHOD("commit_to_arrays"), &SurfaceTool::commit_to_arrays); } SurfaceTool::SurfaceTool() { diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index ef13238c13..e3aec6ce0e 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -62,6 +62,14 @@ private: static _FORCE_INLINE_ uint32_t hash(const Vertex &p_vtx); }; + struct WeightSort { + int index; + float weight; + bool operator<(const WeightSort &p_right) const { + return weight < p_right.weight; + } + }; + bool begun; bool first; Mesh::PrimitiveType primitive; @@ -109,7 +117,7 @@ public: void add_weights(const Vector<float> &p_weights); void add_smooth_group(bool p_smooth); - void add_triangle_fan(const Vector<Vector3> &p_vertexes, const Vector<Vector2> &p_uvs = Vector<Vector2>(), const Vector<Color> &p_colors = Vector<Color>(), const Vector<Vector2> &p_uv2s = Vector<Vector2>(), const Vector<Vector3> &p_normals = Vector<Vector3>(), const Vector<Plane> &p_tangents = Vector<Plane>()); + void add_triangle_fan(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs = Vector<Vector2>(), const Vector<Color> &p_colors = Vector<Color>(), const Vector<Vector2> &p_uv2s = Vector<Vector2>(), const Vector<Vector3> &p_normals = Vector<Vector3>(), const Vector<Plane> &p_tangents = Vector<Plane>()); void add_index(int p_index); @@ -128,6 +136,7 @@ public: static Vector<Vertex> create_vertex_array_from_triangle_arrays(const Array &p_arrays); Array commit_to_arrays(); void create_from(const Ref<Mesh> &p_existing, int p_surface); + void create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name); void append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform &p_xform); Ref<ArrayMesh> commit(const Ref<ArrayMesh> &p_existing = Ref<ArrayMesh>(), uint32_t p_flags = Mesh::ARRAY_COMPRESS_DEFAULT); diff --git a/scene/resources/text_file.cpp b/scene/resources/text_file.cpp index 37bdd691b4..b84f3f1f9e 100644 --- a/scene/resources/text_file.cpp +++ b/scene/resources/text_file.cpp @@ -67,10 +67,7 @@ Error TextFile::load_text(const String &p_path) { w[len] = 0; String s; - if (s.parse_utf8((const char *)w.ptr())) { - ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - ERR_FAIL_V(ERR_INVALID_DATA); - } + ERR_FAIL_COND_V_MSG(s.parse_utf8((const char *)w.ptr()), ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode."); text = s; path = p_path; return OK; diff --git a/scene/resources/text_file.h b/scene/resources/text_file.h index 91bb98a3b1..7127eaeb93 100644 --- a/scene/resources/text_file.h +++ b/scene/resources/text_file.h @@ -36,7 +36,7 @@ class TextFile : public Resource { - GDCLASS(TextFile, Resource) + GDCLASS(TextFile, Resource); private: String text; diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 26036c08a9..e44b17584b 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -34,7 +34,9 @@ #include "core/io/image_loader.h" #include "core/method_bind_ext.gen.inc" #include "core/os/os.h" -#include "scene/resources/bit_mask.h" +#include "mesh.h" +#include "scene/resources/bit_map.h" +#include "servers/camera/camera_feed.h" Size2 Texture::get_size() const { @@ -110,10 +112,12 @@ void ImageTexture::reload_from_file() { Ref<Image> img; img.instance(); - Error err = ImageLoader::load_image(path, img); - ERR_FAIL_COND(err != OK); - - create_from_image(img, flags); + if (ImageLoader::load_image(path, img) == OK) { + create_from_image(img, flags); + } else { + Resource::reload_from_file(); + _change_notify(); + } } bool ImageTexture::_set(const StringName &p_name, const Variant &p_value) { @@ -157,7 +161,7 @@ bool ImageTexture::_get(const StringName &p_name, Variant &r_ret) const { void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter,Anisotropic,sRGB,Mirrored Repeat")); - p_list->push_back(PropertyInfo(Variant::OBJECT, "image", PROPERTY_HINT_RESOURCE_TYPE, "Image")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "image", PROPERTY_HINT_RESOURCE_TYPE, "Image", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT)); p_list->push_back(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "")); } @@ -178,12 +182,6 @@ void ImageTexture::_reload_hook(const RID &p_hook) { _change_notify(); } -bool ImageTexture::keep_images_cached = false; - -void ImageTexture::set_keep_images_cached(bool p_enable) { - keep_images_cached = p_enable; -} - void ImageTexture::create(int p_width, int p_height, Image::Format p_format, uint32_t p_flags) { flags = p_flags; @@ -205,9 +203,7 @@ void ImageTexture::create_from_image(const Ref<Image> &p_image, uint32_t p_flags VisualServer::get_singleton()->texture_set_data(texture, p_image); _change_notify(); - if (keep_images_cached) { - image_cache = p_image; - } + image_stored = true; } void ImageTexture::set_flags(uint32_t p_flags) { @@ -237,7 +233,7 @@ Image::Format ImageTexture::get_format() const { #ifndef DISABLE_DEPRECATED Error ImageTexture::load(const String &p_path) { - WARN_DEPRECATED + WARN_DEPRECATED; Ref<Image> img; img.instance(); Error err = img->load(p_path); @@ -255,10 +251,7 @@ void ImageTexture::set_data(const Ref<Image> &p_image) { _change_notify(); alpha_cache.unref(); - - if (keep_images_cached) { - image_cache = p_image; - } + image_stored = true; } void ImageTexture::_resource_path_changed() { @@ -268,10 +261,10 @@ void ImageTexture::_resource_path_changed() { Ref<Image> ImageTexture::get_data() const { - if (image_cache.is_valid()) { - return image_cache; - } else { + if (image_stored) { return VisualServer::get_singleton()->texture_get_data(texture); + } else { + return Ref<Image>(); } } @@ -437,6 +430,8 @@ ImageTexture::ImageTexture() { texture = VisualServer::get_singleton()->texture_create(); storage = STORAGE_RAW; lossy_storage_quality = 0.7; + image_stored = false; + format = Image::FORMAT_L8; } ImageTexture::~ImageTexture() { @@ -492,7 +487,7 @@ Image::Format StreamTexture::get_format() const { return format; } -Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &flags, Ref<Image> &image, int p_size_limit) { +Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &tw_custom, int &th_custom, int &flags, Ref<Image> &image, int p_size_limit) { alpha_cache.unref(); @@ -508,8 +503,11 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &fla ERR_FAIL_COND_V(header[0] != 'G' || header[1] != 'D' || header[2] != 'S' || header[3] != 'T', ERR_FILE_CORRUPT); } - tw = f->get_32(); - th = f->get_32(); + tw = f->get_16(); + tw_custom = f->get_16(); + th = f->get_16(); + th_custom = f->get_16(); + flags = f->get_32(); //texture flags! uint32_t df = f->get_32(); //data format @@ -643,7 +641,7 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &fla bool mipmaps = df & FORMAT_BIT_HAS_MIPMAPS; if (!mipmaps) { - int size = Image::get_image_data_size(tw, th, format, 0); + int size = Image::get_image_data_size(tw, th, format, false); PoolVector<uint8_t> img_data; img_data.resize(size); @@ -662,22 +660,19 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &fla int sw = tw; int sh = th; - int mipmaps = Image::get_image_required_mipmaps(tw, th, format); + int mipmaps2 = Image::get_image_required_mipmaps(tw, th, format); int total_size = Image::get_image_data_size(tw, th, format, true); int idx = 0; - int ofs = 0; - while (mipmaps > 1 && p_size_limit > 0 && (sw > p_size_limit || sh > p_size_limit)) { + while (mipmaps2 > 1 && p_size_limit > 0 && (sw > p_size_limit || sh > p_size_limit)) { sw = MAX(sw >> 1, 1); sh = MAX(sh >> 1, 1); - mipmaps--; + mipmaps2--; idx++; } - if (idx > 0) { - ofs = Image::get_image_data_size(tw, th, format, idx - 1); - } + int ofs = Image::get_image_mipmap_offset(tw, th, format, idx); if (total_size - ofs <= 0) { memdelete(f); @@ -698,7 +693,7 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &fla int expected = total_size - ofs; if (bytes < expected) { - //this is a compatibility workaround for older format, which saved less mipmaps. It is still recommended the image is reimported. + //this is a compatibility workaround for older format, which saved less mipmaps2. It is still recommended the image is reimported. zeromem(w.ptr() + bytes, (expected - bytes)); } else if (bytes != expected) { ERR_FAIL_V(ERR_FILE_CORRUPT); @@ -716,18 +711,26 @@ Error StreamTexture::_load_data(const String &p_path, int &tw, int &th, int &fla Error StreamTexture::load(const String &p_path) { - int lw, lh, lflags; + int lw, lh, lwc, lhc, lflags; Ref<Image> image; image.instance(); - Error err = _load_data(p_path, lw, lh, lflags, image); + Error err = _load_data(p_path, lw, lh, lwc, lhc, lflags, image); if (err) return err; + if (get_path() == String()) { + //temporarily set path if no path set for resource, helps find errors + VisualServer::get_singleton()->texture_set_path(texture, p_path); + } VS::get_singleton()->texture_allocate(texture, image->get_width(), image->get_height(), 0, image->get_format(), VS::TEXTURE_TYPE_2D, lflags); VS::get_singleton()->texture_set_data(texture, image); + if (lwc || lhc) { + VS::get_singleton()->texture_set_size_override(texture, lwc, lhc, 0); + } else { + } - w = lw; - h = lh; + w = lwc ? lwc : lw; + h = lhc ? lhc : lh; flags = lflags; path_to_file = p_path; format = image->get_format(); @@ -795,6 +798,7 @@ bool StreamTexture::is_pixel_opaque(int p_x, int p_y) const { decom->decompress(); img = decom; } + alpha_cache.instance(); alpha_cache->create_from_image_alpha(img); } @@ -839,6 +843,12 @@ void StreamTexture::reload_from_file() { load(path); } +void StreamTexture::_validate_property(PropertyInfo &property) const { + if (property.name == "flags") { + property.usage = PROPERTY_USAGE_NOEDITOR; + } +} + void StreamTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("load", "path"), &StreamTexture::load); @@ -943,6 +953,7 @@ uint32_t AtlasTexture::get_flags() const { void AtlasTexture::set_atlas(const Ref<Texture> &p_atlas) { + ERR_FAIL_COND(p_atlas == this); if (atlas == p_atlas) return; atlas = p_atlas; @@ -1123,6 +1134,138 @@ AtlasTexture::AtlasTexture() { filter_clip = false; } +///////////////////////////////////////// + +int MeshTexture::get_width() const { + return size.width; +} +int MeshTexture::get_height() const { + return size.height; +} +RID MeshTexture::get_rid() const { + return RID(); +} + +bool MeshTexture::has_alpha() const { + return false; +} + +void MeshTexture::set_flags(uint32_t p_flags) { +} + +uint32_t MeshTexture::get_flags() const { + return 0; +} + +void MeshTexture::set_mesh(const Ref<Mesh> &p_mesh) { + mesh = p_mesh; +} +Ref<Mesh> MeshTexture::get_mesh() const { + return mesh; +} + +void MeshTexture::set_image_size(const Size2 &p_size) { + size = p_size; +} + +Size2 MeshTexture::get_image_size() const { + + return size; +} + +void MeshTexture::set_base_texture(const Ref<Texture> &p_texture) { + base_texture = p_texture; +} + +Ref<Texture> MeshTexture::get_base_texture() const { + return base_texture; +} + +void MeshTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose, const Ref<Texture> &p_normal_map) const { + + if (mesh.is_null() || base_texture.is_null()) { + return; + } + Transform2D xform; + xform.set_origin(p_pos); + if (p_transpose) { + SWAP(xform.elements[0][1], xform.elements[1][0]); + SWAP(xform.elements[0][0], xform.elements[1][1]); + } + RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); + VisualServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid(), normal_rid); +} +void MeshTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose, const Ref<Texture> &p_normal_map) const { + if (mesh.is_null() || base_texture.is_null()) { + return; + } + Transform2D xform; + Vector2 origin = p_rect.position; + if (p_rect.size.x < 0) { + origin.x += size.x; + } + if (p_rect.size.y < 0) { + origin.y += size.y; + } + xform.set_origin(origin); + xform.set_scale(p_rect.size / size); + + if (p_transpose) { + SWAP(xform.elements[0][1], xform.elements[1][0]); + SWAP(xform.elements[0][0], xform.elements[1][1]); + } + RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); + VisualServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid(), normal_rid); +} +void MeshTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, const Ref<Texture> &p_normal_map, bool p_clip_uv) const { + + if (mesh.is_null() || base_texture.is_null()) { + return; + } + Transform2D xform; + Vector2 origin = p_rect.position; + if (p_rect.size.x < 0) { + origin.x += size.x; + } + if (p_rect.size.y < 0) { + origin.y += size.y; + } + xform.set_origin(origin); + xform.set_scale(p_rect.size / size); + + if (p_transpose) { + SWAP(xform.elements[0][1], xform.elements[1][0]); + SWAP(xform.elements[0][0], xform.elements[1][1]); + } + RID normal_rid = p_normal_map.is_valid() ? p_normal_map->get_rid() : RID(); + VisualServer::get_singleton()->canvas_item_add_mesh(p_canvas_item, mesh->get_rid(), xform, p_modulate, base_texture->get_rid(), normal_rid); +} +bool MeshTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const { + r_rect = p_rect; + r_src_rect = p_src_rect; + return true; +} + +bool MeshTexture::is_pixel_opaque(int p_x, int p_y) const { + return true; +} + +void MeshTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshTexture::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &MeshTexture::get_mesh); + ClassDB::bind_method(D_METHOD("set_image_size", "size"), &MeshTexture::set_image_size); + ClassDB::bind_method(D_METHOD("get_image_size"), &MeshTexture::get_image_size); + ClassDB::bind_method(D_METHOD("set_base_texture", "texture"), &MeshTexture::set_base_texture); + ClassDB::bind_method(D_METHOD("get_base_texture"), &MeshTexture::get_base_texture); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_base_texture", "get_base_texture"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "image_size", PROPERTY_HINT_RANGE, "0,16384,1"), "set_image_size", "get_image_size"); +} + +MeshTexture::MeshTexture() { +} + ////////////////////////////////////////// int LargeTexture::get_width() const { @@ -1182,6 +1325,7 @@ void LargeTexture::set_piece_offset(int p_idx, const Point2 &p_offset) { void LargeTexture::set_piece_texture(int p_idx, const Ref<Texture> &p_texture) { + ERR_FAIL_COND(p_texture == this); ERR_FAIL_INDEX(p_idx, pieces.size()); pieces.write[p_idx].texture = p_texture; }; @@ -1343,8 +1487,10 @@ uint32_t CubeMap::get_flags() const { void CubeMap::set_side(Side p_side, const Ref<Image> &p_image) { + ERR_FAIL_COND(p_image.is_null()); ERR_FAIL_COND(p_image->empty()); ERR_FAIL_INDEX(p_side, 6); + if (!_is_valid()) { format = p_image->get_format(); w = p_image->get_width(); @@ -1358,6 +1504,7 @@ void CubeMap::set_side(Side p_side, const Ref<Image> &p_image) { Ref<Image> CubeMap::get_side(Side p_side) const { + ERR_FAIL_INDEX_V(p_side, 6, Ref<Image>()); if (!valid[p_side]) return Ref<Image>(); return VS::get_singleton()->texture_get_data(cubemap, VS::CubeMapSide(p_side)); @@ -1511,6 +1658,7 @@ CubeMap::CubeMap() { cubemap = VisualServer::get_singleton()->texture_create(); storage = STORAGE_RAW; lossy_storage_quality = 0.7; + format = Image::FORMAT_BPTC_RGBA; } CubeMap::~CubeMap() { @@ -1747,6 +1895,7 @@ void ProxyTexture::_bind_methods() { void ProxyTexture::set_base(const Ref<Texture> &p_texture) { + ERR_FAIL_COND(p_texture == this); base = p_texture; if (base.is_valid()) { VS::get_singleton()->texture_set_proxy(proxy, base->get_rid()); @@ -1862,6 +2011,8 @@ int AnimatedTexture::get_frames() const { } void AnimatedTexture::set_frame_texture(int p_frame, const Ref<Texture> &p_texture) { + + ERR_FAIL_COND(p_texture == this); ERR_FAIL_INDEX(p_frame, MAX_FRAMES); RWLockWrite w(rw_lock); @@ -2000,6 +2151,8 @@ void AnimatedTexture::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "frame_" + itos(i) + "/texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_texture", "get_frame_texture", i); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "frame_" + itos(i) + "/delay_sec", PROPERTY_HINT_RANGE, "0.0,16.0,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_frame_delay", "get_frame_delay", i); } + + BIND_CONSTANT(MAX_FRAMES); } AnimatedTexture::AnimatedTexture() { @@ -2198,8 +2351,7 @@ RES ResourceFormatLoaderTextureLayered::load(const String &p_path, const String texarr.instance(); lt = texarr; } else { - ERR_EXPLAIN("Unrecognized layered texture extension"); - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Unrecognized layered texture extension."); } FileAccess *f = FileAccess::open(p_path, FileAccess::READ); @@ -2220,8 +2372,7 @@ RES ResourceFormatLoaderTextureLayered::load(const String &p_path, const String } } else { - ERR_EXPLAIN("Unrecognized layered texture file format: " + String((const char *)header)); - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Unrecognized layered texture file format: " + String((const char *)header) + "."); } int tw = f->get_32(); @@ -2349,3 +2500,107 @@ String ResourceFormatLoaderTextureLayered::get_resource_type(const String &p_pat return "TextureArray"; return ""; } + +void CameraTexture::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_camera_feed_id", "feed_id"), &CameraTexture::set_camera_feed_id); + ClassDB::bind_method(D_METHOD("get_camera_feed_id"), &CameraTexture::get_camera_feed_id); + + ClassDB::bind_method(D_METHOD("set_which_feed", "which_feed"), &CameraTexture::set_which_feed); + ClassDB::bind_method(D_METHOD("get_which_feed"), &CameraTexture::get_which_feed); + + ClassDB::bind_method(D_METHOD("set_camera_active", "active"), &CameraTexture::set_camera_active); + ClassDB::bind_method(D_METHOD("get_camera_active"), &CameraTexture::get_camera_active); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "camera_feed_id"), "set_camera_feed_id", "get_camera_feed_id"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "which_feed"), "set_which_feed", "get_which_feed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "camera_is_active"), "set_camera_active", "get_camera_active"); +} + +int CameraTexture::get_width() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->get_base_width(); + } else { + return 0; + } +} + +int CameraTexture::get_height() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->get_base_height(); + } else { + return 0; + } +} + +bool CameraTexture::has_alpha() const { + return false; +} + +RID CameraTexture::get_rid() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->get_texture(which_feed); + } else { + return RID(); + } +} + +void CameraTexture::set_flags(uint32_t p_flags) { + // not supported +} + +uint32_t CameraTexture::get_flags() const { + // not supported + return 0; +} + +Ref<Image> CameraTexture::get_data() const { + // not (yet) supported + return Ref<Image>(); +} + +void CameraTexture::set_camera_feed_id(int p_new_id) { + camera_feed_id = p_new_id; + _change_notify(); +} + +int CameraTexture::get_camera_feed_id() const { + return camera_feed_id; +} + +void CameraTexture::set_which_feed(CameraServer::FeedImage p_which) { + which_feed = p_which; + _change_notify(); +} + +CameraServer::FeedImage CameraTexture::get_which_feed() const { + return which_feed; +} + +void CameraTexture::set_camera_active(bool p_active) { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + feed->set_active(p_active); + _change_notify(); + } +} + +bool CameraTexture::get_camera_active() const { + Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id); + if (feed.is_valid()) { + return feed->is_active(); + } else { + return false; + } +} + +CameraTexture::CameraTexture() { + camera_feed_id = 0; + which_feed = CameraServer::FEED_RGBA_IMAGE; +} + +CameraTexture::~CameraTexture() { + // nothing to do here yet +} diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 4b5b504510..e535f7544a 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -37,14 +37,11 @@ #include "core/os/rw_lock.h" #include "core/os/thread_safe.h" #include "core/resource.h" -#include "scene/resources/color_ramp.h" #include "scene/resources/curve.h" +#include "scene/resources/gradient.h" +#include "servers/camera_server.h" #include "servers/visual_server.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class Texture : public Resource { GDCLASS(Texture, Resource); @@ -111,7 +108,7 @@ private: Size2 size_override; float lossy_storage_quality; mutable Ref<BitMap> alpha_cache; - Ref<Image> image_cache; + bool image_stored; protected: virtual void reload_from_file(); @@ -126,11 +123,7 @@ protected: void _set_data(Dictionary p_data); - static bool keep_images_cached; - public: - static void set_keep_images_cached(bool p_enable); - void create(int p_width, int p_height, Image::Format p_format, uint32_t p_flags = FLAGS_DEFAULT); void create_from_image(const Ref<Image> &p_image, uint32_t p_flags = FLAGS_DEFAULT); @@ -191,7 +184,7 @@ public: }; private: - Error _load_data(const String &p_path, int &tw, int &th, int &flags, Ref<Image> &image, int p_size_limit = 0); + Error _load_data(const String &p_path, int &tw, int &th, int &tw_custom, int &th_custom, int &flags, Ref<Image> &image, int p_size_limit = 0); String path_to_file; RID texture; Image::Format format; @@ -207,6 +200,7 @@ private: protected: static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; public: typedef void (*TextureFormatRequestCallback)(const Ref<StreamTexture> &); @@ -241,7 +235,6 @@ public: }; class ResourceFormatLoaderStreamTexture : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderStreamTexture, ResourceFormatLoader) public: virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List<String> *p_extensions) const; @@ -296,6 +289,49 @@ public: AtlasTexture(); }; +class Mesh; + +class MeshTexture : public Texture { + + GDCLASS(MeshTexture, Texture); + RES_BASE_EXTENSION("meshtex"); + + Ref<Texture> base_texture; + Ref<Mesh> mesh; + Size2i size; + +protected: + static void _bind_methods(); + +public: + virtual int get_width() const; + virtual int get_height() const; + virtual RID get_rid() const; + + virtual bool has_alpha() const; + + virtual void set_flags(uint32_t p_flags); + virtual uint32_t get_flags() const; + + void set_mesh(const Ref<Mesh> &p_mesh); + Ref<Mesh> get_mesh() const; + + void set_image_size(const Size2 &p_size); + Size2 get_image_size() const; + + void set_base_texture(const Ref<Texture> &p_texture); + Ref<Texture> get_base_texture() const; + + virtual void draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref<Texture> &p_normal_map = Ref<Texture>()) const; + virtual void draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref<Texture> &p_normal_map = Ref<Texture>()) const; + virtual void draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, const Ref<Texture> &p_normal_map = Ref<Texture>(), bool p_clip_uv = true) const; + virtual bool get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const; + + bool is_pixel_opaque(int p_x, int p_y) const; + + MeshTexture(); +}; + class LargeTexture : public Texture { GDCLASS(LargeTexture, Texture); @@ -429,7 +465,7 @@ VARIANT_ENUM_CAST(CubeMap::Storage) class TextureLayered : public Resource { - GDCLASS(TextureLayered, Resource) + GDCLASS(TextureLayered, Resource); public: enum Flags { @@ -481,7 +517,8 @@ VARIANT_ENUM_CAST(TextureLayered::Flags) class Texture3D : public TextureLayered { - GDCLASS(Texture3D, TextureLayered) + GDCLASS(Texture3D, TextureLayered); + public: Texture3D() : TextureLayered(true) {} @@ -489,14 +526,14 @@ public: class TextureArray : public TextureLayered { - GDCLASS(TextureArray, TextureLayered) + GDCLASS(TextureArray, TextureLayered); + public: TextureArray() : TextureLayered(false) {} }; class ResourceFormatLoaderTextureLayered : public ResourceFormatLoader { - GDCLASS(ResourceFormatLoaderTextureLayered, ResourceFormatLoader) public: enum Compression { COMPRESSION_LOSSLESS, @@ -512,7 +549,7 @@ public: class CurveTexture : public Texture { - GDCLASS(CurveTexture, Texture) + GDCLASS(CurveTexture, Texture); RES_BASE_EXTENSION("curvetex") private: @@ -560,7 +597,7 @@ public: //VARIANT_ENUM_CAST( Texture::CubeMapSide ); class GradientTexture : public Texture { - GDCLASS(GradientTexture, Texture) + GDCLASS(GradientTexture, Texture); public: struct Point { @@ -605,7 +642,7 @@ public: }; class ProxyTexture : public Texture { - GDCLASS(ProxyTexture, Texture) + GDCLASS(ProxyTexture, Texture); private: RID proxy; @@ -632,7 +669,7 @@ public: }; class AnimatedTexture : public Texture { - GDCLASS(AnimatedTexture, Texture) + GDCLASS(AnimatedTexture, Texture); //use readers writers lock for this, since its far more times read than written to RWLock *rw_lock; @@ -700,4 +737,38 @@ public: ~AnimatedTexture(); }; +class CameraTexture : public Texture { + GDCLASS(CameraTexture, Texture); + +private: + int camera_feed_id; + CameraServer::FeedImage which_feed; + +protected: + static void _bind_methods(); + +public: + virtual int get_width() const; + virtual int get_height() const; + virtual RID get_rid() const; + virtual bool has_alpha() const; + + virtual void set_flags(uint32_t p_flags); + virtual uint32_t get_flags() const; + + virtual Ref<Image> get_data() const; + + void set_camera_feed_id(int p_new_id); + int get_camera_feed_id() const; + + void set_which_feed(CameraServer::FeedImage p_which); + CameraServer::FeedImage get_which_feed() const; + + void set_camera_active(bool p_active); + bool get_camera_active() const; + + CameraTexture(); + ~CameraTexture(); +}; + #endif diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index 87b40d5447..ae18be1695 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -32,8 +32,6 @@ #include "core/os/file_access.h" #include "core/print_string.h" -Ref<Theme> Theme::default_theme; - void Theme::_emit_theme_changed() { emit_changed(); @@ -186,11 +184,6 @@ void Theme::_get_property_list(List<PropertyInfo> *p_list) const { } } -Ref<Theme> Theme::get_default() { - - return default_theme; -} - void Theme::set_default_theme_font(const Ref<Font> &p_default_font) { if (default_theme_font == p_default_font) @@ -215,14 +208,31 @@ Ref<Font> Theme::get_default_theme_font() const { return default_theme_font; } +Ref<Theme> Theme::project_default_theme; +Ref<Theme> Theme::default_theme; +Ref<Texture> Theme::default_icon; +Ref<StyleBox> Theme::default_style; +Ref<Font> Theme::default_font; + +Ref<Theme> Theme::get_default() { + + return default_theme; +} + void Theme::set_default(const Ref<Theme> &p_default) { default_theme = p_default; } -Ref<Texture> Theme::default_icon; -Ref<StyleBox> Theme::default_style; -Ref<Font> Theme::default_font; +Ref<Theme> Theme::get_project_default() { + + return project_default_theme; +} + +void Theme::set_project_default(const Ref<Theme> &p_project_default) { + + project_default_theme = p_project_default; +} void Theme::set_default_icon(const Ref<Texture> &p_icon) { @@ -621,8 +631,8 @@ void Theme::clear() { void Theme::copy_default_theme() { - Ref<Theme> default_theme = get_default(); - copy_theme(default_theme); + Ref<Theme> default_theme2 = get_default(); + copy_theme(default_theme2); } void Theme::copy_theme(const Ref<Theme> &p_other) { @@ -756,7 +766,7 @@ void Theme::_bind_methods() { ClassDB::bind_method(D_METHOD("_emit_theme_changed"), &Theme::_emit_theme_changed); ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme); - ClassDB::bind_method("copy_theme", &Theme::copy_theme); + ClassDB::bind_method(D_METHOD("copy_theme", "other"), &Theme::copy_theme); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font"); } diff --git a/scene/resources/theme.h b/scene/resources/theme.h index fb59073cbe..187694de65 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -38,15 +38,11 @@ #include "scene/resources/style_box.h" #include "scene/resources/texture.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Theme : public Resource { GDCLASS(Theme, Resource); RES_BASE_EXTENSION("theme"); - static Ref<Theme> default_theme; void _emit_theme_changed(); HashMap<StringName, HashMap<StringName, Ref<Texture> > > icon_map; @@ -61,6 +57,8 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + static Ref<Theme> project_default_theme; + static Ref<Theme> default_theme; static Ref<Texture> default_icon; static Ref<StyleBox> default_style; static Ref<Font> default_font; @@ -137,6 +135,9 @@ public: static Ref<Theme> get_default(); static void set_default(const Ref<Theme> &p_default); + static Ref<Theme> get_project_default(); + static void set_project_default(const Ref<Theme> &p_project_default); + static void set_default_icon(const Ref<Texture> &p_icon); static void set_default_style(const Ref<StyleBox> &p_style); static void set_default_font(const Ref<Font> &p_font); diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 5a2e7245a2..24122a8d99 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -30,6 +30,7 @@ #include "tile_set.h" #include "core/array.h" +#include "core/engine.h" bool TileSet::_set(const StringName &p_name, const Variant &p_value) { @@ -147,15 +148,20 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { } } } else if (what == "shape") - tile_set_shape(id, 0, p_value); + for (int i = 0; i < tile_get_shape_count(id); i++) + tile_set_shape(id, i, p_value); else if (what == "shape_offset") - tile_set_shape_offset(id, 0, p_value); + for (int i = 0; i < tile_get_shape_count(id); i++) + tile_set_shape_offset(id, i, p_value); else if (what == "shape_transform") - tile_set_shape_transform(id, 0, p_value); + for (int i = 0; i < tile_get_shape_count(id); i++) + tile_set_shape_transform(id, i, p_value); else if (what == "shape_one_way") - tile_set_shape_one_way(id, 0, p_value); + for (int i = 0; i < tile_get_shape_count(id); i++) + tile_set_shape_one_way(id, i, p_value); else if (what == "shape_one_way_margin") - tile_set_shape_one_way_margin(id, 0, p_value); + for (int i = 0; i < tile_get_shape_count(id); i++) + tile_set_shape_one_way_margin(id, i, p_value); else if (what == "shapes") _tile_set_shapes(id, p_value); else if (what == "occluder") @@ -214,7 +220,7 @@ bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { r_ret = autotile_get_spacing(id); else if (what == "bitmask_flags") { Array p; - for (Map<Vector2, uint16_t>::Element *E = tile_map[id].autotile_data.flags.front(); E; E = E->next()) { + for (Map<Vector2, uint32_t>::Element *E = tile_map[id].autotile_data.flags.front(); E; E = E->next()) { p.push_back(E->key()); p.push_back(E->value()); } @@ -318,6 +324,7 @@ void TileSet::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, pre + "autotile/spacing", PROPERTY_HINT_RANGE, "0,256,1", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/occluder_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/navpoly_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/priority_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::ARRAY, pre + "autotile/z_index_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); } p_list->push_back(PropertyInfo(Variant::VECTOR2, pre + "occluder_offset")); @@ -543,7 +550,7 @@ const Map<Vector2, int> &TileSet::autotile_get_z_index_map(int p_id) const { return tile_map[p_id].autotile_data.z_index_map; } -void TileSet::autotile_set_bitmask(int p_id, Vector2 p_coord, uint16_t p_flag) { +void TileSet::autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag) { ERR_FAIL_COND(!tile_map.has(p_id)); if (p_flag == 0) { @@ -554,7 +561,7 @@ void TileSet::autotile_set_bitmask(int p_id, Vector2 p_coord, uint16_t p_flag) { } } -uint16_t TileSet::autotile_get_bitmask(int p_id, Vector2 p_coord) { +uint32_t TileSet::autotile_get_bitmask(int p_id, Vector2 p_coord) { ERR_FAIL_COND_V(!tile_map.has(p_id), 0); if (!tile_map[p_id].autotile_data.flags.has(p_coord)) { @@ -563,13 +570,13 @@ uint16_t TileSet::autotile_get_bitmask(int p_id, Vector2 p_coord) { return tile_map[p_id].autotile_data.flags[p_coord]; } -const Map<Vector2, uint16_t> &TileSet::autotile_get_bitmask_map(int p_id) { +const Map<Vector2, uint32_t> &TileSet::autotile_get_bitmask_map(int p_id) { - static Map<Vector2, uint16_t> dummy; - static Map<Vector2, uint16_t> dummy_atlas; + static Map<Vector2, uint32_t> dummy; + static Map<Vector2, uint32_t> dummy_atlas; ERR_FAIL_COND_V(!tile_map.has(p_id), dummy); if (tile_get_tile_mode(p_id) == ATLAS_TILE) { - dummy_atlas = Map<Vector2, uint16_t>(); + dummy_atlas = Map<Vector2, uint32_t>(); Rect2 region = tile_get_region(p_id); Size2 size = autotile_get_size(p_id); float spacing = autotile_get_spacing(p_id); @@ -599,15 +606,72 @@ Vector2 TileSet::autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, } List<Vector2> coords; - uint16_t mask; - for (Map<Vector2, uint16_t>::Element *E = tile_map[p_id].autotile_data.flags.front(); E; E = E->next()) { + List<uint32_t> priorities; + uint32_t priority_sum = 0; + uint32_t mask; + uint16_t mask_; + uint16_t mask_ignore; + for (Map<Vector2, uint32_t>::Element *E = tile_map[p_id].autotile_data.flags.front(); E; E = E->next()) { mask = E->get(); if (tile_map[p_id].autotile_data.bitmask_mode == BITMASK_2X2) { - mask &= (BIND_BOTTOMLEFT | BIND_BOTTOMRIGHT | BIND_TOPLEFT | BIND_TOPRIGHT); + mask |= (BIND_IGNORE_TOP | BIND_IGNORE_LEFT | BIND_IGNORE_CENTER | BIND_IGNORE_RIGHT | BIND_IGNORE_BOTTOM); + } + + mask_ = mask & 0xFFFF; + mask_ignore = mask >> 16; + + if (((mask_ & (~mask_ignore)) == (p_bitmask & (~mask_ignore))) && (((~mask_) | mask_ignore) == ((~p_bitmask) | mask_ignore))) { + uint32_t priority = autotile_get_subtile_priority(p_id, E->key()); + priority_sum += priority; + priorities.push_back(priority); + coords.push_back(E->key()); + } + } + + if (coords.size() == 0) { + return autotile_get_icon_coordinate(p_id); + } else { + uint32_t picked_value = Math::rand() % priority_sum; + uint32_t upper_bound; + uint32_t lower_bound = 0; + Vector2 result = coords.front()->get(); + List<Vector2>::Element *coords_E = coords.front(); + List<uint32_t>::Element *priorities_E = priorities.front(); + while (priorities_E) { + upper_bound = lower_bound + priorities_E->get(); + if (lower_bound <= picked_value && picked_value < upper_bound) { + result = coords_E->get(); + break; + } + lower_bound = upper_bound; + priorities_E = priorities_E->next(); + coords_E = coords_E->next(); + } + + return result; + } +} + +Vector2 TileSet::atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node, const Vector2 &p_tile_location) { + + ERR_FAIL_COND_V(!tile_map.has(p_id), Vector2()); + //First try to forward selection to script + if (get_script_instance() != NULL) { + if (get_script_instance()->has_method("_forward_atlas_subtile_selection")) { + Variant ret = get_script_instance()->call("_forward_atlas_subtile_selection", p_id, p_tilemap_node, p_tile_location); + if (ret.get_type() == Variant::VECTOR2) { + return ret; + } } - if (mask == p_bitmask) { - for (int i = 0; i < autotile_get_subtile_priority(p_id, E->key()); i++) { - coords.push_back(E->key()); + } + + Vector2 coord = tile_get_region(p_id).size / autotile_get_size(p_id); + + List<Vector2> coords; + for (int x = 0; x < coord.x; x++) { + for (int y = 0; y < coord.y; y++) { + for (int i = 0; i < autotile_get_subtile_priority(p_id, Vector2(x, y)); i++) { + coords.push_back(Vector2(x, y)); } } } @@ -633,6 +697,8 @@ String TileSet::tile_get_name(int p_id) const { } void TileSet::tile_clear_shapes(int p_id) { + + ERR_FAIL_COND(!tile_map.has(p_id)); tile_map[p_id].shapes_data.clear(); } @@ -652,23 +718,27 @@ void TileSet::tile_add_shape(int p_id, const Ref<Shape2D> &p_shape, const Transf int TileSet::tile_get_shape_count(int p_id) const { ERR_FAIL_COND_V(!tile_map.has(p_id), 0); - return tile_map[p_id].shapes_data.size(); } void TileSet::tile_set_shape(int p_id, int p_shape_id, const Ref<Shape2D> &p_shape) { ERR_FAIL_COND(!tile_map.has(p_id)); - if (tile_map[p_id].shapes_data.size() <= p_shape_id) + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) tile_map[p_id].shapes_data.resize(p_shape_id + 1); tile_map[p_id].shapes_data.write[p_shape_id].shape = p_shape; + _decompose_convex_shape(p_shape); emit_changed(); } Ref<Shape2D> TileSet::tile_get_shape(int p_id, int p_shape_id) const { ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<Shape2D>()); - if (tile_map[p_id].shapes_data.size() > p_shape_id) + ERR_FAIL_COND_V(p_shape_id < 0, Ref<Shape2D>()); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) return tile_map[p_id].shapes_data[p_shape_id].shape; return Ref<Shape2D>(); @@ -677,7 +747,9 @@ Ref<Shape2D> TileSet::tile_get_shape(int p_id, int p_shape_id) const { void TileSet::tile_set_shape_transform(int p_id, int p_shape_id, const Transform2D &p_offset) { ERR_FAIL_COND(!tile_map.has(p_id)); - if (tile_map[p_id].shapes_data.size() <= p_shape_id) + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) tile_map[p_id].shapes_data.resize(p_shape_id + 1); tile_map[p_id].shapes_data.write[p_shape_id].shape_transform = p_offset; emit_changed(); @@ -686,7 +758,9 @@ void TileSet::tile_set_shape_transform(int p_id, int p_shape_id, const Transform Transform2D TileSet::tile_get_shape_transform(int p_id, int p_shape_id) const { ERR_FAIL_COND_V(!tile_map.has(p_id), Transform2D()); - if (tile_map[p_id].shapes_data.size() > p_shape_id) + ERR_FAIL_COND_V(p_shape_id < 0, Transform2D()); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) return tile_map[p_id].shapes_data[p_shape_id].shape_transform; return Transform2D(); @@ -705,7 +779,9 @@ Vector2 TileSet::tile_get_shape_offset(int p_id, int p_shape_id) const { void TileSet::tile_set_shape_one_way(int p_id, int p_shape_id, const bool p_one_way) { ERR_FAIL_COND(!tile_map.has(p_id)); - if (tile_map[p_id].shapes_data.size() <= p_shape_id) + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) tile_map[p_id].shapes_data.resize(p_shape_id + 1); tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision = p_one_way; emit_changed(); @@ -714,23 +790,31 @@ void TileSet::tile_set_shape_one_way(int p_id, int p_shape_id, const bool p_one_ bool TileSet::tile_get_shape_one_way(int p_id, int p_shape_id) const { ERR_FAIL_COND_V(!tile_map.has(p_id), false); - if (tile_map[p_id].shapes_data.size() > p_shape_id) + ERR_FAIL_COND_V(p_shape_id < 0, false); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) return tile_map[p_id].shapes_data[p_shape_id].one_way_collision; return false; } void TileSet::tile_set_shape_one_way_margin(int p_id, int p_shape_id, float p_margin) { + ERR_FAIL_COND(!tile_map.has(p_id)); - if (tile_map[p_id].shapes_data.size() <= p_shape_id) + ERR_FAIL_COND(p_shape_id < 0); + + if (p_shape_id >= tile_map[p_id].shapes_data.size()) tile_map[p_id].shapes_data.resize(p_shape_id + 1); tile_map[p_id].shapes_data.write[p_shape_id].one_way_collision_margin = p_margin; emit_changed(); } float TileSet::tile_get_shape_one_way_margin(int p_id, int p_shape_id) const { + ERR_FAIL_COND_V(!tile_map.has(p_id), 0); - if (tile_map[p_id].shapes_data.size() > p_shape_id) + ERR_FAIL_COND_V(p_shape_id < 0, 0); + + if (p_shape_id < tile_map[p_id].shapes_data.size()) return tile_map[p_id].shapes_data[p_shape_id].one_way_collision_margin; return 0; @@ -760,7 +844,9 @@ void TileSet::autotile_set_light_occluder(int p_id, const Ref<OccluderPolygon2D> } Ref<OccluderPolygon2D> TileSet::autotile_get_light_occluder(int p_id, const Vector2 &p_coord) const { + ERR_FAIL_COND_V(!tile_map.has(p_id), Ref<OccluderPolygon2D>()); + if (!tile_map[p_id].autotile_data.occluder_map.has(p_coord)) { return Ref<OccluderPolygon2D>(); } else { @@ -844,6 +930,9 @@ void TileSet::tile_set_shapes(int p_id, const Vector<ShapeData> &p_shapes) { ERR_FAIL_COND(!tile_map.has(p_id)); tile_map[p_id].shapes_data = p_shapes; + for (int i = 0; i < p_shapes.size(); i++) { + _decompose_convex_shape(p_shapes[i].shape); + } emit_changed(); } @@ -888,9 +977,10 @@ void TileSet::_tile_set_shapes(int p_id, const Array &p_shapes) { } else if (p_shapes[i].get_type() == Variant::DICTIONARY) { Dictionary d = p_shapes[i]; - if (d.has("shape") && d["shape"].get_type() == Variant::OBJECT) + if (d.has("shape") && d["shape"].get_type() == Variant::OBJECT) { s.shape = d["shape"]; - else + _decompose_convex_shape(s.shape); + } else continue; if (d.has("shape_transform") && d["shape_transform"].get_type() == Variant::TRANSFORM2D) @@ -916,8 +1006,7 @@ void TileSet::_tile_set_shapes(int p_id, const Array &p_shapes) { s.autotile_coord = default_autotile_coord; } else { - ERR_EXPLAIN("Expected an array of objects or dictionaries for tile_set_shapes"); - ERR_CONTINUE(true); + ERR_CONTINUE_MSG(true, "Expected an array of objects or dictionaries for tile_set_shapes."); } shapes_data.push_back(s); @@ -956,6 +1045,26 @@ Array TileSet::_get_tiles_ids() const { return arr; } +void TileSet::_decompose_convex_shape(Ref<Shape2D> p_shape) { + if (Engine::get_singleton()->is_editor_hint()) + return; + Ref<ConvexPolygonShape2D> convex = p_shape; + if (!convex.is_valid()) + return; + Vector<Vector<Vector2> > decomp = Geometry::decompose_polygon_in_convex(convex->get_points()); + if (decomp.size() > 1) { + Array sub_shapes; + for (int i = 0; i < decomp.size(); i++) { + Ref<ConvexPolygonShape2D> _convex = memnew(ConvexPolygonShape2D); + _convex->set_points(decomp[i]); + sub_shapes.append(_convex); + } + convex->set_meta("decomposed", sub_shapes); + } else { + convex->set_meta("decomposed", Variant()); + } +} + void TileSet::get_tile_list(List<int> *p_tiles) const { for (Map<int, TileData>::Element *E = tile_map.front(); E; E = E->next()) { @@ -1088,6 +1197,7 @@ void TileSet::_bind_methods() { BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_tile_bound", PropertyInfo(Variant::INT, "drawn_id"), PropertyInfo(Variant::INT, "neighbor_id"))); 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_VMETHOD(MethodInfo(Variant::VECTOR2, "_forward_atlas_subtile_selection", PropertyInfo(Variant::INT, "atlastile_id"), PropertyInfo(Variant::OBJECT, "tilemap", PROPERTY_HINT_NONE, "TileMap"), PropertyInfo(Variant::VECTOR2, "tile_location"))); BIND_ENUM_CONSTANT(BITMASK_2X2); BIND_ENUM_CONSTANT(BITMASK_3X3_MINIMAL); diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 4800371d3c..5fc22b9fc6 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -35,6 +35,7 @@ #include "core/resource.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/navigation_polygon.h" +#include "scene/resources/convex_polygon_shape_2d.h" #include "scene/resources/shape_2d.h" #include "scene/resources/texture.h" @@ -71,7 +72,17 @@ public: BIND_RIGHT = 32, BIND_BOTTOMLEFT = 64, BIND_BOTTOM = 128, - BIND_BOTTOMRIGHT = 256 + BIND_BOTTOMRIGHT = 256, + + BIND_IGNORE_TOPLEFT = 1 << 16, + BIND_IGNORE_TOP = 1 << 17, + BIND_IGNORE_TOPRIGHT = 1 << 18, + BIND_IGNORE_LEFT = 1 << 19, + BIND_IGNORE_CENTER = 1 << 20, + BIND_IGNORE_RIGHT = 1 << 21, + BIND_IGNORE_BOTTOMLEFT = 1 << 22, + BIND_IGNORE_BOTTOM = 1 << 23, + BIND_IGNORE_BOTTOMRIGHT = 1 << 24 }; enum TileMode { @@ -85,7 +96,7 @@ public: Size2 size; int spacing; Vector2 icon_coord; - Map<Vector2, uint16_t> flags; + Map<Vector2, uint32_t> flags; Map<Vector2, Ref<OccluderPolygon2D> > occluder_map; Map<Vector2, Ref<NavigationPolygon> > navpoly_map; Map<Vector2, int> priority_map; @@ -134,6 +145,7 @@ protected: void _tile_set_shapes(int p_id, const Array &p_shapes); Array _tile_get_shapes(int p_id) const; Array _get_tiles_ids() const; + void _decompose_convex_shape(Ref<Shape2D> p_shape); static void _bind_methods(); @@ -179,10 +191,11 @@ public: int autotile_get_z_index(int p_id, const Vector2 &p_coord); const Map<Vector2, int> &autotile_get_z_index_map(int p_id) const; - void autotile_set_bitmask(int p_id, Vector2 p_coord, uint16_t p_flag); - uint16_t autotile_get_bitmask(int p_id, Vector2 p_coord); - const Map<Vector2, uint16_t> &autotile_get_bitmask_map(int p_id); + void autotile_set_bitmask(int p_id, Vector2 p_coord, uint32_t p_flag); + uint32_t autotile_get_bitmask(int p_id, Vector2 p_coord); + const Map<Vector2, uint32_t> &autotile_get_bitmask_map(int p_id); Vector2 autotile_get_subtile_for_bitmask(int p_id, uint16_t p_bitmask, const Node *p_tilemap_node = NULL, const Vector2 &p_tile_location = Vector2()); + Vector2 atlastile_get_subtile_by_priority(int p_id, const Node *p_tilemap_node = NULL, const Vector2 &p_tile_location = Vector2()); void tile_set_shape(int p_id, int p_shape_id, const Ref<Shape2D> &p_shape); Ref<Shape2D> tile_get_shape(int p_id, int p_shape_id) const; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 931ee98e47..699410719c 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "visual_shader.h" + #include "core/vmap.h" #include "servers/visual/shader_types.h" @@ -66,6 +67,14 @@ String VisualShaderNode::generate_global(Shader::Mode p_mode, VisualShader::Type return String(); } +String VisualShaderNode::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return String(); +} + +String VisualShaderNode::generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return String(); +} + Vector<StringName> VisualShaderNode::get_editable_properties() const { return Vector<StringName>(); } @@ -108,6 +117,12 @@ void VisualShaderNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "output_port_for_preview"), "set_output_port_for_preview", "get_output_port_for_preview"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_default_input_values", "_get_default_input_values"); ADD_SIGNAL(MethodInfo("editor_refresh_request")); + + BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR); + BIND_ENUM_CONSTANT(PORT_TYPE_VECTOR); + BIND_ENUM_CONSTANT(PORT_TYPE_BOOLEAN); + BIND_ENUM_CONSTANT(PORT_TYPE_TRANSFORM); + BIND_ENUM_CONSTANT(PORT_TYPE_ICON_COLOR); } VisualShaderNode::VisualShaderNode() { @@ -116,6 +131,147 @@ VisualShaderNode::VisualShaderNode() { ///////////////////////////////////////////////////////// +void VisualShaderNodeCustom::update_ports() { + ERR_FAIL_COND(!get_script_instance()); + + input_ports.clear(); + if (get_script_instance()->has_method("_get_input_port_count")) { + int input_port_count = (int)get_script_instance()->call("_get_input_port_count"); + bool has_name = get_script_instance()->has_method("_get_input_port_name"); + bool has_type = get_script_instance()->has_method("_get_input_port_type"); + for (int i = 0; i < input_port_count; i++) { + Port port; + if (has_name) { + port.name = (String)get_script_instance()->call("_get_input_port_name", i); + } else { + port.name = "in" + itos(i); + } + if (has_type) { + port.type = (int)get_script_instance()->call("_get_input_port_type", i); + } else { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } + input_ports.push_back(port); + } + } + output_ports.clear(); + if (get_script_instance()->has_method("_get_output_port_count")) { + int output_port_count = (int)get_script_instance()->call("_get_output_port_count"); + bool has_name = get_script_instance()->has_method("_get_output_port_name"); + bool has_type = get_script_instance()->has_method("_get_output_port_type"); + for (int i = 0; i < output_port_count; i++) { + Port port; + if (has_name) { + port.name = (String)get_script_instance()->call("_get_output_port_name", i); + } else { + port.name = "out" + itos(i); + } + if (has_type) { + port.type = (int)get_script_instance()->call("_get_output_port_type", i); + } else { + port.type = (int)PortType::PORT_TYPE_SCALAR; + } + output_ports.push_back(port); + } + } +} + +String VisualShaderNodeCustom::get_caption() const { + ERR_FAIL_COND_V(!get_script_instance(), ""); + if (get_script_instance()->has_method("_get_name")) { + return (String)get_script_instance()->call("_get_name"); + } + return "Unnamed"; +} + +int VisualShaderNodeCustom::get_input_port_count() const { + return input_ports.size(); +} + +VisualShaderNodeCustom::PortType VisualShaderNodeCustom::get_input_port_type(int p_port) const { + ERR_FAIL_INDEX_V(p_port, input_ports.size(), PORT_TYPE_SCALAR); + return (PortType)input_ports[p_port].type; +} + +String VisualShaderNodeCustom::get_input_port_name(int p_port) const { + ERR_FAIL_INDEX_V(p_port, input_ports.size(), ""); + return input_ports[p_port].name; +} + +int VisualShaderNodeCustom::get_output_port_count() const { + return output_ports.size(); +} + +VisualShaderNodeCustom::PortType VisualShaderNodeCustom::get_output_port_type(int p_port) const { + ERR_FAIL_INDEX_V(p_port, output_ports.size(), PORT_TYPE_SCALAR); + return (PortType)output_ports[p_port].type; +} + +String VisualShaderNodeCustom::get_output_port_name(int p_port) const { + ERR_FAIL_INDEX_V(p_port, output_ports.size(), ""); + return output_ports[p_port].name; +} + +String VisualShaderNodeCustom::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + ERR_FAIL_COND_V(!get_script_instance(), ""); + ERR_FAIL_COND_V(!get_script_instance()->has_method("_get_code"), ""); + Array input_vars; + for (int i = 0; i < get_input_port_count(); i++) { + input_vars.push_back(p_input_vars[i]); + } + Array output_vars; + for (int i = 0; i < get_output_port_count(); i++) { + output_vars.push_back(p_output_vars[i]); + } + String code = "\t{\n"; + String _code = (String)get_script_instance()->call("_get_code", input_vars, output_vars, (int)p_mode, (int)p_type); + bool nend = _code.ends_with("\n"); + _code = _code.insert(0, "\t\t"); + _code = _code.replace("\n", "\n\t\t"); + code += _code; + if (!nend) { + code += "\n\t}"; + } else { + code.remove(code.size() - 1); + code += "}"; + } + return code; +} + +String VisualShaderNodeCustom::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + ERR_FAIL_COND_V(!get_script_instance(), ""); + if (get_script_instance()->has_method("_get_global_code")) { + String code = "// " + get_caption() + "\n"; + code += (String)get_script_instance()->call("_get_global_code", (int)p_mode); + code += "\n"; + return code; + } + return ""; +} + +void VisualShaderNodeCustom::_bind_methods() { + + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_name")); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_description")); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_category")); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_subcategory")); + BIND_VMETHOD(MethodInfo(Variant::INT, "_get_return_icon_type")); + BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_count")); + BIND_VMETHOD(MethodInfo(Variant::INT, "_get_input_port_type", PropertyInfo(Variant::INT, "port"))); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_input_port_name", PropertyInfo(Variant::INT, "port"))); + BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_count")); + BIND_VMETHOD(MethodInfo(Variant::INT, "_get_output_port_type", PropertyInfo(Variant::INT, "port"))); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_output_port_name", PropertyInfo(Variant::INT, "port"))); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_code", PropertyInfo(Variant::ARRAY, "input_vars"), PropertyInfo(Variant::ARRAY, "output_vars"), PropertyInfo(Variant::INT, "mode"), PropertyInfo(Variant::INT, "type"))); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_global_code", PropertyInfo(Variant::INT, "mode"))); +} + +VisualShaderNodeCustom::VisualShaderNodeCustom() { +} + +///////////////////////////////////////////////////////// + void VisualShader::add_node(Type p_type, const Ref<VisualShaderNode> &p_node, const Vector2 &p_position, int p_id) { ERR_FAIL_COND(p_node.is_null()); ERR_FAIL_COND(p_id < 2); @@ -141,6 +297,11 @@ void VisualShader::add_node(Type p_type, const Ref<VisualShaderNode> &p_node, co n.node->connect("changed", this, "_queue_update"); + Ref<VisualShaderNodeCustom> custom = n.node; + if (custom.is_valid()) { + custom->update_ports(); + } + g->nodes[p_id] = n; _queue_update(); @@ -159,6 +320,7 @@ Vector2 VisualShader::get_node_position(Type p_type, int p_id) const { ERR_FAIL_COND_V(!g->nodes.has(p_id), Vector2()); return g->nodes[p_id].position; } + Ref<VisualShaderNode> VisualShader::get_node(Type p_type, int p_id) const { ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Ref<VisualShaderNode>()); const Graph *g = &graph[p_type]; @@ -240,6 +402,9 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po if (!g->nodes.has(p_from_node)) return false; + if (p_from_node == p_to_node) + return false; + if (p_from_port < 0 || p_from_port >= g->nodes[p_from_node].node->get_output_port_count()) return false; @@ -252,7 +417,7 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port); VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port); - if (MAX(0, from_port_type - 1) != (MAX(0, to_port_type - 1))) { + if (!is_port_types_compatible(from_port_type, to_port_type)) { return false; } @@ -266,6 +431,22 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po return true; } +bool VisualShader::is_port_types_compatible(int p_a, int p_b) const { + return MAX(0, p_a - 2) == (MAX(0, p_b - 2)); +} + +void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Graph *g = &graph[p_type]; + Connection c; + c.from_node = p_from_node; + c.from_port = p_from_port; + c.to_node = p_to_node; + c.to_port = p_to_port; + g->connections.push_back(c); + _queue_update(); +} + Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ERR_CANT_CONNECT); Graph *g = &graph[p_type]; @@ -278,11 +459,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, VisualShaderNode::PortType from_port_type = g->nodes[p_from_node].node->get_output_port_type(p_from_port); VisualShaderNode::PortType to_port_type = g->nodes[p_to_node].node->get_input_port_type(p_to_port); - if (MAX(0, from_port_type - 1) != (MAX(0, to_port_type - 1))) { - ERR_EXPLAIN("Incompatible port types (scalar/vec with transform"); - ERR_FAIL_V(ERR_INVALID_PARAMETER) - return ERR_INVALID_PARAMETER; - } + ERR_FAIL_COND_V_MSG(!is_port_types_compatible(from_port_type, to_port_type), ERR_INVALID_PARAMETER, "Incompatible port types (scalar/vec/bool) with transform."); for (List<Connection>::Element *E = g->connections.front(); E; E = E->next()) { @@ -301,6 +478,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, _queue_update(); return OK; } + void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { ERR_FAIL_INDEX(p_type, TYPE_MAX); Graph *g = &graph[p_type]; @@ -331,6 +509,7 @@ Array VisualShader::_get_node_connections(Type p_type) const { return ret; } + void VisualShader::get_node_connections(Type p_type, List<Connection> *r_connections) const { ERR_FAIL_INDEX(p_type, TYPE_MAX); const Graph *g = &graph[p_type]; @@ -426,10 +605,32 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port ERR_FAIL_COND_V(node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_TRANSFORM, String()); StringBuilder global_code; + StringBuilder global_code_per_node; + Map<Type, StringBuilder> global_code_per_func; StringBuilder code; + Set<StringName> classes; global_code += String() + "shader_type canvas_item;\n"; + String global_expressions; + for (int i = 0, index = 0; i < TYPE_MAX; i++) { + for (Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { + Ref<VisualShaderNodeGlobalExpression> global_expression = Object::cast_to<VisualShaderNodeGlobalExpression>(E->get().node.ptr()); + if (global_expression.is_valid()) { + + String expr = ""; + expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; + expr += global_expression->generate_global(get_mode(), Type(i), -1); + expr = expr.replace("\n", "\n\t"); + expr += "\n"; + global_expressions += expr; + } + } + } + + global_code += "\n"; + global_code += global_expressions; + //make it faster to go around through shader VMap<ConnectionKey, const List<Connection>::Element *> input_connections; VMap<ConnectionKey, const List<Connection>::Element *> output_connections; @@ -451,11 +652,13 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port code += "\nvoid fragment() {\n"; Set<int> processed; - Error err = _write_node(p_type, global_code, code, default_tex_params, input_connections, output_connections, p_node, processed, true); + Error err = _write_node(p_type, global_code, global_code_per_node, global_code_per_func, code, default_tex_params, input_connections, output_connections, p_node, processed, true, classes); ERR_FAIL_COND_V(err != OK, String()); if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_SCALAR) { code += "\tCOLOR.rgb = vec3( n_out" + itos(p_node) + "p" + itos(p_port) + " );\n"; + } else if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_BOOLEAN) { + code += "\tCOLOR.rgb = vec3( n_out" + itos(p_node) + "p" + itos(p_port) + " ? 1.0 : 0.0 );\n"; } else { code += "\tCOLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n"; } @@ -464,6 +667,7 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port //set code secretly global_code += "\n\n"; String final_code = global_code; + final_code += global_code_per_node; final_code += code; return final_code; } @@ -472,6 +676,54 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port #define IS_SYMBOL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z') || ((m_d) >= '0' && (m_d) <= '9') || (m_d) == '_') +String VisualShader::validate_port_name(const String &p_name, const List<String> &p_input_ports, const List<String> &p_output_ports) const { + String name = p_name; + + while (name.length() && !IS_INITIAL_CHAR(name[0])) { + name = name.substr(1, name.length() - 1); + } + + if (name != String()) { + + String valid_name; + + for (int i = 0; i < name.length(); i++) { + if (IS_SYMBOL_CHAR(name[i])) { + valid_name += String::chr(name[i]); + } else if (name[i] == ' ') { + valid_name += "_"; + } + } + + name = valid_name; + } + + String valid_name = name; + bool is_equal = false; + + for (int i = 0; i < p_input_ports.size(); i++) { + if (name == p_input_ports[i]) { + is_equal = true; + break; + } + } + + if (!is_equal) { + for (int i = 0; i < p_output_ports.size(); i++) { + if (name == p_output_ports[i]) { + is_equal = true; + break; + } + } + } + + if (is_equal) { + name = ""; + } + + return name; +} + String VisualShader::validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const { String name = p_name; //validate name first @@ -569,7 +821,7 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { String mode = name.get_slicec('/', 1); int value = p_value; if (value == 0) { - modes.erase(mode); //means its default anyway, so dont store it + modes.erase(mode); //means it's default anyway, so don't store it } else { modes[mode] = value; } @@ -591,7 +843,7 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { Vector<int> conns = p_value; if (conns.size() % 4 == 0) { for (int i = 0; i < conns.size(); i += 4) { - connect_nodes(type, conns[i + 0], conns[i + 1], conns[i + 2], conns[i + 3]); + connect_nodes_forced(type, conns[i + 0], conns[i + 1], conns[i + 2], conns[i + 3]); } } return true; @@ -606,6 +858,18 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) { } else if (what == "position") { set_node_position(type, id, p_value); return true; + } else if (what == "size") { + ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->set_size(p_value); + return true; + } else if (what == "input_ports") { + ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->set_inputs(p_value); + return true; + } else if (what == "output_ports") { + ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->set_outputs(p_value); + return true; + } else if (what == "expression") { + ((VisualShaderNodeExpression *)get_node(type, id).ptr())->set_expression(p_value); + return true; } } return false; @@ -663,6 +927,18 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const { } else if (what == "position") { r_ret = get_node_position(type, id); return true; + } else if (what == "size") { + r_ret = ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->get_size(); + return true; + } else if (what == "input_ports") { + r_ret = ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->get_inputs(); + return true; + } else if (what == "output_ports") { + r_ret = ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->get_outputs(); + return true; + } else if (what == "expression") { + r_ret = ((VisualShaderNodeExpression *)get_node(type, id).ptr())->get_expression(); + return true; } } return false; @@ -722,12 +998,21 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); } p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + + if (Object::cast_to<VisualShaderNodeGroupBase>(E->get().node.ptr()) != NULL) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/input_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } + if (Object::cast_to<VisualShaderNodeExpression>(E->get().node.ptr()) != NULL) { + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/expression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } } p_list->push_back(PropertyInfo(Variant::POOL_INT_ARRAY, "nodes/" + String(type_string[i]) + "/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } } -Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview) const { +Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBuilder &global_code_per_node, Map<Type, StringBuilder> &global_code_per_func, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview, Set<StringName> &r_classes) const { const Ref<VisualShaderNode> vsnode = graph[type].nodes[node].node; @@ -744,7 +1029,7 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui continue; } - Error err = _write_node(type, global_code, code, def_tex_params, input_connections, output_connections, from_node, processed, for_preview); + Error err = _write_node(type, global_code, global_code_per_node, global_code_per_func, code, def_tex_params, input_connections, output_connections, from_node, processed, for_preview, r_classes); if (err) return err; } @@ -779,6 +1064,14 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui inputs[i] = "dot(" + src_var + ",vec3(0.333333,0.333333,0.333333))"; } else if (in_type == VisualShaderNode::PORT_TYPE_VECTOR && out_type == VisualShaderNode::PORT_TYPE_SCALAR) { inputs[i] = "vec3(" + src_var + ")"; + } else if (in_type == VisualShaderNode::PORT_TYPE_BOOLEAN && out_type == VisualShaderNode::PORT_TYPE_VECTOR) { + inputs[i] = "all(bvec3(" + src_var + "))"; + } else if (in_type == VisualShaderNode::PORT_TYPE_BOOLEAN && out_type == VisualShaderNode::PORT_TYPE_SCALAR) { + inputs[i] = src_var + ">0.0?true:false"; + } else if (in_type == VisualShaderNode::PORT_TYPE_SCALAR && out_type == VisualShaderNode::PORT_TYPE_BOOLEAN) { + inputs[i] = src_var + "?1.0:0.0"; + } else if (in_type == VisualShaderNode::PORT_TYPE_VECTOR && out_type == VisualShaderNode::PORT_TYPE_BOOLEAN) { + inputs[i] = "vec3(" + src_var + "?1.0:0.0)"; } } else { @@ -787,6 +1080,10 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui float val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); code += "\tfloat " + inputs[i] + " = " + vformat("%.5f", val) + ";\n"; + } else if (defval.get_type() == Variant::BOOL) { + bool val = defval; + inputs[i] = "n_in" + itos(node) + "p" + itos(i); + code += "\nbool " + inputs[i] + " = " + (val ? "true" : "false") + ";\n"; } else if (defval.get_type() == Variant::VECTOR3) { Vector3 val = defval; inputs[i] = "n_in" + itos(node) + "p" + itos(i); @@ -823,8 +1120,10 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_SCALAR: code += String() + "\tfloat " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_VECTOR: code += String() + "\tvec3 " + outputs[i] + ";\n"; break; + case VisualShaderNode::PORT_TYPE_BOOLEAN: code += String() + "\tbool " + outputs[i] + ";\n"; break; case VisualShaderNode::PORT_TYPE_TRANSFORM: code += String() + "\tmat4 " + outputs[i] + ";\n"; break; - default: {} + default: { + } } } @@ -837,10 +1136,18 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui bool skip_global = input.is_valid() && for_preview; if (!skip_global) { + global_code += vsnode->generate_global(get_mode(), type, node); + + if (!r_classes.has(vsnode->get_class_name())) { + global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node); + for (int i = 0; i < TYPE_MAX; i++) { + global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node); + } + r_classes.insert(vsnode->get_class_name()); + } } - //handle normally code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview); code += "\n"; // @@ -856,8 +1163,12 @@ void VisualShader::_update_shader() const { dirty = false; StringBuilder global_code; + StringBuilder global_code_per_node; + Map<Type, StringBuilder> global_code_per_func; StringBuilder code; Vector<VisualShader::DefaultTextureParam> default_tex_params; + Set<StringName> classes; + List<int> insertion_pos; static const char *shader_mode_str[Shader::MODE_MAX] = { "spatial", "canvas_item", "particles" }; global_code += String() + "shader_type " + shader_mode_str[shader_mode] + ";\n"; @@ -913,6 +1224,22 @@ void VisualShader::_update_shader() const { static const char *func_name[TYPE_MAX] = { "vertex", "fragment", "light" }; + String global_expressions; + for (int i = 0, index = 0; i < TYPE_MAX; i++) { + for (Map<int, Node>::Element *E = graph[i].nodes.front(); E; E = E->next()) { + Ref<VisualShaderNodeGlobalExpression> global_expression = Object::cast_to<VisualShaderNodeGlobalExpression>(E->get().node.ptr()); + if (global_expression.is_valid()) { + + String expr = ""; + expr += "// " + global_expression->get_caption() + ":" + itos(index++) + "\n"; + expr += global_expression->generate_global(get_mode(), Type(i), -1); + expr = expr.replace("\n", "\n\t"); + expr += "\n"; + global_expressions += expr; + } + } + } + for (int i = 0; i < TYPE_MAX; i++) { //make it faster to go around through shader @@ -936,8 +1263,9 @@ void VisualShader::_update_shader() const { code += "\nvoid " + String(func_name[i]) + "() {\n"; Set<int> processed; - Error err = _write_node(Type(i), global_code, code, default_tex_params, input_connections, output_connections, NODE_ID_OUTPUT, processed, false); + Error err = _write_node(Type(i), global_code, global_code_per_node, global_code_per_func, code, default_tex_params, input_connections, output_connections, NODE_ID_OUTPUT, processed, false, classes); ERR_FAIL_COND(err != OK); + insertion_pos.push_back(code.get_string_length()); code += "}\n"; } @@ -945,11 +1273,22 @@ void VisualShader::_update_shader() const { //set code secretly global_code += "\n\n"; String final_code = global_code; - final_code += code; + final_code += global_code_per_node; + final_code += global_expressions; + String tcode = code; + for (int i = 0; i < TYPE_MAX; i++) { + tcode = tcode.insert(insertion_pos[i], global_code_per_func[Type(i)]); + } + final_code += tcode; + const_cast<VisualShader *>(this)->set_code(final_code); for (int i = 0; i < default_tex_params.size(); i++) { const_cast<VisualShader *>(this)->set_default_texture_param(default_tex_params[i].name, default_tex_params[i].param); } + if (previous_code != final_code) { + const_cast<VisualShader *>(this)->emit_signal("changed"); + } + previous_code = final_code; } void VisualShader::_queue_update() { @@ -974,26 +1313,33 @@ void VisualShader::_input_type_changed(Type p_type, int p_id) { } } +void VisualShader::rebuild() { + dirty = true; + _update_shader(); +} + void VisualShader::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mode", "mode"), &VisualShader::set_mode); ClassDB::bind_method(D_METHOD("add_node", "type", "node", "position", "id"), &VisualShader::add_node); - ClassDB::bind_method(D_METHOD("set_node_position", "type", "id", "position"), &VisualShader::set_node_position); - ClassDB::bind_method(D_METHOD("get_node", "type", "id"), &VisualShader::get_node); + + ClassDB::bind_method(D_METHOD("set_node_position", "type", "id", "position"), &VisualShader::set_node_position); ClassDB::bind_method(D_METHOD("get_node_position", "type", "id"), &VisualShader::get_node_position); ClassDB::bind_method(D_METHOD("get_node_list", "type"), &VisualShader::get_node_list); ClassDB::bind_method(D_METHOD("get_valid_node_id", "type"), &VisualShader::get_valid_node_id); ClassDB::bind_method(D_METHOD("remove_node", "type", "id"), &VisualShader::remove_node); + ClassDB::bind_method(D_METHOD("rebuild"), &VisualShader::rebuild); ClassDB::bind_method(D_METHOD("is_node_connection", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::is_node_connection); ClassDB::bind_method(D_METHOD("can_connect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::is_node_connection); ClassDB::bind_method(D_METHOD("connect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::connect_nodes); ClassDB::bind_method(D_METHOD("disconnect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::disconnect_nodes); + ClassDB::bind_method(D_METHOD("connect_nodes_forced", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::connect_nodes_forced); ClassDB::bind_method(D_METHOD("get_node_connections", "type"), &VisualShader::_get_node_connections); @@ -1046,32 +1392,40 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "modelview", "MODELVIEW_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection", "INV_PROJECTION_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(VIEWPORT_SIZE, 0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(VIEWPORT_SIZE, 0)" }, // Spatial, Fragment + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "VERTEX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "tangent", "TANGENT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "binormal", "BINORMAL" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "view", "VIEW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv2", "vec3(UV2,0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec2(POINT_COORD,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec3(POINT_COORD,0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "float(FRONT_FACING ? 1.0 : 0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection", "INV_PROJECTION_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(VIEWPORT_SIZE, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(VIEWPORT_SIZE, 0.0)" }, // Spatial, Light + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "view", "VIEW" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light", "LIGHT" }, @@ -1079,19 +1433,24 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "attenuation", "ATTENUATION" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "transmission", "TRANSMISSION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "diffuse", "DIFFUSE_LIGHT" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "specular", "SPECULAR_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_camera", "INV_CAMERA_MATRIX" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "camera", "CAMERA_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "inv_projection", "INV_PROJECTION_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(VIEWPORT_SIZE, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(VIEWPORT_SIZE, 0.0)" }, // Canvas Item, Vertex { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "point_size", "POINT_SIZE" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "texture_pixel_size", "vec3(TEXTURE_PIXEL_SIZE, 1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "world", "WORLD_MATRIX" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection", "PROJECTION_MATRIX" }, @@ -1099,14 +1458,18 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "light_pass", "float(AT_LIGHT_PASS ? 1.0 : 0.0)" }, // Canvas Item, Fragment + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec2(POINT_COORD,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "texture_pixel_size", "vec3(TEXTURE_PIXEL_SIZE, 1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_pixel_size", "vec3(SCREEN_PIXEL_SIZE, 1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec3(POINT_COORD,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "light_pass", "float(AT_LIGHT_PASS ? 1.0 : 0.0)" }, // Canvas Item, Light + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "fragcoord", "FRAGCOORD.xyz" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "NORMAL" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, @@ -1117,10 +1480,11 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_alpha", "LIGHT_COLOR.a" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "light_uv", "vec3(LIGHT_UV,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "shadow_color", "SHADOW_COLOR.rgb" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec2(POINT_COORD,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "texture_pixel_size", "vec3(TEXTURE_PIXEL_SIZE, 1.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "point_coord", "vec3(POINT_COORD,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, + // Particles, Vertex { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" }, { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" }, @@ -1151,16 +1515,16 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(UV,0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "side", "1.0" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(1.0,1.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(1.0,1.0, 0.0)" }, // Spatial, Light { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "normal", "vec3(0.0,0.0,1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "vp_size", "vec3(1.0, 1.0, 0.0)" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "viewport_size", "vec3(1.0, 1.0, 0.0)" }, // Canvas Item, Vertex { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "vertex", "vec3(VERTEX,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, @@ -1171,7 +1535,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Canvas Item, Light { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "uv", "vec3(UV,0.0)" }, @@ -1179,7 +1543,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(UV,0.0)" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR, "screen_uv", "vec3(SCREEN_UV,0.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Particles, Vertex { Shader::MODE_PARTICLES, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "vec3(1.0)" }, @@ -1244,6 +1608,11 @@ String VisualShaderNodeInput::generate_code(Shader::Mode p_mode, VisualShader::T case PORT_TYPE_TRANSFORM: { code = "\t" + p_output_vars[0] + " = mat4( vec4(1.0,0.0,0.0,0.0), vec4(0.0,1.0,0.0,0.0), vec4(0.0,0.0,1.0,0.0), vec4(0.0,0.0,0.0,1.0) );\n"; } break; //default (none found) is scalar + case PORT_TYPE_BOOLEAN: { + code = "\t" + p_output_vars[0] + " = false;\n"; + } break; + default: + break; } } @@ -1585,3 +1954,623 @@ void VisualShaderNodeUniform::_bind_methods() { VisualShaderNodeUniform::VisualShaderNodeUniform() { } + +////////////// GroupBase + +String VisualShaderNodeGroupBase::get_caption() const { + return "Group"; +} + +void VisualShaderNodeGroupBase::set_size(const Vector2 &p_size) { + size = p_size; +} + +Vector2 VisualShaderNodeGroupBase::get_size() const { + return size; +} + +void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) { + + if (inputs == p_inputs) + return; + + clear_input_ports(); + + inputs = p_inputs; + + Vector<String> input_strings = inputs.split(";", false); + + int input_port_count = input_strings.size(); + + for (int i = 0; i < input_port_count; i++) { + + Vector<String> arr = input_strings[i].split(","); + ERR_FAIL_COND(arr.size() != 3); + + int port_idx = arr[0].to_int(); + int port_type = arr[1].to_int(); + String port_name = arr[2]; + + Port port; + port.type = (PortType)port_type; + port.name = port_name; + input_ports[port_idx] = port; + } +} + +String VisualShaderNodeGroupBase::get_inputs() const { + return inputs; +} + +void VisualShaderNodeGroupBase::set_outputs(const String &p_outputs) { + + if (outputs == p_outputs) + return; + + clear_output_ports(); + + outputs = p_outputs; + + Vector<String> output_strings = outputs.split(";", false); + + int output_port_count = output_strings.size(); + + for (int i = 0; i < output_port_count; i++) { + + Vector<String> arr = output_strings[i].split(","); + ERR_FAIL_COND(arr.size() != 3); + + int port_idx = arr[0].to_int(); + int port_type = arr[1].to_int(); + String port_name = arr[2]; + + Port port; + port.type = (PortType)port_type; + port.name = port_name; + output_ports[port_idx] = port; + } +} + +String VisualShaderNodeGroupBase::get_outputs() const { + return outputs; +} + +bool VisualShaderNodeGroupBase::is_valid_port_name(const String &p_name) const { + if (!p_name.is_valid_identifier()) { + return false; + } + for (int i = 0; i < get_input_port_count(); i++) { + if (get_input_port_name(i) == p_name) { + return false; + } + } + for (int i = 0; i < get_output_port_count(); i++) { + if (get_output_port_name(i) == p_name) { + return false; + } + } + return true; +} + +void VisualShaderNodeGroupBase::add_input_port(int p_id, int p_type, const String &p_name) { + + String str = itos(p_id) + "," + itos(p_type) + "," + p_name + ";"; + Vector<String> inputs_strings = inputs.split(";", false); + int index = 0; + if (p_id < inputs_strings.size()) { + for (int i = 0; i < inputs_strings.size(); i++) { + if (i == p_id) { + inputs = inputs.insert(index, str); + break; + } + index += inputs_strings[i].size(); + } + } else { + inputs += str; + } + + inputs_strings = inputs.split(";", false); + index = 0; + + for (int i = 0; i < inputs_strings.size(); i++) { + int count = 0; + for (int j = 0; j < inputs_strings[i].size(); j++) { + if (inputs_strings[i][j] == ',') { + break; + } + count++; + } + + inputs.erase(index, count); + inputs = inputs.insert(index, itos(i)); + index += inputs_strings[i].size(); + } + + _apply_port_changes(); +} + +void VisualShaderNodeGroupBase::remove_input_port(int p_id) { + + ERR_FAIL_COND(!has_input_port(p_id)); + + Vector<String> inputs_strings = inputs.split(";", false); + int count = 0; + int index = 0; + for (int i = 0; i < inputs_strings.size(); i++) { + Vector<String> arr = inputs_strings[i].split(","); + if (arr[0].to_int() == p_id) { + count = inputs_strings[i].size(); + break; + } + index += inputs_strings[i].size(); + } + inputs.erase(index, count); + + inputs_strings = inputs.split(";", false); + for (int i = p_id; i < inputs_strings.size(); i++) { + inputs = inputs.replace_first(inputs_strings[i].split(",")[0], itos(i)); + } + + _apply_port_changes(); +} + +int VisualShaderNodeGroupBase::get_input_port_count() const { + return input_ports.size(); +} + +bool VisualShaderNodeGroupBase::has_input_port(int p_id) const { + return input_ports.has(p_id); +} + +void VisualShaderNodeGroupBase::add_output_port(int p_id, int p_type, const String &p_name) { + + String str = itos(p_id) + "," + itos(p_type) + "," + p_name + ";"; + Vector<String> outputs_strings = outputs.split(";", false); + int index = 0; + if (p_id < outputs_strings.size()) { + for (int i = 0; i < outputs_strings.size(); i++) { + if (i == p_id) { + outputs = outputs.insert(index, str); + break; + } + index += outputs_strings[i].size(); + } + } else { + outputs += str; + } + + outputs_strings = outputs.split(";", false); + index = 0; + + for (int i = 0; i < outputs_strings.size(); i++) { + int count = 0; + for (int j = 0; j < outputs_strings[i].size(); j++) { + if (outputs_strings[i][j] == ',') { + break; + } + count++; + } + + outputs.erase(index, count); + outputs = outputs.insert(index, itos(i)); + index += outputs_strings[i].size(); + } + + _apply_port_changes(); +} + +void VisualShaderNodeGroupBase::remove_output_port(int p_id) { + + ERR_FAIL_COND(!has_output_port(p_id)); + + Vector<String> outputs_strings = outputs.split(";", false); + int count = 0; + int index = 0; + for (int i = 0; i < outputs_strings.size(); i++) { + Vector<String> arr = outputs_strings[i].split(","); + if (arr[0].to_int() == p_id) { + count = outputs_strings[i].size(); + break; + } + index += outputs_strings[i].size(); + } + outputs.erase(index, count); + + outputs_strings = outputs.split(";", false); + for (int i = p_id; i < outputs_strings.size(); i++) { + outputs = outputs.replace_first(outputs_strings[i].split(",")[0], itos(i)); + } + + _apply_port_changes(); +} + +int VisualShaderNodeGroupBase::get_output_port_count() const { + return output_ports.size(); +} + +bool VisualShaderNodeGroupBase::has_output_port(int p_id) const { + return output_ports.has(p_id); +} + +void VisualShaderNodeGroupBase::clear_input_ports() { + input_ports.clear(); +} + +void VisualShaderNodeGroupBase::clear_output_ports() { + output_ports.clear(); +} + +void VisualShaderNodeGroupBase::set_input_port_type(int p_id, int p_type) { + + ERR_FAIL_COND(!has_input_port(p_id)); + ERR_FAIL_COND(p_type < 0 || p_type > PORT_TYPE_TRANSFORM); + + if (input_ports[p_id].type == p_type) + return; + + Vector<String> inputs_strings = inputs.split(";", false); + int count = 0; + int index = 0; + for (int i = 0; i < inputs_strings.size(); i++) { + Vector<String> arr = inputs_strings[i].split(","); + if (arr[0].to_int() == p_id) { + index += arr[0].size(); + count = arr[1].size() - 1; + break; + } + index += inputs_strings[i].size(); + } + + inputs.erase(index, count); + + inputs = inputs.insert(index, itos(p_type)); + + _apply_port_changes(); +} + +VisualShaderNodeGroupBase::PortType VisualShaderNodeGroupBase::get_input_port_type(int p_id) const { + ERR_FAIL_COND_V(!input_ports.has(p_id), (PortType)0); + return input_ports[p_id].type; +} + +void VisualShaderNodeGroupBase::set_input_port_name(int p_id, const String &p_name) { + + ERR_FAIL_COND(!has_input_port(p_id)); + ERR_FAIL_COND(!is_valid_port_name(p_name)); + + if (input_ports[p_id].name == p_name) + return; + + Vector<String> inputs_strings = inputs.split(";", false); + int count = 0; + int index = 0; + for (int i = 0; i < inputs_strings.size(); i++) { + Vector<String> arr = inputs_strings[i].split(","); + if (arr[0].to_int() == p_id) { + index += arr[0].size() + arr[1].size(); + count = arr[2].size() - 1; + break; + } + index += inputs_strings[i].size(); + } + + inputs.erase(index, count); + + inputs = inputs.insert(index, p_name); + + _apply_port_changes(); +} + +String VisualShaderNodeGroupBase::get_input_port_name(int p_id) const { + ERR_FAIL_COND_V(!input_ports.has(p_id), ""); + return input_ports[p_id].name; +} + +void VisualShaderNodeGroupBase::set_output_port_type(int p_id, int p_type) { + + ERR_FAIL_COND(!has_output_port(p_id)); + ERR_FAIL_COND(p_type < 0 || p_type > PORT_TYPE_TRANSFORM); + + if (output_ports[p_id].type == p_type) + return; + + Vector<String> output_strings = outputs.split(";", false); + int count = 0; + int index = 0; + for (int i = 0; i < output_strings.size(); i++) { + Vector<String> arr = output_strings[i].split(","); + if (arr[0].to_int() == p_id) { + index += arr[0].size(); + count = arr[1].size() - 1; + break; + } + index += output_strings[i].size(); + } + + outputs.erase(index, count); + + outputs = outputs.insert(index, itos(p_type)); + + _apply_port_changes(); +} + +VisualShaderNodeGroupBase::PortType VisualShaderNodeGroupBase::get_output_port_type(int p_id) const { + ERR_FAIL_COND_V(!output_ports.has(p_id), (PortType)0); + return output_ports[p_id].type; +} + +void VisualShaderNodeGroupBase::set_output_port_name(int p_id, const String &p_name) { + + ERR_FAIL_COND(!has_output_port(p_id)); + ERR_FAIL_COND(!is_valid_port_name(p_name)); + + if (output_ports[p_id].name == p_name) + return; + + Vector<String> output_strings = outputs.split(";", false); + int count = 0; + int index = 0; + for (int i = 0; i < output_strings.size(); i++) { + Vector<String> arr = output_strings[i].split(","); + if (arr[0].to_int() == p_id) { + index += arr[0].size() + arr[1].size(); + count = arr[2].size() - 1; + break; + } + index += output_strings[i].size(); + } + + outputs.erase(index, count); + + outputs = outputs.insert(index, p_name); + + _apply_port_changes(); +} + +String VisualShaderNodeGroupBase::get_output_port_name(int p_id) const { + ERR_FAIL_COND_V(!output_ports.has(p_id), ""); + return output_ports[p_id].name; +} + +int VisualShaderNodeGroupBase::get_free_input_port_id() const { + return input_ports.size(); +} + +int VisualShaderNodeGroupBase::get_free_output_port_id() const { + return output_ports.size(); +} + +void VisualShaderNodeGroupBase::set_control(Control *p_control, int p_index) { + controls[p_index] = p_control; +} + +Control *VisualShaderNodeGroupBase::get_control(int p_index) { + ERR_FAIL_COND_V(!controls.has(p_index), NULL); + return controls[p_index]; +} + +void VisualShaderNodeGroupBase::_apply_port_changes() { + + Vector<String> inputs_strings = inputs.split(";", false); + Vector<String> outputs_strings = outputs.split(";", false); + + clear_input_ports(); + clear_output_ports(); + + for (int i = 0; i < inputs_strings.size(); i++) { + Vector<String> arr = inputs_strings[i].split(","); + Port port; + port.type = (PortType)arr[1].to_int(); + port.name = arr[2]; + input_ports[i] = port; + } + for (int i = 0; i < outputs_strings.size(); i++) { + Vector<String> arr = outputs_strings[i].split(","); + Port port; + port.type = (PortType)arr[1].to_int(); + port.name = arr[2]; + output_ports[i] = port; + } +} + +void VisualShaderNodeGroupBase::set_editable(bool p_enabled) { + editable = p_enabled; +} + +bool VisualShaderNodeGroupBase::is_editable() const { + return editable; +} + +void VisualShaderNodeGroupBase::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_size", "size"), &VisualShaderNodeGroupBase::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &VisualShaderNodeGroupBase::get_size); + + ClassDB::bind_method(D_METHOD("set_inputs", "inputs"), &VisualShaderNodeGroupBase::set_inputs); + ClassDB::bind_method(D_METHOD("get_inputs"), &VisualShaderNodeGroupBase::get_inputs); + + ClassDB::bind_method(D_METHOD("set_outputs", "outputs"), &VisualShaderNodeGroupBase::set_outputs); + ClassDB::bind_method(D_METHOD("get_outputs"), &VisualShaderNodeGroupBase::get_outputs); + + ClassDB::bind_method(D_METHOD("is_valid_port_name", "name"), &VisualShaderNodeGroupBase::is_valid_port_name); + + ClassDB::bind_method(D_METHOD("add_input_port", "id", "type", "name"), &VisualShaderNodeGroupBase::add_input_port); + ClassDB::bind_method(D_METHOD("remove_input_port", "id"), &VisualShaderNodeGroupBase::remove_input_port); + ClassDB::bind_method(D_METHOD("get_input_port_count"), &VisualShaderNodeGroupBase::get_input_port_count); + ClassDB::bind_method(D_METHOD("has_input_port", "id"), &VisualShaderNodeGroupBase::has_input_port); + ClassDB::bind_method(D_METHOD("clear_input_ports"), &VisualShaderNodeGroupBase::clear_input_ports); + + ClassDB::bind_method(D_METHOD("add_output_port", "id", "type", "name"), &VisualShaderNodeGroupBase::add_output_port); + ClassDB::bind_method(D_METHOD("remove_output_port", "id"), &VisualShaderNodeGroupBase::remove_output_port); + ClassDB::bind_method(D_METHOD("get_output_port_count"), &VisualShaderNodeGroupBase::get_output_port_count); + ClassDB::bind_method(D_METHOD("has_output_port", "id"), &VisualShaderNodeGroupBase::has_output_port); + ClassDB::bind_method(D_METHOD("clear_output_ports"), &VisualShaderNodeGroupBase::clear_output_ports); + + ClassDB::bind_method(D_METHOD("set_input_port_name"), &VisualShaderNodeGroupBase::set_input_port_name); + ClassDB::bind_method(D_METHOD("set_input_port_type"), &VisualShaderNodeGroupBase::set_input_port_type); + ClassDB::bind_method(D_METHOD("set_output_port_name"), &VisualShaderNodeGroupBase::set_output_port_name); + ClassDB::bind_method(D_METHOD("set_output_port_type"), &VisualShaderNodeGroupBase::set_output_port_type); + + ClassDB::bind_method(D_METHOD("get_free_input_port_id"), &VisualShaderNodeGroupBase::get_free_input_port_id); + ClassDB::bind_method(D_METHOD("get_free_output_port_id"), &VisualShaderNodeGroupBase::get_free_output_port_id); + + ClassDB::bind_method(D_METHOD("set_control", "control", "index"), &VisualShaderNodeGroupBase::set_control); + ClassDB::bind_method(D_METHOD("get_control", "index"), &VisualShaderNodeGroupBase::get_control); + + ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &VisualShaderNodeGroupBase::set_editable); + ClassDB::bind_method(D_METHOD("is_editable"), &VisualShaderNodeGroupBase::is_editable); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); +} + +String VisualShaderNodeGroupBase::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return ""; +} + +VisualShaderNodeGroupBase::VisualShaderNodeGroupBase() { + size = Size2(0, 0); + inputs = ""; + outputs = ""; + editable = false; +} + +////////////// Expression + +String VisualShaderNodeExpression::get_caption() const { + return "Expression"; +} + +void VisualShaderNodeExpression::set_expression(const String &p_expression) { + expression = p_expression; +} + +void VisualShaderNodeExpression::build() { + emit_changed(); +} + +String VisualShaderNodeExpression::get_expression() const { + return expression; +} + +String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + String _expression = expression; + + _expression = _expression.insert(0, "\n"); + _expression = _expression.replace("\n", "\n\t\t"); + + static Vector<String> pre_symbols; + if (pre_symbols.empty()) { + pre_symbols.push_back("\t"); + pre_symbols.push_back(","); + pre_symbols.push_back("{"); + pre_symbols.push_back("["); + pre_symbols.push_back("("); + pre_symbols.push_back(" "); + pre_symbols.push_back("-"); + pre_symbols.push_back("*"); + pre_symbols.push_back("/"); + pre_symbols.push_back("+"); + pre_symbols.push_back("="); + pre_symbols.push_back("&"); + pre_symbols.push_back("|"); + pre_symbols.push_back("!"); + } + + static Vector<String> post_symbols; + if (post_symbols.empty()) { + post_symbols.push_back("\0"); + post_symbols.push_back("\t"); + post_symbols.push_back("\n"); + post_symbols.push_back(";"); + post_symbols.push_back("}"); + post_symbols.push_back("]"); + post_symbols.push_back(")"); + post_symbols.push_back(" "); + post_symbols.push_back("."); + post_symbols.push_back("-"); + post_symbols.push_back("*"); + post_symbols.push_back("/"); + post_symbols.push_back("+"); + post_symbols.push_back("="); + post_symbols.push_back("&"); + post_symbols.push_back("|"); + post_symbols.push_back("!"); + } + + for (int i = 0; i < get_input_port_count(); i++) { + for (int j = 0; j < pre_symbols.size(); j++) { + for (int k = 0; k < post_symbols.size(); k++) { + _expression = _expression.replace(pre_symbols[j] + get_input_port_name(i) + post_symbols[k], pre_symbols[j] + p_input_vars[i] + post_symbols[k]); + } + } + } + for (int i = 0; i < get_output_port_count(); i++) { + for (int j = 0; j < pre_symbols.size(); j++) { + for (int k = 0; k < post_symbols.size(); k++) { + _expression = _expression.replace(pre_symbols[j] + get_output_port_name(i) + post_symbols[k], pre_symbols[j] + p_output_vars[i] + post_symbols[k]); + } + } + } + + String output_initializer; + + for (int i = 0; i < get_output_port_count(); i++) { + int port_type = get_output_port_type(i); + String tk = ""; + switch (port_type) { + case PORT_TYPE_SCALAR: + tk = "0.0"; + break; + case PORT_TYPE_VECTOR: + tk = "vec3(0.0, 0.0, 0.0)"; + break; + case PORT_TYPE_BOOLEAN: + tk = "false"; + break; + case PORT_TYPE_TRANSFORM: + tk = "mat4(1.0)"; + break; + default: + continue; + } + output_initializer += "\t" + p_output_vars[i] + "=" + tk + ";\n"; + } + + String code; + code += output_initializer; + code += "\t{"; + code += _expression; + code += "\n\t}"; + + return code; +} + +void VisualShaderNodeExpression::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_expression", "expression"), &VisualShaderNodeExpression::set_expression); + ClassDB::bind_method(D_METHOD("get_expression"), &VisualShaderNodeExpression::get_expression); + + ClassDB::bind_method(D_METHOD("build"), &VisualShaderNodeExpression::build); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "expression"), "set_expression", "get_expression"); +} + +VisualShaderNodeExpression::VisualShaderNodeExpression() { + expression = ""; + set_editable(true); +} + +////////////// Global Expression + +String VisualShaderNodeGlobalExpression::get_caption() const { + return "GlobalExpression"; +} + +String VisualShaderNodeGlobalExpression::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return expression; +} + +VisualShaderNodeGlobalExpression::VisualShaderNodeGlobalExpression() { + set_editable(false); +} diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 4792038351..45beb8e6ca 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -32,13 +32,15 @@ #define VISUAL_SHADER_H #include "core/string_builder.h" +#include "scene/gui/control.h" #include "scene/resources/shader.h" class VisualShaderNodeUniform; class VisualShaderNode; class VisualShader : public Shader { - GDCLASS(VisualShader, Shader) + GDCLASS(VisualShader, Shader); + public: enum Type { TYPE_VERTEX, @@ -71,6 +73,7 @@ private: } graph[TYPE_MAX]; Shader::Mode shader_mode; + mutable String previous_code; Array _get_node_connections(Type p_type) const; @@ -101,7 +104,7 @@ private: } }; - Error _write_node(Type p_type, StringBuilder &global_code, StringBuilder &code, Vector<DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview) const; + Error _write_node(Type p_type, StringBuilder &global_code, StringBuilder &global_code_per_node, Map<Type, StringBuilder> &global_code_per_func, StringBuilder &code, Vector<DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview, Set<StringName> &r_classes) const; void _input_type_changed(Type p_type, int p_id); @@ -135,7 +138,10 @@ public: bool can_connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const; Error connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); void disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); + void connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); + bool is_port_types_compatible(int p_a, int p_b) const; + void rebuild(); void get_node_connections(Type p_type, List<Connection> *r_connections) const; void set_mode(Mode p_mode); @@ -148,6 +154,7 @@ public: String generate_preview_shader(Type p_type, int p_node, int p_port, Vector<DefaultTextureParam> &r_default_tex_params) const; + String validate_port_name(const String &p_name, const List<String> &p_input_ports, const List<String> &p_output_ports) const; String validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const; VisualShader(); @@ -159,7 +166,7 @@ VARIANT_ENUM_CAST(VisualShader::Type) /// class VisualShaderNode : public Resource { - GDCLASS(VisualShaderNode, Resource) + GDCLASS(VisualShaderNode, Resource); int port_preview; @@ -175,7 +182,9 @@ public: enum PortType { PORT_TYPE_SCALAR, PORT_TYPE_VECTOR, + PORT_TYPE_BOOLEAN, PORT_TYPE_TRANSFORM, + PORT_TYPE_ICON_COLOR // just a hint for node tree icons, do not use it as actual port type ! }; virtual String get_caption() const = 0; @@ -200,16 +209,56 @@ public: virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const = 0; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty virtual String get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const; VisualShaderNode(); }; + +VARIANT_ENUM_CAST(VisualShaderNode::PortType) + +class VisualShaderNodeCustom : public VisualShaderNode { + GDCLASS(VisualShaderNodeCustom, VisualShaderNode); + + struct Port { + String name; + int type; + }; + + List<Port> input_ports; + List<Port> output_ports; + + friend class VisualShaderEditor; + +protected: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + +protected: + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + + static void _bind_methods(); + +public: + VisualShaderNodeCustom(); + void update_ports(); +}; + ///// class VisualShaderNodeInput : public VisualShaderNode { - GDCLASS(VisualShaderNodeInput, VisualShaderNode) + GDCLASS(VisualShaderNodeInput, VisualShaderNode); friend class VisualShader; VisualShader::Type shader_type; @@ -262,7 +311,8 @@ public: /// class VisualShaderNodeOutput : public VisualShaderNode { - GDCLASS(VisualShaderNodeOutput, VisualShaderNode) + GDCLASS(VisualShaderNodeOutput, VisualShaderNode); + public: friend class VisualShader; VisualShader::Type shader_type; @@ -298,7 +348,7 @@ public: }; class VisualShaderNodeUniform : public VisualShaderNode { - GDCLASS(VisualShaderNodeUniform, VisualShaderNode) + GDCLASS(VisualShaderNodeUniform, VisualShaderNode); String uniform_name; @@ -312,4 +362,110 @@ public: VisualShaderNodeUniform(); }; +class VisualShaderNodeGroupBase : public VisualShaderNode { + GDCLASS(VisualShaderNodeGroupBase, VisualShaderNode); + +private: + void _apply_port_changes(); + +protected: + Vector2 size; + String inputs; + String outputs; + bool editable; + + struct Port { + PortType type; + String name; + }; + + Map<int, Port> input_ports; + Map<int, Port> output_ports; + Map<int, Control *> controls; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_size(const Vector2 &p_size); + Vector2 get_size() const; + + void set_inputs(const String &p_inputs); + String get_inputs() const; + + void set_outputs(const String &p_outputs); + String get_outputs() const; + + bool is_valid_port_name(const String &p_name) const; + + void add_input_port(int p_id, int p_type, const String &p_name); + void remove_input_port(int p_id); + virtual int get_input_port_count() const; + bool has_input_port(int p_id) const; + void clear_input_ports(); + + void add_output_port(int p_id, int p_type, const String &p_name); + void remove_output_port(int p_id); + virtual int get_output_port_count() const; + bool has_output_port(int p_id) const; + void clear_output_ports(); + + void set_input_port_type(int p_id, int p_type); + virtual PortType get_input_port_type(int p_id) const; + void set_input_port_name(int p_id, const String &p_name); + virtual String get_input_port_name(int p_id) const; + + void set_output_port_type(int p_id, int p_type); + virtual PortType get_output_port_type(int p_id) const; + void set_output_port_name(int p_id, const String &p_name); + virtual String get_output_port_name(int p_id) const; + + int get_free_input_port_id() const; + int get_free_output_port_id() const; + + void set_control(Control *p_control, int p_index); + Control *get_control(int p_index); + + void set_editable(bool p_enabled); + bool is_editable() const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + + VisualShaderNodeGroupBase(); +}; + +class VisualShaderNodeExpression : public VisualShaderNodeGroupBase { + GDCLASS(VisualShaderNodeExpression, VisualShaderNodeGroupBase); + +protected: + String expression; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_expression(const String &p_expression); + String get_expression() const; + + void build(); + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + + VisualShaderNodeExpression(); +}; + +class VisualShaderNodeGlobalExpression : public VisualShaderNodeExpression { + GDCLASS(VisualShaderNodeGlobalExpression, VisualShaderNodeExpression); + +public: + virtual String get_caption() const; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + + VisualShaderNodeGlobalExpression(); +}; + #endif // VISUAL_SHADER_H diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index a89b70d1ea..9195d80cfc 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -88,6 +88,67 @@ VisualShaderNodeScalarConstant::VisualShaderNodeScalarConstant() { constant = 0; } +////////////// Boolean + +String VisualShaderNodeBooleanConstant::get_caption() const { + return "Boolean"; +} + +int VisualShaderNodeBooleanConstant::get_input_port_count() const { + return 0; +} + +VisualShaderNodeBooleanConstant::PortType VisualShaderNodeBooleanConstant::get_input_port_type(int p_port) const { + return PORT_TYPE_BOOLEAN; +} + +String VisualShaderNodeBooleanConstant::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeBooleanConstant::get_output_port_count() const { + return 1; +} + +VisualShaderNodeBooleanConstant::PortType VisualShaderNodeBooleanConstant::get_output_port_type(int p_port) const { + return PORT_TYPE_BOOLEAN; +} + +String VisualShaderNodeBooleanConstant::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeBooleanConstant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = " + (constant ? "true" : "false") + ";\n"; +} + +void VisualShaderNodeBooleanConstant::set_constant(bool p_value) { + constant = p_value; + emit_changed(); +} + +bool VisualShaderNodeBooleanConstant::get_constant() const { + return constant; +} + +Vector<StringName> VisualShaderNodeBooleanConstant::get_editable_properties() const { + Vector<StringName> props; + props.push_back("constant"); + return props; +} + +void VisualShaderNodeBooleanConstant::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_constant", "value"), &VisualShaderNodeBooleanConstant::set_constant); + ClassDB::bind_method(D_METHOD("get_constant"), &VisualShaderNodeBooleanConstant::get_constant); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constant"), "set_constant", "get_constant"); +} + +VisualShaderNodeBooleanConstant::VisualShaderNodeBooleanConstant() { + constant = false; +} + ////////////// Color String VisualShaderNodeColorConstant::get_caption() const { @@ -296,9 +357,13 @@ int VisualShaderNodeTexture::get_output_port_count() const { return 2; } VisualShaderNodeTexture::PortType VisualShaderNodeTexture::get_output_port_type(int p_port) const { + if (p_port == 0 && source == SOURCE_DEPTH) + return PORT_TYPE_SCALAR; return p_port == 0 ? PORT_TYPE_VECTOR : PORT_TYPE_SCALAR; } String VisualShaderNodeTexture::get_output_port_name(int p_port) const { + if (p_port == 0 && source == SOURCE_DEPTH) + return "depth"; return p_port == 0 ? "rgb" : "alpha"; } @@ -324,7 +389,7 @@ String VisualShaderNodeTexture::generate_global(Shader::Mode p_mode, VisualShade String u = "uniform sampler2D " + make_unique_id(p_type, p_id, "tex"); switch (texture_type) { case TYPE_DATA: break; - case TYPE_COLOR: u += " : hint_color"; break; + case TYPE_COLOR: u += " : hint_albedo"; break; case TYPE_NORMALMAP: u += " : hint_normal"; break; } return u + ";"; @@ -414,6 +479,41 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: return code; } + if (p_for_preview) // DEPTH_TEXTURE is not supported in preview(canvas_item) shader + { + if (source == SOURCE_DEPTH) { + String code; + code += "\t" + p_output_vars[0] + " = 0.0;\n"; + code += "\t" + p_output_vars[1] + " = 1.0;\n"; + return code; + } + } + + if (source == SOURCE_DEPTH && p_mode == Shader::MODE_SPATIAL && p_type == VisualShader::TYPE_FRAGMENT) { + + String code = "\t{\n"; + if (p_input_vars[0] == String()) { //none bound, do nothing + + code += "\t\tfloat _depth = 0.0;\n"; + + } else if (p_input_vars[1] == String()) { + //no lod + code += "\t\tfloat _depth = texture( DEPTH_TEXTURE , " + p_input_vars[0] + ".xy ).r;\n"; + } else { + code += "\t\tfloat _depth = textureLod( DEPTH_TEXTURE , " + p_input_vars[0] + ".xy , " + p_input_vars[1] + " ).r;\n"; + } + + code += "\t\t" + p_output_vars[0] + " = _depth;\n"; + code += "\t\t" + p_output_vars[1] + " = 1.0;\n"; + code += "\t}\n"; + return code; + } else if (source == SOURCE_DEPTH) { + String code; + code += "\t" + p_output_vars[0] + " = 0.0;\n"; + code += "\t" + p_output_vars[1] + " = 1.0;\n"; + return code; + } + //none String code; code += "\t" + p_output_vars[0] + " = vec3(0.0);\n"; @@ -482,6 +582,14 @@ String VisualShaderNodeTexture::get_warning(Shader::Mode p_mode, VisualShader::T return String(); // all good } + if (source == SOURCE_DEPTH && p_mode == Shader::MODE_SPATIAL && p_type == VisualShader::TYPE_FRAGMENT) { + + if (get_output_port_for_preview() == 0) { // DEPTH_TEXTURE is not supported in preview(canvas_item) shader + return TTR("Invalid source for preview."); + } + return String(); // all good + } + return TTR("Invalid source for shader."); } @@ -496,7 +604,7 @@ void VisualShaderNodeTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture_type", "value"), &VisualShaderNodeTexture::set_texture_type); ClassDB::bind_method(D_METHOD("get_texture_type"), &VisualShaderNodeTexture::get_texture_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "source", PROPERTY_HINT_ENUM, "Texture,Screen,Texture2D,NormalMap2D"), "set_source", "get_source"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "source", PROPERTY_HINT_ENUM, "Texture,Screen,Texture2D,NormalMap2D,Depth"), "set_source", "get_source"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normalmap"), "set_texture_type", "get_texture_type"); @@ -504,6 +612,7 @@ void VisualShaderNodeTexture::_bind_methods() { BIND_ENUM_CONSTANT(SOURCE_SCREEN); BIND_ENUM_CONSTANT(SOURCE_2D_TEXTURE); BIND_ENUM_CONSTANT(SOURCE_2D_NORMAL); + BIND_ENUM_CONSTANT(SOURCE_DEPTH); BIND_ENUM_CONSTANT(TYPE_DATA); BIND_ENUM_CONSTANT(TYPE_COLOR); BIND_ENUM_CONSTANT(TYPE_NORMALMAP); @@ -554,7 +663,7 @@ String VisualShaderNodeCubeMap::generate_global(Shader::Mode p_mode, VisualShade String u = "uniform sampler2DCube " + make_unique_id(p_type, p_id, "cube"); switch (texture_type) { case TYPE_DATA: break; - case TYPE_COLOR: u += " : hint_color"; break; + case TYPE_COLOR: u += " : hint_albedo"; break; case TYPE_NORMALMAP: u += " : hint_normal"; break; } return u + ";"; @@ -666,6 +775,7 @@ String VisualShaderNodeScalarOp::generate_code(Shader::Mode p_mode, VisualShader case OP_MAX: code += "max( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; case OP_MIN: code += "min( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; case OP_ATAN2: code += "atan( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_STEP: code += "step( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; } return code; @@ -693,7 +803,7 @@ void VisualShaderNodeScalarOp::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeScalarOp::set_operator); ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeScalarOp::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Atan2"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Atan2,Step"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_ADD); BIND_ENUM_CONSTANT(OP_SUB); @@ -704,6 +814,7 @@ void VisualShaderNodeScalarOp::_bind_methods() { BIND_ENUM_CONSTANT(OP_MAX); BIND_ENUM_CONSTANT(OP_MIN); BIND_ENUM_CONSTANT(OP_ATAN2); + BIND_ENUM_CONSTANT(OP_STEP); } VisualShaderNodeScalarOp::VisualShaderNodeScalarOp() { @@ -752,6 +863,9 @@ String VisualShaderNodeVectorOp::generate_code(Shader::Mode p_mode, VisualShader case OP_MAX: code += "max( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; case OP_MIN: code += "min( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; case OP_CROSS: code += "cross( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_ATAN2: code += "atan( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_REFLECT: code += "reflect( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; + case OP_STEP: code += "step( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; break; } return code; @@ -779,7 +893,7 @@ void VisualShaderNodeVectorOp::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeVectorOp::set_operator); ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeVectorOp::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Cross"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "Add,Sub,Multiply,Divide,Remainder,Power,Max,Min,Cross,Atan2,Reflect,Step"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_ADD); BIND_ENUM_CONSTANT(OP_SUB); @@ -790,6 +904,9 @@ void VisualShaderNodeVectorOp::_bind_methods() { BIND_ENUM_CONSTANT(OP_MAX); BIND_ENUM_CONSTANT(OP_MIN); BIND_ENUM_CONSTANT(OP_CROSS); + BIND_ENUM_CONSTANT(OP_ATAN2); + BIND_ENUM_CONSTANT(OP_REFLECT); + BIND_ENUM_CONSTANT(OP_STEP); } VisualShaderNodeVectorOp::VisualShaderNodeVectorOp() { @@ -976,8 +1093,12 @@ String VisualShaderNodeTransformMult::generate_code(Shader::Mode p_mode, VisualS if (op == OP_AxB) { return "\t" + p_output_vars[0] + " = " + p_input_vars[0] + " * " + p_input_vars[1] + ";\n"; - } else { + } else if (op == OP_BxA) { return "\t" + p_output_vars[0] + " = " + p_input_vars[1] + " * " + p_input_vars[0] + ";\n"; + } else if (op == OP_AxB_COMP) { + return "\t" + p_output_vars[0] + " = matrixCompMult( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; + } else { + return "\t" + p_output_vars[0] + " = matrixCompMult( " + p_input_vars[1] + " , " + p_input_vars[0] + " );\n"; } } @@ -1003,10 +1124,12 @@ void VisualShaderNodeTransformMult::_bind_methods() { ClassDB::bind_method(D_METHOD("set_operator", "op"), &VisualShaderNodeTransformMult::set_operator); ClassDB::bind_method(D_METHOD("get_operator"), &VisualShaderNodeTransformMult::get_operator); - ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "A x B,B x A"), "set_operator", "get_operator"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operator", PROPERTY_HINT_ENUM, "A x B,B x A,A x B(per component),B x A(per component)"), "set_operator", "get_operator"); BIND_ENUM_CONSTANT(OP_AxB); BIND_ENUM_CONSTANT(OP_BxA); + BIND_ENUM_CONSTANT(OP_AxB_COMP); + BIND_ENUM_CONSTANT(OP_BxA_COMP); } VisualShaderNodeTransformMult::VisualShaderNodeTransformMult() { @@ -1117,7 +1240,7 @@ String VisualShaderNodeScalarFunc::get_output_port_name(int p_port) const { String VisualShaderNodeScalarFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - static const char *scalar_func_id[FUNC_NEGATE + 1] = { + static const char *scalar_func_id[FUNC_ONEMINUS + 1] = { "sin($)", "cos($)", "tan($)", @@ -1136,8 +1259,20 @@ String VisualShaderNodeScalarFunc::generate_code(Shader::Mode p_mode, VisualShad "round($)", "ceil($)", "fract($)", - "min(max($,0),1)", + "min(max($,0.0),1.0)", "-($)", + "acosh($)", + "asinh($)", + "atanh($)", + "degrees($)", + "exp2($)", + "inversesqrt($)", + "log2($)", + "radians($)", + "1.0/($)", + "roundEven($)", + "trunc($)", + "1.0-$" }; return "\t" + p_output_vars[0] + " = " + String(scalar_func_id[func]).replace("$", p_input_vars[0]) + ";\n"; @@ -1165,7 +1300,7 @@ void VisualShaderNodeScalarFunc::_bind_methods() { ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeScalarFunc::set_function); ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeScalarFunc::get_function); - ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sin,Cos,Tan,ASin,ACos,ATan,SinH,CosH,TanH,Log,Exp,Sqrt,Abs,Sign,Floor,Round,Ceil,Frac,Saturate,Negate"), "set_function", "get_function"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sin,Cos,Tan,ASin,ACos,ATan,SinH,CosH,TanH,Log,Exp,Sqrt,Abs,Sign,Floor,Round,Ceil,Frac,Saturate,Negate,ACosH,ASinH,ATanH,Degrees,Exp2,InverseSqrt,Log2,Radians,Reciprocal,RoundEven,Trunc,OneMinus"), "set_function", "get_function"); BIND_ENUM_CONSTANT(FUNC_SIN); BIND_ENUM_CONSTANT(FUNC_COS); @@ -1187,6 +1322,18 @@ void VisualShaderNodeScalarFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_FRAC); BIND_ENUM_CONSTANT(FUNC_SATURATE); BIND_ENUM_CONSTANT(FUNC_NEGATE); + BIND_ENUM_CONSTANT(FUNC_ACOSH); + BIND_ENUM_CONSTANT(FUNC_ASINH); + BIND_ENUM_CONSTANT(FUNC_ATANH); + BIND_ENUM_CONSTANT(FUNC_DEGREES); + BIND_ENUM_CONSTANT(FUNC_EXP2); + BIND_ENUM_CONSTANT(FUNC_INVERSE_SQRT); + BIND_ENUM_CONSTANT(FUNC_LOG2); + BIND_ENUM_CONSTANT(FUNC_RADIANS); + BIND_ENUM_CONSTANT(FUNC_RECIPROCAL); + BIND_ENUM_CONSTANT(FUNC_ROUNDEVEN); + BIND_ENUM_CONSTANT(FUNC_TRUNC); + BIND_ENUM_CONSTANT(FUNC_ONEMINUS); } VisualShaderNodeScalarFunc::VisualShaderNodeScalarFunc() { @@ -1222,13 +1369,42 @@ String VisualShaderNodeVectorFunc::get_output_port_name(int p_port) const { String VisualShaderNodeVectorFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { - static const char *vec_func_id[FUNC_HSV2RGB + 1] = { + static const char *vec_func_id[FUNC_ONEMINUS + 1] = { "normalize($)", "max(min($,vec3(1.0)),vec3(0.0))", "-($)", "1.0/($)", "", "", + "abs($)", + "acos($)", + "acosh($)", + "asin($)", + "asinh($)", + "atan($)", + "atanh($)", + "ceil($)", + "cos($)", + "cosh($)", + "degrees($)", + "exp($)", + "exp2($)", + "floor($)", + "fract($)", + "inversesqrt($)", + "log($)", + "log2($)", + "radians($)", + "round($)", + "roundEven($)", + "sign($)", + "sin($)", + "sinh($)", + "sqrt($)", + "tan($)", + "tanh($)", + "trunc($)", + "vec3(1.0, 1.0, 1.0)-$" }; String code; @@ -1280,7 +1456,7 @@ void VisualShaderNodeVectorFunc::_bind_methods() { ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeVectorFunc::set_function); ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeVectorFunc::get_function); - ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Normalize,Saturate,Negate,Reciprocal,RGB2HSV,HSV2RGB"), "set_function", "get_function"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Normalize,Saturate,Negate,Reciprocal,RGB2HSV,HSV2RGB,Abs,ACos,ACosH,ASin,ASinH,ATan,ATanH,Ceil,Cos,CosH,Degrees,Exp,Exp2,Floor,Frac,InverseSqrt,Log,Log2,Radians,Round,RoundEven,Sign,Sin,SinH,Sqrt,Tan,TanH,Trunc,OneMinus"), "set_function", "get_function"); BIND_ENUM_CONSTANT(FUNC_NORMALIZE); BIND_ENUM_CONSTANT(FUNC_SATURATE); @@ -1288,6 +1464,35 @@ void VisualShaderNodeVectorFunc::_bind_methods() { BIND_ENUM_CONSTANT(FUNC_RECIPROCAL); BIND_ENUM_CONSTANT(FUNC_RGB2HSV); BIND_ENUM_CONSTANT(FUNC_HSV2RGB); + BIND_ENUM_CONSTANT(FUNC_ABS); + BIND_ENUM_CONSTANT(FUNC_ACOS); + BIND_ENUM_CONSTANT(FUNC_ACOSH); + BIND_ENUM_CONSTANT(FUNC_ASIN); + BIND_ENUM_CONSTANT(FUNC_ASINH); + BIND_ENUM_CONSTANT(FUNC_ATAN); + BIND_ENUM_CONSTANT(FUNC_ATANH); + BIND_ENUM_CONSTANT(FUNC_CEIL); + BIND_ENUM_CONSTANT(FUNC_COS); + BIND_ENUM_CONSTANT(FUNC_COSH); + BIND_ENUM_CONSTANT(FUNC_DEGREES); + BIND_ENUM_CONSTANT(FUNC_EXP); + BIND_ENUM_CONSTANT(FUNC_EXP2); + BIND_ENUM_CONSTANT(FUNC_FLOOR); + BIND_ENUM_CONSTANT(FUNC_FRAC); + BIND_ENUM_CONSTANT(FUNC_INVERSE_SQRT); + BIND_ENUM_CONSTANT(FUNC_LOG); + BIND_ENUM_CONSTANT(FUNC_LOG2); + BIND_ENUM_CONSTANT(FUNC_RADIANS); + BIND_ENUM_CONSTANT(FUNC_ROUND); + BIND_ENUM_CONSTANT(FUNC_ROUNDEVEN); + BIND_ENUM_CONSTANT(FUNC_SIGN); + BIND_ENUM_CONSTANT(FUNC_SIN); + BIND_ENUM_CONSTANT(FUNC_SINH); + BIND_ENUM_CONSTANT(FUNC_SQRT); + BIND_ENUM_CONSTANT(FUNC_TAN); + BIND_ENUM_CONSTANT(FUNC_TANH); + BIND_ENUM_CONSTANT(FUNC_TRUNC); + BIND_ENUM_CONSTANT(FUNC_ONEMINUS); } VisualShaderNodeVectorFunc::VisualShaderNodeVectorFunc() { @@ -1295,6 +1500,172 @@ VisualShaderNodeVectorFunc::VisualShaderNodeVectorFunc() { set_input_port_default_value(0, Vector3()); } +////////////// ColorFunc + +String VisualShaderNodeColorFunc::get_caption() const { + return "ColorFunc"; +} + +int VisualShaderNodeColorFunc::get_input_port_count() const { + return 1; +} + +VisualShaderNodeColorFunc::PortType VisualShaderNodeColorFunc::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeColorFunc::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeColorFunc::get_output_port_count() const { + return 1; +} + +VisualShaderNodeColorFunc::PortType VisualShaderNodeColorFunc::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeColorFunc::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeColorFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + String code; + + switch (func) { + case FUNC_GRAYSCALE: + code += "\t{\n"; + code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; + code += "\t\tfloat max1 = max(c.r, c.g);\n"; + code += "\t\tfloat max2 = max(max1, c.b);\n"; + code += "\t\tfloat max3 = max(max1, max2);\n"; + code += "\t\t" + p_output_vars[0] + " = vec3(max3, max3, max3);\n"; + code += "\t}\n"; + break; + case FUNC_SEPIA: + code += "\t{\n"; + code += "\t\tvec3 c = " + p_input_vars[0] + ";\n"; + code += "\t\tfloat r = (c.r * .393) + (c.g *.769) + (c.b * .189);\n"; + code += "\t\tfloat g = (c.r * .349) + (c.g *.686) + (c.b * .168);\n"; + code += "\t\tfloat b = (c.r * .272) + (c.g *.534) + (c.b * .131);\n"; + code += "\t\t" + p_output_vars[0] + " = vec3(r, g, b);\n"; + code += "\t}\n"; + break; + } + + return code; +} + +void VisualShaderNodeColorFunc::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeColorFunc::Function VisualShaderNodeColorFunc::get_function() const { + + return func; +} + +Vector<StringName> VisualShaderNodeColorFunc::get_editable_properties() const { + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeColorFunc::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeColorFunc::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeColorFunc::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Grayscale,Sepia"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_GRAYSCALE); + BIND_ENUM_CONSTANT(FUNC_SEPIA); +} + +VisualShaderNodeColorFunc::VisualShaderNodeColorFunc() { + func = FUNC_GRAYSCALE; + set_input_port_default_value(0, Vector3()); +} + +////////////// Transform Func + +String VisualShaderNodeTransformFunc::get_caption() const { + return "TransformFunc"; +} + +int VisualShaderNodeTransformFunc::get_input_port_count() const { + return 1; +} + +VisualShaderNodeTransformFunc::PortType VisualShaderNodeTransformFunc::get_input_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} + +String VisualShaderNodeTransformFunc::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeTransformFunc::get_output_port_count() const { + return 1; +} + +VisualShaderNodeTransformFunc::PortType VisualShaderNodeTransformFunc::get_output_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} + +String VisualShaderNodeTransformFunc::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeTransformFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + static const char *funcs[FUNC_TRANSPOSE + 1] = { + "inverse($)", + "transpose($)" + }; + + String code; + code += "\t" + p_output_vars[0] + "=" + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + return code; +} + +void VisualShaderNodeTransformFunc::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeTransformFunc::Function VisualShaderNodeTransformFunc::get_function() const { + + return func; +} + +Vector<StringName> VisualShaderNodeTransformFunc::get_editable_properties() const { + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeTransformFunc::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeTransformFunc::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeTransformFunc::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Inverse,Transpose"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_INVERSE); + BIND_ENUM_CONSTANT(FUNC_TRANSPOSE); +} + +VisualShaderNodeTransformFunc::VisualShaderNodeTransformFunc() { + func = FUNC_INVERSE; + set_input_port_default_value(0, Transform()); +} + ////////////// Dot Product String VisualShaderNodeDotProduct::get_caption() const { @@ -1364,34 +1735,702 @@ VisualShaderNodeVectorLen::VisualShaderNodeVectorLen() { set_input_port_default_value(0, Vector3()); } -////////////// Scalar Interp +////////////// Determinant + +String VisualShaderNodeDeterminant::get_caption() const { + return "Determinant"; +} + +int VisualShaderNodeDeterminant::get_input_port_count() const { + return 1; +} + +VisualShaderNodeScalarClamp::PortType VisualShaderNodeDeterminant::get_input_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} + +String VisualShaderNodeDeterminant::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeDeterminant::get_output_port_count() const { + return 1; +} + +VisualShaderNodeDeterminant::PortType VisualShaderNodeDeterminant::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeDeterminant::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeDeterminant::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = determinant( " + p_input_vars[0] + " );\n"; +} + +VisualShaderNodeDeterminant::VisualShaderNodeDeterminant() { + set_input_port_default_value(0, Transform()); +} + +////////////// Scalar Derivative Function + +String VisualShaderNodeScalarDerivativeFunc::get_caption() const { + return "ScalarDerivativeFunc"; +} + +int VisualShaderNodeScalarDerivativeFunc::get_input_port_count() const { + return 1; +} + +VisualShaderNodeScalarDerivativeFunc::PortType VisualShaderNodeScalarDerivativeFunc::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeScalarDerivativeFunc::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeScalarDerivativeFunc::get_output_port_count() const { + return 1; +} + +VisualShaderNodeScalarDerivativeFunc::PortType VisualShaderNodeScalarDerivativeFunc::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeScalarDerivativeFunc::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeScalarDerivativeFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + static const char *funcs[FUNC_Y + 1] = { + "fwidth($)", + "dFdx($)", + "dFdy($)" + }; + + String code; + code += "\t" + p_output_vars[0] + "=" + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + return code; +} + +void VisualShaderNodeScalarDerivativeFunc::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeScalarDerivativeFunc::Function VisualShaderNodeScalarDerivativeFunc::get_function() const { + + return func; +} + +Vector<StringName> VisualShaderNodeScalarDerivativeFunc::get_editable_properties() const { + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeScalarDerivativeFunc::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeScalarDerivativeFunc::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeScalarDerivativeFunc::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sum,X,Y"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_SUM); + BIND_ENUM_CONSTANT(FUNC_X); + BIND_ENUM_CONSTANT(FUNC_Y); +} + +VisualShaderNodeScalarDerivativeFunc::VisualShaderNodeScalarDerivativeFunc() { + func = FUNC_SUM; + set_input_port_default_value(0, 0.0); +} + +////////////// Vector Derivative Function + +String VisualShaderNodeVectorDerivativeFunc::get_caption() const { + return "VectorDerivativeFunc"; +} + +int VisualShaderNodeVectorDerivativeFunc::get_input_port_count() const { + return 1; +} + +VisualShaderNodeVectorDerivativeFunc::PortType VisualShaderNodeVectorDerivativeFunc::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorDerivativeFunc::get_input_port_name(int p_port) const { + return ""; +} + +int VisualShaderNodeVectorDerivativeFunc::get_output_port_count() const { + return 1; +} + +VisualShaderNodeScalarDerivativeFunc::PortType VisualShaderNodeVectorDerivativeFunc::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorDerivativeFunc::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeVectorDerivativeFunc::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + static const char *funcs[FUNC_Y + 1] = { + "fwidth($)", + "dFdx($)", + "dFdy($)" + }; + + String code; + code += "\t" + p_output_vars[0] + "=" + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + return code; +} + +void VisualShaderNodeVectorDerivativeFunc::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeVectorDerivativeFunc::Function VisualShaderNodeVectorDerivativeFunc::get_function() const { + + return func; +} + +Vector<StringName> VisualShaderNodeVectorDerivativeFunc::get_editable_properties() const { + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeVectorDerivativeFunc::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeVectorDerivativeFunc::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeVectorDerivativeFunc::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Sum,X,Y"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_SUM); + BIND_ENUM_CONSTANT(FUNC_X); + BIND_ENUM_CONSTANT(FUNC_Y); +} + +VisualShaderNodeVectorDerivativeFunc::VisualShaderNodeVectorDerivativeFunc() { + func = FUNC_SUM; + set_input_port_default_value(0, Vector3()); +} + +////////////// Scalar Clamp + +String VisualShaderNodeScalarClamp::get_caption() const { + return "ScalarClamp"; +} + +int VisualShaderNodeScalarClamp::get_input_port_count() const { + return 3; +} + +VisualShaderNodeScalarClamp::PortType VisualShaderNodeScalarClamp::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeScalarClamp::get_input_port_name(int p_port) const { + if (p_port == 0) + return ""; + else if (p_port == 1) + return "min"; + else if (p_port == 2) + return "max"; + return ""; +} + +int VisualShaderNodeScalarClamp::get_output_port_count() const { + return 1; +} + +VisualShaderNodeScalarClamp::PortType VisualShaderNodeScalarClamp::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeScalarClamp::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeScalarClamp::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = clamp( " + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeScalarClamp::VisualShaderNodeScalarClamp() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, 1.0); +} + +////////////// Vector Clamp + +String VisualShaderNodeVectorClamp::get_caption() const { + return "VectorClamp"; +} + +int VisualShaderNodeVectorClamp::get_input_port_count() const { + return 3; +} + +VisualShaderNodeVectorClamp::PortType VisualShaderNodeVectorClamp::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorClamp::get_input_port_name(int p_port) const { + if (p_port == 0) + return ""; + else if (p_port == 1) + return "min"; + else if (p_port == 2) + return "max"; + return ""; +} + +int VisualShaderNodeVectorClamp::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVectorClamp::PortType VisualShaderNodeVectorClamp::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorClamp::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeVectorClamp::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = clamp( " + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeVectorClamp::VisualShaderNodeVectorClamp() { + set_input_port_default_value(0, Vector3(0, 0, 0)); + set_input_port_default_value(1, Vector3(0, 0, 0)); + set_input_port_default_value(2, Vector3(1, 1, 1)); +} + +////////////// FaceForward + +String VisualShaderNodeFaceForward::get_caption() const { + return "FaceForward"; +} + +int VisualShaderNodeFaceForward::get_input_port_count() const { + return 3; +} + +VisualShaderNodeFaceForward::PortType VisualShaderNodeFaceForward::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeFaceForward::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "N"; + case 1: + return "I"; + case 2: + return "Nref"; + default: + return ""; + } +} + +int VisualShaderNodeFaceForward::get_output_port_count() const { + return 1; +} + +VisualShaderNodeFaceForward::PortType VisualShaderNodeFaceForward::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeFaceForward::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeFaceForward::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = faceforward( " + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeFaceForward::VisualShaderNodeFaceForward() { + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Outer Product + +String VisualShaderNodeOuterProduct::get_caption() const { + return "OuterProduct"; +} + +int VisualShaderNodeOuterProduct::get_input_port_count() const { + return 2; +} + +VisualShaderNodeFaceForward::PortType VisualShaderNodeOuterProduct::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeOuterProduct::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "c"; + case 1: + return "r"; + default: + return ""; + } +} + +int VisualShaderNodeOuterProduct::get_output_port_count() const { + return 1; +} + +VisualShaderNodeOuterProduct::PortType VisualShaderNodeOuterProduct::get_output_port_type(int p_port) const { + return PORT_TYPE_TRANSFORM; +} + +String VisualShaderNodeOuterProduct::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeOuterProduct::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = outerProduct( vec4(" + p_input_vars[0] + ", 0.0), vec4(" + p_input_vars[1] + ", 0.0) );\n"; +} + +VisualShaderNodeOuterProduct::VisualShaderNodeOuterProduct() { + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Vector-Scalar Step + +String VisualShaderNodeVectorScalarStep::get_caption() const { + return "VectorScalarStep"; +} + +int VisualShaderNodeVectorScalarStep::get_input_port_count() const { + return 2; +} + +VisualShaderNodeVectorScalarStep::PortType VisualShaderNodeVectorScalarStep::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_SCALAR; + } + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorScalarStep::get_input_port_name(int p_port) const { + if (p_port == 0) + return "edge"; + else if (p_port == 1) + return "x"; + return ""; +} + +int VisualShaderNodeVectorScalarStep::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVectorClamp::PortType VisualShaderNodeVectorScalarStep::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorScalarStep::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeVectorScalarStep::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = step( " + p_input_vars[0] + ", " + p_input_vars[1] + " );\n"; +} + +VisualShaderNodeVectorScalarStep::VisualShaderNodeVectorScalarStep() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Scalar SmoothStep + +String VisualShaderNodeScalarSmoothStep::get_caption() const { + return "ScalarSmoothStep"; +} + +int VisualShaderNodeScalarSmoothStep::get_input_port_count() const { + return 3; +} + +VisualShaderNodeScalarSmoothStep::PortType VisualShaderNodeScalarSmoothStep::get_input_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeScalarSmoothStep::get_input_port_name(int p_port) const { + if (p_port == 0) + return "edge0"; + else if (p_port == 1) + return "edge1"; + else if (p_port == 2) + return "x"; + return ""; +} + +int VisualShaderNodeScalarSmoothStep::get_output_port_count() const { + return 1; +} + +VisualShaderNodeScalarSmoothStep::PortType VisualShaderNodeScalarSmoothStep::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeScalarSmoothStep::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeScalarSmoothStep::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = smoothstep( " + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeScalarSmoothStep::VisualShaderNodeScalarSmoothStep() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, 0.0); +} + +////////////// Vector SmoothStep + +String VisualShaderNodeVectorSmoothStep::get_caption() const { + return "VectorSmoothStep"; +} + +int VisualShaderNodeVectorSmoothStep::get_input_port_count() const { + return 3; +} + +VisualShaderNodeVectorSmoothStep::PortType VisualShaderNodeVectorSmoothStep::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorSmoothStep::get_input_port_name(int p_port) const { + if (p_port == 0) + return "edge0"; + else if (p_port == 1) + return "edge1"; + else if (p_port == 2) + return "x"; + return ""; +} + +int VisualShaderNodeVectorSmoothStep::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVectorClamp::PortType VisualShaderNodeVectorSmoothStep::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorSmoothStep::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeVectorSmoothStep::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = smoothstep( " + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeVectorSmoothStep::VisualShaderNodeVectorSmoothStep() { + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Vector-Scalar SmoothStep + +String VisualShaderNodeVectorScalarSmoothStep::get_caption() const { + return "VectorScalarSmoothStep"; +} + +int VisualShaderNodeVectorScalarSmoothStep::get_input_port_count() const { + return 3; +} + +VisualShaderNodeVectorScalarSmoothStep::PortType VisualShaderNodeVectorScalarSmoothStep::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_SCALAR; + } else if (p_port == 1) { + return PORT_TYPE_SCALAR; + } + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorScalarSmoothStep::get_input_port_name(int p_port) const { + if (p_port == 0) + return "edge0"; + else if (p_port == 1) + return "edge1"; + else if (p_port == 2) + return "x"; + return ""; +} + +int VisualShaderNodeVectorScalarSmoothStep::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVectorClamp::PortType VisualShaderNodeVectorScalarSmoothStep::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorScalarSmoothStep::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeVectorScalarSmoothStep::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = smoothstep( " + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeVectorScalarSmoothStep::VisualShaderNodeVectorScalarSmoothStep() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Distance + +String VisualShaderNodeVectorDistance::get_caption() const { + return "Distance"; +} + +int VisualShaderNodeVectorDistance::get_input_port_count() const { + return 2; +} + +VisualShaderNodeVectorDistance::PortType VisualShaderNodeVectorDistance::get_input_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorDistance::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "p0"; + } else if (p_port == 1) { + return "p1"; + } + return ""; +} + +int VisualShaderNodeVectorDistance::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVectorDistance::PortType VisualShaderNodeVectorDistance::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeVectorDistance::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeVectorDistance::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = distance( " + p_input_vars[0] + " , " + p_input_vars[1] + " );\n"; +} + +VisualShaderNodeVectorDistance::VisualShaderNodeVectorDistance() { + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Refract Vector + +String VisualShaderNodeVectorRefract::get_caption() const { + return "Refract"; +} + +int VisualShaderNodeVectorRefract::get_input_port_count() const { + return 3; +} + +VisualShaderNodeVectorRefract::PortType VisualShaderNodeVectorRefract::get_input_port_type(int p_port) const { + + if (p_port == 2) { + return PORT_TYPE_SCALAR; + } + + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorRefract::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "I"; + } else if (p_port == 1) { + return "N"; + } else if (p_port == 2) { + return "eta"; + } + return ""; +} + +int VisualShaderNodeVectorRefract::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVectorRefract::PortType VisualShaderNodeVectorRefract::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorRefract::get_output_port_name(int p_port) const { + return ""; +} + +String VisualShaderNodeVectorRefract::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = refract( " + p_input_vars[0] + ", " + p_input_vars[1] + ", " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeVectorRefract::VisualShaderNodeVectorRefract() { + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(2, 0.0); +} + +////////////// Scalar Mix String VisualShaderNodeScalarInterp::get_caption() const { - return "ScalarInterp"; + return "ScalarMix"; } int VisualShaderNodeScalarInterp::get_input_port_count() const { return 3; } + VisualShaderNodeScalarInterp::PortType VisualShaderNodeScalarInterp::get_input_port_type(int p_port) const { return PORT_TYPE_SCALAR; } + String VisualShaderNodeScalarInterp::get_input_port_name(int p_port) const { if (p_port == 0) { return "a"; } else if (p_port == 1) { return "b"; } else { - return "c"; + return "weight"; } } int VisualShaderNodeScalarInterp::get_output_port_count() const { return 1; } + VisualShaderNodeScalarInterp::PortType VisualShaderNodeScalarInterp::get_output_port_type(int p_port) const { return PORT_TYPE_SCALAR; } + String VisualShaderNodeScalarInterp::get_output_port_name(int p_port) const { return "mix"; } @@ -1402,38 +2441,42 @@ String VisualShaderNodeScalarInterp::generate_code(Shader::Mode p_mode, VisualSh VisualShaderNodeScalarInterp::VisualShaderNodeScalarInterp() { set_input_port_default_value(0, 0.0); - set_input_port_default_value(1, 0.0); - set_input_port_default_value(2, 0.0); + set_input_port_default_value(1, 1.0); + set_input_port_default_value(2, 0.5); } -////////////// Vector Interp +////////////// Vector Mix String VisualShaderNodeVectorInterp::get_caption() const { - return "VectorInterp"; + return "VectorMix"; } int VisualShaderNodeVectorInterp::get_input_port_count() const { return 3; } + VisualShaderNodeVectorInterp::PortType VisualShaderNodeVectorInterp::get_input_port_type(int p_port) const { return PORT_TYPE_VECTOR; } + String VisualShaderNodeVectorInterp::get_input_port_name(int p_port) const { if (p_port == 0) { return "a"; } else if (p_port == 1) { return "b"; } else { - return "c"; + return "weight"; } } int VisualShaderNodeVectorInterp::get_output_port_count() const { return 1; } + VisualShaderNodeVectorInterp::PortType VisualShaderNodeVectorInterp::get_output_port_type(int p_port) const { return PORT_TYPE_VECTOR; } + String VisualShaderNodeVectorInterp::get_output_port_name(int p_port) const { return "mix"; } @@ -1443,9 +2486,57 @@ String VisualShaderNodeVectorInterp::generate_code(Shader::Mode p_mode, VisualSh } VisualShaderNodeVectorInterp::VisualShaderNodeVectorInterp() { - set_input_port_default_value(0, Vector3()); - set_input_port_default_value(1, Vector3()); - set_input_port_default_value(2, Vector3()); + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0)); + set_input_port_default_value(2, Vector3(0.5, 0.5, 0.5)); +} + +////////////// Vector Mix (by scalar) + +String VisualShaderNodeVectorScalarMix::get_caption() const { + return "VectorScalarMix"; +} + +int VisualShaderNodeVectorScalarMix::get_input_port_count() const { + return 3; +} + +VisualShaderNodeVectorScalarMix::PortType VisualShaderNodeVectorScalarMix::get_input_port_type(int p_port) const { + if (p_port == 2) + return PORT_TYPE_SCALAR; + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorScalarMix::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "a"; + } else if (p_port == 1) { + return "b"; + } else { + return "weight"; + } +} + +int VisualShaderNodeVectorScalarMix::get_output_port_count() const { + return 1; +} + +VisualShaderNodeVectorScalarMix::PortType VisualShaderNodeVectorScalarMix::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeVectorScalarMix::get_output_port_name(int p_port) const { + return "mix"; +} + +String VisualShaderNodeVectorScalarMix::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = mix( " + p_input_vars[0] + " , " + p_input_vars[1] + " , " + p_input_vars[2] + " );\n"; +} + +VisualShaderNodeVectorScalarMix::VisualShaderNodeVectorScalarMix() { + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0)); + set_input_port_default_value(2, 0.5); } ////////////// Vector Compose @@ -1662,6 +2753,47 @@ String VisualShaderNodeScalarUniform::generate_code(Shader::Mode p_mode, VisualS VisualShaderNodeScalarUniform::VisualShaderNodeScalarUniform() { } +////////////// Boolean Uniform + +String VisualShaderNodeBooleanUniform::get_caption() const { + return "BooleanUniform"; +} + +int VisualShaderNodeBooleanUniform::get_input_port_count() const { + return 0; +} + +VisualShaderNodeBooleanUniform::PortType VisualShaderNodeBooleanUniform::get_input_port_type(int p_port) const { + return PORT_TYPE_BOOLEAN; +} + +String VisualShaderNodeBooleanUniform::get_input_port_name(int p_port) const { + return String(); +} + +int VisualShaderNodeBooleanUniform::get_output_port_count() const { + return 1; +} + +VisualShaderNodeBooleanUniform::PortType VisualShaderNodeBooleanUniform::get_output_port_type(int p_port) const { + return PORT_TYPE_BOOLEAN; +} + +String VisualShaderNodeBooleanUniform::get_output_port_name(int p_port) const { + return ""; //no output port means the editor will be used as port +} + +String VisualShaderNodeBooleanUniform::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + return "uniform bool " + get_uniform_name() + ";\n"; +} + +String VisualShaderNodeBooleanUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = " + get_uniform_name() + ";\n"; +} + +VisualShaderNodeBooleanUniform::VisualShaderNodeBooleanUniform() { +} + ////////////// Color Uniform String VisualShaderNodeColorUniform::get_caption() const { @@ -1892,6 +3024,98 @@ VisualShaderNodeTextureUniform::VisualShaderNodeTextureUniform() { color_default = COLOR_DEFAULT_WHITE; } +////////////// Texture Uniform (Triplanar) + +String VisualShaderNodeTextureUniformTriplanar::get_caption() const { + return "TextureUniformTriplanar"; +} + +int VisualShaderNodeTextureUniformTriplanar::get_input_port_count() const { + return 2; +} + +VisualShaderNodeTextureUniform::PortType VisualShaderNodeTextureUniformTriplanar::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_VECTOR; + } else if (p_port == 1) { + return PORT_TYPE_VECTOR; + } + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeTextureUniformTriplanar::get_input_port_name(int p_port) const { + if (p_port == 0) { + return "weights"; + } else if (p_port == 1) { + return "pos"; + } + return ""; +} + +String VisualShaderNodeTextureUniformTriplanar::generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + + String code; + + code += "// TRIPLANAR FUNCTION GLOBAL CODE\n"; + code += "\tvec4 triplanar_texture(sampler2D p_sampler, vec3 p_weights, vec3 p_triplanar_pos) {\n"; + code += "\t\tvec4 samp = vec4(0.0);\n"; + code += "\t\tsamp += texture(p_sampler, p_triplanar_pos.xy) * p_weights.z;\n"; + code += "\t\tsamp += texture(p_sampler, p_triplanar_pos.xz) * p_weights.y;\n"; + code += "\t\tsamp += texture(p_sampler, p_triplanar_pos.zy * vec2(-1.0, 1.0)) * p_weights.x;\n"; + code += "\t\treturn samp;\n"; + code += "\t}\n"; + code += "\n"; + code += "\tuniform vec3 triplanar_scale = vec3(1.0, 1.0, 1.0);\n"; + code += "\tuniform vec3 triplanar_offset;\n"; + code += "\tuniform float triplanar_sharpness = 0.5;\n"; + code += "\n"; + code += "\tvarying vec3 triplanar_power_normal;\n"; + code += "\tvarying vec3 triplanar_pos;\n"; + + return code; +} + +String VisualShaderNodeTextureUniformTriplanar::generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { + + String code; + + if (p_type == VisualShader::TYPE_VERTEX) { + + code += "\t// TRIPLANAR FUNCTION VERTEX CODE\n"; + code += "\t\ttriplanar_power_normal = pow(abs(NORMAL), vec3(triplanar_sharpness));\n"; + code += "\t\ttriplanar_power_normal /= dot(triplanar_power_normal, vec3(1.0));\n"; + code += "\t\ttriplanar_pos = VERTEX * triplanar_scale + triplanar_offset;\n"; + code += "\t\ttriplanar_pos *= vec3(1.0, -1.0, 1.0);\n"; + } + + return code; +} + +String VisualShaderNodeTextureUniformTriplanar::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + String id = get_uniform_name(); + String code = "\t{\n"; + + if (p_input_vars[0] == String() && p_input_vars[1] == String()) { + code += "\t\tvec4 n_tex_read = triplanar_texture( " + id + ", triplanar_power_normal, triplanar_pos );\n"; + } else if (p_input_vars[0] != String() && p_input_vars[1] == String()) { + code += "\t\tvec4 n_tex_read = triplanar_texture( " + id + ", " + p_input_vars[0] + ", triplanar_pos );\n"; + } else if (p_input_vars[0] == String() && p_input_vars[1] != String()) { + code += "\t\tvec4 n_tex_read = triplanar_texture( " + id + ", triplanar_power_normal," + p_input_vars[1] + " );\n"; + } else { + code += "\t\tvec4 n_tex_read = triplanar_texture( " + id + ", " + p_input_vars[0] + ", " + p_input_vars[1] + " );\n"; + } + + code += "\t\t" + p_output_vars[0] + " = n_tex_read.rgb;\n"; + code += "\t\t" + p_output_vars[1] + " = n_tex_read.a;\n"; + code += "\t}\n"; + + return code; +} + +VisualShaderNodeTextureUniformTriplanar::VisualShaderNodeTextureUniformTriplanar() { +} + ////////////// CubeMap Uniform String VisualShaderNodeCubeMapUniform::get_caption() const { @@ -1924,3 +3148,517 @@ String VisualShaderNodeCubeMapUniform::generate_code(Shader::Mode p_mode, Visual VisualShaderNodeCubeMapUniform::VisualShaderNodeCubeMapUniform() { } + +////////////// If + +String VisualShaderNodeIf::get_caption() const { + return "If"; +} + +int VisualShaderNodeIf::get_input_port_count() const { + return 6; +} + +VisualShaderNodeIf::PortType VisualShaderNodeIf::get_input_port_type(int p_port) const { + if (p_port == 0 || p_port == 1 || p_port == 2) { + return PORT_TYPE_SCALAR; + } + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeIf::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "a"; + case 1: + return "b"; + case 2: + return "tolerance"; + case 3: + return "a == b"; + case 4: + return "a > b"; + case 5: + return "a < b"; + default: + return ""; + } +} + +int VisualShaderNodeIf::get_output_port_count() const { + return 1; +} + +VisualShaderNodeIf::PortType VisualShaderNodeIf::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeIf::get_output_port_name(int p_port) const { + return "result"; +} + +String VisualShaderNodeIf::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + String code; + code += "\tif(abs(" + p_input_vars[0] + "-" + p_input_vars[1] + ")<" + p_input_vars[2] + ")\n"; // abs(a - b) < tolerance eg. a == b + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[3] + ";\n"; + code += "\t}\n"; + code += "\telse if(" + p_input_vars[0] + "<" + p_input_vars[1] + ")\n"; // a < b + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[5] + ";\n"; + code += "\t}\n"; + code += "\telse\n"; // a > b (or a >= b if abs(a - b) < tolerance is false) + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[4] + ";\n"; + code += "\t}\n"; + return code; +} + +VisualShaderNodeIf::VisualShaderNodeIf() { + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, CMP_EPSILON); + set_input_port_default_value(3, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(4, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(5, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Switch + +String VisualShaderNodeSwitch::get_caption() const { + return "Switch"; +} + +int VisualShaderNodeSwitch::get_input_port_count() const { + return 3; +} + +VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_input_port_type(int p_port) const { + if (p_port == 0) { + return PORT_TYPE_BOOLEAN; + } + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeSwitch::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "value"; + case 1: + return "true"; + case 2: + return "false"; + default: + return ""; + } +} + +int VisualShaderNodeSwitch::get_output_port_count() const { + return 1; +} + +VisualShaderNodeSwitch::PortType VisualShaderNodeSwitch::get_output_port_type(int p_port) const { + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeSwitch::get_output_port_name(int p_port) const { + return "result"; +} + +String VisualShaderNodeSwitch::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + String code; + code += "\tif(" + p_input_vars[0] + ")\n"; + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[1] + ";\n"; + code += "\t}\n"; + code += "\telse\n"; + code += "\t{\n"; + code += "\t\t" + p_output_vars[0] + "=" + p_input_vars[2] + ";\n"; + code += "\t}\n"; + return code; +} + +VisualShaderNodeSwitch::VisualShaderNodeSwitch() { + set_input_port_default_value(0, false); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0)); +} + +////////////// Fresnel + +String VisualShaderNodeFresnel::get_caption() const { + return "Fresnel"; +} + +int VisualShaderNodeFresnel::get_input_port_count() const { + return 4; +} + +VisualShaderNodeFresnel::PortType VisualShaderNodeFresnel::get_input_port_type(int p_port) const { + switch (p_port) { + case 0: + return PORT_TYPE_VECTOR; + case 1: + return PORT_TYPE_VECTOR; + case 2: + return PORT_TYPE_BOOLEAN; + case 3: + return PORT_TYPE_SCALAR; + default: + return PORT_TYPE_VECTOR; + } +} + +String VisualShaderNodeFresnel::get_input_port_name(int p_port) const { + switch (p_port) { + case 0: + return "normal"; + case 1: + return "view"; + case 2: + return "invert"; + case 3: + return "power"; + default: + return ""; + } +} + +int VisualShaderNodeFresnel::get_output_port_count() const { + return 1; +} + +VisualShaderNodeFresnel::PortType VisualShaderNodeFresnel::get_output_port_type(int p_port) const { + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeFresnel::get_output_port_name(int p_port) const { + return "result"; +} + +String VisualShaderNodeFresnel::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + return "\t" + p_output_vars[0] + " = " + p_input_vars[2] + " ? (pow(clamp(dot(" + p_input_vars[0] + ", " + p_input_vars[1] + "), 0.0, 1.0), " + p_input_vars[3] + ")) : (pow(1.0 - clamp(dot(" + p_input_vars[0] + ", " + p_input_vars[1] + "), 0.0, 1.0), " + p_input_vars[3] + "));"; +} + +VisualShaderNodeFresnel::VisualShaderNodeFresnel() { + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(2, false); + set_input_port_default_value(3, 1.0); +} + +////////////// Is + +String VisualShaderNodeIs::get_caption() const { + + return "Is"; +} + +int VisualShaderNodeIs::get_input_port_count() const { + + return 1; +} + +VisualShaderNodeIs::PortType VisualShaderNodeIs::get_input_port_type(int p_port) const { + + return PORT_TYPE_SCALAR; +} + +String VisualShaderNodeIs::get_input_port_name(int p_port) const { + + return ""; +} + +int VisualShaderNodeIs::get_output_port_count() const { + + return 1; +} + +VisualShaderNodeIs::PortType VisualShaderNodeIs::get_output_port_type(int p_port) const { + + return PORT_TYPE_BOOLEAN; +} + +String VisualShaderNodeIs::get_output_port_name(int p_port) const { + + return ""; +} + +String VisualShaderNodeIs::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + static const char *funcs[FUNC_IS_NAN + 1] = { + "isinf($)", + "isnan($)" + }; + + String code; + code += "\t" + p_output_vars[0] + "=" + String(funcs[func]).replace("$", p_input_vars[0]) + ";\n"; + return code; +} + +void VisualShaderNodeIs::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeIs::Function VisualShaderNodeIs::get_function() const { + + return func; +} + +Vector<StringName> VisualShaderNodeIs::get_editable_properties() const { + + Vector<StringName> props; + props.push_back("function"); + return props; +} + +void VisualShaderNodeIs::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeIs::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeIs::get_function); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "Inf,NaN"), "set_function", "get_function"); + + BIND_ENUM_CONSTANT(FUNC_IS_INF); + BIND_ENUM_CONSTANT(FUNC_IS_NAN); +} + +VisualShaderNodeIs::VisualShaderNodeIs() { + + func = FUNC_IS_INF; + set_input_port_default_value(0, 0.0); +} + +////////////// Compare + +String VisualShaderNodeCompare::get_caption() const { + + return "Compare"; +} + +int VisualShaderNodeCompare::get_input_port_count() const { + + if (ctype == CTYPE_SCALAR && (func == FUNC_EQUAL || func == FUNC_NOT_EQUAL)) { + return 3; + } + return 2; +} + +VisualShaderNodeCompare::PortType VisualShaderNodeCompare::get_input_port_type(int p_port) const { + + if (p_port == 2) + return PORT_TYPE_SCALAR; + switch (ctype) { + case CTYPE_SCALAR: + return PORT_TYPE_SCALAR; + case CTYPE_VECTOR: + return PORT_TYPE_VECTOR; + case CTYPE_BOOLEAN: + return PORT_TYPE_BOOLEAN; + case CTYPE_TRANSFORM: + return PORT_TYPE_TRANSFORM; + } + return PORT_TYPE_VECTOR; +} + +String VisualShaderNodeCompare::get_input_port_name(int p_port) const { + if (p_port == 0) + return "a"; + else if (p_port == 1) + return "b"; + else if (p_port == 2) + return "tolerance"; + return ""; +} + +int VisualShaderNodeCompare::get_output_port_count() const { + return 1; +} + +VisualShaderNodeCompare::PortType VisualShaderNodeCompare::get_output_port_type(int p_port) const { + return PORT_TYPE_BOOLEAN; +} + +String VisualShaderNodeCompare::get_output_port_name(int p_port) const { + if (p_port == 0) + return "result"; + return ""; +} + +String VisualShaderNodeCompare::get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const { + + if (ctype == CTYPE_BOOLEAN || ctype == CTYPE_TRANSFORM) { + if (func > FUNC_NOT_EQUAL) { + return TTR("Invalid comparison function for that type."); + } + } + + return ""; +} + +String VisualShaderNodeCompare::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { + + static const char *ops[FUNC_LESS_THAN_EQUAL + 1] = { + "==", + "!=", + ">", + ">=", + "<", + "<=", + }; + + static const char *funcs[FUNC_LESS_THAN_EQUAL + 1] = { + "equal($)", + "notEqual($)", + "greaterThan($)", + "greaterThanEqual($)", + "lessThan($)", + "lessThanEqual($)", + }; + + static const char *conds[COND_ANY + 1] = { + "all($)", + "any($)", + }; + + String code; + switch (ctype) { + case CTYPE_SCALAR: + if (func == FUNC_EQUAL) { + code += "\t" + p_output_vars[0] + "=(abs(" + p_input_vars[0] + "-" + p_input_vars[1] + ")<" + p_input_vars[2] + ");"; + } else if (func == FUNC_NOT_EQUAL) { + code += "\t" + p_output_vars[0] + "=!(abs(" + p_input_vars[0] + "-" + p_input_vars[1] + ")<" + p_input_vars[2] + ");"; + } else { + code += "\t" + p_output_vars[0] + "=" + (p_input_vars[0] + "$" + p_input_vars[1]).replace("$", ops[func]) + ";\n"; + } + break; + + case CTYPE_VECTOR: + code += "\t{\n"; + code += "\t\tbvec3 _bv=" + String(funcs[func]).replace("$", p_input_vars[0] + ", " + p_input_vars[1]) + ";\n"; + code += "\t\t" + p_output_vars[0] + "=" + String(conds[condition]).replace("$", "_bv") + ";\n"; + code += "\t}\n"; + break; + + case CTYPE_BOOLEAN: + if (func > FUNC_NOT_EQUAL) + return "\t" + p_output_vars[0] + "=false;\n"; + code += "\t" + p_output_vars[0] + "=" + (p_input_vars[0] + "$" + p_input_vars[1]).replace("$", ops[func]) + ";\n"; + break; + + case CTYPE_TRANSFORM: + if (func > FUNC_NOT_EQUAL) + return "\t" + p_output_vars[0] + "=false;\n"; + code += "\t" + p_output_vars[0] + "=" + (p_input_vars[0] + "$" + p_input_vars[1]).replace("$", ops[func]) + ";\n"; + break; + + default: + break; + } + return code; +} + +void VisualShaderNodeCompare::set_comparsion_type(ComparsionType p_type) { + + ctype = p_type; + + switch (ctype) { + case CTYPE_SCALAR: + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + break; + case CTYPE_VECTOR: + set_input_port_default_value(0, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(1, Vector3(0.0, 0.0, 0.0)); + break; + case CTYPE_BOOLEAN: + set_input_port_default_value(0, false); + set_input_port_default_value(1, false); + break; + case CTYPE_TRANSFORM: + set_input_port_default_value(0, Transform()); + set_input_port_default_value(1, Transform()); + break; + } + emit_changed(); +} + +VisualShaderNodeCompare::ComparsionType VisualShaderNodeCompare::get_comparsion_type() const { + + return ctype; +} + +void VisualShaderNodeCompare::set_function(Function p_func) { + + func = p_func; + emit_changed(); +} + +VisualShaderNodeCompare::Function VisualShaderNodeCompare::get_function() const { + + return func; +} + +void VisualShaderNodeCompare::set_condition(Condition p_cond) { + + condition = p_cond; + emit_changed(); +} + +VisualShaderNodeCompare::Condition VisualShaderNodeCompare::get_condition() const { + + return condition; +} + +Vector<StringName> VisualShaderNodeCompare::get_editable_properties() const { + Vector<StringName> props; + props.push_back("type"); + props.push_back("function"); + if (ctype == CTYPE_VECTOR) + props.push_back("condition"); + return props; +} + +void VisualShaderNodeCompare::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_comparsion_type", "type"), &VisualShaderNodeCompare::set_comparsion_type); + ClassDB::bind_method(D_METHOD("get_comparsion_type"), &VisualShaderNodeCompare::get_comparsion_type); + + ClassDB::bind_method(D_METHOD("set_function", "func"), &VisualShaderNodeCompare::set_function); + ClassDB::bind_method(D_METHOD("get_function"), &VisualShaderNodeCompare::get_function); + + ClassDB::bind_method(D_METHOD("set_condition", "condition"), &VisualShaderNodeCompare::set_condition); + ClassDB::bind_method(D_METHOD("get_condition"), &VisualShaderNodeCompare::get_condition); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, "Scalar,Vector,Boolean,Transform"), "set_comparsion_type", "get_comparsion_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "function", PROPERTY_HINT_ENUM, "a == b,a != b,a > b,a >= b,a < b,a <= b"), "set_function", "get_function"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "condition", PROPERTY_HINT_ENUM, "All,Any"), "set_condition", "get_condition"); + + BIND_ENUM_CONSTANT(CTYPE_SCALAR); + BIND_ENUM_CONSTANT(CTYPE_VECTOR); + BIND_ENUM_CONSTANT(CTYPE_BOOLEAN); + BIND_ENUM_CONSTANT(CTYPE_TRANSFORM); + + BIND_ENUM_CONSTANT(FUNC_EQUAL); + BIND_ENUM_CONSTANT(FUNC_NOT_EQUAL); + BIND_ENUM_CONSTANT(FUNC_GREATER_THAN); + BIND_ENUM_CONSTANT(FUNC_GREATER_THAN_EQUAL); + BIND_ENUM_CONSTANT(FUNC_LESS_THAN); + BIND_ENUM_CONSTANT(FUNC_LESS_THAN_EQUAL); + + BIND_ENUM_CONSTANT(COND_ALL); + BIND_ENUM_CONSTANT(COND_ANY); +} + +VisualShaderNodeCompare::VisualShaderNodeCompare() { + ctype = CTYPE_SCALAR; + func = FUNC_EQUAL; + condition = COND_ALL; + set_input_port_default_value(0, 0.0); + set_input_port_default_value(1, 0.0); + set_input_port_default_value(2, CMP_EPSILON); +} diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 27b557494a..f7efa396dc 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -33,10 +33,12 @@ #include "scene/resources/visual_shader.h" -/// CONSTANTS /// +/////////////////////////////////////// +/// CONSTANTS +/////////////////////////////////////// class VisualShaderNodeScalarConstant : public VisualShaderNode { - GDCLASS(VisualShaderNodeScalarConstant, VisualShaderNode) + GDCLASS(VisualShaderNodeScalarConstant, VisualShaderNode); float constant; protected: @@ -63,8 +65,40 @@ public: VisualShaderNodeScalarConstant(); }; +/////////////////////////////////////// + +class VisualShaderNodeBooleanConstant : public VisualShaderNode { + GDCLASS(VisualShaderNodeBooleanConstant, VisualShaderNode); + bool constant; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_constant(bool p_value); + bool get_constant() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeBooleanConstant(); +}; + +/////////////////////////////////////// + class VisualShaderNodeColorConstant : public VisualShaderNode { - GDCLASS(VisualShaderNodeColorConstant, VisualShaderNode) + GDCLASS(VisualShaderNodeColorConstant, VisualShaderNode); Color constant; protected: @@ -91,8 +125,10 @@ public: VisualShaderNodeColorConstant(); }; +/////////////////////////////////////// + class VisualShaderNodeVec3Constant : public VisualShaderNode { - GDCLASS(VisualShaderNodeVec3Constant, VisualShaderNode) + GDCLASS(VisualShaderNodeVec3Constant, VisualShaderNode); Vector3 constant; protected: @@ -119,8 +155,10 @@ public: VisualShaderNodeVec3Constant(); }; +/////////////////////////////////////// + class VisualShaderNodeTransformConstant : public VisualShaderNode { - GDCLASS(VisualShaderNodeTransformConstant, VisualShaderNode) + GDCLASS(VisualShaderNodeTransformConstant, VisualShaderNode); Transform constant; protected: @@ -147,10 +185,12 @@ public: VisualShaderNodeTransformConstant(); }; -////////////////////////////////// +/////////////////////////////////////// +/// TEXTURES +/////////////////////////////////////// class VisualShaderNodeTexture : public VisualShaderNode { - GDCLASS(VisualShaderNodeTexture, VisualShaderNode) + GDCLASS(VisualShaderNodeTexture, VisualShaderNode); Ref<Texture> texture; public: @@ -158,7 +198,8 @@ public: SOURCE_TEXTURE, SOURCE_SCREEN, SOURCE_2D_TEXTURE, - SOURCE_2D_NORMAL + SOURCE_2D_NORMAL, + SOURCE_DEPTH }; enum TextureType { @@ -208,10 +249,10 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeTexture::TextureType) VARIANT_ENUM_CAST(VisualShaderNodeTexture::Source) -////////////////////////////////// +/////////////////////////////////////// class VisualShaderNodeCubeMap : public VisualShaderNode { - GDCLASS(VisualShaderNodeCubeMap, VisualShaderNode) + GDCLASS(VisualShaderNodeCubeMap, VisualShaderNode); Ref<CubeMap> cube_map; public: @@ -254,10 +295,13 @@ public: }; VARIANT_ENUM_CAST(VisualShaderNodeCubeMap::TextureType) + +/////////////////////////////////////// +/// OPS /////////////////////////////////////// class VisualShaderNodeScalarOp : public VisualShaderNode { - GDCLASS(VisualShaderNodeScalarOp, VisualShaderNode) + GDCLASS(VisualShaderNodeScalarOp, VisualShaderNode); public: enum Operator { @@ -269,7 +313,8 @@ public: OP_POW, OP_MAX, OP_MIN, - OP_ATAN2 + OP_ATAN2, + OP_STEP }; protected: @@ -301,7 +346,7 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeScalarOp::Operator) class VisualShaderNodeVectorOp : public VisualShaderNode { - GDCLASS(VisualShaderNodeVectorOp, VisualShaderNode) + GDCLASS(VisualShaderNodeVectorOp, VisualShaderNode); public: enum Operator { @@ -313,8 +358,10 @@ public: OP_POW, OP_MAX, OP_MIN, - OP_CROSS - + OP_CROSS, + OP_ATAN2, + OP_REFLECT, + OP_STEP }; protected: @@ -345,8 +392,10 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeVectorOp::Operator) +/////////////////////////////////////// + class VisualShaderNodeColorOp : public VisualShaderNode { - GDCLASS(VisualShaderNodeColorOp, VisualShaderNode) + GDCLASS(VisualShaderNodeColorOp, VisualShaderNode); public: enum Operator { @@ -358,7 +407,7 @@ public: OP_DODGE, OP_BURN, OP_SOFT_LIGHT, - OP_HARD_LIGHT, + OP_HARD_LIGHT }; protected: @@ -389,13 +438,19 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeColorOp::Operator) +/////////////////////////////////////// +/// TRANSFORM-TRANSFORM MULTIPLICATION +/////////////////////////////////////// + class VisualShaderNodeTransformMult : public VisualShaderNode { - GDCLASS(VisualShaderNodeTransformMult, VisualShaderNode) + GDCLASS(VisualShaderNodeTransformMult, VisualShaderNode); public: enum Operator { OP_AxB, OP_BxA, + OP_AxB_COMP, + OP_BxA_COMP }; protected: @@ -426,8 +481,12 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeTransformMult::Operator) +/////////////////////////////////////// +/// TRANSFORM-VECTOR MULTIPLICATION +/////////////////////////////////////// + class VisualShaderNodeTransformVecMult : public VisualShaderNode { - GDCLASS(VisualShaderNodeTransformVecMult, VisualShaderNode) + GDCLASS(VisualShaderNodeTransformVecMult, VisualShaderNode); public: enum Operator { @@ -466,9 +525,11 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeTransformVecMult::Operator) /////////////////////////////////////// +/// SCALAR FUNC +/////////////////////////////////////// class VisualShaderNodeScalarFunc : public VisualShaderNode { - GDCLASS(VisualShaderNodeScalarFunc, VisualShaderNode) + GDCLASS(VisualShaderNodeScalarFunc, VisualShaderNode); public: enum Function { @@ -492,6 +553,18 @@ public: FUNC_FRAC, FUNC_SATURATE, FUNC_NEGATE, + FUNC_ACOSH, + FUNC_ASINH, + FUNC_ATANH, + FUNC_DEGREES, + FUNC_EXP2, + FUNC_INVERSE_SQRT, + FUNC_LOG2, + FUNC_RADIANS, + FUNC_RECIPROCAL, + FUNC_ROUNDEVEN, + FUNC_TRUNC, + FUNC_ONEMINUS }; protected: @@ -523,9 +596,11 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeScalarFunc::Function) /////////////////////////////////////// +/// VECTOR FUNC +/////////////////////////////////////// class VisualShaderNodeVectorFunc : public VisualShaderNode { - GDCLASS(VisualShaderNodeVectorFunc, VisualShaderNode) + GDCLASS(VisualShaderNodeVectorFunc, VisualShaderNode); public: enum Function { @@ -535,6 +610,35 @@ public: FUNC_RECIPROCAL, FUNC_RGB2HSV, FUNC_HSV2RGB, + FUNC_ABS, + FUNC_ACOS, + FUNC_ACOSH, + FUNC_ASIN, + FUNC_ASINH, + FUNC_ATAN, + FUNC_ATANH, + FUNC_CEIL, + FUNC_COS, + FUNC_COSH, + FUNC_DEGREES, + FUNC_EXP, + FUNC_EXP2, + FUNC_FLOOR, + FUNC_FRAC, + FUNC_INVERSE_SQRT, + FUNC_LOG, + FUNC_LOG2, + FUNC_RADIANS, + FUNC_ROUND, + FUNC_ROUNDEVEN, + FUNC_SIGN, + FUNC_SIN, + FUNC_SINH, + FUNC_SQRT, + FUNC_TAN, + FUNC_TANH, + FUNC_TRUNC, + FUNC_ONEMINUS }; protected: @@ -566,9 +670,93 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeVectorFunc::Function) /////////////////////////////////////// +/// COLOR FUNC +/////////////////////////////////////// + +class VisualShaderNodeColorFunc : public VisualShaderNode { + GDCLASS(VisualShaderNodeColorFunc, VisualShaderNode); + +public: + enum Function { + FUNC_GRAYSCALE, + FUNC_SEPIA + }; + +protected: + Function func; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_function(Function p_func); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeColorFunc(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeColorFunc::Function) + +/////////////////////////////////////// +/// TRANSFORM FUNC +/////////////////////////////////////// + +class VisualShaderNodeTransformFunc : public VisualShaderNode { + GDCLASS(VisualShaderNodeTransformFunc, VisualShaderNode); + +public: + enum Function { + FUNC_INVERSE, + FUNC_TRANSPOSE + }; + +protected: + Function func; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_function(Function p_func); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeTransformFunc(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeTransformFunc::Function) + +/////////////////////////////////////// +/// DOT +/////////////////////////////////////// class VisualShaderNodeDotProduct : public VisualShaderNode { - GDCLASS(VisualShaderNodeDotProduct, VisualShaderNode) + GDCLASS(VisualShaderNodeDotProduct, VisualShaderNode); public: virtual String get_caption() const; @@ -587,9 +775,11 @@ public: }; /////////////////////////////////////// +/// LENGTH +/////////////////////////////////////// class VisualShaderNodeVectorLen : public VisualShaderNode { - GDCLASS(VisualShaderNodeVectorLen, VisualShaderNode) + GDCLASS(VisualShaderNodeVectorLen, VisualShaderNode); public: virtual String get_caption() const; @@ -608,9 +798,340 @@ public: }; /////////////////////////////////////// +/// DETERMINANT +/////////////////////////////////////// + +class VisualShaderNodeDeterminant : public VisualShaderNode { + GDCLASS(VisualShaderNodeDeterminant, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeDeterminant(); +}; + +/////////////////////////////////////// +/// CLAMP +/////////////////////////////////////// + +class VisualShaderNodeScalarClamp : public VisualShaderNode { + GDCLASS(VisualShaderNodeScalarClamp, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeScalarClamp(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeVectorClamp : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorClamp, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorClamp(); +}; + +/////////////////////////////////////// +/// DERIVATIVE FUNCTIONS +/////////////////////////////////////// + +class VisualShaderNodeScalarDerivativeFunc : public VisualShaderNode { + GDCLASS(VisualShaderNodeScalarDerivativeFunc, VisualShaderNode); + +public: + enum Function { + FUNC_SUM, + FUNC_X, + FUNC_Y + }; + +protected: + Function func; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_function(Function p_func); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeScalarDerivativeFunc(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeScalarDerivativeFunc::Function) + +/////////////////////////////////////// + +class VisualShaderNodeVectorDerivativeFunc : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorDerivativeFunc, VisualShaderNode); + +public: + enum Function { + FUNC_SUM, + FUNC_X, + FUNC_Y + }; + +protected: + Function func; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_function(Function p_func); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeVectorDerivativeFunc(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeVectorDerivativeFunc::Function) + +/////////////////////////////////////// +/// FACEFORWARD +/////////////////////////////////////// + +class VisualShaderNodeFaceForward : public VisualShaderNode { + GDCLASS(VisualShaderNodeFaceForward, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeFaceForward(); +}; + +/////////////////////////////////////// +/// OUTER PRODUCT +/////////////////////////////////////// + +class VisualShaderNodeOuterProduct : public VisualShaderNode { + GDCLASS(VisualShaderNodeOuterProduct, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeOuterProduct(); +}; + +/////////////////////////////////////// +/// STEP +/////////////////////////////////////// + +class VisualShaderNodeVectorScalarStep : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorScalarStep, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorScalarStep(); +}; + +/////////////////////////////////////// +/// SMOOTHSTEP +/////////////////////////////////////// + +class VisualShaderNodeScalarSmoothStep : public VisualShaderNode { + GDCLASS(VisualShaderNodeScalarSmoothStep, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeScalarSmoothStep(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeVectorSmoothStep : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorSmoothStep, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorSmoothStep(); +}; + +/////////////////////////////////////// + +class VisualShaderNodeVectorScalarSmoothStep : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorScalarSmoothStep, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorScalarSmoothStep(); +}; + +/////////////////////////////////////// +/// DISTANCE +/////////////////////////////////////// + +class VisualShaderNodeVectorDistance : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorDistance, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorDistance(); +}; + +/////////////////////////////////////// +/// REFRACT +/////////////////////////////////////// + +class VisualShaderNodeVectorRefract : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorRefract, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorRefract(); +}; + +/////////////////////////////////////// +/// MIX +/////////////////////////////////////// class VisualShaderNodeScalarInterp : public VisualShaderNode { - GDCLASS(VisualShaderNodeScalarInterp, VisualShaderNode) + GDCLASS(VisualShaderNodeScalarInterp, VisualShaderNode); public: virtual String get_caption() const; @@ -631,7 +1152,7 @@ public: /////////////////////////////////////// class VisualShaderNodeVectorInterp : public VisualShaderNode { - GDCLASS(VisualShaderNodeVectorInterp, VisualShaderNode) + GDCLASS(VisualShaderNodeVectorInterp, VisualShaderNode); public: virtual String get_caption() const; @@ -651,8 +1172,31 @@ public: /////////////////////////////////////// +class VisualShaderNodeVectorScalarMix : public VisualShaderNode { + GDCLASS(VisualShaderNodeVectorScalarMix, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeVectorScalarMix(); +}; + +/////////////////////////////////////// +/// COMPOSE +/////////////////////////////////////// + class VisualShaderNodeVectorCompose : public VisualShaderNode { - GDCLASS(VisualShaderNodeVectorCompose, VisualShaderNode) + GDCLASS(VisualShaderNodeVectorCompose, VisualShaderNode); public: virtual String get_caption() const; @@ -673,7 +1217,7 @@ public: /////////////////////////////////////// class VisualShaderNodeTransformCompose : public VisualShaderNode { - GDCLASS(VisualShaderNodeTransformCompose, VisualShaderNode) + GDCLASS(VisualShaderNodeTransformCompose, VisualShaderNode); public: virtual String get_caption() const; @@ -692,9 +1236,11 @@ public: }; /////////////////////////////////////// +/// DECOMPOSE +/////////////////////////////////////// class VisualShaderNodeVectorDecompose : public VisualShaderNode { - GDCLASS(VisualShaderNodeVectorDecompose, VisualShaderNode) + GDCLASS(VisualShaderNodeVectorDecompose, VisualShaderNode); public: virtual String get_caption() const; @@ -715,7 +1261,7 @@ public: /////////////////////////////////////// class VisualShaderNodeTransformDecompose : public VisualShaderNode { - GDCLASS(VisualShaderNodeTransformDecompose, VisualShaderNode) + GDCLASS(VisualShaderNodeTransformDecompose, VisualShaderNode); public: virtual String get_caption() const; @@ -734,9 +1280,11 @@ public: }; /////////////////////////////////////// +/// UNIFORMS +/////////////////////////////////////// class VisualShaderNodeScalarUniform : public VisualShaderNodeUniform { - GDCLASS(VisualShaderNodeScalarUniform, VisualShaderNodeUniform) + GDCLASS(VisualShaderNodeScalarUniform, VisualShaderNodeUniform); public: virtual String get_caption() const; @@ -755,8 +1303,32 @@ public: VisualShaderNodeScalarUniform(); }; +/////////////////////////////////////// + +class VisualShaderNodeBooleanUniform : public VisualShaderNodeUniform { + GDCLASS(VisualShaderNodeBooleanUniform, VisualShaderNodeUniform); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeBooleanUniform(); +}; + +/////////////////////////////////////// + class VisualShaderNodeColorUniform : public VisualShaderNodeUniform { - GDCLASS(VisualShaderNodeColorUniform, VisualShaderNodeUniform) + GDCLASS(VisualShaderNodeColorUniform, VisualShaderNodeUniform); public: virtual String get_caption() const; @@ -775,8 +1347,10 @@ public: VisualShaderNodeColorUniform(); }; +/////////////////////////////////////// + class VisualShaderNodeVec3Uniform : public VisualShaderNodeUniform { - GDCLASS(VisualShaderNodeVec3Uniform, VisualShaderNodeUniform) + GDCLASS(VisualShaderNodeVec3Uniform, VisualShaderNodeUniform); public: virtual String get_caption() const; @@ -795,8 +1369,10 @@ public: VisualShaderNodeVec3Uniform(); }; +/////////////////////////////////////// + class VisualShaderNodeTransformUniform : public VisualShaderNodeUniform { - GDCLASS(VisualShaderNodeTransformUniform, VisualShaderNodeUniform) + GDCLASS(VisualShaderNodeTransformUniform, VisualShaderNodeUniform); public: virtual String get_caption() const; @@ -815,10 +1391,11 @@ public: VisualShaderNodeTransformUniform(); }; -////////////////////////////////// +/////////////////////////////////////// class VisualShaderNodeTextureUniform : public VisualShaderNodeUniform { - GDCLASS(VisualShaderNodeTextureUniform, VisualShaderNodeUniform) + GDCLASS(VisualShaderNodeTextureUniform, VisualShaderNodeUniform); + public: enum TextureType { TYPE_DATA, @@ -867,10 +1444,29 @@ public: VARIANT_ENUM_CAST(VisualShaderNodeTextureUniform::TextureType) VARIANT_ENUM_CAST(VisualShaderNodeTextureUniform::ColorDefault) -////////////////////////////////// +/////////////////////////////////////// + +class VisualShaderNodeTextureUniformTriplanar : public VisualShaderNodeTextureUniform { + GDCLASS(VisualShaderNodeTextureUniformTriplanar, VisualShaderNodeTextureUniform); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + VisualShaderNodeTextureUniformTriplanar(); +}; + +/////////////////////////////////////// class VisualShaderNodeCubeMapUniform : public VisualShaderNode { - GDCLASS(VisualShaderNodeCubeMapUniform, VisualShaderNode) + GDCLASS(VisualShaderNodeCubeMapUniform, VisualShaderNode); public: virtual String get_caption() const; @@ -888,4 +1484,184 @@ public: VisualShaderNodeCubeMapUniform(); }; +/////////////////////////////////////// +/// IF +/////////////////////////////////////// + +class VisualShaderNodeIf : public VisualShaderNode { + GDCLASS(VisualShaderNodeIf, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + + VisualShaderNodeIf(); +}; + +/////////////////////////////////////// +/// SWITCH +/////////////////////////////////////// + +class VisualShaderNodeSwitch : public VisualShaderNode { + GDCLASS(VisualShaderNodeSwitch, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + + VisualShaderNodeSwitch(); +}; + +/////////////////////////////////////// +/// FRESNEL +/////////////////////////////////////// + +class VisualShaderNodeFresnel : public VisualShaderNode { + GDCLASS(VisualShaderNodeFresnel, VisualShaderNode); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; + + VisualShaderNodeFresnel(); +}; + +/////////////////////////////////////// +/// Is +/////////////////////////////////////// + +class VisualShaderNodeIs : public VisualShaderNode { + GDCLASS(VisualShaderNodeIs, VisualShaderNode); + +public: + enum Function { + FUNC_IS_INF, + FUNC_IS_NAN, + }; + +protected: + Function func; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_function(Function p_func); + Function get_function() const; + + virtual Vector<StringName> get_editable_properties() const; + + VisualShaderNodeIs(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeIs::Function) + +/////////////////////////////////////// +/// Compare +/////////////////////////////////////// + +class VisualShaderNodeCompare : public VisualShaderNode { + GDCLASS(VisualShaderNodeCompare, VisualShaderNode); + +public: + enum ComparsionType { + CTYPE_SCALAR, + CTYPE_VECTOR, + CTYPE_BOOLEAN, + CTYPE_TRANSFORM + }; + + enum Function { + FUNC_EQUAL, + FUNC_NOT_EQUAL, + FUNC_GREATER_THAN, + FUNC_GREATER_THAN_EQUAL, + FUNC_LESS_THAN, + FUNC_LESS_THAN_EQUAL, + }; + + enum Condition { + COND_ALL, + COND_ANY, + }; + +protected: + ComparsionType ctype; + Function func; + Condition condition; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + virtual int get_input_port_count() const; + virtual PortType get_input_port_type(int p_port) const; + virtual String get_input_port_name(int p_port) const; + + virtual int get_output_port_count() const; + virtual PortType get_output_port_type(int p_port) const; + virtual String get_output_port_name(int p_port) const; + + virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty + + void set_comparsion_type(ComparsionType p_type); + ComparsionType get_comparsion_type() const; + + void set_function(Function p_func); + Function get_function() const; + + void set_condition(Condition p_cond); + Condition get_condition() const; + + virtual Vector<StringName> get_editable_properties() const; + virtual String get_warning(Shader::Mode p_mode, VisualShader::Type p_type) const; + + VisualShaderNodeCompare(); +}; + +VARIANT_ENUM_CAST(VisualShaderNodeCompare::ComparsionType) +VARIANT_ENUM_CAST(VisualShaderNodeCompare::Function) +VARIANT_ENUM_CAST(VisualShaderNodeCompare::Condition) + #endif // VISUAL_SHADER_NODES_H diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 305e4fc735..634f4d7160 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -203,4 +203,6 @@ SceneStringNames::SceneStringNames() { _mesh_changed = StaticCString::create("_mesh_changed"); parameters_base_path = "parameters/"; + + tracks_changed = "tracks_changed"; } diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 103b06dbc1..bc75165b8c 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -32,7 +32,7 @@ #define SCENE_STRING_NAMES_H #include "core/node_path.h" -#include "core/string_db.h" +#include "core/string_name.h" class SceneStringNames { friend void register_scene_types(); @@ -205,6 +205,8 @@ public: StringName parameters_base_path; + StringName tracks_changed; + enum { MAX_MATERIALS = 32 }; |