diff options
125 files changed, 9243 insertions, 1482 deletions
diff --git a/core/object.h b/core/object.h index c405e22557..7963a43fd6 100644 --- a/core/object.h +++ b/core/object.h @@ -55,7 +55,7 @@ enum PropertyHint { PROPERTY_HINT_RANGE, ///< hint_text = "min,max,step,slider; //slider is optional" PROPERTY_HINT_EXP_RANGE, ///< hint_text = "min,max,step", exponential edit PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" - PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) + PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "full" to also include in/out. (ie: "attenuation,inout") PROPERTY_HINT_LENGTH, ///< hint_text= "length" (as integer) PROPERTY_HINT_SPRITE_FRAME, PROPERTY_HINT_KEY_ACCEL, ///< hint_text= "length" (as integer) diff --git a/core/script_language.h b/core/script_language.h index 55a20c7478..b4c55cac9e 100644 --- a/core/script_language.h +++ b/core/script_language.h @@ -236,6 +236,8 @@ public: virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const = 0; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) = 0; + virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) {} + virtual void remove_named_global_constant(const StringName &p_name) {} /* MULTITHREAD FUNCTIONS */ diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml index 38aab5231e..80d7b7783f 100644 --- a/doc/classes/ArrayMesh.xml +++ b/doc/classes/ArrayMesh.xml @@ -91,7 +91,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the length in indices of the index array in the requested surface (see [method add_surface]). + Return the length in indices of the index array in the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_get_array_len" qualifiers="const"> @@ -100,7 +100,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the length in vertices of the vertex array in the requested surface (see [method add_surface]). + Return the length in vertices of the vertex array in the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_get_arrays" qualifiers="const"> @@ -125,7 +125,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the format mask of the requested surface (see [method add_surface]). + Return the format mask of the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_get_material" qualifiers="const"> @@ -151,7 +151,7 @@ <argument index="0" name="surf_idx" type="int"> </argument> <description> - Return the primitive type of the requested surface (see [method add_surface]). + Return the primitive type of the requested surface (see [method add_surface_from_arrays]). </description> </method> <method name="surface_remove"> diff --git a/doc/classes/PacketPeerUDP.xml b/doc/classes/PacketPeerUDP.xml index 1b821e06cf..d4e3d17de6 100644 --- a/doc/classes/PacketPeerUDP.xml +++ b/doc/classes/PacketPeerUDP.xml @@ -22,14 +22,14 @@ <return type="String"> </return> <description> - Return the IP of the remote peer that sent the last packet(that was received with [method get_packet] or [method get_var]). + Return the IP of the remote peer that sent the last packet(that was received with [method PacketPeer.get_packet] or [method PacketPeer.get_var]). </description> </method> <method name="get_packet_port" qualifiers="const"> <return type="int"> </return> <description> - Return the port of the remote peer that sent the last packet(that was received with [method get_packet] or [method get_var]). + Return the port of the remote peer that sent the last packet(that was received with [method PacketPeer.get_packet] or [method PacketPeer.get_var]). </description> </method> <method name="is_listening" qualifiers="const"> diff --git a/doc/classes/Physics2DServer.xml b/doc/classes/Physics2DServer.xml index 98535dd330..1098b0d6b1 100644 --- a/doc/classes/Physics2DServer.xml +++ b/doc/classes/Physics2DServer.xml @@ -688,7 +688,7 @@ <argument index="1" name="space" type="RID"> </argument> <description> - Assigns a space to the body (see [method create_space]). + Assigns a space to the body (see [method space_create]). </description> </method> <method name="body_set_state"> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 76d94fbfbc..11fc00d129 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -99,6 +99,8 @@ </argument> <argument index="2" name="can_be_hidden" type="bool" default="true"> </argument> + <argument index="3" name="wrap_index" type="int" default="0"> + </argument> <description> </description> </method> diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 54d803f13c..312d5aa378 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -127,7 +127,26 @@ public: String path; }; + struct DummySurface { + uint32_t format; + VS::PrimitiveType primitive; + PoolVector<uint8_t> array; + int vertex_count; + PoolVector<uint8_t> index_array; + int index_count; + AABB aabb; + Vector<PoolVector<uint8_t> > blend_shapes; + Vector<AABB> bone_aabbs; + }; + + struct DummyMesh : public RID_Data { + Vector<DummySurface> surfaces; + int blend_shape_count; + VS::BlendShapeMode blend_shape_mode; + }; + mutable RID_Owner<DummyTexture> texture_owner; + mutable RID_Owner<DummyMesh> mesh_owner; RID texture_create() { @@ -256,46 +275,128 @@ public: /* MESH API */ - RID mesh_create() { return RID(); } - - void mesh_add_surface_from_arrays(RID p_mesh, VS::PrimitiveType p_primitive, const Array &p_arrays, const Array &p_blend_shapes = Array(), uint32_t p_compress_format = Mesh::ARRAY_COMPRESS_DEFAULT) {} - void mesh_add_surface(RID p_mesh, uint32_t p_format, VS::PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<PoolVector<uint8_t> > &p_blend_shapes = Vector<PoolVector<uint8_t> >(), const Vector<AABB> &p_bone_aabbs = Vector<AABB>()) {} + RID mesh_create() { + DummyMesh *mesh = memnew(DummyMesh); + ERR_FAIL_COND_V(!mesh, RID()); + mesh->blend_shape_count = 0; + mesh->blend_shape_mode = VS::BLEND_SHAPE_MODE_NORMALIZED; + return mesh_owner.make_rid(mesh); + } - void mesh_add_surface_from_mesh_data(RID p_mesh, const Geometry::MeshData &p_mesh_data) {} - void mesh_add_surface_from_planes(RID p_mesh, const PoolVector<Plane> &p_planes) {} + void mesh_add_surface(RID p_mesh, uint32_t p_format, VS::PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const AABB &p_aabb, const Vector<PoolVector<uint8_t> > &p_blend_shapes = Vector<PoolVector<uint8_t> >(), const Vector<AABB> &p_bone_aabbs = Vector<AABB>()) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + + m->surfaces.push_back(DummySurface()); + DummySurface *s = &m->surfaces[m->surfaces.size() - 1]; + s->format = p_format; + s->primitive = p_primitive; + s->array = p_array; + s->vertex_count = p_vertex_count; + s->index_array = p_index_array; + s->index_count = p_index_count; + s->aabb = p_aabb; + s->blend_shapes = p_blend_shapes; + s->bone_aabbs = p_bone_aabbs; + } - void mesh_set_blend_shape_count(RID p_mesh, int p_amount) {} - int mesh_get_blend_shape_count(RID p_mesh) const { return 0; } + void mesh_set_blend_shape_count(RID p_mesh, int p_amount) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + m->blend_shape_count = p_amount; + } + int mesh_get_blend_shape_count(RID p_mesh) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + return m->blend_shape_count; + } - void mesh_set_blend_shape_mode(RID p_mesh, VS::BlendShapeMode p_mode) {} - VS::BlendShapeMode mesh_get_blend_shape_mode(RID p_mesh) const { return VS::BLEND_SHAPE_MODE_NORMALIZED; } + void mesh_set_blend_shape_mode(RID p_mesh, VS::BlendShapeMode p_mode) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + m->blend_shape_mode = p_mode; + } + VS::BlendShapeMode mesh_get_blend_shape_mode(RID p_mesh) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, VS::BLEND_SHAPE_MODE_NORMALIZED); + return m->blend_shape_mode; + } void mesh_surface_update_region(RID p_mesh, int p_surface, int p_offset, const PoolVector<uint8_t> &p_data) {} void mesh_surface_set_material(RID p_mesh, int p_surface, RID p_material) {} RID mesh_surface_get_material(RID p_mesh, int p_surface) const { return RID(); } - int mesh_surface_get_array_len(RID p_mesh, int p_surface) const { return 0; } - int mesh_surface_get_array_index_len(RID p_mesh, int p_surface) const { return 0; } + int mesh_surface_get_array_len(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + + return m->surfaces[p_surface].vertex_count; + } + int mesh_surface_get_array_index_len(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + + return m->surfaces[p_surface].index_count; + } PoolVector<uint8_t> mesh_surface_get_array(RID p_mesh, int p_surface) const { - PoolVector<uint8_t> p; - return p; + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, PoolVector<uint8_t>()); + + return m->surfaces[p_surface].array; } PoolVector<uint8_t> mesh_surface_get_index_array(RID p_mesh, int p_surface) const { - PoolVector<uint8_t> p; - return p; + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, PoolVector<uint8_t>()); + + return m->surfaces[p_surface].index_array; + } + + uint32_t mesh_surface_get_format(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + + return m->surfaces[p_surface].format; + } + VS::PrimitiveType mesh_surface_get_primitive_type(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, VS::PRIMITIVE_POINTS); + + return m->surfaces[p_surface].primitive; + } + + AABB mesh_surface_get_aabb(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, AABB()); + + return m->surfaces[p_surface].aabb; } + Vector<PoolVector<uint8_t> > mesh_surface_get_blend_shapes(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, Vector<PoolVector<uint8_t> >()); - uint32_t mesh_surface_get_format(RID p_mesh, int p_surface) const { return 0; } - VS::PrimitiveType mesh_surface_get_primitive_type(RID p_mesh, int p_surface) const { return VS::PRIMITIVE_POINTS; } + return m->surfaces[p_surface].blend_shapes; + } + Vector<AABB> mesh_surface_get_skeleton_aabb(RID p_mesh, int p_surface) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, Vector<AABB>()); + + return m->surfaces[p_surface].bone_aabbs; + } - AABB mesh_surface_get_aabb(RID p_mesh, int p_surface) const { return AABB(); } - Vector<PoolVector<uint8_t> > mesh_surface_get_blend_shapes(RID p_mesh, int p_surface) const { return Vector<PoolVector<uint8_t> >(); } - Vector<AABB> mesh_surface_get_skeleton_aabb(RID p_mesh, int p_surface) const { return Vector<AABB>(); } + void mesh_remove_surface(RID p_mesh, int p_index) { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND(!m); + ERR_FAIL_COND(p_index >= m->surfaces.size()); - void mesh_remove_surface(RID p_mesh, int p_index) {} - int mesh_get_surface_count(RID p_mesh) const { return 0; } + m->surfaces.remove(p_index); + } + int mesh_get_surface_count(RID p_mesh) const { + DummyMesh *m = mesh_owner.getornull(p_mesh); + ERR_FAIL_COND_V(!m, 0); + return m->surfaces.size(); + } void mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb) {} AABB mesh_get_custom_aabb(RID p_mesh) const { return AABB(); } @@ -598,7 +699,14 @@ public: RID canvas_light_occluder_create() { return RID(); } void canvas_light_occluder_set_polylines(RID p_occluder, const PoolVector<Vector2> &p_lines) {} - VS::InstanceType get_base_type(RID p_rid) const { return VS::INSTANCE_NONE; } + VS::InstanceType get_base_type(RID p_rid) const { + if (mesh_owner.owns(p_rid)) { + return VS::INSTANCE_MESH; + } + + return VS::INSTANCE_NONE; + } + bool free(RID p_rid) { if (texture_owner.owns(p_rid)) { diff --git a/drivers/dummy/texture_loader_dummy.cpp b/drivers/dummy/texture_loader_dummy.cpp new file mode 100644 index 0000000000..6d3e176bbb --- /dev/null +++ b/drivers/dummy/texture_loader_dummy.cpp @@ -0,0 +1,87 @@ +/*************************************************************************/ +/* texture_loader_dummy.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "texture_loader_dummy.h" +#include "core/os/file_access.h" +#include "print_string.h" +#include <string.h> + +RES ResourceFormatDummyTexture::load(const String &p_path, const String &p_original_path, Error *r_error) { + unsigned int width = 8; + unsigned int height = 8; + + //We just use some format + Image::Format fmt = Image::FORMAT_RGB8; + int rowsize = 3 * width; + + PoolVector<uint8_t> dstbuff; + + dstbuff.resize(rowsize * height); + + PoolVector<uint8_t>::Write dstbuff_write = dstbuff.write(); + + uint8_t *data = dstbuff_write.ptr(); + + uint8_t **row_p = memnew_arr(uint8_t *, height); + + for (unsigned int i = 0; i < height; i++) { + row_p[i] = 0; //No colors any more, I want them to turn black + } + + memdelete_arr(row_p); + + Ref<Image> img = memnew(Image(width, height, 0, fmt, dstbuff)); + + Ref<ImageTexture> texture = memnew(ImageTexture); + texture->create_from_image(img); + + if (r_error) + *r_error = OK; + + return texture; +} + +void ResourceFormatDummyTexture::get_recognized_extensions(List<String> *p_extensions) const { + p_extensions->push_back("png"); + p_extensions->push_back("hdr"); + p_extensions->push_back("jpg"); + p_extensions->push_back("tga"); +} + +bool ResourceFormatDummyTexture::handles_type(const String &p_type) const { + return ClassDB::is_parent_class(p_type, "Texture"); +} + +String ResourceFormatDummyTexture::get_resource_type(const String &p_path) const { + String extension = p_path.get_extension().to_lower(); + if (extension == "png" || extension == "hdr" || extension == "jpg" || extension == "tga") + return "ImageTexture"; + return ""; +} diff --git a/drivers/dummy/texture_loader_dummy.h b/drivers/dummy/texture_loader_dummy.h new file mode 100644 index 0000000000..f0a355ec12 --- /dev/null +++ b/drivers/dummy/texture_loader_dummy.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* texture_loader_dummy.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEXTURE_LOADER_DUMMY_H +#define TEXTURE_LOADER_DUMMY_H + +#include "core/io/resource_loader.h" +#include "scene/resources/texture.h" + +class ResourceFormatDummyTexture : public 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; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; + + virtual ~ResourceFormatDummyTexture() {} +}; + +#endif // TEXTURE_LOADER_DUMMY_H diff --git a/editor/animation_editor.cpp b/editor/animation_editor.cpp index f7c8cac93f..a03bf76d1b 100644 --- a/editor/animation_editor.cpp +++ b/editor/animation_editor.cpp @@ -2969,6 +2969,7 @@ void AnimationKeyEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { + update_keying(); EditorNode::get_singleton()->update_keying(); emit_signal("keying_changed"); } break; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 24e86770bf..93c86d920a 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -779,6 +779,7 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_draw_breakpoint_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/show_breakpoint_gutter")); text_editor->set_hiding_enabled(EditorSettings::get_singleton()->get("text_editor/line_numbers/code_folding")); text_editor->set_draw_fold_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/code_folding")); + text_editor->set_wrap_enabled(EditorSettings::get_singleton()->get("text_editor/line_numbers/word_wrap")); text_editor->cursor_set_block_mode(EditorSettings::get_singleton()->get("text_editor/cursor/block_caret")); text_editor->set_smooth_scroll_enabled(EditorSettings::get_singleton()->get("text_editor/open_scripts/smooth_scrolling")); text_editor->set_v_scroll_speed(EditorSettings::get_singleton()->get("text_editor/open_scripts/v_scroll_speed")); diff --git a/editor/doc/doc_data.cpp b/editor/doc/doc_data.cpp index 3434aa33f9..c992ac5f16 100644 --- a/editor/doc/doc_data.cpp +++ b/editor/doc/doc_data.cpp @@ -567,6 +567,9 @@ void DocData::generate(bool p_basic_types) { PropertyDoc pd; Engine::Singleton &s = E->get(); + if (!s.ptr) { + continue; + } pd.name = s.name; pd.type = s.ptr->get_class(); while (String(ClassDB::get_parent_class(pd.type)) != "Object") diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index a2f5c1aa1a..708bff252a 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -33,6 +33,8 @@ #include "editor_node.h" #include "global_constants.h" #include "project_settings.h" +#include "scene/main/viewport.h" +#include "scene/resources/packed_scene.h" #define PREVIEW_LIST_MAX_SIZE 10 @@ -155,8 +157,8 @@ void EditorAutoloadSettings::_autoload_edited() { undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", selected_autoload, order); undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", name); - undo_redo->add_do_method(this, "update_autoload"); - undo_redo->add_undo_method(this, "update_autoload"); + undo_redo->add_do_method(this, "call_deferred", "update_autoload"); + undo_redo->add_undo_method(this, "call_deferred", "update_autoload"); undo_redo->add_do_method(this, "emit_signal", autoload_changed); undo_redo->add_undo_method(this, "emit_signal", autoload_changed); @@ -187,8 +189,8 @@ void EditorAutoloadSettings::_autoload_edited() { undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", base, order); undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", base, order); - undo_redo->add_do_method(this, "update_autoload"); - undo_redo->add_undo_method(this, "update_autoload"); + undo_redo->add_do_method(this, "call_deferred", "update_autoload"); + undo_redo->add_undo_method(this, "call_deferred", "update_autoload"); undo_redo->add_do_method(this, "emit_signal", autoload_changed); undo_redo->add_undo_method(this, "emit_signal", autoload_changed); @@ -296,6 +298,18 @@ void EditorAutoloadSettings::update_autoload() { updating_autoload = true; + Map<String, AutoLoadInfo> to_remove; + Map<String, AutoLoadInfo> to_remove_singleton; + List<AutoLoadInfo> to_add; + List<String> to_add_singleton; // Only for when the node is still the same + + for (List<AutoLoadInfo>::Element *E = autoload_cache.front(); E; E = E->next()) { + to_remove.insert(E->get().name, E->get()); + if (E->get().is_singleton) { + to_remove_singleton.insert(E->get().name, E->get()); + } + } + autoload_cache.clear(); tree->clear(); @@ -317,19 +331,44 @@ void EditorAutoloadSettings::update_autoload() { if (name.empty()) continue; + AutoLoadInfo old_info; + if (to_remove.has(name)) { + old_info = to_remove[name]; + } + AutoLoadInfo info; - info.name = pi.name; - info.order = ProjectSettings::get_singleton()->get_order(pi.name); + info.is_singleton = path.begins_with("*"); - autoload_cache.push_back(info); + if (info.is_singleton) { + path = path.substr(1, path.length()); + } - bool global = false; + info.name = name; + info.path = path; + info.order = ProjectSettings::get_singleton()->get_order(pi.name); - if (path.begins_with("*")) { - global = true; - path = path.substr(1, path.length()); + if (old_info.name == info.name) { + if (old_info.path == info.path) { + // Still the same resource, check singleton status + to_remove.erase(name); + if (info.is_singleton) { + if (old_info.is_singleton) { + to_remove_singleton.erase(name); + } else { + to_add_singleton.push_back(name); + } + } + } else { + // Resource changed + to_add.push_back(info); + } + } else { + // New autoload + to_add.push_back(info); } + autoload_cache.push_back(info); + TreeItem *item = tree->create_item(root); item->set_text(0, name); item->set_editable(0, true); @@ -340,7 +379,7 @@ void EditorAutoloadSettings::update_autoload() { item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK); item->set_editable(2, true); item->set_text(2, TTR("Enable")); - item->set_checked(2, global); + item->set_checked(2, info.is_singleton); item->add_button(3, get_icon("FileList", "EditorIcons"), BUTTON_OPEN); item->add_button(3, get_icon("MoveUp", "EditorIcons"), BUTTON_MOVE_UP); item->add_button(3, get_icon("MoveDown", "EditorIcons"), BUTTON_MOVE_DOWN); @@ -348,6 +387,77 @@ void EditorAutoloadSettings::update_autoload() { item->set_selectable(3, false); } + // Remove autoload constants + for (Map<String, AutoLoadInfo>::Element *E = to_remove_singleton.front(); E; E = E->next()) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->remove_named_global_constant(E->get().name); + } + } + + // Remove obsolete nodes from the tree + for (Map<String, AutoLoadInfo>::Element *E = to_remove.front(); E; E = E->next()) { + AutoLoadInfo &info = E->get(); + Node *al = get_node("/root/" + info.name); + ERR_CONTINUE(!al); + get_tree()->get_root()->remove_child(al); + memdelete(al); + } + + // Register new singletons already in the tree + for (List<String>::Element *E = to_add_singleton.front(); E; E = E->next()) { + Node *al = get_node("/root/" + E->get()); + ERR_CONTINUE(!al); + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_named_global_constant(E->get(), al); + } + } + + // Add new nodes to the tree + List<Node *> nodes_to_add; + for (List<AutoLoadInfo>::Element *E = to_add.front(); E; E = E->next()) { + AutoLoadInfo &info = E->get(); + + RES res = ResourceLoader::load(info.path); + ERR_EXPLAIN("Can't autoload: " + info.path); + ERR_CONTINUE(res.is_null()); + Node *n = NULL; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instance(); + } else if (res->is_class("Script")) { + Ref<Script> s = res; + StringName ibt = s->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_EXPLAIN("Script does not inherit a Node: " + info.path); + ERR_CONTINUE(!valid_type); + + Object *obj = ClassDB::instance(ibt); + + ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt)); + ERR_CONTINUE(obj == NULL); + + n = Object::cast_to<Node>(obj); + n->set_script(s.get_ref_ptr()); + } + + ERR_EXPLAIN("Path in autoload not a node or script: " + info.path); + ERR_CONTINUE(!n); + n->set_name(info.name); + + //defer so references are all valid on _ready() + nodes_to_add.push_back(n); + + if (info.is_singleton) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_named_global_constant(info.name, n); + } + } + } + + for (List<Node *>::Element *E = nodes_to_add.front(); E; E = E->next()) { + get_tree()->get_root()->add_child(E->get()); + } + updating_autoload = false; } @@ -592,6 +702,36 @@ void EditorAutoloadSettings::_bind_methods() { EditorAutoloadSettings::EditorAutoloadSettings() { + // Make first cache + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + const PropertyInfo &pi = E->get(); + + if (!pi.name.begins_with("autoload/")) + continue; + + String name = pi.name.get_slice("/", 1); + String path = ProjectSettings::get_singleton()->get(pi.name); + + if (name.empty()) + continue; + + AutoLoadInfo info; + info.is_singleton = path.begins_with("*"); + + if (info.is_singleton) { + path = path.substr(1, path.length()); + } + + info.name = name; + info.path = path; + info.order = ProjectSettings::get_singleton()->get_order(pi.name); + + autoload_cache.push_back(info); + } + autoload_changed = "autoload_changed"; updating_autoload = false; diff --git a/editor/editor_autoload_settings.h b/editor/editor_autoload_settings.h index 6f622de6d5..1797c10e61 100644 --- a/editor/editor_autoload_settings.h +++ b/editor/editor_autoload_settings.h @@ -50,6 +50,8 @@ class EditorAutoloadSettings : public VBoxContainer { struct AutoLoadInfo { String name; + String path; + bool is_singleton; int order; bool operator==(const AutoLoadInfo &p_info) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp new file mode 100644 index 0000000000..21ad71fe06 --- /dev/null +++ b/editor/editor_inspector.cpp @@ -0,0 +1,1797 @@ +#include "editor_inspector.h" +#include "array_property_edit.h" +#include "dictionary_property_edit.h" +#include "editor_node.h" +#include "editor_scale.h" +#include "multi_node_edit.h" +#include "scene/resources/packed_scene.h" + +// TODO: +// arrays + +Size2 EditorProperty::get_minimum_size() const { + + Size2 ms; + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + if (!c->is_visible()) + continue; + Size2 minsize = c->get_combined_minimum_size(); + ms.width = MAX(ms.width, minsize.width); + ms.height = MAX(ms.height, minsize.height); + } + + if (keying) { + Ref<Texture> key = get_icon("Key", "EditorIcons"); + ms.width += key->get_width() + get_constant("hseparator", "Tree"); + } + + if (checkable) { + Ref<Texture> check = get_icon("checked", "CheckBox"); + ms.width += check->get_width() + get_constant("hseparator", "Tree"); + } + + if (label_layout == LABEL_LAYOUT_TOP) { + Ref<Font> font = get_font("font", "Tree"); + ms.height += font->get_height(); + ms.height += get_constant("vseparation", "Tree"); + } + + return ms; +} + +void EditorProperty::_notification(int p_what) { + + if (p_what == NOTIFICATION_SORT_CHILDREN) { + + Size2 size = get_size(); + Rect2 rect; + + if (label_layout == LABEL_LAYOUT_LEFT) { + int child_room = size.width / 2; + + //compute room needed + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + + Size2 minsize = c->get_combined_minimum_size(); + child_room = MAX(child_room, minsize.width); + } + + text_size = MAX(0, size.width - child_room + 4 * EDSCALE); + + rect = Rect2(text_size, 0, size.width - text_size, size.height); + } else { + Ref<Font> font = get_font("font", "Tree"); + + text_size = size.width; + rect.position.x = 0; + rect.position.y = font->get_height() + get_constant("vseparation", "Tree"); + rect.size = get_size(); + rect.size.height -= rect.position.y; + } + + if (keying) { + Ref<Texture> key; + + if (use_keying_next()) { + key = get_icon("KeyNext", "EditorIcons"); + } else { + key = get_icon("Key", "EditorIcons"); + } + + rect.size.x -= key->get_width() + get_constant("hseparator", "Tree"); + } + + //set children + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + + fit_child_in_rect(c, rect); + } + + update(); //need to redraw text + } + + if (p_what == NOTIFICATION_DRAW) { + Ref<Font> font = get_font("font", "Tree"); + + Size2 size = get_size(); + if (label_layout == LABEL_LAYOUT_TOP) { + size.height = font->get_height(); + } else if (label_reference) { + size.height = label_reference->get_size().height; + } + + if (selected) { + Ref<StyleBox> sb = get_stylebox("selected", "Tree"); + draw_style_box(sb, Rect2(Vector2(), size)); + } + + Color color; + if (draw_red) { + color = get_color("error_color", "Editor"); + } else { + color = get_color("font_color", "Tree"); + } + if (label.find(".") != -1) { + color.a = 0.5; //this should be un-hacked honestly, as it's used for editor overrides + } + + int ofs = 0; + if (checkable) { + Ref<Texture> checkbox; + if (checked) + checkbox = get_icon("checked", "CheckBox"); + else + checkbox = get_icon("unchecked", "CheckBox"); + + Color color(1, 1, 1); + if (check_hover) { + color.r *= 1.2; + color.g *= 1.2; + color.b *= 1.2; + } + check_rect = Rect2(ofs, ((size.height - checkbox->get_height()) / 2), checkbox->get_width(), checkbox->get_height()); + draw_texture(checkbox, check_rect.position, color); + ofs += get_constant("hseparator", "Tree"); + ofs += checkbox->get_width(); + } else { + check_rect = Rect2(); + } + + int text_limit = text_size; + + if (can_revert) { + Ref<Texture> reload_icon = get_icon("ReloadSmall", "EditorIcons"); + text_limit -= reload_icon->get_width() + get_constant("hseparator", "Tree") * 2; + revert_rect = Rect2(text_limit + get_constant("hseparator", "Tree"), (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height()); + + Color color(1, 1, 1); + if (revert_hover) { + color.r *= 1.2; + color.g *= 1.2; + color.b *= 1.2; + } + + draw_texture(reload_icon, revert_rect.position, color); + } else { + revert_rect = Rect2(); + } + + int v_ofs = (size.height - font->get_height()) / 2; + draw_string(font, Point2(ofs, v_ofs + font->get_ascent()), label, color, text_limit); + + if (keying) { + Ref<Texture> key; + + if (use_keying_next()) { + key = get_icon("KeyNext", "EditorIcons"); + } else { + key = get_icon("Key", "EditorIcons"); + } + + ofs = size.width - key->get_width() - get_constant("hseparator", "Tree"); + + Color color(1, 1, 1); + if (keying_hover) { + color.r *= 1.2; + color.g *= 1.2; + color.b *= 1.2; + } + keying_rect = Rect2(ofs, ((size.height - key->get_height()) / 2), key->get_width(), key->get_height()); + draw_texture(key, keying_rect.position, color); + } else { + keying_rect = Rect2(); + } + + //int vs = get_constant("vseparation", "Tree"); + Color guide_color = get_color("guide_color", "Tree"); + int vs_height = get_size().height; // vs / 2; + draw_line(Point2(0, vs_height), Point2(get_size().width, vs_height), guide_color); + } +} + +void EditorProperty::set_label(const String &p_label) { + label = p_label; + update(); +} + +String EditorProperty::get_label() const { + return label; +} + +Object *EditorProperty::get_edited_object() { + return object; +} + +StringName EditorProperty::get_edited_property() { + return property; +} + +void EditorProperty::update_property() { + if (get_script_instance()) + get_script_instance()->call("update_property"); +} + +void EditorProperty::set_read_only(bool p_read_only) { + read_only = p_read_only; +} + +bool EditorProperty::is_read_only() const { + return read_only; +} + +bool EditorProperty::_might_be_in_instance() { + + if (!object) + return false; + + Node *node = Object::cast_to<Node>(object); + + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + + bool might_be = false; + + while (node) { + + if (node->get_scene_instance_state().is_valid()) { + might_be = true; + break; + } + if (node == edited_scene) { + if (node->get_scene_inherited_state().is_valid()) { + might_be = true; + break; + } + might_be = false; + break; + } + node = node->get_owner(); + } + + return might_be; // or might not be +} + +bool EditorProperty::_get_instanced_node_original_property(const StringName &p_prop, Variant &value) { + + Node *node = Object::cast_to<Node>(object); + + if (!node) + return false; + + Node *orig = node; + + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + + bool found = false; + + while (node) { + + Ref<SceneState> ss; + + if (node == edited_scene) { + ss = node->get_scene_inherited_state(); + + } else { + ss = node->get_scene_instance_state(); + } + + if (ss.is_valid()) { + + NodePath np = node->get_path_to(orig); + int node_idx = ss->find_node_by_path(np); + if (node_idx >= 0) { + bool lfound = false; + Variant lvar; + lvar = ss->get_property_value(node_idx, p_prop, lfound); + if (lfound) { + + found = true; + value = lvar; + } + } + } + if (node == edited_scene) { + //just in case + break; + } + node = node->get_owner(); + } + + return found; +} + +bool EditorProperty::_is_property_different(const Variant &p_current, const Variant &p_orig, int p_usage) { + + // this is a pretty difficult function, because a property may not be saved but may have + // the flag to not save if one or if zero + + { + Node *node = Object::cast_to<Node>(object); + if (!node) + return false; + + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + bool found_state = false; + + while (node) { + + Ref<SceneState> ss; + + if (node == edited_scene) { + ss = node->get_scene_inherited_state(); + + } else { + ss = node->get_scene_instance_state(); + } + + if (ss.is_valid()) { + found_state = true; + } + if (node == edited_scene) { + //just in case + break; + } + node = node->get_owner(); + } + + if (!found_state) + return false; //pointless to check if we are not comparing against anything. + } + + if (p_orig.get_type() == Variant::NIL) { + // not found (was not saved) + // check if it was not saved due to being zero or one + if (p_current.is_zero() && property_usage & PROPERTY_USAGE_STORE_IF_NONZERO) + return false; + if (p_current.is_one() && property_usage & PROPERTY_USAGE_STORE_IF_NONONE) + return false; + } + + if (p_current.get_type() == Variant::REAL && p_orig.get_type() == Variant::REAL) { + float a = p_current; + float b = p_orig; + + return Math::abs(a - b) > CMP_EPSILON; //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error + } + + return bool(Variant::evaluate(Variant::OP_NOT_EQUAL, p_current, p_orig)); +} + +bool EditorProperty::_is_instanced_node_with_original_property_different() { + + bool mbi = _might_be_in_instance(); + if (mbi) { + Variant vorig; + int usage = property_usage & (PROPERTY_USAGE_STORE_IF_NONONE | PROPERTY_USAGE_STORE_IF_NONZERO); + if (_get_instanced_node_original_property(property, vorig) || usage) { + Variant v = object->get(property); + + if (_is_property_different(v, vorig, usage)) { + return true; + } + } + } + return false; +} + +void EditorProperty::update_reload_status() { + + if (property == StringName()) + return; //no property, so nothing to do + + bool has_reload = false; + + if (_is_instanced_node_with_original_property_different()) { + has_reload = true; + } + + if (object->call("property_can_revert", property).operator bool()) { + + has_reload = true; + } + + if (!has_reload && !object->get_script().is_null()) { + Ref<Script> scr = object->get_script(); + Variant orig_value; + if (scr->get_property_default_value(property, orig_value)) { + if (orig_value != object->get(property)) { + has_reload = true; + } + } + } + + if (has_reload != can_revert) { + can_revert = has_reload; + update(); + } +} + +bool EditorProperty::use_keying_next() const { + return false; +} +void EditorProperty::set_checkable(bool p_checkable) { + + checkable = p_checkable; + update(); + queue_sort(); +} + +bool EditorProperty::is_checkable() const { + + return checkable; +} + +void EditorProperty::set_checked(bool p_checked) { + + checked = p_checked; + update(); +} + +bool EditorProperty::is_checked() const { + + return checked; +} + +void EditorProperty::set_draw_red(bool p_draw_red) { + + draw_red = p_draw_red; + update(); +} + +void EditorProperty::set_keying(bool p_keying) { + keying = p_keying; + update(); + queue_sort(); +} + +bool EditorProperty::is_keying() const { + return keying; +} + +bool EditorProperty::is_draw_red() const { + + return draw_red; +} + +void EditorProperty::_focusable_focused(int p_index) { + + bool already_selected = selected; + selected = true; + selected_focusable = p_index; + update(); + if (!already_selected && selected) { + emit_signal("selected", property, selected_focusable); + } +} + +void EditorProperty::add_focusable(Control *p_control) { + + p_control->connect("focus_entered", this, "_focusable_focused", varray(focusables.size())); + focusables.push_back(p_control); +} + +void EditorProperty::select(int p_focusable) { + + bool already_selected = selected; + + if (p_focusable >= 0) { + ERR_FAIL_INDEX(p_focusable, focusables.size()); + focusables[p_focusable]->grab_focus(); + } else { + selected = true; + update(); + } + + if (!already_selected && selected) { + emit_signal("selected", property, selected_focusable); + } +} + +void EditorProperty::deselect() { + selected = false; + selected_focusable = -1; + update(); +} + +bool EditorProperty::is_selected() const { + return selected; +} + +void EditorProperty::_gui_input(const Ref<InputEvent> &p_event) { + + if (property == StringName()) + return; + + Ref<InputEventMouse> me = p_event; + + if (me.is_valid()) { + + bool button_left = me->get_button_mask() & BUTTON_MASK_LEFT; + + bool new_keying_hover = keying_rect.has_point(me->get_position()) && !button_left; + if (new_keying_hover != keying_hover) { + keying_hover = new_keying_hover; + update(); + } + + bool new_revert_hover = revert_rect.has_point(me->get_position()) && !button_left; + if (new_revert_hover != revert_hover) { + revert_hover = new_revert_hover; + update(); + } + + bool new_check_hover = check_rect.has_point(me->get_position()) && !button_left; + if (new_check_hover != check_hover) { + check_hover = new_check_hover; + update(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + if (!selected) { + selected = true; + emit_signal("selected", property, -1); + update(); + } + + if (keying_rect.has_point(mb->get_position())) { + emit_signal("property_keyed", property); + } + + if (revert_rect.has_point(mb->get_position())) { + + Variant vorig; + + if (_might_be_in_instance() && _get_instanced_node_original_property(property, vorig)) { + + emit_signal("property_changed", property, vorig.duplicate(true)); + update_property(); + return; + } + + if (object->call("property_can_revert", property).operator bool()) { + Variant rev = object->call("property_get_revert", property); + emit_signal("property_changed", property, rev); + update_property(); + } + + if (!object->get_script().is_null()) { + Ref<Script> scr = object->get_script(); + Variant orig_value; + if (scr->get_property_default_value(property, orig_value)) { + emit_signal("property_changed", property, orig_value); + update_property(); + } + } + } + if (check_rect.has_point(mb->get_position())) { + checked = !checked; + update(); + emit_signal("property_checked", property, checked); + } + } +} + +void EditorProperty::set_label_reference(Control *p_control) { + + label_reference = p_control; +} + +Variant EditorProperty::get_drag_data(const Point2 &p_point) { + + if (property == StringName()) + return Variant(); + + Dictionary dp; + dp["type"] = "obj_property"; + dp["object"] = object; + dp["property"] = property; + dp["value"] = object->get(property); + + Label *label = memnew(Label); + label->set_text(property); + set_drag_preview(label); + return dp; +} + +void EditorProperty::set_label_layout(LabelLayout p_layout) { + label_layout = p_layout; + queue_sort(); + update(); +} + +void EditorProperty::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_label", "text"), &EditorProperty::set_label); + ClassDB::bind_method(D_METHOD("get_label"), &EditorProperty::get_label); + + ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorProperty::set_read_only); + ClassDB::bind_method(D_METHOD("is_read_only"), &EditorProperty::is_read_only); + + ClassDB::bind_method(D_METHOD("set_checkable", "checkable"), &EditorProperty::set_checkable); + ClassDB::bind_method(D_METHOD("is_checkable"), &EditorProperty::is_checkable); + + ClassDB::bind_method(D_METHOD("set_checked", "checked"), &EditorProperty::set_checked); + ClassDB::bind_method(D_METHOD("is_checked"), &EditorProperty::is_checked); + + ClassDB::bind_method(D_METHOD("set_draw_red", "draw_red"), &EditorProperty::set_draw_red); + ClassDB::bind_method(D_METHOD("is_draw_red"), &EditorProperty::is_draw_red); + + ClassDB::bind_method(D_METHOD("set_keying", "keying"), &EditorProperty::set_keying); + ClassDB::bind_method(D_METHOD("is_keying"), &EditorProperty::is_keying); + + ClassDB::bind_method(D_METHOD("get_edited_property"), &EditorProperty::get_edited_property); + ClassDB::bind_method(D_METHOD("get_edited_object"), &EditorProperty::get_edited_object); + + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorProperty::_gui_input); + ClassDB::bind_method(D_METHOD("_focusable_focused"), &EditorProperty::_focusable_focused); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable"), "set_checkable", "is_checkable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked"), "set_checked", "is_checked"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_red"), "set_draw_red", "is_draw_red"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying"), "set_keying", "is_keying"); + ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); + ADD_SIGNAL(MethodInfo("multiple_properties_changed", PropertyInfo(Variant::POOL_STRING_ARRAY, "properties"), PropertyInfo(Variant::ARRAY, "value"))); + ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"))); + ADD_SIGNAL(MethodInfo("property_checked", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::STRING, "bool"))); + ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); + ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "focusable_idx"))); + + MethodInfo vm; + vm.name = "update_property"; + BIND_VMETHOD(vm); +} + +EditorProperty::EditorProperty() { + + text_size = 0; + read_only = false; + checkable = false; + checked = false; + draw_red = false; + keying = false; + keying_hover = false; + revert_hover = false; + check_hover = false; + can_revert = false; + property_usage = 0; + selected = false; + selected_focusable = -1; + label_reference = NULL; + label_layout = LABEL_LAYOUT_LEFT; +} +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +void EditorInspectorPlugin::add_custom_control(Control *control) { + + AddedEditor ae; + ae.property_editor = control; + added_editors.push_back(ae); +} + +void EditorInspectorPlugin::add_property_editor(const String &p_for_property, Control *p_prop) { + + ERR_FAIL_COND(Object::cast_to<EditorProperty>(p_prop) == NULL); + + AddedEditor ae; + ae.properties.push_back(p_for_property); + ae.property_editor = p_prop; + added_editors.push_back(ae); +} + +void EditorInspectorPlugin::add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop) { + + AddedEditor ae; + ae.properties = p_properties; + ae.property_editor = p_prop; + ae.label = p_label; + added_editors.push_back(ae); +} + +bool EditorInspectorPlugin::can_handle(Object *p_object) { + + if (get_script_instance()) { + return get_script_instance()->call("can_handle", p_object); + } + return false; +} +void EditorInspectorPlugin::parse_begin(Object *p_object) { + + if (get_script_instance()) { + get_script_instance()->call("parse_begin", p_object); + } +} +bool EditorInspectorPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) { + + if (get_script_instance()) { + Variant arg[6] = { + p_object, p_type, p_path, p_hint, p_hint_text, p_usage + }; + const Variant *argptr[6] = { + &arg[0], &arg[1], &arg[2], &arg[3], &arg[4], &arg[5] + }; + + Variant::CallError err; + return get_script_instance()->call("parse_property", (const Variant **)&argptr, 6, err); + } + return false; +} +void EditorInspectorPlugin::parse_end() { + + if (get_script_instance()) { + get_script_instance()->call("parse_end"); + } +} + +void EditorInspectorPlugin::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_custom_control", "control"), &EditorInspectorPlugin::add_custom_control); + ClassDB::bind_method(D_METHOD("add_property_editor", "property", "editor"), &EditorInspectorPlugin::add_property_editor); + ClassDB::bind_method(D_METHOD("add_property_editor_for_multiple_properties", "label", "properties", "editor"), &EditorInspectorPlugin::add_property_editor_for_multiple_properties); + + MethodInfo vm; + vm.name = "can_handle"; + vm.arguments.push_back(PropertyInfo(Variant::OBJECT, "object")); + BIND_VMETHOD(vm); + vm.name = "parse_begin"; + BIND_VMETHOD(vm); + vm.name = "parse_property"; + vm.return_val.type = Variant::BOOL; + vm.arguments.push_back(PropertyInfo(Variant::INT, "type")); + vm.arguments.push_back(PropertyInfo(Variant::STRING, "path")); + vm.arguments.push_back(PropertyInfo(Variant::INT, "hint")); + vm.arguments.push_back(PropertyInfo(Variant::STRING, "hint_text")); + vm.arguments.push_back(PropertyInfo(Variant::INT, "usage")); + BIND_VMETHOD(vm); + vm.arguments.clear(); + vm.return_val.type = Variant::NIL; + vm.name = "parse_end"; + BIND_VMETHOD(vm); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +void EditorInspectorCategory::_notification(int p_what) { + + if (p_what == NOTIFICATION_DRAW) { + + draw_rect(Rect2(Vector2(), get_size()), bg_color); + Ref<Font> font = get_font("font", "Tree"); + + int hs = get_constant("hseparation", "Tree"); + + int w = font->get_string_size(label).width; + if (icon.is_valid()) { + w += hs + icon->get_width(); + } + + int ofs = (get_size().width - w) / 2; + + if (icon.is_valid()) { + draw_texture(icon, Point2(ofs, (get_size().height - icon->get_height()) / 2).floor()); + ofs += hs + icon->get_width(); + } + + Color color = get_color("font_color", "Tree"); + draw_string(font, Point2(ofs, font->get_ascent() + (get_size().height - font->get_height()) / 2).floor(), label, color, get_size().width); + } +} + +Size2 EditorInspectorCategory::get_minimum_size() const { + + Ref<Font> font = get_font("font", "Tree"); + + Size2 ms; + ms.width = 1; + ms.height = font->get_height(); + if (icon.is_valid()) { + ms.height = MAX(icon->get_height(), ms.height); + } + ms.height += get_constant("vseparation", "Tree"); + + return ms; +} + +EditorInspectorCategory::EditorInspectorCategory() { +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +void EditorInspectorSection::_notification(int p_what) { + + if (p_what == NOTIFICATION_SORT_CHILDREN) { + + Ref<Font> font = get_font("font", "Tree"); + Ref<Texture> arrow; + +#ifdef TOOLS_ENABLED + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_icon("arrow", "Tree"); + } else { + arrow = get_icon("arrow_collapsed", "Tree"); + } + } +#endif + + Size2 size = get_size(); + Point2 offset; + offset.y = font->get_height(); + if (arrow.is_valid()) { + offset.y = MAX(offset.y, arrow->get_height()); + } + + offset.y += get_constant("vseparation", "Tree"); + offset.x += get_constant("item_margin", "Tree"); + + Rect2 rect(offset, size - offset); + + //set children + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + if (!c->is_visible_in_tree()) + continue; + + fit_child_in_rect(c, rect); + } + + update(); //need to redraw text + } + + if (p_what == NOTIFICATION_DRAW) { + + Ref<Texture> arrow; + +#ifdef TOOLS_ENABLED + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + arrow = get_icon("arrow", "Tree"); + } else { + arrow = get_icon("arrow_collapsed", "Tree"); + } + } +#endif + + Ref<Font> font = get_font("font", "Tree"); + + int h = font->get_height(); + if (arrow.is_valid()) { + h = MAX(h, arrow->get_height()); + } + h += get_constant("vseparation", "Tree"); + + draw_rect(Rect2(Vector2(), Vector2(get_size().width, h)), bg_color); + + int hs = get_constant("hseparation", "Tree"); + + int ofs = 0; + if (arrow.is_valid()) { + draw_texture(arrow, Point2(ofs, (h - arrow->get_height()) / 2).floor()); + ofs += hs + arrow->get_width(); + } + + Color color = get_color("font_color", "Tree"); + draw_string(font, Point2(ofs, font->get_ascent() + (h - font->get_height()) / 2).floor(), label, color, get_size().width); + } +} + +Size2 EditorInspectorSection::get_minimum_size() const { + + Size2 ms; + for (int i = 0; i < get_child_count(); i++) { + + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) + continue; + if (c->is_set_as_toplevel()) + continue; + if (!c->is_visible()) + continue; + Size2 minsize = c->get_combined_minimum_size(); + ms.width = MAX(ms.width, minsize.width); + ms.height = MAX(ms.height, minsize.height); + } + + Ref<Font> font = get_font("font", "Tree"); + ms.height += font->get_ascent() + get_constant("vseparation", "Tree"); + ms.width += get_constant("item_margin", "Tree"); + + return ms; +} + +void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable) { + + section = p_section; + label = p_label; + object = p_object; + bg_color = p_bg_color; + foldable = p_foldable; + +#ifdef TOOLS_ENABLED + if (foldable) { + if (object->editor_is_section_unfolded(section)) { + vbox->show(); + } else { + vbox->hide(); + } + } + // void editor_set_section_unfold(const String &p_section, bool p_unfolded); + +#endif +} + +void EditorInspectorSection::_gui_input(const Ref<InputEvent> &p_event) { + + if (!foldable) + return; + +#ifdef TOOLS_ENABLED + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + bool unfold = !object->editor_is_section_unfolded(section); + object->editor_set_section_unfold(section, unfold); + if (unfold) { + vbox->show(); + } else { + vbox->hide(); + } + } +#endif +} + +VBoxContainer *EditorInspectorSection::get_vbox() { + return vbox; +} + +void EditorInspectorSection::unfold() { + + if (!foldable) + return; +#ifdef TOOLS_ENABLED + + object->editor_set_section_unfold(section, true); + vbox->show(); + update(); +#endif +} + +void EditorInspectorSection::fold() { + if (!foldable) + return; + +#ifdef TOOLS_ENABLED + + object->editor_set_section_unfold(section, false); + vbox->hide(); + update(); +#endif +} + +void EditorInspectorSection::_bind_methods() { + + ClassDB::bind_method(D_METHOD("setup", "section", "label", "object", "bg_color", "foldable"), &EditorInspectorSection::setup); + ClassDB::bind_method(D_METHOD("get_vbox"), &EditorInspectorSection::get_vbox); + ClassDB::bind_method(D_METHOD("unfold"), &EditorInspectorSection::unfold); + ClassDB::bind_method(D_METHOD("fold"), &EditorInspectorSection::fold); + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorInspectorSection::_gui_input); +} + +EditorInspectorSection::EditorInspectorSection() { + object = NULL; + foldable = false; + vbox = memnew(VBoxContainer); + add_child(vbox); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; +int EditorInspector::inspector_plugin_count = 0; + +void EditorInspector::add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) { + + ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS); + + for (int i = 0; i < inspector_plugin_count; i++) { + if (inspector_plugins[i] == p_plugin) + return; //already exists + } + inspector_plugins[inspector_plugin_count++] = p_plugin; +} + +void EditorInspector::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) { + + ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS); + + int idx = -1; + for (int i = 0; i < inspector_plugin_count; i++) { + if (inspector_plugins[i] == p_plugin) { + idx = i; + break; + } + } + + for (int i = idx; i < inspector_plugin_count - 1; i++) { + inspector_plugins[i] = inspector_plugins[i + 1]; + } + inspector_plugin_count--; +} + +void EditorInspector::cleanup_plugins() { + for (int i = 0; i < inspector_plugin_count; i++) { + inspector_plugins[i].unref(); + } + inspector_plugin_count = 0; +} + +void EditorInspector::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +String EditorInspector::get_selected_path() const { + + return property_selected; +} + +void EditorInspector::update_tree() { + + //to update properly if all is refreshed + StringName current_selected = property_selected; + int current_focusable = property_focusable; + + _clear(); + + if (!object) + return; + + List<Ref<EditorInspectorPlugin> > valid_plugins; + + for (int i = inspector_plugin_count - 1; i >= 0; i--) { //start by last, so lastly added can override newly added + if (!inspector_plugins[i]->can_handle(object)) + continue; + valid_plugins.push_back(inspector_plugins[i]); + } + + bool draw_red = false; + + { + Node *nod = Object::cast_to<Node>(object); + Node *es = EditorNode::get_singleton()->get_edited_scene(); + if (nod && es != nod && nod->get_owner() != es) { + draw_red = true; + } + } + + // TreeItem *current_category = NULL; + + String filter = search_box ? search_box->get_text() : ""; + String group; + String group_base; + + List<PropertyInfo> plist; + object->get_property_list(&plist, true); + + HashMap<String, VBoxContainer *> item_path; + item_path[""] = main_vbox; + + Color sscolor = get_color("prop_subsection", "Editor"); + + for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) { + + PropertyInfo &p = I->get(); + + //make sure the property can be edited + + if (p.usage & PROPERTY_USAGE_GROUP) { + + group = p.name; + group_base = p.hint_string; + + continue; + + } else if (p.usage & PROPERTY_USAGE_CATEGORY) { + + group = ""; + group_base = ""; + + if (!show_categories) + continue; + + List<PropertyInfo>::Element *N = I->next(); + bool valid = true; + //if no properties in category, skip + while (N) { + if (N->get().usage & PROPERTY_USAGE_EDITOR) + break; + if (N->get().usage & PROPERTY_USAGE_CATEGORY) { + valid = false; + break; + } + N = N->next(); + } + if (!valid) + continue; //empty, ignore + + EditorInspectorCategory *category = memnew(EditorInspectorCategory); + main_vbox->add_child(category); + + String type = p.name; + if (has_icon(type, "EditorIcons")) + category->icon = get_icon(type, "EditorIcons"); + else + category->icon = get_icon("Object", "EditorIcons"); + category->label = type; + + category->bg_color = get_color("prop_category", "Editor"); + if (use_doc_hints) { + StringName type = p.name; + if (!class_descr_cache.has(type)) { + + String descr; + DocData *dd = EditorHelp::get_doc_data(); + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(type); + if (E) { + descr = E->get().brief_description; + } + class_descr_cache[type] = descr.word_wrap(80); + } + + category->set_tooltip(TTR("Class:") + " " + p.name + (class_descr_cache[type] == "" ? "" : "\n\n" + class_descr_cache[type])); + } + continue; + + } else if (!(p.usage & PROPERTY_USAGE_EDITOR)) + continue; + + if (hide_script && p.name == "script") + continue; + + String basename = p.name; + if (group != "") { + if (group_base != "") { + if (basename.begins_with(group_base)) { + basename = basename.replace_first(group_base, ""); + } else if (group_base.begins_with(basename)) { + //keep it, this is used pretty often + } else { + group = ""; //no longer using group base, clear + } + } + } + + if (group != "") { + basename = group + "/" + basename; + } + + String name = (basename.find("/") != -1) ? basename.right(basename.find_last("/") + 1) : basename; + + if (capitalize_paths) { + int dot = name.find("."); + if (dot != -1) { + String ov = name.right(dot); + name = name.substr(0, dot); + name = name.camelcase_to_underscore().capitalize(); + name += ov; + + } else { + name = name.camelcase_to_underscore().capitalize(); + } + } + + String path = basename.left(basename.find_last("/")); + + if (use_filter && filter != "") { + + String cat = path; + + if (capitalize_paths) + cat = cat.capitalize(); + + if (!filter.is_subsequence_ofi(cat) && !filter.is_subsequence_ofi(name)) + continue; + } + + VBoxContainer *current_vbox = main_vbox; + + { + + String acc_path = ""; + int level = 1; + for (int i = 0; i < path.get_slice_count("/"); i++) { + String path_name = path.get_slice("/", i); + if (i > 0) + acc_path += "/"; + acc_path += path_name; + if (!item_path.has(acc_path)) { + EditorInspectorSection *section = memnew(EditorInspectorSection); + current_vbox->add_child(section); + sections.push_back(section); + + if (capitalize_paths) + path_name = path_name.capitalize(); + Color c = sscolor; + c.a /= level; + section->setup(path_name, acc_path, object, c, use_folding); + + item_path[acc_path] = section->get_vbox(); + } + current_vbox = item_path[acc_path]; + level = (MIN(level + 1, 4)); + } + } + + bool checkable = false; + bool checked = false; + if (p.usage & PROPERTY_USAGE_CHECKABLE) { + checkable = true; + checked = p.usage & PROPERTY_USAGE_CHECKED; + } + + String doc_hint; + + if (use_doc_hints) { + + StringName classname = object->get_class_name(); + StringName propname = p.name; + String descr; + bool found = false; + + Map<StringName, Map<StringName, String> >::Element *E = descr_cache.find(classname); + if (E) { + Map<StringName, String>::Element *F = E->get().find(propname); + if (F) { + found = true; + descr = F->get(); + } + } + + if (!found) { + DocData *dd = EditorHelp::get_doc_data(); + Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(classname); + while (E && descr == String()) { + for (int i = 0; i < E->get().properties.size(); i++) { + if (E->get().properties[i].name == propname.operator String()) { + descr = E->get().properties[i].description.strip_edges().word_wrap(80); + break; + } + } + if (!E->get().inherits.empty()) { + E = dd->class_list.find(E->get().inherits); + } else { + break; + } + } + descr_cache[classname][propname] = descr; + } + + doc_hint = descr; + } + +#if 0 + if (p.name == selected_property) { + + item->select(1); + } +#endif + for (List<Ref<EditorInspectorPlugin> >::Element *E = valid_plugins.front(); E; E = E->next()) { + Ref<EditorInspectorPlugin> ped = E->get(); + ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage); + for (List<EditorInspectorPlugin::AddedEditor>::Element *F = ped->added_editors.front(); F; F = F->next()) { + + EditorProperty *ep = Object::cast_to<EditorProperty>(F->get().property_editor); + current_vbox->add_child(F->get().property_editor); + + if (ep) { + + ep->object = object; + ep->connect("property_changed", this, "_property_changed"); + ep->connect("property_keyed", this, "_property_keyed"); + ep->connect("property_checked", this, "_property_checked"); + ep->connect("selected", this, "_property_selected"); + ep->connect("multiple_properties_changed", this, "_multiple_properties_changed"); + ep->connect("resource_selected", this, "_resource_selected", varray(), CONNECT_DEFERRED); + ep->connect("object_id_selected", this, "_object_id_selected", varray(), CONNECT_DEFERRED); + ep->set_tooltip(doc_hint); + ep->set_draw_red(draw_red); + ep->set_checkable(checkable); + ep->set_checked(checked); + ep->set_keying(keying); + + if (F->get().properties.size()) { + + if (F->get().properties.size() == 1) { + //since it's one, associate: + ep->property = F->get().properties[0]; + ep->property_usage = p.usage; + //and set label? + } + + if (F->get().label != String()) { + ep->set_label(F->get().label); + } else { + //use existin one + ep->set_label(name); + } + for (int i = 0; i < F->get().properties.size(); i++) { + String prop = F->get().properties[i]; + + if (!editor_property_map.has(prop)) { + editor_property_map[prop] = List<EditorProperty *>(); + } + editor_property_map[prop].push_back(ep); + } + } + + ep->set_read_only(read_only); + ep->update_property(); + ep->update_reload_status(); + + if (current_selected && ep->property == current_selected) { + ep->select(current_focusable); + } + } + } + ped->added_editors.clear(); + } + } + + //see if this property exists and should be kept +} +void EditorInspector::update_property(const String &p_prop) { + if (!editor_property_map.has(p_prop)) + return; + + for (List<EditorProperty *>::Element *E = editor_property_map[p_prop].front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } +} + +void EditorInspector::_clear() { + + editor_property_map.clear(); + sections.clear(); + pending.clear(); + property_selected = StringName(); + property_focusable = -1; + while (main_vbox->get_child_count()) { + memdelete(main_vbox->get_child(0)); + } +} + +void EditorInspector::refresh() { + + if (refresh_countdown > 0) + return; + refresh_countdown = EditorSettings::get_singleton()->get("docks/property_editor/auto_refresh_interval"); +} + +void EditorInspector::edit(Object *p_object) { + if (object != p_object) { + _clear(); + } + + if (object) { + + object->remove_change_receptor(this); + } + + object = p_object; + if (object) { + object->add_change_receptor(this); + update_tree(); + } +} + +void EditorInspector::set_keying(bool p_active) { + if (keying == p_active) + return; + keying = p_active; + update_tree(); +} +void EditorInspector::set_read_only(bool p_read_only) { + read_only = p_read_only; + update_tree(); +} + +bool EditorInspector::is_capitalize_paths_enabled() const { + + return capitalize_paths; +} +void EditorInspector::set_enable_capitalize_paths(bool p_capitalize) { + capitalize_paths = p_capitalize; + update_tree(); +} + +void EditorInspector::set_autoclear(bool p_enable) { + autoclear = p_enable; +} + +void EditorInspector::set_show_categories(bool p_show) { + show_categories = p_show; + update_tree(); +} + +void EditorInspector::set_use_doc_hints(bool p_enable) { + use_doc_hints = p_enable; + update_tree(); +} +void EditorInspector::set_hide_script(bool p_hide) { + hide_script = p_hide; + update_tree(); +} +void EditorInspector::set_use_filter(bool p_use) { + use_filter = p_use; + update_tree(); +} +void EditorInspector::register_text_enter(Node *p_line_edit) { + search_box = Object::cast_to<LineEdit>(p_line_edit); + if (search_box) + search_box->connect("text_changed", this, "_filter_changed"); +} + +void EditorInspector::_filter_changed(const String &p_text) { + + update_tree(); +} + +void EditorInspector::set_subsection_selectable(bool p_selectable) { +} + +void EditorInspector::set_property_selectable(bool p_selectable) { +} + +void EditorInspector::set_use_folding(bool p_enable) { + use_folding = p_enable; + update_tree(); +} + +void EditorInspector::collapse_all_folding() { + + for (List<EditorInspectorSection *>::Element *E = sections.front(); E; E = E->next()) { + E->get()->fold(); + } +} + +void EditorInspector::expand_all_folding() { + for (List<EditorInspectorSection *>::Element *E = sections.front(); E; E = E->next()) { + E->get()->unfold(); + } +} + +void EditorInspector::set_scroll_offset(int p_offset) { + set_v_scroll(p_offset); +} + +int EditorInspector::get_scroll_offset() const { + return get_v_scroll(); +} + +void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) { + + if (object != p_object) //may be undoing/redoing for a non edited object, so ignore + return; + + if (changing) + return; + + if (p_property == String()) + update_tree_pending = true; + else { + pending.insert(p_property); + } +} + +void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field) { + + if (autoclear && editor_property_map.has(p_name)) { + for (List<EditorProperty *>::Element *E = editor_property_map[p_name].front(); E; E = E->next()) { + if (E->get()->is_checkable()) { + E->get()->set_checked(true); + } + } + } + + if (!undo_redo || Object::cast_to<ArrayPropertyEdit>(object) || Object::cast_to<DictionaryPropertyEdit>(object)) { //kind of hacky + + object->set(p_name, p_value); + if (p_refresh_all) + _edit_request_change(object, ""); + else + _edit_request_change(object, p_name); + + emit_signal(_prop_edited, p_name); + + } else if (Object::cast_to<MultiNodeEdit>(object)) { + + Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field); + _edit_request_change(object, p_name); + emit_signal(_prop_edited, p_name); + } else { + + undo_redo->create_action(TTR("Set") + " " + p_name, UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(object, p_name, p_value); + undo_redo->add_undo_property(object, p_name, object->get(p_name)); + + if (p_refresh_all) { + undo_redo->add_do_method(this, "_edit_request_change", object, ""); + undo_redo->add_undo_method(this, "_edit_request_change", object, ""); + } else { + + undo_redo->add_do_method(this, "_edit_request_change", object, p_name); + undo_redo->add_undo_method(this, "_edit_request_change", object, p_name); + } + + Resource *r = Object::cast_to<Resource>(object); + if (r) { + if (!r->is_edited() && String(p_name) != "resource/edited") { + undo_redo->add_do_method(r, "set_edited", true); + undo_redo->add_undo_method(r, "set_edited", false); + } + + if (String(p_name) == "resource_local_to_scene") { + bool prev = object->get(p_name); + bool next = p_value; + if (next) { + undo_redo->add_do_method(r, "setup_local_to_scene"); + } + if (prev) { + undo_redo->add_undo_method(r, "setup_local_to_scene"); + } + } + } + undo_redo->add_do_method(this, "emit_signal", _prop_edited, p_name); + undo_redo->add_undo_method(this, "emit_signal", _prop_edited, p_name); + changing++; + undo_redo->commit_action(); + changing--; + } + + if (editor_property_map.has(p_name)) { + for (List<EditorProperty *>::Element *E = editor_property_map[p_name].front(); E; E = E->next()) { + E->get()->update_reload_status(); + } + } +} + +void EditorInspector::_property_changed(const String &p_path, const Variant &p_value) { + + _edit_set(p_path, p_value, false, ""); +} + +void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) { + + ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); + ERR_FAIL_COND(p_paths.size() != p_values.size()); + String names; + for (int i = 0; i < p_paths.size(); i++) { + if (i > 0) + names += ","; + names += p_paths[i]; + } + undo_redo->create_action(TTR("Set Multiple:") + " " + names, UndoRedo::MERGE_ENDS); + for (int i = 0; i < p_paths.size(); i++) { + _edit_set(p_paths[i], p_values[i], false, ""); + } + changing++; + undo_redo->commit_action(); + changing--; +} + +void EditorInspector::_property_keyed(const String &p_path) { + + if (!object) + return; + + emit_signal("property_keyed", p_path, object->get(p_path), false); //second param is deprecated +} + +void EditorInspector::_property_checked(const String &p_path, bool p_checked) { + + if (!object) + return; + + //property checked + if (autoclear) { + + if (!p_checked) { + object->set(p_path, Variant()); + } else { + + Variant to_create; + List<PropertyInfo> pinfo; + object->get_property_list(&pinfo); + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + if (E->get().name == p_path) { + Variant::CallError ce; + to_create = Variant::construct(E->get().type, NULL, 0, ce); + break; + } + } + object->set(p_path, to_create); + } + + if (editor_property_map.has(p_path)) { + for (List<EditorProperty *>::Element *E = editor_property_map[p_path].front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } + } + + } else { + emit_signal("property_toggled", p_path, p_checked); + } +} + +void EditorInspector::_property_selected(const String &p_path, int p_focusable) { + + property_selected = p_path; + property_focusable = p_focusable; + //deselect the others + for (Map<StringName, List<EditorProperty *> >::Element *F = editor_property_map.front(); F; F = F->next()) { + if (F->key() == property_selected) + continue; + for (List<EditorProperty *>::Element *E = F->get().front(); E; E = E->next()) { + if (E->get()->is_selected()) + E->get()->deselect(); + } + } +} + +void EditorInspector::_object_id_selected(const String &p_path, ObjectID p_id) { + + emit_signal("object_id_selected", p_id); +} + +void EditorInspector::_resource_selected(const String &p_path, RES p_resource) { + emit_signal("resource_selected", p_resource, p_path); +} + +void EditorInspector::_node_removed(Node *p_node) { + + if (p_node == object) { + edit(NULL); + } +} + +void EditorInspector::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + get_tree()->connect("node_removed", this, "_node_removed"); + add_style_override("bg", get_stylebox("bg", "Tree")); + } + if (p_what == NOTIFICATION_EXIT_TREE) { + + get_tree()->disconnect("node_removed", this, "_node_removed"); + edit(NULL); + } + + if (p_what == NOTIFICATION_PROCESS) { + + if (refresh_countdown > 0) { + refresh_countdown -= get_process_delta_time(); + if (refresh_countdown <= 0) { + for (Map<StringName, List<EditorProperty *> >::Element *F = editor_property_map.front(); F; F = F->next()) { + for (List<EditorProperty *>::Element *E = F->get().front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } + } + } + } + + changing++; + + if (update_tree_pending) { + + update_tree(); + update_tree_pending = false; + pending.clear(); + + } else { + + while (pending.size()) { + StringName prop = pending.front()->get(); + if (editor_property_map.has(prop)) { + for (List<EditorProperty *>::Element *E = editor_property_map[prop].front(); E; E = E->next()) { + E->get()->update_property(); + E->get()->update_reload_status(); + } + } + pending.erase(pending.front()); + } + } + + changing--; + } + + if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { + update_tree(); + } +} + +void EditorInspector::_changed_callback(Object *p_changed, const char *p_prop) { + //this is called when property change is notified via _change_notify() + _edit_request_change(p_changed, p_prop); +} + +void EditorInspector::_bind_methods() { + + ClassDB::bind_method("_multiple_properties_changed", &EditorInspector::_multiple_properties_changed); + ClassDB::bind_method("_property_changed", &EditorInspector::_property_changed); + ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change); + ClassDB::bind_method("_node_removed", &EditorInspector::_node_removed); + ClassDB::bind_method("_filter_changed", &EditorInspector::_filter_changed); + ClassDB::bind_method("_property_keyed", &EditorInspector::_property_keyed); + ClassDB::bind_method("_property_checked", &EditorInspector::_property_checked); + ClassDB::bind_method("_property_selected", &EditorInspector::_property_selected); + ClassDB::bind_method("_resource_selected", &EditorInspector::_resource_selected); + ClassDB::bind_method("_object_id_selected", &EditorInspector::_object_id_selected); + + ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"))); + ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "res"), PropertyInfo(Variant::STRING, "prop"))); + ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::INT, "id"))); +} + +EditorInspector::EditorInspector() { + object = NULL; + undo_redo = NULL; + main_vbox = memnew(VBoxContainer); + main_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(main_vbox); + main_vbox->set_name("pipirulo"); + set_h_scroll(false); + set_v_scroll(true); + + show_categories = false; + hide_script = true; + use_doc_hints = false; + capitalize_paths = false; + use_filter = false; + autoclear = false; + changing = 0; + use_folding = false; + update_all_pending = false; + update_tree_pending = false; + refresh_countdown = 0; + read_only = false; + search_box = NULL; + keying = false; + _prop_edited = "property_edited"; + set_process(true); + property_focusable = -1; +} diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h new file mode 100644 index 0000000000..64be316b0e --- /dev/null +++ b/editor/editor_inspector.h @@ -0,0 +1,283 @@ +#ifndef EDITOR_INSPECTOR_H +#define EDITOR_INSPECTOR_H + +#include "editor_data.h" +#include "scene/gui/scroll_container.h" + +class EditorProperty : public Container { + + GDCLASS(EditorProperty, Container) +public: + enum LabelLayout { + LABEL_LAYOUT_LEFT, + LABEL_LAYOUT_TOP, + }; + +private: + String label; + int text_size; + friend class EditorInspector; + Object *object; + StringName property; + + LabelLayout label_layout; + + int property_usage; + + bool read_only; + bool checkable; + bool checked; + bool draw_red; + bool keying; + + Rect2 keying_rect; + bool keying_hover; + Rect2 revert_rect; + bool revert_hover; + Rect2 check_rect; + bool check_hover; + + bool can_revert; + + bool _might_be_in_instance(); + bool _is_property_different(const Variant &p_current, const Variant &p_orig, int p_usage); + bool _is_instanced_node_with_original_property_different(); + bool _get_instanced_node_original_property(const StringName &p_prop, Variant &value); + void _focusable_focused(int p_index); + + bool selected; + int selected_focusable; + + Vector<Control *> focusables; + Control *label_reference; + +protected: + void _notification(int p_what); + static void _bind_methods(); + + void _gui_input(const Ref<InputEvent> &p_event); + +public: + virtual Size2 get_minimum_size() const; + + void set_label(const String &p_label); + String get_label() const; + + void set_read_only(bool p_read_only); + bool is_read_only() const; + + Object *get_edited_object(); + StringName get_edited_property(); + + virtual void update_property(); + void update_reload_status(); + + virtual bool use_keying_next() const; + + void set_checkable(bool p_checkable); + bool is_checkable() const; + + void set_checked(bool p_checked); + bool is_checked() const; + + void set_draw_red(bool p_draw_red); + bool is_draw_red() const; + + void set_keying(bool p_keying); + bool is_keying() const; + + void add_focusable(Control *p_control); + void select(int p_focusable = -1); + void deselect(); + bool is_selected() const; + + void set_label_reference(Control *p_control); + + virtual Variant get_drag_data(const Point2 &p_point); + + void set_label_layout(LabelLayout p_layout); + EditorProperty(); +}; + +class EditorInspectorPlugin : public Reference { + GDCLASS(EditorInspectorPlugin, Reference) + + friend class EditorInspector; + struct AddedEditor { + Control *property_editor; + Vector<String> properties; + String label; + }; + + List<AddedEditor> added_editors; + +protected: + static void _bind_methods(); + +public: + void add_custom_control(Control *control); + void add_property_editor(const String &p_for_property, Control *p_prop); + void add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop); + + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); + virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage); + virtual void parse_end(); +}; + +class EditorInspectorCategory : public Control { + GDCLASS(EditorInspectorCategory, Control); + + friend class EditorInspector; + Ref<Texture> icon; + String label; + Color bg_color; + +protected: + void _notification(int p_what); + +public: + virtual Size2 get_minimum_size() const; + + EditorInspectorCategory(); +}; + +class EditorInspectorSection : public Container { + GDCLASS(EditorInspectorSection, Container); + + String label; + String section; + Object *object; + VBoxContainer *vbox; + Color bg_color; + bool foldable; + +protected: + void _notification(int p_what); + static void _bind_methods(); + void _gui_input(const Ref<InputEvent> &p_event); + +public: + virtual Size2 get_minimum_size() const; + + void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable); + VBoxContainer *get_vbox(); + void unfold(); + void fold(); + + Object *get_edited_object(); + + EditorInspectorSection(); +}; + +class EditorInspector : public ScrollContainer { + GDCLASS(EditorInspector, ScrollContainer); + + UndoRedo *undo_redo; + enum { + MAX_PLUGINS = 1024 + }; + static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS]; + static int inspector_plugin_count; + + VBoxContainer *main_vbox; + + //map use to cache the instanced editors + Map<StringName, List<EditorProperty *> > editor_property_map; + List<EditorInspectorSection *> sections; + Set<StringName> pending; + + void _clear(); + Object *object; + + // + + LineEdit *search_box; + bool show_categories; + bool hide_script; + bool use_doc_hints; + bool capitalize_paths; + bool use_filter; + bool autoclear; + bool use_folding; + int changing; + bool update_all_pending; + bool read_only; + bool keying; + + int refresh_countdown; + bool update_tree_pending; + StringName _prop_edited; + StringName property_selected; + int property_focusable; + + Map<StringName, Map<StringName, String> > descr_cache; + Map<StringName, String> class_descr_cache; + + void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); + + void _property_changed(const String &p_path, const Variant &p_value); + void _multiple_properties_changed(Vector<String> p_paths, Array p_values); + void _property_keyed(const String &p_path); + void _property_checked(const String &p_path, bool p_checked); + + void _resource_selected(const String &p_path, RES p_resource); + void _property_selected(const String &p_path, int p_focusable); + void _object_id_selected(const String &p_path, ObjectID p_id); + + void _node_removed(Node *p_node); + + void _changed_callback(Object *p_changed, const char *p_prop); + void _edit_request_change(Object *p_changed, const String &p_prop); + + void _filter_changed(const String &p_text); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + static void add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); + static void remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin); + static void cleanup_plugins(); + + void set_undo_redo(UndoRedo *p_undo_redo); + + String get_selected_path() const; + + void update_tree(); + void update_property(const String &p_prop); + + void refresh(); + + void edit(Object *p_object); + + void set_keying(bool p_active); + void set_read_only(bool p_read_only); + + bool is_capitalize_paths_enabled() const; + void set_enable_capitalize_paths(bool p_capitalize); + void set_autoclear(bool p_enable); + + void set_show_categories(bool p_show); + void set_use_doc_hints(bool p_enable); + void set_hide_script(bool p_hide); + + void set_use_filter(bool p_use); + void register_text_enter(Node *p_line_edit); + + void set_subsection_selectable(bool p_selectable); + void set_property_selectable(bool p_selectable); + + void set_use_folding(bool p_enable); + + void collapse_all_folding(); + void expand_all_folding(); + + void set_scroll_offset(int p_offset); + int get_scroll_offset() const; + + EditorInspector(); +}; + +#endif // INSPECTOR_H diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 3f0a09cfd9..49f70e3215 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -56,6 +56,7 @@ #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_initialize_ssl.h" +#include "editor/editor_properties.h" #include "editor/editor_settings.h" #include "editor/editor_themes.h" #include "editor/import/editor_import_collada.h" @@ -548,8 +549,8 @@ void EditorNode::_vp_resized() { void EditorNode::_node_renamed() { - if (property_editor) - property_editor->update_tree(); + if (inspector) + inspector->update_tree(); } void EditorNode::_editor_select_next() { @@ -1351,7 +1352,7 @@ void EditorNode::_dialog_action(String p_file) { void EditorNode::push_item(Object *p_object, const String &p_property) { if (!p_object) { - property_editor->edit(NULL); + inspector->edit(NULL); node_dock->set_node(NULL); scene_tree_dock->set_selected(NULL); return; @@ -1444,12 +1445,12 @@ void EditorNode::_property_editor_back() { void EditorNode::_menu_collapseall() { - property_editor->collapse_all_folding(); + inspector->collapse_all_folding(); } void EditorNode::_menu_expandall() { - property_editor->expand_all_folding(); + inspector->expand_all_folding(); } void EditorNode::_save_default_environment() { @@ -1515,7 +1516,7 @@ void EditorNode::_edit_current() { if (!current_obj) { scene_tree_dock->set_selected(NULL); - property_editor->edit(NULL); + inspector->edit(NULL); node_dock->set_node(NULL); object_menu->set_disabled(true); @@ -1536,7 +1537,7 @@ void EditorNode::_edit_current() { Resource *current_res = Object::cast_to<Resource>(current_obj); ERR_FAIL_COND(!current_res); scene_tree_dock->set_selected(NULL); - property_editor->edit(current_res); + inspector->edit(current_res); node_dock->set_node(NULL); object_menu->set_disabled(false); EditorNode::get_singleton()->get_import_dock()->set_edit_path(current_res->get_path()); @@ -1561,7 +1562,7 @@ void EditorNode::_edit_current() { Node *current_node = Object::cast_to<Node>(current_obj); ERR_FAIL_COND(!current_node); - property_editor->edit(current_node); + inspector->edit(current_node); if (current_node->is_inside_tree()) { node_dock->set_node(current_node); scene_tree_dock->set_selected(current_node); @@ -1585,7 +1586,7 @@ void EditorNode::_edit_current() { capitalize = false; } - property_editor->edit(current_obj); + inspector->edit(current_obj); node_dock->set_node(NULL); } @@ -1594,8 +1595,8 @@ void EditorNode::_edit_current() { property_editable_warning_dialog->set_text(editable_warning); } - if (property_editor->is_capitalize_paths_enabled() != capitalize) { - property_editor->set_enable_capitalize_paths(capitalize); + if (inspector->is_capitalize_paths_enabled() != capitalize) { + inspector->set_enable_capitalize_paths(capitalize); } /* Take care of PLUGIN EDITOR */ @@ -2939,7 +2940,7 @@ Dictionary EditorNode::_get_main_scene_state() { Dictionary state; state["main_tab"] = _get_current_main_editor(); state["scene_tree_offset"] = scene_tree_dock->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->get_value(); - state["property_edit_offset"] = get_property_editor()->get_scene_tree()->get_vscroll_bar()->get_value(); + state["property_edit_offset"] = get_inspector()->get_scroll_offset(); state["saved_version"] = saved_version; state["node_filter"] = scene_tree_dock->get_filter(); return state; @@ -2985,7 +2986,7 @@ void EditorNode::_set_main_scene_state(Dictionary p_state, Node *p_for_scene) { if (p_state.has("scene_tree_offset")) scene_tree_dock->get_tree_editor()->get_scene_tree()->get_vscroll_bar()->set_value(p_state["scene_tree_offset"]); if (p_state.has("property_edit_offset")) - get_property_editor()->get_scene_tree()->get_vscroll_bar()->set_value(p_state["property_edit_offset"]); + get_inspector()->set_scroll_offset(p_state["property_edit_offset"]); if (p_state.has("node_filter")) scene_tree_dock->set_filter(p_state["node_filter"]); @@ -3278,9 +3279,7 @@ void EditorNode::update_keying() { } } - property_editor->set_keying(valid); - - AnimationPlayerEditor::singleton->get_key_editor()->update_keying(); + inspector->set_keying(valid); } void EditorNode::_close_messages() { @@ -3425,6 +3424,9 @@ void EditorNode::register_editor_types() { ClassDB::register_class<EditorExportPlugin>(); ClassDB::register_class<EditorResourceConversionPlugin>(); ClassDB::register_class<EditorSceneImporter>(); + ClassDB::register_class<EditorInspector>(); + ClassDB::register_class<EditorInspectorPlugin>(); + ClassDB::register_class<EditorProperty>(); // FIXME: Is this stuff obsolete, or should it be ported to new APIs? ClassDB::register_class<EditorScenePostImport>(); @@ -4236,7 +4238,7 @@ void EditorNode::_scene_tab_changed(int p_tab) { void EditorNode::_toggle_search_bar(bool p_pressed) { - property_editor->set_use_filter(p_pressed); + inspector->set_use_filter(p_pressed); if (p_pressed) { @@ -4255,7 +4257,7 @@ void EditorNode::_clear_search_box() { return; search_box->clear(); - property_editor->update_tree(); + inspector->update_tree(); } ToolButton *EditorNode::add_bottom_panel_item(String p_text, Control *p_item) { @@ -5007,6 +5009,12 @@ EditorNode::EditorNode() { ResourceFormatImporter::get_singleton()->add_importer(import_bitmap); } + { + Ref<EditorInspectorDefaultPlugin> eidp; + eidp.instance(); + EditorInspector::add_inspector_plugin(eidp); + } + _pvrtc_register_compressors(); editor_selection = memnew(EditorSelection); @@ -5665,21 +5673,21 @@ EditorNode::EditorNode() { property_editable_warning->hide(); property_editable_warning->connect("pressed", this, "_property_editable_warning_pressed"); - property_editor = memnew(PropertyEditor); - property_editor->set_autoclear(true); - property_editor->set_show_categories(true); - property_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - property_editor->set_use_doc_hints(true); - property_editor->set_hide_script(false); - property_editor->set_enable_capitalize_paths(bool(EDITOR_DEF("interface/editor/capitalize_properties", true))); - property_editor->set_use_folding(!bool(EDITOR_DEF("interface/editor/disable_inspector_folding", false))); + inspector = memnew(EditorInspector); + inspector->set_autoclear(true); + inspector->set_show_categories(true); + inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); + inspector->set_use_doc_hints(true); + inspector->set_hide_script(false); + inspector->set_enable_capitalize_paths(bool(EDITOR_DEF("interface/editor/capitalize_properties", true))); + inspector->set_use_folding(!bool(EDITOR_DEF("interface/editor/disable_inspector_folding", false))); - property_editor->hide_top_label(); - property_editor->register_text_enter(search_box); + // inspector->hide_top_label(); + inspector->register_text_enter(search_box); Button *property_editable_warning; - prop_editor_base->add_child(property_editor); - property_editor->set_undo_redo(&editor_data.get_undo_redo()); + prop_editor_base->add_child(inspector); + inspector->set_undo_redo(&editor_data.get_undo_redo()); import_dock = memnew(ImportDock); dock_slot[DOCK_SLOT_RIGHT_UL]->add_child(import_dock); @@ -5815,8 +5823,8 @@ EditorNode::EditorNode() { file->connect("file_selected", this, "_dialog_action"); file_templates->connect("file_selected", this, "_dialog_action"); - property_editor->connect("resource_selected", this, "_resource_selected"); - property_editor->connect("property_keyed", this, "_property_keyed"); + inspector->connect("resource_selected", this, "_resource_selected"); + inspector->connect("property_keyed", this, "_property_keyed"); //plugin stuff @@ -6039,6 +6047,8 @@ EditorNode::EditorNode() { EditorNode::~EditorNode() { + EditorInspector::cleanup_plugins(); + remove_print_handler(&print_handler); memdelete(EditorHelp::get_doc_data()); memdelete(editor_selection); diff --git a/editor/editor_node.h b/editor/editor_node.h index f774fa0a2e..86b85663ab 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -37,6 +37,7 @@ #include "editor/editor_about.h" #include "editor/editor_data.h" #include "editor/editor_export.h" +#include "editor/editor_inspector.h" #include "editor/editor_log.h" #include "editor/editor_name_dialog.h" #include "editor/editor_path.h" @@ -80,7 +81,6 @@ #include "scene/gui/tool_button.h" #include "scene/gui/tree.h" #include "scene/gui/viewport_container.h" - /** @author Juan Linietsky <reduzio@gmail.com> */ @@ -267,7 +267,7 @@ private: Button *property_back; Button *property_forward; SceneTreeDock *scene_tree_dock; - PropertyEditor *property_editor; + EditorInspector *inspector; Button *property_editable_warning; AcceptDialog *property_editable_warning_dialog; void _property_editable_warning_pressed(); @@ -640,7 +640,7 @@ public: EditorPluginList *get_editor_plugins_over() { return editor_plugins_over; } EditorPluginList *get_editor_plugins_force_over() { return editor_plugins_force_over; } EditorPluginList *get_editor_plugins_force_input_forwarding() { return editor_plugins_force_input_forwarding; } - PropertyEditor *get_property_editor() { return property_editor; } + EditorInspector *get_inspector() { return inspector; } VBoxContainer *get_property_editor_vb() { return prop_editor_vb; } ProjectSettingsEditor *get_project_settings() { return project_settings; } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp new file mode 100644 index 0000000000..51be64bd3f --- /dev/null +++ b/editor/editor_properties.cpp @@ -0,0 +1,2411 @@ +#include "editor_properties.h" +#include "editor/editor_resource_preview.h" +#include "editor_node.h" +#include "scene/main/viewport.h" +///////////////////// TEXT ///////////////////////// +void EditorPropertyText::_text_changed(const String &p_string) { + if (updating) + return; + + emit_signal("property_changed", get_edited_property(), p_string); +} + +void EditorPropertyText::update_property() { + String s = get_edited_object()->get(get_edited_property()); + updating = true; + text->set_text(s); + text->set_editable(!is_read_only()); + updating = false; +} + +void EditorPropertyText::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_text_changed", "txt"), &EditorPropertyText::_text_changed); +} + +EditorPropertyText::EditorPropertyText() { + text = memnew(LineEdit); + add_child(text); + add_focusable(text); + text->connect("text_changed", this, "_text_changed"); + updating = false; +} + +///////////////////// MULTILINE TEXT ///////////////////////// + +void EditorPropertyMultilineText::_big_text_changed() { + text->set_text(big_text->get_text()); + emit_signal("property_changed", get_edited_property(), big_text->get_text()); +} + +void EditorPropertyMultilineText::_text_changed() { + + emit_signal("property_changed", get_edited_property(), text->get_text()); +} + +void EditorPropertyMultilineText::_open_big_text() { + + if (!big_text_dialog) { + big_text = memnew(TextEdit); + big_text->connect("text_changed", this, "_big_text_changed"); + big_text_dialog = memnew(AcceptDialog); + big_text_dialog->add_child(big_text); + big_text_dialog->set_title("Edit Text:"); + add_child(big_text_dialog); + } + + big_text->set_text(text->get_text()); + big_text_dialog->popup_centered_ratio(); +} + +void EditorPropertyMultilineText::update_property() { + String t = get_edited_object()->get(get_edited_property()); + text->set_text(t); + if (big_text && big_text->is_visible_in_tree()) { + big_text->set_text(t); + } +} + +void EditorPropertyMultilineText::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { + Ref<Texture> df = get_icon("DistractionFree", "EditorIcons"); + open_big_text->set_icon(df); + Ref<Font> font = get_font("font", "Label"); + text->set_custom_minimum_size(Vector2(0, font->get_height() * 6)); + + } break; + } +} + +void EditorPropertyMultilineText::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_text_changed"), &EditorPropertyMultilineText::_text_changed); + ClassDB::bind_method(D_METHOD("_big_text_changed"), &EditorPropertyMultilineText::_big_text_changed); + ClassDB::bind_method(D_METHOD("_open_big_text"), &EditorPropertyMultilineText::_open_big_text); +} + +EditorPropertyMultilineText::EditorPropertyMultilineText() { + HBoxContainer *hb = memnew(HBoxContainer); + set_label_layout(LABEL_LAYOUT_TOP); + add_child(hb); + text = memnew(TextEdit); + text->connect("text_changed", this, "_text_changed"); + add_focusable(text); + hb->add_child(text); + text->set_h_size_flags(SIZE_EXPAND_FILL); + open_big_text = memnew(ToolButton); + open_big_text->connect("pressed", this, "_open_big_text"); + hb->add_child(open_big_text); + big_text_dialog = NULL; + big_text = NULL; +} + +///////////////////// TEXT ENUM ///////////////////////// + +void EditorPropertyTextEnum::_option_selected(int p_which) { + + emit_signal("property_changed", get_edited_property(), options->get_item_text(p_which)); +} + +void EditorPropertyTextEnum::update_property() { + + String which = get_edited_object()->get(get_edited_property()); + for (int i = 0; i < options->get_item_count(); i++) { + String t = options->get_item_text(i); + if (t == which) { + options->select(i); + return; + } + } +} + +void EditorPropertyTextEnum::setup(const Vector<String> &p_options) { + for (int i = 0; i < p_options.size(); i++) { + options->add_item(p_options[i], i); + } +} + +void EditorPropertyTextEnum::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyTextEnum::_option_selected); +} + +EditorPropertyTextEnum::EditorPropertyTextEnum() { + options = memnew(OptionButton); + options->set_clip_text(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", this, "_option_selected"); +} +///////////////////// PATH ///////////////////////// + +void EditorPropertyPath::_path_selected(const String &p_path) { + + emit_signal("property_changed", get_edited_property(), p_path); + update_property(); +} +void EditorPropertyPath::_path_pressed() { + + if (!dialog) { + dialog = memnew(EditorFileDialog); + dialog->connect("file_selected", this, "_path_selected"); + dialog->connect("dir_selected", this, "_path_selected"); + add_child(dialog); + } + + String full_path = get_edited_object()->get(get_edited_property()); + + dialog->clear_filters(); + + if (global) { + dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + } else { + dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); + } + + if (folder) { + dialog->set_mode(EditorFileDialog::MODE_OPEN_DIR); + dialog->set_current_dir(full_path); + } else { + dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); + for (int i = 0; i < extensions.size(); i++) { + String e = extensions[i].strip_edges(); + if (e != String()) { + dialog->add_filter(extensions[i].strip_edges()); + } + } + dialog->set_current_path(full_path); + } + + dialog->popup_centered_ratio(); +} + +void EditorPropertyPath::update_property() { + + String full_path = get_edited_object()->get(get_edited_property()); + path->set_text(full_path); + path->set_tooltip(full_path); +} + +void EditorPropertyPath::setup(const Vector<String> &p_extensions, bool p_folder, bool p_global) { + + extensions = p_extensions; + folder = p_folder; + global = p_global; +} + +void EditorPropertyPath::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_path_pressed"), &EditorPropertyPath::_path_pressed); + ClassDB::bind_method(D_METHOD("_path_selected"), &EditorPropertyPath::_path_selected); +} + +EditorPropertyPath::EditorPropertyPath() { + path = memnew(Button); + path->set_clip_text(true); + add_child(path); + add_focusable(path); + dialog = NULL; + path->connect("pressed", this, "_path_pressed"); + folder = false; + global = false; +} + +///////////////////// MEMBER ///////////////////////// + +void EditorPropertyMember::_property_selected(const String &p_selected) { + + emit_signal("property_changed", get_edited_property(), p_selected); + update_property(); +} + +void EditorPropertyMember::_property_select() { + + if (!selector) { + selector = memnew(PropertySelector); + selector->connect("selected", this, "_property_selected"); + add_child(selector); + } + + String current = get_edited_object()->get(get_edited_property()); + + if (hint == MEMBER_METHOD_OF_VARIANT_TYPE) { + + Variant::Type type = Variant::NIL; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (hint_text == Variant::get_type_name(Variant::Type(i))) { + type = Variant::Type(i); + } + } + if (type) + selector->select_method_from_basic_type(type, current); + + } else if (hint == MEMBER_METHOD_OF_BASE_TYPE) { + + selector->select_method_from_base_type(hint_text, current); + + } else if (hint == MEMBER_METHOD_OF_INSTANCE) { + + Object *instance = ObjectDB::get_instance(hint_text.to_int64()); + if (instance) + selector->select_method_from_instance(instance, current); + + } else if (hint == MEMBER_METHOD_OF_SCRIPT) { + + Object *obj = ObjectDB::get_instance(hint_text.to_int64()); + if (Object::cast_to<Script>(obj)) { + selector->select_method_from_script(Object::cast_to<Script>(obj), current); + } + + } else if (hint == MEMBER_PROPERTY_OF_VARIANT_TYPE) { + + Variant::Type type = Variant::NIL; + String tname = hint_text; + if (tname.find(".") != -1) + tname = tname.get_slice(".", 0); + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (tname == Variant::get_type_name(Variant::Type(i))) { + type = Variant::Type(Variant::Type(i)); + } + } + + if (type != Variant::NIL) + selector->select_property_from_basic_type(type, current); + + } else if (hint == MEMBER_PROPERTY_OF_BASE_TYPE) { + + selector->select_property_from_base_type(hint_text, current); + + } else if (hint == MEMBER_PROPERTY_OF_INSTANCE) { + + Object *instance = ObjectDB::get_instance(hint_text.to_int64()); + if (instance) + selector->select_property_from_instance(instance, current); + + } else if (hint == MEMBER_PROPERTY_OF_SCRIPT) { + + Object *obj = ObjectDB::get_instance(hint_text.to_int64()); + if (Object::cast_to<Script>(obj)) { + selector->select_property_from_script(Object::cast_to<Script>(obj), current); + } + } +} + +void EditorPropertyMember::setup(Type p_hint, const String &p_hint_text) { + hint = p_hint; + hint_text = p_hint_text; +} + +void EditorPropertyMember::update_property() { + + String full_path = get_edited_object()->get(get_edited_property()); + property->set_text(full_path); +} + +void EditorPropertyMember::_bind_methods() { + ClassDB::bind_method(D_METHOD("_property_selected"), &EditorPropertyMember::_property_selected); + ClassDB::bind_method(D_METHOD("_property_select"), &EditorPropertyMember::_property_select); +} + +EditorPropertyMember::EditorPropertyMember() { + selector = NULL; + property = memnew(Button); + property->set_clip_text(true); + add_child(property); + add_focusable(property); + property->connect("pressed", this, "_property_select"); +} + +///////////////////// CHECK ///////////////////////// +void EditorPropertyCheck::_checkbox_pressed() { + + emit_signal("property_changed", get_edited_property(), checkbox->is_pressed()); +} + +void EditorPropertyCheck::update_property() { + bool c = get_edited_object()->get(get_edited_property()); + checkbox->set_pressed(c); + checkbox->set_disabled(is_read_only()); +} + +void EditorPropertyCheck::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_checkbox_pressed"), &EditorPropertyCheck::_checkbox_pressed); +} + +EditorPropertyCheck::EditorPropertyCheck() { + checkbox = memnew(CheckBox); + checkbox->set_text(TTR("On")); + add_child(checkbox); + add_focusable(checkbox); + checkbox->connect("pressed", this, "_checkbox_pressed"); +} + +///////////////////// ENUM ///////////////////////// + +void EditorPropertyEnum::_option_selected(int p_which) { + + emit_signal("property_changed", get_edited_property(), p_which); +} + +void EditorPropertyEnum::update_property() { + + int which = get_edited_object()->get(get_edited_property()); + options->select(which); +} + +void EditorPropertyEnum::setup(const Vector<String> &p_options) { + for (int i = 0; i < p_options.size(); i++) { + options->add_item(p_options[i], i); + } +} + +void EditorPropertyEnum::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_option_selected"), &EditorPropertyEnum::_option_selected); +} + +EditorPropertyEnum::EditorPropertyEnum() { + options = memnew(OptionButton); + options->set_clip_text(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", this, "_option_selected"); +} + +///////////////////// FLAGS ///////////////////////// + +void EditorPropertyFlags::_flag_toggled() { + + uint32_t value = 0; + for (int i = 0; i < flags.size(); i++) { + if (flags[i]->is_pressed()) { + uint32_t val = 1; + val <<= flag_indices[i]; + value |= val; + } + } + + emit_signal("property_changed", get_edited_property(), value); +} + +void EditorPropertyFlags::update_property() { + + uint32_t value = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < flags.size(); i++) { + uint32_t val = 1; + val <<= flag_indices[i]; + if (value & val) { + + flags[i]->set_pressed(true); + } else { + flags[i]->set_pressed(false); + } + } +} + +void EditorPropertyFlags::setup(const Vector<String> &p_options) { + ERR_FAIL_COND(flags.size()); + + bool first = true; + for (int i = 0; i < p_options.size(); i++) { + String option = p_options[i].strip_edges(); + if (option != "") { + CheckBox *cb = memnew(CheckBox); + cb->set_text(option); + cb->set_clip_text(true); + cb->connect("pressed", this, "_flag_toggled"); + add_focusable(cb); + vbox->add_child(cb); + flags.push_back(cb); + flag_indices.push_back(i); + if (first) { + set_label_reference(cb); + first = false; + } + } + } +} + +void EditorPropertyFlags::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_flag_toggled"), &EditorPropertyFlags::_flag_toggled); +} + +EditorPropertyFlags::EditorPropertyFlags() { + + vbox = memnew(VBoxContainer); + add_child(vbox); +} + +///////////////////// LAYERS ///////////////////////// + +class EditorPropertyLayersGrid : public Control { + GDCLASS(EditorPropertyLayersGrid, Control) +public: + uint32_t value; + Vector<Rect2> flag_rects; + Vector<String> names; + + virtual Size2 get_minimum_size() const { + Ref<Font> font = get_font("font", "Label"); + return Vector2(0, font->get_height() * 2); + } + + virtual String get_tooltip(const Point2 &p_pos) const { + for (int i = 0; i < flag_rects.size(); i++) { + if (flag_rects[i].has_point(p_pos) && i < names.size()) { + return names[i]; + } + } + return String(); + } + void _gui_input(const Ref<InputEvent> &p_ev) { + Ref<InputEventMouseButton> mb = p_ev; + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { + for (int i = 0; i < flag_rects.size(); i++) { + if (flag_rects[i].has_point(mb->get_position())) { + //toggle + if (value & (1 << i)) { + value &= ~(1 << i); + } else { + value |= (1 << i); + } + emit_signal("flag_changed", value); + update(); + } + } + } + } + + void _notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) { + + Rect2 rect; + rect.size = get_size(); + flag_rects.clear(); + + int bsize = (rect.size.height * 80 / 100) / 2; + + int h = bsize * 2 + 1; + int vofs = (rect.size.height - h) / 2; + + for (int i = 0; i < 2; i++) { + + Point2 ofs(4, vofs); + if (i == 1) + ofs.y += bsize + 1; + + ofs += rect.position; + for (int j = 0; j < 10; j++) { + + Point2 o = ofs + Point2(j * (bsize + 1), 0); + if (j >= 5) + o.x += 1; + + uint32_t idx = i * 10 + j; + bool on = value & (1 << idx); + Rect2 rect = Rect2(o, Size2(bsize, bsize)); + draw_rect(rect, Color(0, 0, 0, on ? 0.8 : 0.3)); + flag_rects.push_back(rect); + } + } + } + } + + void set_flag(uint32_t p_flag) { + value = p_flag; + update(); + } + + static void _bind_methods() { + + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input); + ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag"))); + } + + EditorPropertyLayersGrid() { + value = 0; + } +}; +void EditorPropertyLayers::_grid_changed(uint32_t p_grid) { + + emit_signal("property_changed", get_edited_property(), p_grid); +} + +void EditorPropertyLayers::update_property() { + + uint32_t value = get_edited_object()->get(get_edited_property()); + + grid->set_flag(value); +} + +void EditorPropertyLayers::setup(LayerType p_layer_type) { + + String basename; + switch (p_layer_type) { + case LAYER_RENDER_2D: + basename = "layer_names/2d_render"; + break; + case LAYER_PHYSICS_2D: + basename = "layer_names/2d_physics"; + break; + case LAYER_RENDER_3D: + basename = "layer_names/3d_render"; + break; + case LAYER_PHYSICS_3D: + basename = "layer_names/3d_physics"; + break; + } + + Vector<String> names; + for (int i = 0; i < 20; i++) { + String name; + + if (ProjectSettings::get_singleton()->has_setting(basename + "/layer_" + itos(i + 1))) { + name = ProjectSettings::get_singleton()->get(basename + "/layer_" + itos(i + 1)); + } + + if (name == "") { + name = "Layer " + itos(i + 1); + } + + names.push_back(name); + } + + grid->names = names; +} + +void EditorPropertyLayers::_button_pressed() { + + layers->clear(); + for (int i = 0; i < 20; i++) { + if (i == 5 || i == 10 || i == 15) { + layers->add_separator(); + } + layers->add_check_item(grid->names[i], i); + int idx = layers->get_item_index(i); + layers->set_item_checked(idx, grid->value & (1 << i)); + } + + Rect2 gp = button->get_global_rect(); + Vector2 popup_pos = gp.position - Vector2(layers->get_combined_minimum_size().x, 0); + layers->set_global_position(popup_pos); + layers->popup(); +} + +void EditorPropertyLayers::_menu_pressed(int p_menu) { + if (grid->value & (1 << p_menu)) { + grid->value &= ~(1 << p_menu); + } else { + grid->value |= (1 << p_menu); + } + grid->update(); + _grid_changed(grid->value); +} + +void EditorPropertyLayers::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_grid_changed"), &EditorPropertyLayers::_grid_changed); + ClassDB::bind_method(D_METHOD("_button_pressed"), &EditorPropertyLayers::_button_pressed); + ClassDB::bind_method(D_METHOD("_menu_pressed"), &EditorPropertyLayers::_menu_pressed); +} + +EditorPropertyLayers::EditorPropertyLayers() { + + HBoxContainer *hb = memnew(HBoxContainer); + add_child(hb); + grid = memnew(EditorPropertyLayersGrid); + grid->connect("flag_changed", this, "_grid_changed"); + grid->set_h_size_flags(SIZE_EXPAND_FILL); + hb->add_child(grid); + button = memnew(Button); + button->set_text(".."); + button->connect("pressed", this, "_button_pressed"); + hb->add_child(button); + set_label_layout(LABEL_LAYOUT_TOP); + layers = memnew(PopupMenu); + add_child(layers); + layers->connect("id_pressed", this, "_menu_pressed"); +} +///////////////////// INT ///////////////////////// + +void EditorPropertyInteger::_value_changed(double val) { + if (setting) + return; + emit_signal("property_changed", get_edited_property(), int(val)); +} + +void EditorPropertyInteger::update_property() { + int val = get_edited_object()->get(get_edited_property()); + setting = true; + spin->set_value(val); + setting = false; +} + +void EditorPropertyInteger::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyInteger::_value_changed); +} + +void EditorPropertyInteger::setup(int p_min, int p_max) { + spin->set_min(p_min); + spin->set_max(p_max); + spin->set_step(1); +} + +EditorPropertyInteger::EditorPropertyInteger() { + spin = memnew(EditorSpinSlider); + add_child(spin); + add_focusable(spin); + spin->connect("value_changed", this, "_value_changed"); + setting = false; +} + +///////////////////// OBJECT ID ///////////////////////// + +void EditorPropertyObjectID::_edit_pressed() { + + emit_signal("object_id_selected", get_edited_property(), get_edited_object()->get(get_edited_property())); +} + +void EditorPropertyObjectID::update_property() { + String type = base_type; + if (type == "") + type = "Object"; + + String icon_type = type; + if (has_icon(icon_type, "EditorIcons")) { + type = icon_type; + } else { + type = "Object"; + } + + ObjectID id = get_edited_object()->get(get_edited_property()); + if (id != 0) { + edit->set_text(type + " ID: " + itos(id)); + edit->set_disabled(false); + edit->set_icon(get_icon(icon_type, "EditorIcons")); + } else { + edit->set_text(TTR("[Empty]")); + edit->set_disabled(true); + edit->set_icon(Ref<Texture>()); + } +} + +void EditorPropertyObjectID::setup(const String &p_base_type) { + base_type = p_base_type; +} + +void EditorPropertyObjectID::_bind_methods() { + ClassDB::bind_method(D_METHOD("_edit_pressed"), &EditorPropertyObjectID::_edit_pressed); +} + +EditorPropertyObjectID::EditorPropertyObjectID() { + edit = memnew(Button); + add_child(edit); + add_focusable(edit); + edit->connect("pressed", this, "_edit_pressed"); +} + +///////////////////// FLOAT ///////////////////////// + +void EditorPropertyFloat::_value_changed(double val) { + if (setting) + return; + + emit_signal("property_changed", get_edited_property(), val); +} + +void EditorPropertyFloat::update_property() { + double val = get_edited_object()->get(get_edited_property()); + setting = true; + spin->set_value(val); + setting = false; +} + +void EditorPropertyFloat::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyFloat::_value_changed); +} + +void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range) { + + spin->set_min(p_min); + spin->set_max(p_max); + spin->set_step(p_step); + spin->set_hide_slider(p_no_slider); + spin->set_exp_ratio(p_exp_range); +} + +EditorPropertyFloat::EditorPropertyFloat() { + spin = memnew(EditorSpinSlider); + add_child(spin); + add_focusable(spin); + spin->connect("value_changed", this, "_value_changed"); + setting = false; +} + +///////////////////// EASING ///////////////////////// + +void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) { + + Ref<InputEventMouseMotion> mm = p_ev; + + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + + float rel = mm->get_relative().x; + if (rel == 0) + return; + + if (flip) + rel = -rel; + + float val = get_edited_object()->get(get_edited_property()); + if (val == 0) + return; + bool sg = val < 0; + val = Math::absf(val); + + val = Math::log(val) / Math::log((float)2.0); + //logspace + val += rel * 0.05; + + val = Math::pow(2.0f, val); + if (sg) + val = -val; + + emit_signal("property_changed", get_edited_property(), val); + easing_draw->update(); + } +} + +void EditorPropertyEasing::_draw_easing() { + + RID ci = easing_draw->get_canvas_item(); + + Size2 s = easing_draw->get_size(); + Rect2 r(Point2(), s); + r = r.grow(3); + get_stylebox("normal", "LineEdit")->draw(ci, r); + + int points = 48; + + float prev = 1.0; + float exp = get_edited_object()->get(get_edited_property()); + + Ref<Font> f = get_font("font", "Label"); + Color color = get_color("font_color", "Label"); + + for (int i = 1; i <= points; i++) { + + float ifl = i / float(points); + float iflp = (i - 1) / float(points); + + float h = 1.0 - Math::ease(ifl, exp); + + if (flip) { + ifl = 1.0 - ifl; + iflp = 1.0 - iflp; + } + + VisualServer::get_singleton()->canvas_item_add_line(ci, Point2(iflp * s.width, prev * s.height), Point2(ifl * s.width, h * s.height), color); + prev = h; + } + + f->draw(ci, Point2(10, 10 + f->get_ascent()), String::num(exp, 2), color); +} + +void EditorPropertyEasing::update_property() { + easing_draw->update(); +} + +void EditorPropertyEasing::_set_preset(float p_val) { + emit_signal("property_changed", get_edited_property(), p_val); + easing_draw->update(); +} + +void EditorPropertyEasing::setup(bool p_full, bool p_flip) { + + flip = p_flip; + if (p_full) { + HBoxContainer *hb2 = memnew(HBoxContainer); + vb->add_child(hb2); + button_out_in = memnew(ToolButton); + button_out_in->set_tooltip(TTR("Out-In")); + button_out_in->set_h_size_flags(SIZE_EXPAND_FILL); + button_out_in->connect("pressed", this, "_set_preset", varray(-0.5)); + hb2->add_child(button_out_in); + + button_in_out = memnew(ToolButton); + button_in_out->set_tooltip(TTR("In")); + button_in_out->set_h_size_flags(SIZE_EXPAND_FILL); + button_in_out->connect("pressed", this, "_set_preset", varray(-2)); + hb2->add_child(button_in_out); + } +} + +void EditorPropertyEasing::_notification(int p_what) { + + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { + easing_draw->set_custom_minimum_size(Size2(0, get_font("font", "Label")->get_height() * 2)); + button_linear->set_icon(get_icon("CurveLinear", "EditorIcons")); + button_out->set_icon(get_icon("CurveOut", "EditorIcons")); + button_in->set_icon(get_icon("CurveIn", "EditorIcons")); + button_constant->set_icon(get_icon("CurveConstant", "EditorIcons")); + if (button_out_in) + button_out_in->set_icon(get_icon("CurveOutIn", "EditorIcons")); + if (button_in_out) + button_in_out->set_icon(get_icon("CurveInOut", "EditorIcons")); + } break; + } +} + +void EditorPropertyEasing::_bind_methods() { + + ClassDB::bind_method("_draw_easing", &EditorPropertyEasing::_draw_easing); + ClassDB::bind_method("_drag_easing", &EditorPropertyEasing::_drag_easing); + ClassDB::bind_method("_set_preset", &EditorPropertyEasing::_set_preset); +} + +EditorPropertyEasing::EditorPropertyEasing() { + + vb = memnew(VBoxContainer); + add_child(vb); + HBoxContainer *hb = memnew(HBoxContainer); + set_label_reference(hb); + + vb->add_child(hb); + + button_linear = memnew(ToolButton); + button_linear->set_tooltip(TTR("Linear")); + button_linear->set_h_size_flags(SIZE_EXPAND_FILL); + button_linear->connect("pressed", this, "_set_preset", varray(1)); + hb->add_child(button_linear); + + button_constant = memnew(ToolButton); + button_constant->set_tooltip(TTR("Linear")); + button_constant->set_h_size_flags(SIZE_EXPAND_FILL); + button_constant->connect("pressed", this, "_set_preset", varray(0)); + hb->add_child(button_constant); + + button_out = memnew(ToolButton); + button_out->set_tooltip(TTR("Out")); + button_out->set_h_size_flags(SIZE_EXPAND_FILL); + button_out->connect("pressed", this, "_set_preset", varray(0.5)); + hb->add_child(button_out); + + button_in = memnew(ToolButton); + button_in->set_tooltip(TTR("In")); + button_in->set_h_size_flags(SIZE_EXPAND_FILL); + button_in->connect("pressed", this, "_set_preset", varray(2)); + hb->add_child(button_in); + + button_in_out = NULL; + button_out_in = NULL; + + easing_draw = memnew(Control); + easing_draw->connect("draw", this, "_draw_easing"); + easing_draw->connect("gui_input", this, "_drag_easing"); + easing_draw->set_default_cursor_shape(Control::CURSOR_MOVE); + vb->add_child(easing_draw); + + flip = false; +} + +///////////////////// VECTOR2 ///////////////////////// + +void EditorPropertyVector2::_value_changed(double val) { + if (setting) + return; + + Vector2 v2; + v2.x = spin[0]->get_value(); + v2.y = spin[1]->get_value(); + emit_signal("property_changed", get_edited_property(), v2); +} + +void EditorPropertyVector2::update_property() { + Vector2 val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.x); + spin[1]->set_value(val.y); + setting = false; +} + +void EditorPropertyVector2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyVector2::_value_changed); +} + +void EditorPropertyVector2::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 2; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyVector2::EditorPropertyVector2() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[2] = { "x", "y" }; + for (int i = 0; i < 2; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} + +///////////////////// RECT2 ///////////////////////// + +void EditorPropertyRect2::_value_changed(double val) { + if (setting) + return; + + Rect2 r2; + r2.position.x = spin[0]->get_value(); + r2.position.x = spin[1]->get_value(); + r2.size.y = spin[2]->get_value(); + r2.size.y = spin[3]->get_value(); + emit_signal("property_changed", get_edited_property(), r2); +} + +void EditorPropertyRect2::update_property() { + Rect2 val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.position.x); + spin[1]->set_value(val.position.y); + spin[2]->set_value(val.size.x); + spin[3]->set_value(val.size.y); + setting = false; +} + +void EditorPropertyRect2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyRect2::_value_changed); +} + +void EditorPropertyRect2::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 4; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyRect2::EditorPropertyRect2() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[4] = { "x", "y", "w", "h" }; + for (int i = 0; i < 4; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} +///////////////////// VECTOR3 ///////////////////////// + +void EditorPropertyVector3::_value_changed(double val) { + if (setting) + return; + + Vector3 v3; + v3.x = spin[0]->get_value(); + v3.y = spin[1]->get_value(); + v3.z = spin[2]->get_value(); + emit_signal("property_changed", get_edited_property(), v3); +} + +void EditorPropertyVector3::update_property() { + Vector3 val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.x); + spin[1]->set_value(val.y); + spin[2]->set_value(val.z); + setting = false; +} + +void EditorPropertyVector3::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyVector3::_value_changed); +} + +void EditorPropertyVector3::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 3; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyVector3::EditorPropertyVector3() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[3] = { "x", "y", "z" }; + for (int i = 0; i < 3; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} +///////////////////// PLANE ///////////////////////// + +void EditorPropertyPlane::_value_changed(double val) { + if (setting) + return; + + Plane p; + p.normal.x = spin[0]->get_value(); + p.normal.y = spin[1]->get_value(); + p.normal.z = spin[2]->get_value(); + p.d = spin[3]->get_value(); + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyPlane::update_property() { + Plane val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.normal.x); + spin[1]->set_value(val.normal.y); + spin[2]->set_value(val.normal.z); + spin[3]->set_value(val.d); + setting = false; +} + +void EditorPropertyPlane::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyPlane::_value_changed); +} + +void EditorPropertyPlane::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 4; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyPlane::EditorPropertyPlane() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[4] = { "x", "y", "z", "d" }; + for (int i = 0; i < 4; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} + +///////////////////// QUAT ///////////////////////// + +void EditorPropertyQuat::_value_changed(double val) { + if (setting) + return; + + Quat p; + p.x = spin[0]->get_value(); + p.y = spin[1]->get_value(); + p.z = spin[2]->get_value(); + p.w = spin[3]->get_value(); + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyQuat::update_property() { + Quat val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.x); + spin[1]->set_value(val.y); + spin[2]->set_value(val.z); + spin[3]->set_value(val.w); + setting = false; +} + +void EditorPropertyQuat::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyQuat::_value_changed); +} + +void EditorPropertyQuat::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 4; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyQuat::EditorPropertyQuat() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + static const char *desc[4] = { "x", "y", "z", "w" }; + for (int i = 0; i < 4; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + vb->add_child(spin[i]); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + setting = false; +} + +///////////////////// AABB ///////////////////////// + +void EditorPropertyAABB::_value_changed(double val) { + if (setting) + return; + + AABB p; + p.position.x = spin[0]->get_value(); + p.position.y = spin[1]->get_value(); + p.position.z = spin[2]->get_value(); + p.size.x = spin[3]->get_value(); + p.size.y = spin[4]->get_value(); + p.size.z = spin[5]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyAABB::update_property() { + AABB val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.position.x); + spin[1]->set_value(val.position.y); + spin[2]->set_value(val.position.z); + spin[3]->set_value(val.size.x); + spin[4]->set_value(val.size.y); + spin[5]->set_value(val.size.z); + + setting = false; +} + +void EditorPropertyAABB::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyAABB::_value_changed); +} + +void EditorPropertyAABB::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 6; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyAABB::EditorPropertyAABB() { + GridContainer *g = memnew(GridContainer); + g->set_columns(3); + add_child(g); + + static const char *desc[6] = { "x", "y", "z", "w", "h", "d" }; + for (int i = 0; i < 6; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +///////////////////// TRANSFORM2D ///////////////////////// + +void EditorPropertyTransform2D::_value_changed(double val) { + if (setting) + return; + + Transform2D p; + p[0][0] = spin[0]->get_value(); + p[0][1] = spin[1]->get_value(); + p[1][0] = spin[2]->get_value(); + p[1][1] = spin[3]->get_value(); + p[2][0] = spin[4]->get_value(); + p[2][1] = spin[5]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyTransform2D::update_property() { + Transform2D val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val[0][0]); + spin[1]->set_value(val[0][1]); + spin[2]->set_value(val[1][0]); + spin[3]->set_value(val[1][1]); + spin[4]->set_value(val[2][0]); + spin[5]->set_value(val[2][1]); + + setting = false; +} + +void EditorPropertyTransform2D::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyTransform2D::_value_changed); +} + +void EditorPropertyTransform2D::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 6; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyTransform2D::EditorPropertyTransform2D() { + GridContainer *g = memnew(GridContainer); + g->set_columns(2); + add_child(g); + + static const char *desc[6] = { "xx", "xy", "yx", "yy", "ox", "oy" }; + for (int i = 0; i < 6; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +///////////////////// BASIS ///////////////////////// + +void EditorPropertyBasis::_value_changed(double val) { + if (setting) + return; + + Basis p; + p[0][0] = spin[0]->get_value(); + p[1][0] = spin[1]->get_value(); + p[2][0] = spin[2]->get_value(); + p[0][1] = spin[3]->get_value(); + p[1][1] = spin[4]->get_value(); + p[2][1] = spin[5]->get_value(); + p[0][2] = spin[6]->get_value(); + p[1][2] = spin[7]->get_value(); + p[2][2] = spin[8]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyBasis::update_property() { + Basis val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val[0][0]); + spin[1]->set_value(val[1][0]); + spin[2]->set_value(val[2][0]); + spin[3]->set_value(val[0][1]); + spin[4]->set_value(val[1][1]); + spin[5]->set_value(val[2][1]); + spin[6]->set_value(val[0][2]); + spin[7]->set_value(val[1][2]); + spin[8]->set_value(val[2][2]); + + setting = false; +} + +void EditorPropertyBasis::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyBasis::_value_changed); +} + +void EditorPropertyBasis::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 9; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyBasis::EditorPropertyBasis() { + GridContainer *g = memnew(GridContainer); + g->set_columns(3); + add_child(g); + + static const char *desc[9] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz" }; + for (int i = 0; i < 9; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +///////////////////// TRANSFORM ///////////////////////// + +void EditorPropertyTransform::_value_changed(double val) { + if (setting) + return; + + Transform p; + p.basis[0][0] = spin[0]->get_value(); + p.basis[1][0] = spin[1]->get_value(); + p.basis[2][0] = spin[2]->get_value(); + p.basis[0][1] = spin[3]->get_value(); + p.basis[1][1] = spin[4]->get_value(); + p.basis[2][1] = spin[5]->get_value(); + p.basis[0][2] = spin[6]->get_value(); + p.basis[1][2] = spin[7]->get_value(); + p.basis[2][2] = spin[8]->get_value(); + p.origin[0] = spin[9]->get_value(); + p.origin[1] = spin[10]->get_value(); + p.origin[2] = spin[11]->get_value(); + + emit_signal("property_changed", get_edited_property(), p); +} + +void EditorPropertyTransform::update_property() { + Transform val = get_edited_object()->get(get_edited_property()); + setting = true; + spin[0]->set_value(val.basis[0][0]); + spin[1]->set_value(val.basis[1][0]); + spin[2]->set_value(val.basis[2][0]); + spin[3]->set_value(val.basis[0][1]); + spin[4]->set_value(val.basis[1][1]); + spin[5]->set_value(val.basis[2][1]); + spin[6]->set_value(val.basis[0][2]); + spin[7]->set_value(val.basis[1][2]); + spin[8]->set_value(val.basis[2][2]); + spin[9]->set_value(val.origin[0]); + spin[10]->set_value(val.origin[1]); + spin[11]->set_value(val.origin[2]); + + setting = false; +} + +void EditorPropertyTransform::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_value_changed"), &EditorPropertyTransform::_value_changed); +} + +void EditorPropertyTransform::setup(double p_min, double p_max, double p_step, bool p_no_slider) { + for (int i = 0; i < 12; i++) { + spin[i]->set_min(p_min); + spin[i]->set_max(p_max); + spin[i]->set_step(p_step); + spin[i]->set_hide_slider(p_no_slider); + } +} + +EditorPropertyTransform::EditorPropertyTransform() { + GridContainer *g = memnew(GridContainer); + g->set_columns(3); + add_child(g); + + static const char *desc[12] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz", "ox", "oy", "oz" }; + for (int i = 0; i < 12; i++) { + spin[i] = memnew(EditorSpinSlider); + spin[i]->set_label(desc[i]); + g->add_child(spin[i]); + spin[i]->set_h_size_flags(SIZE_EXPAND_FILL); + add_focusable(spin[i]); + spin[i]->connect("value_changed", this, "_value_changed"); + } + set_label_reference(spin[0]); //show text and buttons around this + set_label_layout(LABEL_LAYOUT_TOP); + setting = false; +} + +////////////// COLOR PICKER ////////////////////// + +void EditorPropertyColor::_color_changed(const Color &p_color) { + + emit_signal("property_changed", get_edited_property(), p_color); +} + +void EditorPropertyColor::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_color_changed"), &EditorPropertyColor::_color_changed); +} + +void EditorPropertyColor::update_property() { + + picker->set_pick_color(get_edited_object()->get(get_edited_property())); +} + +void EditorPropertyColor::setup(bool p_show_alpha) { + picker->set_edit_alpha(p_show_alpha); +} + +EditorPropertyColor::EditorPropertyColor() { + + picker = memnew(ColorPickerButton); + add_child(picker); + picker->set_flat(true); + picker->connect("color_changed", this, "_color_changed"); +} + +////////////// NODE PATH ////////////////////// + +void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { + + emit_signal("property_changed", get_edited_property(), p_path); + update_property(); +} + +void EditorPropertyNodePath::_node_assign() { + if (!scene_tree) { + scene_tree = memnew(SceneTreeDialog); + add_child(scene_tree); + scene_tree->connect("selected", this, "_node_selected"); + } + scene_tree->popup_centered_ratio(); +} + +void EditorPropertyNodePath::_node_clear() { + + emit_signal("property_changed", get_edited_property(), NodePath()); + update_property(); +} + +void EditorPropertyNodePath::update_property() { + + NodePath p = get_edited_object()->get(get_edited_property()); + + assign->set_tooltip(p); + if (p == NodePath()) { + assign->set_icon(Ref<Texture>()); + assign->set_text(TTR("Assign..")); + assign->set_flat(false); + return; + } + assign->set_flat(true); + + Node *base_node = NULL; + if (base_hint != NodePath()) { + if (get_tree()->get_root()->has_node(base_hint)) { + base_node = get_tree()->get_root()->get_node(base_hint); + } + } else { + base_node = Object::cast_to<Node>(get_edited_object()); + } + + if (!base_node || !base_node->has_node(p)) { + assign->set_icon(Ref<Texture>()); + assign->set_text(p); + return; + } + + Node *target_node = base_node->get_node(p); + ERR_FAIL_COND(!target_node); + + assign->set_text(target_node->get_name()); + + Ref<Texture> icon; + if (has_icon(target_node->get_class(), "EditorIcons")) + icon = get_icon(target_node->get_class(), "EditorIcons"); + else + icon = get_icon("Node", "EditorIcons"); + + assign->set_icon(icon); +} + +void EditorPropertyNodePath::setup(const NodePath &p_base_hint) { + + base_hint = p_base_hint; +} + +void EditorPropertyNodePath::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Ref<Texture> t = get_icon("Clear", "EditorIcons"); + clear->set_icon(t); + } +} + +void EditorPropertyNodePath::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_node_selected"), &EditorPropertyNodePath::_node_selected); + ClassDB::bind_method(D_METHOD("_node_assign"), &EditorPropertyNodePath::_node_assign); + ClassDB::bind_method(D_METHOD("_node_clear"), &EditorPropertyNodePath::_node_clear); +} + +EditorPropertyNodePath::EditorPropertyNodePath() { + + HBoxContainer *hbc = memnew(HBoxContainer); + add_child(hbc); + assign = memnew(Button); + assign->set_flat(true); + assign->set_h_size_flags(SIZE_EXPAND_FILL); + assign->set_clip_text(true); + assign->connect("pressed", this, "_node_assign"); + hbc->add_child(assign); + + clear = memnew(Button); + clear->set_flat(true); + clear->connect("pressed", this, "_node_clear"); + hbc->add_child(clear); + + scene_tree = NULL; //do not allocate unnecesarily +} + +////////////// RESOURCE ////////////////////// + +void EditorPropertyResource::_file_selected(const String &p_path) { + + RES res = ResourceLoader::load(p_path); + emit_signal("property_changed", get_edited_property(), res); + update_property(); +} + +void EditorPropertyResource::_menu_option(int p_which) { + + // scene_tree->popup_centered_ratio(); + switch (p_which) { + case OBJ_MENU_LOAD: { + + if (!file) { + file = memnew(EditorFileDialog); + file->connect("file_selected", this, "_file_selected"); + add_child(file); + } + file->set_mode(EditorFileDialog::MODE_OPEN_FILE); + String type = base_type; + + List<String> extensions; + for (int i = 0; i < type.get_slice_count(","); i++) { + + ResourceLoader::get_recognized_extensions_for_type(type.get_slice(",", i), &extensions); + } + + Set<String> valid_extensions; + for (List<String>::Element *E = extensions.front(); E; E = E->next()) { + valid_extensions.insert(E->get()); + } + + file->clear_filters(); + for (Set<String>::Element *E = valid_extensions.front(); E; E = E->next()) { + + file->add_filter("*." + E->get() + " ; " + E->get().to_upper()); + } + + file->popup_centered_ratio(); + } break; + + case OBJ_MENU_EDIT: { + + RES res = get_edited_object()->get(get_edited_property()); + + if (!res.is_null()) { + + emit_signal("resource_selected", get_edited_property(), res); + } + } break; + case OBJ_MENU_CLEAR: { + + emit_signal("property_changed", get_edited_property(), RES()); + update_property(); + + } break; + + case OBJ_MENU_MAKE_UNIQUE: { + + RES res_orig = get_edited_object()->get(get_edited_property()); + if (res_orig.is_null()) + return; + + List<PropertyInfo> property_list; + res_orig->get_property_list(&property_list); + List<Pair<String, Variant> > propvalues; + + for (List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) { + + Pair<String, Variant> p; + PropertyInfo &pi = E->get(); + if (pi.usage & PROPERTY_USAGE_STORAGE) { + + p.first = pi.name; + p.second = res_orig->get(pi.name); + } + + propvalues.push_back(p); + } + + String orig_type = res_orig->get_class(); + + Object *inst = ClassDB::instance(orig_type); + + Ref<Resource> res = Ref<Resource>(Object::cast_to<Resource>(inst)); + + ERR_FAIL_COND(res.is_null()); + + for (List<Pair<String, Variant> >::Element *E = propvalues.front(); E; E = E->next()) { + + Pair<String, Variant> &p = E->get(); + res->set(p.first, p.second); + } + + emit_signal("property_changed", get_edited_property(), res); + update_property(); + + } break; + + case OBJ_MENU_COPY: { + RES res = get_edited_object()->get(get_edited_property()); + + EditorSettings::get_singleton()->set_resource_clipboard(res); + + } break; + case OBJ_MENU_PASTE: { + + RES res = EditorSettings::get_singleton()->get_resource_clipboard(); + emit_signal("property_changed", get_edited_property(), res); + update_property(); + + } break; + case OBJ_MENU_NEW_SCRIPT: { + + if (Object::cast_to<Node>(get_edited_object())) { + EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(get_edited_object())); + } + + } break; + case OBJ_MENU_SHOW_IN_FILE_SYSTEM: { + RES res = get_edited_object()->get(get_edited_property()); + + FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); + file_system_dock->navigate_to_path(res->get_path()); + // Ensure that the FileSystem dock is visible. + TabContainer *tab_container = (TabContainer *)file_system_dock->get_parent_control(); + tab_container->set_current_tab(file_system_dock->get_position_in_parent()); + } break; + default: { + + RES res = get_edited_object()->get(get_edited_property()); + + if (p_which >= CONVERT_BASE_ID) { + + int to_type = p_which - CONVERT_BASE_ID; + + Vector<Ref<EditorResourceConversionPlugin> > conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(res); + + ERR_FAIL_INDEX(to_type, conversions.size()); + + Ref<Resource> new_res = conversions[to_type]->convert(res); + + emit_signal("property_changed", get_edited_property(), new_res); + update_property(); + break; + } + ERR_FAIL_COND(inheritors_array.empty()); + + String intype = inheritors_array[p_which - TYPE_BASE_ID]; + + if (intype == "ViewportTexture") { + + if (!scene_tree) { + scene_tree = memnew(SceneTreeDialog); + add_child(scene_tree); + scene_tree->connect("selected", this, "_viewport_selected"); + scene_tree->set_title(TTR("Pick a Viewport")); + } + scene_tree->popup_centered_ratio(); + + return; + } + + Object *obj = ClassDB::instance(intype); + + if (!obj) { + obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource"); + } + + ERR_BREAK(!obj); + Resource *resp = Object::cast_to<Resource>(obj); + ERR_BREAK(!resp); + if (get_edited_object() && base_type != String() && base_type == "Script") { + //make visual script the right type + res->call("set_instance_base_type", get_edited_object()->get_class()); + } + + res = Ref<Resource>(resp); + emit_signal("property_changed", get_edited_property(), res); + update_property(); + + } break; + } +} + +void EditorPropertyResource::_resource_preview(const String &p_path, const Ref<Texture> &p_preview, ObjectID p_obj) { + + RES p = get_edited_object()->get(get_edited_property()); + if (p.is_valid() && p->get_instance_id() == p_obj) { + if (p_preview.is_valid()) { + assign->set_icon(p_preview); + } + } +} + +void EditorPropertyResource::_update_menu() { + //////////////////// UPDATE MENU ////////////////////////// + RES res = get_edited_object()->get(get_edited_property()); + + menu->clear(); + + if (get_edited_property() == "script" && base_type == "Script" && Object::cast_to<Node>(get_edited_object())) { + menu->add_icon_item(get_icon("Script", "EditorIcons"), TTR("New Script"), OBJ_MENU_NEW_SCRIPT); + menu->add_separator(); + } else if (base_type != "") { + int idx = 0; + + Vector<EditorData::CustomType> custom_resources; + + if (EditorNode::get_editor_data().get_custom_types().has("Resource")) { + custom_resources = EditorNode::get_editor_data().get_custom_types()["Resource"]; + } + + for (int i = 0; i < base_type.get_slice_count(","); i++) { + + String base = base_type.get_slice(",", i); + + Set<String> valid_inheritors; + valid_inheritors.insert(base); + List<StringName> inheritors; + ClassDB::get_inheriters_from_class(base.strip_edges(), &inheritors); + + for (int i = 0; i < custom_resources.size(); i++) { + inheritors.push_back(custom_resources[i].name); + } + + List<StringName>::Element *E = inheritors.front(); + while (E) { + valid_inheritors.insert(E->get()); + E = E->next(); + } + + for (Set<String>::Element *E = valid_inheritors.front(); E; E = E->next()) { + String t = E->get(); + + bool is_custom_resource = false; + Ref<Texture> icon; + if (!custom_resources.empty()) { + for (int i = 0; i < custom_resources.size(); i++) { + if (custom_resources[i].name == t) { + is_custom_resource = true; + if (custom_resources[i].icon.is_valid()) + icon = custom_resources[i].icon; + break; + } + } + } + + if (!is_custom_resource && !ClassDB::can_instance(t)) + continue; + + inheritors_array.push_back(t); + + int id = TYPE_BASE_ID + idx; + + if (!icon.is_valid() && has_icon(t, "EditorIcons")) { + icon = get_icon(t, "EditorIcons"); + } + + if (icon.is_valid()) { + + menu->add_icon_item(icon, vformat(TTR("New %s"), t), id); + } else { + + menu->add_item(vformat(TTR("New %s"), t), id); + } + + idx++; + } + } + + if (menu->get_item_count()) + menu->add_separator(); + } + + menu->add_icon_item(get_icon("Load", "EditorIcons"), TTR("Load"), OBJ_MENU_LOAD); + + if (!res.is_null()) { + + menu->add_icon_item(get_icon("Edit", "EditorIcons"), TTR("Edit"), OBJ_MENU_EDIT); + menu->add_icon_item(get_icon("Clear", "EditorIcons"), TTR("Clear"), OBJ_MENU_CLEAR); + menu->add_icon_item(get_icon("Duplicate", "EditorIcons"), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE); + RES r = res; + if (r.is_valid() && r->get_path().is_resource_file()) { + menu->add_separator(); + menu->add_item(TTR("Show in File System"), OBJ_MENU_SHOW_IN_FILE_SYSTEM); + } + } else { + } + + RES cb = EditorSettings::get_singleton()->get_resource_clipboard(); + bool paste_valid = false; + if (cb.is_valid()) { + if (base_type == "") + paste_valid = true; + else + for (int i = 0; i < base_type.get_slice_count(","); i++) + if (ClassDB::is_parent_class(cb->get_class(), base_type.get_slice(",", i))) { + paste_valid = true; + break; + } + } + + if (!res.is_null() || paste_valid) { + menu->add_separator(); + + if (!res.is_null()) { + + menu->add_item(TTR("Copy"), OBJ_MENU_COPY); + } + + if (paste_valid) { + + menu->add_item(TTR("Paste"), OBJ_MENU_PASTE); + } + } + + if (!res.is_null()) { + + Vector<Ref<EditorResourceConversionPlugin> > conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(res); + if (conversions.size()) { + menu->add_separator(); + } + for (int i = 0; i < conversions.size(); i++) { + String what = conversions[i]->converts_to(); + Ref<Texture> icon; + if (has_icon(what, "EditorIcons")) { + + icon = get_icon(what, "EditorIcons"); + } else { + + icon = get_icon(what, "Resource"); + } + + menu->add_icon_item(icon, vformat(TTR("Convert To %s"), what), CONVERT_BASE_ID + i); + } + } + + Rect2 gt = get_global_rect(); + int ms = menu->get_combined_minimum_size().width; + Vector2 popup_pos = gt.position + gt.size - Vector2(ms, 0); + menu->set_position(popup_pos); + menu->popup(); +} + +void EditorPropertyResource::update_property() { + + RES res = get_edited_object()->get(get_edited_property()); + + if (res == RES()) { + assign->set_icon(Ref<Texture>()); + assign->set_text(TTR("[empty]")); + assign->set_disabled(true); + } else { + assign->set_disabled(false); + + Ref<Texture> icon; + if (has_icon(res->get_class(), "EditorIcons")) + icon = get_icon(res->get_class(), "EditorIcons"); + else + icon = get_icon("Node", "EditorIcons"); + + assign->set_icon(icon); + + if (res->get_name() != String()) { + assign->set_text(res->get_name()); + } else if (res->get_path().is_resource_file()) { + assign->set_text(res->get_name()); + assign->set_tooltip(res->get_path()); + } else { + assign->set_text(res->get_class()); + } + + if (res->get_path().is_resource_file()) { + assign->set_tooltip(res->get_path()); + } + + //preview will override the above, so called at the end + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_resource_preview", res->get_instance_id()); + } +} + +void EditorPropertyResource::_resource_selected() { + RES res = get_edited_object()->get(get_edited_property()); + + if (!res.is_null()) { + + emit_signal("resource_selected", get_edited_property(), res); + } +} + +void EditorPropertyResource::setup(const String &p_base_type) { + base_type = p_base_type; +} + +void EditorPropertyResource::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Ref<Texture> t = get_icon("select_arrow", "Tree"); + edit->set_icon(t); + } +} + +void EditorPropertyResource::_viewport_selected(const NodePath &p_path) { + + Node *to_node = get_node(p_path); + if (!Object::cast_to<Viewport>(to_node)) { + EditorNode::get_singleton()->show_warning(TTR("Selected node is not a Viewport!")); + return; + } + + Ref<ViewportTexture> vt; + vt.instance(); + vt->set_viewport_path_in_scene(get_tree()->get_edited_scene_root()->get_path_to(to_node)); + vt->setup_local_to_scene(); + + emit_signal("property_changed", get_edited_property(), vt); + update_property(); +} +void EditorPropertyResource::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_file_selected"), &EditorPropertyResource::_file_selected); + ClassDB::bind_method(D_METHOD("_menu_option"), &EditorPropertyResource::_menu_option); + ClassDB::bind_method(D_METHOD("_update_menu"), &EditorPropertyResource::_update_menu); + ClassDB::bind_method(D_METHOD("_resource_preview"), &EditorPropertyResource::_resource_preview); + ClassDB::bind_method(D_METHOD("_resource_selected"), &EditorPropertyResource::_resource_selected); + ClassDB::bind_method(D_METHOD("_viewport_selected"), &EditorPropertyResource::_viewport_selected); +} + +EditorPropertyResource::EditorPropertyResource() { + + HBoxContainer *hbc = memnew(HBoxContainer); + add_child(hbc); + assign = memnew(Button); + assign->set_flat(true); + assign->set_h_size_flags(SIZE_EXPAND_FILL); + assign->set_clip_text(true); + assign->connect("pressed", this, "_resource_selected"); + hbc->add_child(assign); + + menu = memnew(PopupMenu); + add_child(menu); + edit = memnew(Button); + edit->set_flat(true); + menu->connect("id_pressed", this, "_menu_option"); + edit->connect("pressed", this, "_update_menu"); + hbc->add_child(edit); + + file = NULL; + scene_tree = NULL; +} + +////////////// DEFAULT PLUGIN ////////////////////// + +bool EditorInspectorDefaultPlugin::can_handle(Object *p_object) { + return true; //can handle everything +} + +void EditorInspectorDefaultPlugin::parse_begin(Object *p_object) { + //do none +} + +bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) { + + switch (p_type) { + + // atomic types + case Variant::BOOL: { + EditorPropertyCheck *editor = memnew(EditorPropertyCheck); + add_property_editor(p_path, editor); + } break; + case Variant::INT: { + + if (p_hint == PROPERTY_HINT_ENUM) { + EditorPropertyEnum *editor = memnew(EditorPropertyEnum); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + + } else if (p_hint == PROPERTY_HINT_FLAGS) { + EditorPropertyFlags *editor = memnew(EditorPropertyFlags); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + + } else if (p_hint == PROPERTY_HINT_LAYERS_2D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_2D_RENDER || p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_3D_RENDER) { + + EditorPropertyLayers::LayerType lt; + switch (p_hint) { + case PROPERTY_HINT_LAYERS_2D_RENDER: + lt = EditorPropertyLayers::LAYER_RENDER_2D; + break; + case PROPERTY_HINT_LAYERS_2D_PHYSICS: + lt = EditorPropertyLayers::LAYER_PHYSICS_2D; + break; + case PROPERTY_HINT_LAYERS_3D_RENDER: + lt = EditorPropertyLayers::LAYER_RENDER_3D; + break; + case PROPERTY_HINT_LAYERS_3D_PHYSICS: + lt = EditorPropertyLayers::LAYER_PHYSICS_3D; + break; + default: {} //compiler could be smarter here and realize this cant happen + } + EditorPropertyLayers *editor = memnew(EditorPropertyLayers); + editor->setup(lt); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_OBJECT_ID) { + + EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); + editor->setup(p_hint_text); + add_property_editor(p_path, editor); + + } else { + EditorPropertyInteger *editor = memnew(EditorPropertyInteger); + int min = 0, max = 65535; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_int(); + max = p_hint_text.get_slice(",", 1).to_int(); + } + + editor->setup(min, max); + + add_property_editor(p_path, editor); + } + } break; + case Variant::REAL: { + + if (p_hint == PROPERTY_HINT_EXP_EASING) { + EditorPropertyEasing *editor = memnew(EditorPropertyEasing); + bool full = true; + bool flip = false; + Vector<String> hints = p_hint_text.split(","); + for (int i = 0; i < hints.size(); i++) { + String h = hints[i].strip_edges(); + if (h == "attenuation") { + flip = true; + } + if (h == "inout") { + full = true; + } + } + + editor->setup(full, flip); + add_property_editor(p_path, editor); + + } else { + EditorPropertyFloat *editor = memnew(EditorPropertyFloat); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + bool exp_range = false; + + if ((p_hint == PROPERTY_HINT_RANGE || p_hint == PROPERTY_HINT_EXP_RANGE) && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + exp_range = p_hint == PROPERTY_HINT_EXP_RANGE; + } + + editor->setup(min, max, step, hide_slider, exp_range); + + add_property_editor(p_path, editor); + } + } break; + case Variant::STRING: { + + if (p_hint == PROPERTY_HINT_ENUM) { + EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum); + Vector<String> options = p_hint_text.split(","); + editor->setup(options); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_MULTILINE_TEXT) { + EditorPropertyMultilineText *editor = memnew(EditorPropertyMultilineText); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_FILE || p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE) { + + Vector<String> extensions = p_hint_text.split(","); + bool global = p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE; + bool folder = p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_GLOBAL_DIR; + EditorPropertyPath *editor = memnew(EditorPropertyPath); + editor->setup(extensions, folder, global); + add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_METHOD_OF_VARIANT_TYPE || + p_hint == PROPERTY_HINT_METHOD_OF_BASE_TYPE || + p_hint == PROPERTY_HINT_METHOD_OF_INSTANCE || + p_hint == PROPERTY_HINT_METHOD_OF_SCRIPT || + p_hint == PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE || + p_hint == PROPERTY_HINT_PROPERTY_OF_BASE_TYPE || + p_hint == PROPERTY_HINT_PROPERTY_OF_INSTANCE || + p_hint == PROPERTY_HINT_PROPERTY_OF_SCRIPT) { + + EditorPropertyMember *editor = memnew(EditorPropertyMember); + + EditorPropertyMember::Type type = EditorPropertyMember::MEMBER_METHOD_OF_BASE_TYPE; + switch (p_hint) { + case PROPERTY_HINT_METHOD_OF_BASE_TYPE: type = EditorPropertyMember::MEMBER_METHOD_OF_BASE_TYPE; break; + case PROPERTY_HINT_METHOD_OF_INSTANCE: type = EditorPropertyMember::MEMBER_METHOD_OF_INSTANCE; break; + case PROPERTY_HINT_METHOD_OF_SCRIPT: type = EditorPropertyMember::MEMBER_METHOD_OF_SCRIPT; break; + case PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE: type = EditorPropertyMember::MEMBER_PROPERTY_OF_VARIANT_TYPE; break; + case PROPERTY_HINT_PROPERTY_OF_BASE_TYPE: type = EditorPropertyMember::MEMBER_PROPERTY_OF_BASE_TYPE; break; + case PROPERTY_HINT_PROPERTY_OF_INSTANCE: type = EditorPropertyMember::MEMBER_PROPERTY_OF_INSTANCE; break; + case PROPERTY_HINT_PROPERTY_OF_SCRIPT: type = EditorPropertyMember::MEMBER_PROPERTY_OF_SCRIPT; break; + default: {} + } + editor->setup(type, p_hint_text); + add_property_editor(p_path, editor); + + } else { + + EditorPropertyText *editor = memnew(EditorPropertyText); + add_property_editor(p_path, editor); + } + } break; + + // math types + + case Variant::VECTOR2: { + EditorPropertyVector2 *editor = memnew(EditorPropertyVector2); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; // 5 + case Variant::RECT2: { + EditorPropertyRect2 *editor = memnew(EditorPropertyRect2); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::VECTOR3: { + EditorPropertyVector3 *editor = memnew(EditorPropertyVector3); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; + case Variant::TRANSFORM2D: { + EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; + case Variant::PLANE: { + EditorPropertyPlane *editor = memnew(EditorPropertyPlane); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::QUAT: { + EditorPropertyQuat *editor = memnew(EditorPropertyQuat); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; // 10 + case Variant::AABB: { + EditorPropertyAABB *editor = memnew(EditorPropertyAABB); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::BASIS: { + EditorPropertyBasis *editor = memnew(EditorPropertyBasis); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + } break; + case Variant::TRANSFORM: { + EditorPropertyTransform *editor = memnew(EditorPropertyTransform); + double min = -65535, max = 65535, step = 0.001; + bool hide_slider = true; + + if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) { + min = p_hint_text.get_slice(",", 0).to_double(); + max = p_hint_text.get_slice(",", 1).to_double(); + if (p_hint_text.get_slice_count(",") >= 3) { + step = p_hint_text.get_slice(",", 2).to_double(); + } + hide_slider = false; + } + + editor->setup(min, max, step, hide_slider); + add_property_editor(p_path, editor); + + } break; + + // misc types + case Variant::COLOR: { + EditorPropertyColor *editor = memnew(EditorPropertyColor); + editor->setup(p_hint != PROPERTY_HINT_COLOR_NO_ALPHA); + add_property_editor(p_path, editor); + } break; + case Variant::NODE_PATH: { + + EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath); + if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) { + editor->setup(p_hint_text); + } + add_property_editor(p_path, editor); + + } break; // 15 + case Variant::_RID: { + } break; + case Variant::OBJECT: { + EditorPropertyResource *editor = memnew(EditorPropertyResource); + editor->setup(p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource"); + add_property_editor(p_path, editor); + + } break; + case Variant::DICTIONARY: { + } break; + case Variant::ARRAY: { + } break; + + // arrays + case Variant::POOL_BYTE_ARRAY: { + } break; // 20 + case Variant::POOL_INT_ARRAY: { + } break; + case Variant::POOL_REAL_ARRAY: { + } break; + case Variant::POOL_STRING_ARRAY: { + } break; + case Variant::POOL_VECTOR2_ARRAY: { + } break; + case Variant::POOL_VECTOR3_ARRAY: { + } break; // 25 + case Variant::POOL_COLOR_ARRAY: { + } break; + default: {} + } + + return false; //can be overriden, although it will most likely be last anyway +} + +void EditorInspectorDefaultPlugin::parse_end() { + //do none +} diff --git a/editor/editor_properties.h b/editor/editor_properties.h new file mode 100644 index 0000000000..0a831bfcfe --- /dev/null +++ b/editor/editor_properties.h @@ -0,0 +1,490 @@ +#ifndef EDITOR_PROPERTIES_H +#define EDITOR_PROPERTIES_H + +#include "editor/create_dialog.h" +#include "editor/editor_file_system.h" +#include "editor/editor_inspector.h" +#include "editor/editor_spin_slider.h" +#include "editor/property_selector.h" +#include "editor/scene_tree_editor.h" +#include "scene/gui/color_picker.h" + +class EditorPropertyText : public EditorProperty { + GDCLASS(EditorPropertyText, EditorProperty) + LineEdit *text; + + bool updating; + void _text_changed(const String &p_string); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + EditorPropertyText(); +}; + +class EditorPropertyMultilineText : public EditorProperty { + GDCLASS(EditorPropertyMultilineText, EditorProperty) + TextEdit *text; + + AcceptDialog *big_text_dialog; + TextEdit *big_text; + Button *open_big_text; + + void _big_text_changed(); + void _text_changed(); + void _open_big_text(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual void update_property(); + EditorPropertyMultilineText(); +}; + +class EditorPropertyTextEnum : public EditorProperty { + GDCLASS(EditorPropertyTextEnum, EditorProperty) + OptionButton *options; + + void _option_selected(int p_which); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + EditorPropertyTextEnum(); +}; + +class EditorPropertyPath : public EditorProperty { + GDCLASS(EditorPropertyPath, EditorProperty) + Vector<String> extensions; + bool folder; + bool global; + EditorFileDialog *dialog; + Button *path; + + void _path_selected(const String &p_path); + void _path_pressed(); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_extensions, bool p_folder, bool p_global); + virtual void update_property(); + EditorPropertyPath(); +}; + +class EditorPropertyMember : public EditorProperty { + GDCLASS(EditorPropertyMember, EditorProperty) +public: + enum Type { + MEMBER_METHOD_OF_VARIANT_TYPE, ///< a method of a type + MEMBER_METHOD_OF_BASE_TYPE, ///< a method of a base type + MEMBER_METHOD_OF_INSTANCE, ///< a method of an instance + MEMBER_METHOD_OF_SCRIPT, ///< a method of a script & base + MEMBER_PROPERTY_OF_VARIANT_TYPE, ///< a property of a type + MEMBER_PROPERTY_OF_BASE_TYPE, ///< a property of a base type + MEMBER_PROPERTY_OF_INSTANCE, ///< a property of an instance + MEMBER_PROPERTY_OF_SCRIPT, ///< a property of a script & base + + }; + +private: + Type hint; + PropertySelector *selector; + Button *property; + String hint_text; + + void _property_selected(const String &p_selected); + void _property_select(); + +protected: + static void _bind_methods(); + +public: + void setup(Type p_hint, const String &p_hint_text); + virtual void update_property(); + EditorPropertyMember(); +}; + +class EditorPropertyCheck : public EditorProperty { + GDCLASS(EditorPropertyCheck, EditorProperty) + CheckBox *checkbox; + + void _checkbox_pressed(); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + EditorPropertyCheck(); +}; + +class EditorPropertyEnum : public EditorProperty { + GDCLASS(EditorPropertyEnum, EditorProperty) + OptionButton *options; + + void _option_selected(int p_which); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + EditorPropertyEnum(); +}; + +class EditorPropertyFlags : public EditorProperty { + GDCLASS(EditorPropertyFlags, EditorProperty) + VBoxContainer *vbox; + Vector<CheckBox *> flags; + Vector<int> flag_indices; + + void _flag_toggled(); + +protected: + static void _bind_methods(); + +public: + void setup(const Vector<String> &p_options); + virtual void update_property(); + EditorPropertyFlags(); +}; + +class EditorPropertyLayersGrid; + +class EditorPropertyLayers : public EditorProperty { + GDCLASS(EditorPropertyLayers, EditorProperty) +public: + enum LayerType { + LAYER_PHYSICS_2D, + LAYER_RENDER_2D, + LAYER_PHYSICS_3D, + LAYER_RENDER_3D, + }; + +private: + EditorPropertyLayersGrid *grid; + void _grid_changed(uint32_t p_grid); + LayerType layer_type; + PopupMenu *layers; + Button *button; + + void _button_pressed(); + void _menu_pressed(int p_menu); + +protected: + static void _bind_methods(); + +public: + void setup(LayerType p_layer_type); + virtual void update_property(); + EditorPropertyLayers(); +}; + +class EditorPropertyInteger : public EditorProperty { + GDCLASS(EditorPropertyInteger, EditorProperty) + EditorSpinSlider *spin; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(int p_min, int p_max); + EditorPropertyInteger(); +}; + +class EditorPropertyObjectID : public EditorProperty { + GDCLASS(EditorPropertyObjectID, EditorProperty) + Button *edit; + String base_type; + void _edit_pressed(); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(const String &p_base_type); + EditorPropertyObjectID(); +}; + +class EditorPropertyFloat : public EditorProperty { + GDCLASS(EditorPropertyFloat, EditorProperty) + EditorSpinSlider *spin; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider, bool p_exp_range); + EditorPropertyFloat(); +}; + +class EditorPropertyEasing : public EditorProperty { + GDCLASS(EditorPropertyEasing, EditorProperty) + Control *easing_draw; + ToolButton *button_out, *button_in, *button_linear, *button_constant; + ToolButton *button_in_out, *button_out_in; + VBoxContainer *vb; + + bool flip; + + void _drag_easing(const Ref<InputEvent> &p_ev); + void _draw_easing(); + void _notification(int p_what); + void _set_preset(float p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(bool p_full, bool p_flip); + EditorPropertyEasing(); +}; + +class EditorPropertyVector2 : public EditorProperty { + GDCLASS(EditorPropertyVector2, EditorProperty) + EditorSpinSlider *spin[2]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyVector2(); +}; + +class EditorPropertyRect2 : public EditorProperty { + GDCLASS(EditorPropertyRect2, EditorProperty) + EditorSpinSlider *spin[4]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyRect2(); +}; + +class EditorPropertyVector3 : public EditorProperty { + GDCLASS(EditorPropertyVector3, EditorProperty) + EditorSpinSlider *spin[3]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyVector3(); +}; + +class EditorPropertyPlane : public EditorProperty { + GDCLASS(EditorPropertyPlane, EditorProperty) + EditorSpinSlider *spin[4]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyPlane(); +}; + +class EditorPropertyQuat : public EditorProperty { + GDCLASS(EditorPropertyQuat, EditorProperty) + EditorSpinSlider *spin[4]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyQuat(); +}; + +class EditorPropertyAABB : public EditorProperty { + GDCLASS(EditorPropertyAABB, EditorProperty) + EditorSpinSlider *spin[6]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyAABB(); +}; + +class EditorPropertyTransform2D : public EditorProperty { + GDCLASS(EditorPropertyTransform2D, EditorProperty) + EditorSpinSlider *spin[6]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyTransform2D(); +}; + +class EditorPropertyBasis : public EditorProperty { + GDCLASS(EditorPropertyBasis, EditorProperty) + EditorSpinSlider *spin[9]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyBasis(); +}; + +class EditorPropertyTransform : public EditorProperty { + GDCLASS(EditorPropertyTransform, EditorProperty) + EditorSpinSlider *spin[12]; + bool setting; + void _value_changed(double p_val); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(double p_min, double p_max, double p_step, bool p_no_slider); + EditorPropertyTransform(); +}; + +class EditorPropertyColor : public EditorProperty { + GDCLASS(EditorPropertyColor, EditorProperty) + ColorPickerButton *picker; + void _color_changed(const Color &p_color); + +protected: + static void _bind_methods(); + +public: + virtual void update_property(); + void setup(bool p_show_alpha); + EditorPropertyColor(); +}; + +class EditorPropertyNodePath : public EditorProperty { + GDCLASS(EditorPropertyNodePath, EditorProperty) + Button *assign; + Button *clear; + SceneTreeDialog *scene_tree; + NodePath base_hint; + + void _node_selected(const NodePath &p_path); + void _node_assign(); + void _node_clear(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + virtual void update_property(); + void setup(const NodePath &p_base_hint); + EditorPropertyNodePath(); +}; + +class EditorPropertyResource : public EditorProperty { + GDCLASS(EditorPropertyResource, EditorProperty) + + enum MenuOption { + + OBJ_MENU_LOAD = 0, + OBJ_MENU_EDIT = 1, + OBJ_MENU_CLEAR = 2, + OBJ_MENU_MAKE_UNIQUE = 3, + OBJ_MENU_COPY = 4, + OBJ_MENU_PASTE = 5, + OBJ_MENU_NEW_SCRIPT = 6, + OBJ_MENU_SHOW_IN_FILE_SYSTEM = 7, + TYPE_BASE_ID = 100, + CONVERT_BASE_ID = 1000 + + }; + + Button *assign; + Button *edit; + PopupMenu *menu; + EditorFileDialog *file; + Vector<String> inheritors_array; + + String base_type; + + SceneTreeDialog *scene_tree; + + void _file_selected(const String &p_path); + void _menu_option(int p_which); + void _resource_preview(const String &p_path, const Ref<Texture> &p_preview, ObjectID p_obj); + void _resource_selected(); + void _viewport_selected(const NodePath &p_path); + + void _update_menu(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + virtual void update_property(); + void setup(const String &p_base_type); + EditorPropertyResource(); +}; + +/////////////////////////////////////////////////// +/// \brief The EditorInspectorDefaultPlugin class +/// +class EditorInspectorDefaultPlugin : public EditorInspectorPlugin { + GDCLASS(EditorInspectorDefaultPlugin, EditorInspectorPlugin) + +public: + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); + virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage); + virtual void parse_end(); +}; + +#endif // EDITOR_PROPERTIES_H diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 7aca92e3ab..68705fa29d 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -371,6 +371,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/line_numbers/line_numbers_zero_padded", false); _initial_set("text_editor/line_numbers/show_breakpoint_gutter", true); _initial_set("text_editor/line_numbers/code_folding", true); + _initial_set("text_editor/line_numbers/word_wrap", false); _initial_set("text_editor/line_numbers/show_line_length_guideline", false); _initial_set("text_editor/line_numbers/line_length_guideline_column", 80); hints["text_editor/line_numbers/line_length_guideline_column"] = PropertyInfo(Variant::INT, "text_editor/line_numbers/line_length_guideline_column", PROPERTY_HINT_RANGE, "20, 160, 1"); diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp new file mode 100644 index 0000000000..9764364273 --- /dev/null +++ b/editor/editor_spin_slider.cpp @@ -0,0 +1,315 @@ +#include "editor_spin_slider.h" +#include "editor_scale.h" +#include "os/input.h" +String EditorSpinSlider::get_text_value() const { + int zeros = Math::step_decimals(get_step()); + return String::num(get_value(), zeros); +} +void EditorSpinSlider::_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + + if (mb->is_pressed()) { + + if (updown_offset != -1 && mb->get_position().x > updown_offset) { + //there is an updown, so use it. + if (mb->get_position().y < get_size().height / 2) { + set_value(get_value() + get_step()); + } else { + set_value(get_value() - get_step()); + } + return; + } else { + + grabbing_spinner_attempt = true; + grabbing_spinner = false; + grabbing_spinner_mouse_pos = Input::get_singleton()->get_mouse_position(); + } + } else { + + if (grabbing_spinner_attempt) { + + if (grabbing_spinner) { + + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->warp_mouse_position(grabbing_spinner_mouse_pos); + update(); + } else { + Rect2 gr = get_global_rect(); + value_input->set_text(get_text_value()); + value_input->set_position(gr.position); + value_input->set_size(gr.size); + value_input->call_deferred("show_modal"); + value_input->call_deferred("grab_focus"); + value_input->call_deferred("select_all"); + } + + grabbing_spinner = false; + grabbing_spinner_attempt = false; + } + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + + if (grabbing_spinner_attempt) { + + if (!grabbing_spinner) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); + grabbing_spinner = true; + } + + double v = get_value(); + + double diff_x = mm->get_relative().x; + diff_x = Math::pow(ABS(diff_x), 1.8) * SGN(diff_x); + diff_x *= 0.1; + + v += diff_x * get_step(); + + set_value(v); + + } else if (updown_offset != -1) { + bool new_hover = (mm->get_position().x > updown_offset); + if (new_hover != hover_updown) { + hover_updown = new_hover; + update(); + } + } + } + + Ref<InputEventKey> k = p_event; + if (k.is_valid() && k->is_pressed() && k->is_action("ui_accept")) { + Rect2 gr = get_global_rect(); + value_input->set_text(get_text_value()); + value_input->set_position(gr.position); + value_input->set_size(gr.size); + value_input->call_deferred("show_modal"); + value_input->call_deferred("grab_focus"); + value_input->call_deferred("select_all"); + } +} + +void EditorSpinSlider::_value_input_closed() { + set_value(value_input->get_text().to_double()); +} + +void EditorSpinSlider::_value_input_entered(const String &p_text) { + set_value(p_text.to_double()); + value_input->hide(); +} + +void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + + if (mb->is_pressed()) { + + grabbing_grabber = true; + grabbing_ratio = get_as_ratio(); + grabbing_from = grabber->get_transform().xform(mb->get_position()).x; + } else { + grabbing_grabber = false; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && grabbing_grabber) { + + float grabbing_ofs = (grabber->get_transform().xform(mm->get_position()).x - grabbing_from) / float(grabber_range); + set_as_ratio(grabbing_ratio + grabbing_ofs); + update(); + } +} + +void EditorSpinSlider::_notification(int p_what) { + + if (p_what == MainLoop::NOTIFICATION_WM_FOCUS_OUT || p_what == MainLoop::NOTIFICATION_WM_FOCUS_OUT) { + if (grabbing_spinner) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + grabbing_spinner = false; + grabbing_spinner_attempt = false; + } + } + + if (p_what == NOTIFICATION_DRAW) { + + updown_offset = -1; + + Ref<StyleBox> sb = get_stylebox("normal", "LineEdit"); + draw_style_box(sb, Rect2(Vector2(), get_size())); + Ref<Font> font = get_font("font", "LineEdit"); + + int avail_width = get_size().width - sb->get_minimum_size().width - sb->get_minimum_size().width; + avail_width -= font->get_string_size(label).width; + Ref<Texture> updown = get_icon("updown", "SpinBox"); + + if (get_step() == 1) { + avail_width -= updown->get_width(); + } + + if (has_focus()) { + Ref<StyleBox> focus = get_stylebox("focus", "LineEdit"); + draw_style_box(focus, Rect2(Vector2(), get_size())); + } + + String numstr = get_text_value(); + + int vofs = (get_size().height - font->get_height()) / 2 + font->get_ascent(); + + Color fc = get_color("font_color", "LineEdit"); + + int label_ofs = sb->get_offset().x + avail_width; + draw_string(font, Vector2(label_ofs, vofs), label, fc * Color(1, 1, 1, 0.5)); + draw_string(font, Vector2(sb->get_offset().x, vofs), numstr, fc, avail_width); + + if (get_step() == 1) { + Ref<Texture> updown = get_icon("updown", "SpinBox"); + int updown_vofs = (get_size().height - updown->get_height()) / 2; + updown_offset = get_size().width - sb->get_margin(MARGIN_RIGHT) - updown->get_width(); + Color c(1, 1, 1); + if (hover_updown) { + c *= Color(1.2, 1.2, 1.2); + } + draw_texture(updown, Vector2(updown_offset, updown_vofs), c); + if (grabber->is_visible()) { + grabber->hide(); + } + } else if (!hide_slider) { + int grabber_w = 4 * EDSCALE; + int width = get_size().width - sb->get_minimum_size().width - grabber_w; + int ofs = sb->get_offset().x; + int svofs = (get_size().height + vofs) / 2 - 1; + Color c = fc; + c.a = 0.2; + + draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c); + int gofs = get_as_ratio() * width; + c.a = 0.9; + Rect2 grabber_rect = Rect2(ofs + gofs, svofs + 1, grabber_w, 2 * EDSCALE); + draw_rect(grabber_rect, c); + + bool display_grabber = (mouse_over_spin || mouse_over_grabber) && !grabbing_spinner; + if (grabber->is_visible() != display_grabber) { + if (display_grabber) { + grabber->show(); + } else { + grabber->hide(); + } + } + + if (display_grabber) { + Ref<Texture> grabber_tex; + if (mouse_over_grabber) { + grabber_tex = get_icon("grabber_highlight", "HSlider"); + } else { + grabber_tex = get_icon("grabber", "HSlider"); + } + + if (grabber->get_texture() != grabber_tex) { + grabber->set_texture(grabber_tex); + } + + grabber->set_size(Size2(0, 0)); + grabber->set_position(get_global_position() + grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5); + grabber_range = width; + } + } + } + + if (p_what == NOTIFICATION_MOUSE_ENTER) { + + mouse_over_spin = true; + update(); + } + if (p_what == NOTIFICATION_MOUSE_EXIT) { + + mouse_over_spin = false; + update(); + } +} + +Size2 EditorSpinSlider::get_minimum_size() const { + + Ref<StyleBox> sb = get_stylebox("normal", "LineEdit"); + Ref<Font> font = get_font("font", "LineEdit"); + + Size2 ms = sb->get_minimum_size(); + ms.height += font->get_height(); + + return ms; +} + +void EditorSpinSlider::set_hide_slider(bool p_hide) { + hide_slider = p_hide; + update(); +} + +bool EditorSpinSlider::is_hiding_slider() const { + return hide_slider; +} + +void EditorSpinSlider::set_label(const String &p_label) { + label = p_label; + update(); +} + +String EditorSpinSlider::get_label() const { + return label; +} + +void EditorSpinSlider::_grabber_mouse_entered() { + mouse_over_grabber = true; + update(); +} + +void EditorSpinSlider::_grabber_mouse_exited() { + mouse_over_grabber = false; + update(); +} + +void EditorSpinSlider::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_label", "label"), &EditorSpinSlider::set_label); + ClassDB::bind_method(D_METHOD("get_label"), &EditorSpinSlider::get_label); + + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorSpinSlider::_gui_input); + ClassDB::bind_method(D_METHOD("_grabber_mouse_entered"), &EditorSpinSlider::_grabber_mouse_entered); + ClassDB::bind_method(D_METHOD("_grabber_mouse_exited"), &EditorSpinSlider::_grabber_mouse_exited); + ClassDB::bind_method(D_METHOD("_grabber_gui_input"), &EditorSpinSlider::_grabber_gui_input); + ClassDB::bind_method(D_METHOD("_value_input_closed"), &EditorSpinSlider::_value_input_closed); + ClassDB::bind_method(D_METHOD("_value_input_entered"), &EditorSpinSlider::_value_input_entered); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); +} + +EditorSpinSlider::EditorSpinSlider() { + + grabbing_spinner_attempt = false; + grabbing_spinner = false; + + set_focus_mode(FOCUS_ALL); + updown_offset = -1; + hover_updown = false; + grabber = memnew(TextureRect); + add_child(grabber); + grabber->hide(); + grabber->set_as_toplevel(true); + grabber->set_mouse_filter(MOUSE_FILTER_STOP); + grabber->connect("mouse_entered", this, "_grabber_mouse_entered"); + grabber->connect("mouse_exited", this, "_grabber_mouse_exited"); + grabber->connect("gui_input", this, "_grabber_gui_input"); + mouse_over_spin = false; + mouse_over_grabber = false; + grabbing_grabber = false; + grabber_range = 1; + value_input = memnew(LineEdit); + add_child(value_input); + value_input->set_as_toplevel(true); + value_input->hide(); + value_input->connect("modal_closed", this, "_value_input_closed"); + value_input->connect("text_entered", this, "_value_input_entered"); + hide_slider = false; +} diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h new file mode 100644 index 0000000000..ac8d9e15d6 --- /dev/null +++ b/editor/editor_spin_slider.h @@ -0,0 +1,57 @@ +#ifndef EDITOR_SPIN_SLIDER_H +#define EDITOR_SPIN_SLIDER_H + +#include "scene/gui/line_edit.h" +#include "scene/gui/range.h" +#include "scene/gui/texture_rect.h" + +class EditorSpinSlider : public Range { + GDCLASS(EditorSpinSlider, Range) + + String label; + int updown_offset; + bool hover_updown; + bool mouse_hover; + + TextureRect *grabber; + int grabber_range; + + bool mouse_over_spin; + bool mouse_over_grabber; + + bool grabbing_grabber; + int grabbing_from; + float grabbing_ratio; + + bool grabbing_spinner_attempt; + bool grabbing_spinner; + Vector2 grabbing_spinner_mouse_pos; + + LineEdit *value_input; + + void _grabber_gui_input(const Ref<InputEvent> &p_event); + void _value_input_closed(); + void _value_input_entered(const String &); + + bool hide_slider; + +protected: + void _notification(int p_what); + void _gui_input(const Ref<InputEvent> &p_event); + static void _bind_methods(); + void _grabber_mouse_entered(); + void _grabber_mouse_exited(); + +public: + String get_text_value() const; + void set_label(const String &p_label); + String get_label() const; + + void set_hide_slider(bool p_hide); + bool is_hiding_slider() const; + + virtual Size2 get_minimum_size() const; + EditorSpinSlider(); +}; + +#endif // EDITOR_SPIN_SLIDER_H diff --git a/editor/icons/icon_GUI_slider_grabber.svg b/editor/icons/icon_GUI_slider_grabber.svg index b1dcf980a5..b8e6f0a654 100644 --- a/editor/icons/icon_GUI_slider_grabber.svg +++ b/editor/icons/icon_GUI_slider_grabber.svg @@ -1,5 +1,82 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 15.999999" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<circle cx="8" cy="1044.4" r="3" fill="#fff" fill-opacity=".78431" stroke-linejoin="round" stroke-opacity=".39216" stroke-width="3"/> -</g> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 15.999999" + id="svg8" + sodipodi:docname="icon_GUI_slider_grabber.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1211" + inkscape:window-height="644" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="-5.7627119" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g6" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <path + transform="translate(0 1036.4)" + d="m8 1a7 7 0 0 0 -7 7 7 7 0 0 0 7 7 7 7 0 0 0 7 -7 7 7 0 0 0 -7 -7zm0 2a5 5 0 0 1 0.5 0.025391 5 5 0 0 1 0.49414 0.074219 5 5 0 0 1 0.48438 0.12305 5 5 0 0 1 0.46875 0.17188 5 5 0 0 1 0.44922 0.2168 5 5 0 0 1 0.42578 0.26172 5 5 0 0 1 0.39844 0.30273 5 5 0 0 1 0.36524 0.33984 5 5 0 0 1 0.33008 0.37695 5 5 0 0 1 0.29102 0.40625 5 5 0 0 1 0.24805 0.43359 5 5 0 0 1 0.20508 0.45508 5 5 0 0 1 0.1582 0.47461 5 5 0 0 1 0.10938 0.48828 5 5 0 0 1 0.060547 0.49609 5 5 0 0 1 0.011719 0.35352 5 5 0 0 1 -0.025391 0.5 5 5 0 0 1 -0.074218 0.49414 5 5 0 0 1 -0.12305 0.48438 5 5 0 0 1 -0.17188 0.46875 5 5 0 0 1 -0.2168 0.44922 5 5 0 0 1 -0.26172 0.42578 5 5 0 0 1 -0.30273 0.39844 5 5 0 0 1 -0.33984 0.36524 5 5 0 0 1 -0.37695 0.33008 5 5 0 0 1 -0.40625 0.29102 5 5 0 0 1 -0.43359 0.24805 5 5 0 0 1 -0.45508 0.20508 5 5 0 0 1 -0.47461 0.1582 5 5 0 0 1 -0.48828 0.10938 5 5 0 0 1 -0.49609 0.060547 5 5 0 0 1 -0.35352 0.011719 5 5 0 0 1 -0.5 -0.025391 5 5 0 0 1 -0.49414 -0.074218 5 5 0 0 1 -0.48438 -0.12305 5 5 0 0 1 -0.46875 -0.17188 5 5 0 0 1 -0.44922 -0.2168 5 5 0 0 1 -0.42578 -0.26172 5 5 0 0 1 -0.39844 -0.30273 5 5 0 0 1 -0.36523 -0.33984 5 5 0 0 1 -0.33008 -0.37695 5 5 0 0 1 -0.29102 -0.40625 5 5 0 0 1 -0.24805 -0.43359 5 5 0 0 1 -0.20508 -0.45508 5 5 0 0 1 -0.1582 -0.47461 5 5 0 0 1 -0.10938 -0.48828 5 5 0 0 1 -0.060547 -0.49609 5 5 0 0 1 -0.011719 -0.35352 5 5 0 0 1 0.025391 -0.5 5 5 0 0 1 0.074219 -0.49414 5 5 0 0 1 0.12305 -0.48438 5 5 0 0 1 0.17188 -0.46875 5 5 0 0 1 0.2168 -0.44922 5 5 0 0 1 0.26172 -0.42578 5 5 0 0 1 0.30273 -0.39844 5 5 0 0 1 0.33984 -0.36523 5 5 0 0 1 0.37695 -0.33008 5 5 0 0 1 0.40625 -0.29102 5 5 0 0 1 0.43359 -0.24805 5 5 0 0 1 0.45508 -0.20508 5 5 0 0 1 0.47461 -0.1582 5 5 0 0 1 0.48828 -0.10938 5 5 0 0 1 0.49609 -0.060547 5 5 0 0 1 0.35352 -0.011719z" + fill="#e0e0e0" + id="path2" + style="fill:#e0e0e0;fill-opacity:0.28925619" /> + <circle + cx="8" + cy="1044.4" + r="3" + fill="#fff" + fill-opacity=".58824" + stroke-linecap="round" + stroke-linejoin="round" + stroke-opacity=".32549" + stroke-width="3" + id="circle4" /> + </g> + <g + transform="translate(-0.06779632,-1036.4)" + id="g18"> + <circle + style="fill:#ffffff;fill-opacity:0.78430996;stroke-width:3;stroke-linejoin:round;stroke-opacity:0.39216003" + cx="8" + cy="1044.4" + r="3" + id="circle16" /> + </g> </svg> diff --git a/editor/icons/icon_GUI_slider_grabber_hl.svg b/editor/icons/icon_GUI_slider_grabber_hl.svg index 73252751ce..a04ac44cf6 100644 --- a/editor/icons/icon_GUI_slider_grabber_hl.svg +++ b/editor/icons/icon_GUI_slider_grabber_hl.svg @@ -1,6 +1,80 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 15.999999" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<path transform="translate(0 1036.4)" d="m8 1a7 7 0 0 0 -7 7 7 7 0 0 0 7 7 7 7 0 0 0 7 -7 7 7 0 0 0 -7 -7zm0 2a5 5 0 0 1 0.5 0.025391 5 5 0 0 1 0.49414 0.074219 5 5 0 0 1 0.48438 0.12305 5 5 0 0 1 0.46875 0.17188 5 5 0 0 1 0.44922 0.2168 5 5 0 0 1 0.42578 0.26172 5 5 0 0 1 0.39844 0.30273 5 5 0 0 1 0.36524 0.33984 5 5 0 0 1 0.33008 0.37695 5 5 0 0 1 0.29102 0.40625 5 5 0 0 1 0.24805 0.43359 5 5 0 0 1 0.20508 0.45508 5 5 0 0 1 0.1582 0.47461 5 5 0 0 1 0.10938 0.48828 5 5 0 0 1 0.060547 0.49609 5 5 0 0 1 0.011719 0.35352 5 5 0 0 1 -0.025391 0.5 5 5 0 0 1 -0.074218 0.49414 5 5 0 0 1 -0.12305 0.48438 5 5 0 0 1 -0.17188 0.46875 5 5 0 0 1 -0.2168 0.44922 5 5 0 0 1 -0.26172 0.42578 5 5 0 0 1 -0.30273 0.39844 5 5 0 0 1 -0.33984 0.36524 5 5 0 0 1 -0.37695 0.33008 5 5 0 0 1 -0.40625 0.29102 5 5 0 0 1 -0.43359 0.24805 5 5 0 0 1 -0.45508 0.20508 5 5 0 0 1 -0.47461 0.1582 5 5 0 0 1 -0.48828 0.10938 5 5 0 0 1 -0.49609 0.060547 5 5 0 0 1 -0.35352 0.011719 5 5 0 0 1 -0.5 -0.025391 5 5 0 0 1 -0.49414 -0.074218 5 5 0 0 1 -0.48438 -0.12305 5 5 0 0 1 -0.46875 -0.17188 5 5 0 0 1 -0.44922 -0.2168 5 5 0 0 1 -0.42578 -0.26172 5 5 0 0 1 -0.39844 -0.30273 5 5 0 0 1 -0.36523 -0.33984 5 5 0 0 1 -0.33008 -0.37695 5 5 0 0 1 -0.29102 -0.40625 5 5 0 0 1 -0.24805 -0.43359 5 5 0 0 1 -0.20508 -0.45508 5 5 0 0 1 -0.1582 -0.47461 5 5 0 0 1 -0.10938 -0.48828 5 5 0 0 1 -0.060547 -0.49609 5 5 0 0 1 -0.011719 -0.35352 5 5 0 0 1 0.025391 -0.5 5 5 0 0 1 0.074219 -0.49414 5 5 0 0 1 0.12305 -0.48438 5 5 0 0 1 0.17188 -0.46875 5 5 0 0 1 0.2168 -0.44922 5 5 0 0 1 0.26172 -0.42578 5 5 0 0 1 0.30273 -0.39844 5 5 0 0 1 0.33984 -0.36523 5 5 0 0 1 0.37695 -0.33008 5 5 0 0 1 0.40625 -0.29102 5 5 0 0 1 0.43359 -0.24805 5 5 0 0 1 0.45508 -0.20508 5 5 0 0 1 0.47461 -0.1582 5 5 0 0 1 0.48828 -0.10938 5 5 0 0 1 0.49609 -0.060547 5 5 0 0 1 0.35352 -0.011719z" fill="#e0e0e0"/> -<circle cx="8" cy="1044.4" r="3" fill="#fff" fill-opacity=".58824" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".32549" stroke-width="3"/> -</g> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 15.999999" + id="svg8" + sodipodi:docname="icon_GUI_slider_grabber_hl.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="944" + inkscape:window-height="480" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <path + transform="translate(0 1036.4)" + d="m8 1a7 7 0 0 0 -7 7 7 7 0 0 0 7 7 7 7 0 0 0 7 -7 7 7 0 0 0 -7 -7zm0 2a5 5 0 0 1 0.5 0.025391 5 5 0 0 1 0.49414 0.074219 5 5 0 0 1 0.48438 0.12305 5 5 0 0 1 0.46875 0.17188 5 5 0 0 1 0.44922 0.2168 5 5 0 0 1 0.42578 0.26172 5 5 0 0 1 0.39844 0.30273 5 5 0 0 1 0.36524 0.33984 5 5 0 0 1 0.33008 0.37695 5 5 0 0 1 0.29102 0.40625 5 5 0 0 1 0.24805 0.43359 5 5 0 0 1 0.20508 0.45508 5 5 0 0 1 0.1582 0.47461 5 5 0 0 1 0.10938 0.48828 5 5 0 0 1 0.060547 0.49609 5 5 0 0 1 0.011719 0.35352 5 5 0 0 1 -0.025391 0.5 5 5 0 0 1 -0.074218 0.49414 5 5 0 0 1 -0.12305 0.48438 5 5 0 0 1 -0.17188 0.46875 5 5 0 0 1 -0.2168 0.44922 5 5 0 0 1 -0.26172 0.42578 5 5 0 0 1 -0.30273 0.39844 5 5 0 0 1 -0.33984 0.36524 5 5 0 0 1 -0.37695 0.33008 5 5 0 0 1 -0.40625 0.29102 5 5 0 0 1 -0.43359 0.24805 5 5 0 0 1 -0.45508 0.20508 5 5 0 0 1 -0.47461 0.1582 5 5 0 0 1 -0.48828 0.10938 5 5 0 0 1 -0.49609 0.060547 5 5 0 0 1 -0.35352 0.011719 5 5 0 0 1 -0.5 -0.025391 5 5 0 0 1 -0.49414 -0.074218 5 5 0 0 1 -0.48438 -0.12305 5 5 0 0 1 -0.46875 -0.17188 5 5 0 0 1 -0.44922 -0.2168 5 5 0 0 1 -0.42578 -0.26172 5 5 0 0 1 -0.39844 -0.30273 5 5 0 0 1 -0.36523 -0.33984 5 5 0 0 1 -0.33008 -0.37695 5 5 0 0 1 -0.29102 -0.40625 5 5 0 0 1 -0.24805 -0.43359 5 5 0 0 1 -0.20508 -0.45508 5 5 0 0 1 -0.1582 -0.47461 5 5 0 0 1 -0.10938 -0.48828 5 5 0 0 1 -0.060547 -0.49609 5 5 0 0 1 -0.011719 -0.35352 5 5 0 0 1 0.025391 -0.5 5 5 0 0 1 0.074219 -0.49414 5 5 0 0 1 0.12305 -0.48438 5 5 0 0 1 0.17188 -0.46875 5 5 0 0 1 0.2168 -0.44922 5 5 0 0 1 0.26172 -0.42578 5 5 0 0 1 0.30273 -0.39844 5 5 0 0 1 0.33984 -0.36523 5 5 0 0 1 0.37695 -0.33008 5 5 0 0 1 0.40625 -0.29102 5 5 0 0 1 0.43359 -0.24805 5 5 0 0 1 0.45508 -0.20508 5 5 0 0 1 0.47461 -0.1582 5 5 0 0 1 0.48828 -0.10938 5 5 0 0 1 0.49609 -0.060547 5 5 0 0 1 0.35352 -0.011719z" + fill="#e0e0e0" + id="path2" /> + <circle + cx="8" + cy="1044.4" + r="3" + fill="#fff" + fill-opacity=".58824" + stroke-linecap="round" + stroke-linejoin="round" + stroke-opacity=".32549" + stroke-width="3" + id="circle4" /> + </g> + <g + transform="translate(-0.06779632,-1036.4)" + id="g18"> + <circle + style="fill:#ffffff;fill-opacity:0.78430996;stroke-width:3;stroke-linejoin:round;stroke-opacity:0.39216003" + cx="8" + cy="1044.4" + r="3" + id="circle16" /> + </g> </svg> diff --git a/editor/icons/icon_play_overlay.svg b/editor/icons/icon_play_overlay.svg new file mode 100644 index 0000000000..eff33f1b6b --- /dev/null +++ b/editor/icons/icon_play_overlay.svg @@ -0,0 +1,4 @@ +<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"> + <rect x="0" y="0" width="64" height="64" rx="5" ry="5" fill="#044B94" fill-opacity="0.6"/> + <path d="M16 16 L48 32 L16 48" fill="#f2f2f2"/> +</svg>
\ No newline at end of file diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp index 73e78ddf2a..173be01586 100644 --- a/editor/multi_node_edit.cpp +++ b/editor/multi_node_edit.cpp @@ -80,8 +80,8 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, ur->add_undo_property(n, name, n->get(name)); } - ur->add_do_method(EditorNode::get_singleton()->get_property_editor(), "refresh"); - ur->add_undo_method(EditorNode::get_singleton()->get_property_editor(), "refresh"); + ur->add_do_method(EditorNode::get_singleton()->get_inspector(), "refresh"); + ur->add_undo_method(EditorNode::get_singleton()->get_inspector(), "refresh"); ur->commit_action(); return true; diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index b387972558..23c5e36a92 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -85,7 +85,7 @@ void AnimationPlayerEditor::_notification(int p_what) { } frame->set_value(player->get_current_animation_position()); key_editor->set_anim_pos(player->get_current_animation_position()); - EditorNode::get_singleton()->get_property_editor()->refresh(); + EditorNode::get_singleton()->get_inspector()->refresh(); } else if (last_active) { //need the last frame after it stopped @@ -1073,7 +1073,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag) updating = false; _seek_value_changed(p_pos, !p_drag); - EditorNode::get_singleton()->get_property_editor()->refresh(); + EditorNode::get_singleton()->get_inspector()->refresh(); //seekit } diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index eef72a8b3e..0ff316a286 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -171,7 +171,23 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const for (int i = 0; i < preview_images.size(); i++) { if (preview_images[i].id == p_index) { - preview_images[i].button->set_icon(p_image); + if (preview_images[i].is_video) { + Ref<Image> overlay = get_icon("PlayOverlay", "EditorIcons")->get_data(); + Ref<Image> thumbnail = p_image->get_data(); + Point2 overlay_pos = Point2((thumbnail->get_width() - overlay->get_width()) / 2, (thumbnail->get_height() - overlay->get_height()) / 2); + + thumbnail->lock(); + thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos); + thumbnail->unlock(); + + Ref<ImageTexture> tex; + tex.instance(); + tex->create_from_image(thumbnail); + + preview_images[i].button->set_icon(tex); + } else { + preview_images[i].button->set_icon(p_image); + } break; } } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 93aeca6632..f27796db5e 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -520,8 +520,17 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel node = node->get_parent(); } + // Check if the canvas item is already in the list (for groups or scenes) + bool duplicate = false; + for (int j = 0; j < i; j++) { + if (r_items[j].item == canvas_item) { + duplicate = true; + break; + } + } + //Remove the item if invalid - if (!canvas_item || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner())) || (canvas_item->has_meta("_edit_lock_") && canvas_item->get_meta("_edit_lock_"))) { + if (!canvas_item || duplicate || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner())) || (canvas_item->has_meta("_edit_lock_") && canvas_item->get_meta("_edit_lock_"))) { r_items.remove(i); i--; } else { diff --git a/editor/plugins/cube_grid_theme_editor_plugin.cpp b/editor/plugins/cube_grid_theme_editor_plugin.cpp index 81f45b9f55..68d5ea5247 100644 --- a/editor/plugins/cube_grid_theme_editor_plugin.cpp +++ b/editor/plugins/cube_grid_theme_editor_plugin.cpp @@ -198,7 +198,7 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { } break; case MENU_OPTION_REMOVE_ITEM: { - String p = editor->get_property_editor()->get_selected_path(); + String p = editor->get_inspector()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); diff --git a/editor/plugins/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp index 8b44f672b0..f75fb0d109 100644 --- a/editor/plugins/item_list_editor_plugin.cpp +++ b/editor/plugins/item_list_editor_plugin.cpp @@ -388,7 +388,7 @@ ItemListEditor::ItemListEditor() { vbc->add_child(property_editor); property_editor->set_v_size_flags(SIZE_EXPAND_FILL); - tree = property_editor->get_scene_tree(); + tree = property_editor->get_property_tree(); } ItemListEditor::~ItemListEditor() { diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index a4265b4e32..82fd727620 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -785,7 +785,7 @@ void ProjectSettingsEditor::popup_project_settings() { void ProjectSettingsEditor::_item_selected() { - TreeItem *ti = globals_editor->get_property_editor()->get_scene_tree()->get_selected(); + TreeItem *ti = globals_editor->get_property_editor()->get_property_tree()->get_selected(); if (!ti) return; if (!ti->get_parent()) @@ -1727,7 +1727,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { //globals_editor->hide_top_label(); globals_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); globals_editor->register_search_box(search_box); - globals_editor->get_property_editor()->get_scene_tree()->connect("cell_selected", this, "_item_selected"); + globals_editor->get_property_editor()->get_property_tree()->connect("cell_selected", this, "_item_selected"); globals_editor->get_property_editor()->connect("property_toggled", this, "_item_checked", varray(), CONNECT_DEFERRED); globals_editor->get_property_editor()->connect("property_edited", this, "_settings_prop_edited"); diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index e0063925b1..e912ebe03a 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -4212,7 +4212,7 @@ void PropertyEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("property_edited", PropertyInfo(Variant::STRING, "property"))); } -Tree *PropertyEditor::get_scene_tree() { +Tree *PropertyEditor::get_property_tree() { return tree; } @@ -4695,7 +4695,7 @@ SectionedPropertyEditor::SectionedPropertyEditor() { editor->set_v_size_flags(SIZE_EXPAND_FILL); right_vb->add_child(editor, true); - editor->get_scene_tree()->set_column_titles_visible(false); + editor->get_property_tree()->set_column_titles_visible(false); editor->hide_top_label(); diff --git a/editor/property_editor.h b/editor/property_editor.h index 017a190adb..56743822d2 100644 --- a/editor/property_editor.h +++ b/editor/property_editor.h @@ -275,7 +275,7 @@ public: String get_selected_path() const; - Tree *get_scene_tree(); + Tree *get_property_tree(); Label *get_top_label(); void hide_top_label(); void update_tree(); @@ -309,6 +309,7 @@ public: void collapse_all_folding(); void expand_all_folding(); + PropertyEditor(); ~PropertyEditor(); }; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 8b99a3d503..f5cee4cd65 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -732,7 +732,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { if (node) { node->set_scene_inherited_state(Ref<SceneState>()); scene_tree->update_tree(); - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); } } } break; diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 64d278c0c5..dd79ae63d6 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -70,8 +70,18 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } else if (p_id == BUTTON_VISIBILITY) { undo_redo->create_action(TTR("Toggle Visible")); - undo_redo->add_do_method(this, "toggle_visible", n); - undo_redo->add_undo_method(this, "toggle_visible", n); + _toggle_visible(n); + List<Node *> selection = editor_selection->get_selected_node_list(); + if (selection.size() > 1) { + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + Node *nv = E->get(); + ERR_FAIL_COND(!nv); + if (nv == n) { + continue; + } + _toggle_visible(nv); + } + } undo_redo->commit_action(); } else if (p_id == BUTTON_LOCK) { @@ -118,33 +128,13 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } } void SceneTreeEditor::_toggle_visible(Node *p_node) { - if (p_node->is_class("Spatial")) { - bool v = bool(p_node->call("is_visible")); - p_node->call("set_visible", !v); - } else if (p_node->is_class("CanvasItem")) { + if (p_node->has_method("is_visible") && p_node->has_method("set_visible")) { bool v = bool(p_node->call("is_visible")); - if (v) { - p_node->call("hide"); - } else { - p_node->call("show"); - } + undo_redo->add_do_method(p_node, "set_visible", !v); + undo_redo->add_undo_method(p_node, "set_visible", v); } } -void SceneTreeEditor::toggle_visible(Node *p_node) { - _toggle_visible(p_node); - List<Node *> selection = editor_selection->get_selected_node_list(); - if (selection.size() > 1) { - for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { - Node *nv = E->get(); - ERR_FAIL_COND(!nv); - if (nv == p_node) { - continue; - } - _toggle_visible(nv); - } - } -} bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { if (!p_node) @@ -968,8 +958,6 @@ void SceneTreeEditor::_bind_methods() { ClassDB::bind_method("_cell_collapsed", &SceneTreeEditor::_cell_collapsed); ClassDB::bind_method("_rmb_select", &SceneTreeEditor::_rmb_select); ClassDB::bind_method("_warning_changed", &SceneTreeEditor::_warning_changed); - ClassDB::bind_method("_toggle_visible", &SceneTreeEditor::_toggle_visible); - ClassDB::bind_method("toggle_visible", &SceneTreeEditor::toggle_visible); ClassDB::bind_method("_node_script_changed", &SceneTreeEditor::_node_script_changed); ClassDB::bind_method("_node_visibility_changed", &SceneTreeEditor::_node_visibility_changed); diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index b63eb2a1f0..896fd6c431 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -70,8 +70,6 @@ class SceneTreeEditor : public Control { void _compute_hash(Node *p_node, uint64_t &hash); - void toggle_visible(Node *p_node); - bool _add_nodes(Node *p_node, TreeItem *p_parent); void _test_update_tree(); void _update_tree(); diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index a83de1627d..9ce4305683 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -1851,7 +1851,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { ppeer = Ref<PacketPeerStream>(memnew(PacketPeerStream)); ppeer->set_input_buffer_max_size(1024 * 1024 * 8); //8mb should be enough editor = p_editor; - editor->get_property_editor()->connect("object_id_selected", this, "_scene_tree_property_select_object"); + editor->get_inspector()->connect("object_id_selected", this, "_scene_tree_property_select_object"); tabs = memnew(TabContainer); tabs->set_tab_align(TabContainer::ALIGN_LEFT); @@ -1936,7 +1936,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { inspector = memnew(PropertyEditor); inspector->set_h_size_flags(SIZE_EXPAND_FILL); inspector->hide_top_label(); - inspector->get_scene_tree()->set_column_title(0, TTR("Variable")); + inspector->get_property_tree()->set_column_title(0, TTR("Variable")); inspector->set_enable_capitalize_paths(false); inspector->set_read_only(true); inspector->connect("object_id_selected", this, "_scene_tree_property_select_object"); diff --git a/main/main.cpp b/main/main.cpp index 5836c9c739..c287bc81cb 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -82,6 +82,8 @@ #include "version.h" #include "version_hash.gen.h" +#include "main/timer_sync.h" + static ProjectSettings *globals = NULL; static Engine *engine = NULL; static InputMap *input_map = NULL; @@ -1221,227 +1223,8 @@ Error Main::setup2(Thread::ID p_main_tid_override) { } // everything the main loop needs to know about frame timings -struct _FrameTime { - float animation_step; // time to advance animations for (argument to process()) - int physics_steps; // number of times to iterate the physics engine - - void clamp_animation(float min_animation_step, float max_animation_step) { - if (animation_step < min_animation_step) { - animation_step = min_animation_step; - } else if (animation_step > max_animation_step) { - animation_step = max_animation_step; - } - } -}; - -class _TimerSync { - // wall clock time measured on the main thread - uint64_t last_cpu_ticks_usec; - uint64_t current_cpu_ticks_usec; - - // logical game time since last physics timestep - float time_accum; - - // current difference between wall clock time and reported sum of animation_steps - float time_deficit; - - // number of frames back for keeping accumulated physics steps roughly constant. - // value of 12 chosen because that is what is required to make 144 Hz monitors - // behave well with 60 Hz physics updates. The only worse commonly available refresh - // would be 85, requiring CONTROL_STEPS = 17. - static const int CONTROL_STEPS = 12; - - // sum of physics steps done over the last (i+1) frames - int accumulated_physics_steps[CONTROL_STEPS]; - - // typical value for accumulated_physics_steps[i] is either this or this plus one - int typical_physics_steps[CONTROL_STEPS]; - -protected: - // returns the fraction of p_frame_slice required for the timer to overshoot - // before advance_core considers changing the physics_steps return from - // the typical values as defined by typical_physics_steps - float get_physics_jitter_fix() { - return Engine::get_singleton()->get_physics_jitter_fix(); - } - - // gets our best bet for the average number of physics steps per render frame - // return value: number of frames back this data is consistent - int get_average_physics_steps(float &p_min, float &p_max) { - p_min = typical_physics_steps[0]; - p_max = p_min + 1; - - for (int i = 1; i < CONTROL_STEPS; ++i) { - const float typical_lower = typical_physics_steps[i]; - const float current_min = typical_lower / (i + 1); - if (current_min > p_max) - return i; // bail out of further restrictions would void the interval - else if (current_min > p_min) - p_min = current_min; - const float current_max = (typical_lower + 1) / (i + 1); - if (current_max < p_min) - return i; - else if (current_max < p_max) - p_max = current_max; - } - - return CONTROL_STEPS; - } - - // advance physics clock by p_animation_step, return appropriate number of steps to simulate - _FrameTime advance_core(float p_frame_slice, int p_iterations_per_second, float p_animation_step) { - _FrameTime ret; - - ret.animation_step = p_animation_step; - - // simple determination of number of physics iteration - time_accum += ret.animation_step; - ret.physics_steps = floor(time_accum * p_iterations_per_second); - - int min_typical_steps = typical_physics_steps[0]; - int max_typical_steps = min_typical_steps + 1; - - // given the past recorded steps and typcial steps to match, calculate bounds for this - // step to be typical - bool update_typical = false; - - for (int i = 0; i < CONTROL_STEPS - 1; ++i) { - int steps_left_to_match_typical = typical_physics_steps[i + 1] - accumulated_physics_steps[i]; - if (steps_left_to_match_typical > max_typical_steps || - steps_left_to_match_typical + 1 < min_typical_steps) { - update_typical = true; - break; - } - - if (steps_left_to_match_typical > min_typical_steps) - min_typical_steps = steps_left_to_match_typical; - if (steps_left_to_match_typical + 1 < max_typical_steps) - max_typical_steps = steps_left_to_match_typical + 1; - } - - // try to keep it consistent with previous iterations - if (ret.physics_steps < min_typical_steps) { - const int max_possible_steps = floor((time_accum)*p_iterations_per_second + get_physics_jitter_fix()); - if (max_possible_steps < min_typical_steps) { - ret.physics_steps = max_possible_steps; - update_typical = true; - } else { - ret.physics_steps = min_typical_steps; - } - } else if (ret.physics_steps > max_typical_steps) { - const int min_possible_steps = floor((time_accum)*p_iterations_per_second - get_physics_jitter_fix()); - if (min_possible_steps > max_typical_steps) { - ret.physics_steps = min_possible_steps; - update_typical = true; - } else { - ret.physics_steps = max_typical_steps; - } - } - - time_accum -= ret.physics_steps * p_frame_slice; - - // keep track of accumulated step counts - for (int i = CONTROL_STEPS - 2; i >= 0; --i) { - accumulated_physics_steps[i + 1] = accumulated_physics_steps[i] + ret.physics_steps; - } - accumulated_physics_steps[0] = ret.physics_steps; - - if (update_typical) { - for (int i = CONTROL_STEPS - 1; i >= 0; --i) { - if (typical_physics_steps[i] > accumulated_physics_steps[i]) { - typical_physics_steps[i] = accumulated_physics_steps[i]; - } else if (typical_physics_steps[i] < accumulated_physics_steps[i] - 1) { - typical_physics_steps[i] = accumulated_physics_steps[i] - 1; - } - } - } - - return ret; - } - - // calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero - _FrameTime advance_checked(float p_frame_slice, int p_iterations_per_second, float p_animation_step) { - if (fixed_fps != -1) - p_animation_step = 1.0 / fixed_fps; - - // compensate for last deficit - p_animation_step += time_deficit; - - _FrameTime ret = advance_core(p_frame_slice, p_iterations_per_second, p_animation_step); - - // we will do some clamping on ret.animation_step and need to sync those changes to time_accum, - // that's easiest if we just remember their fixed difference now - const double animation_minus_accum = ret.animation_step - time_accum; - - // first, least important clamping: keep ret.animation_step consistent with typical_physics_steps. - // this smoothes out the animation steps and culls small but quick variations. - { - float min_average_physics_steps, max_average_physics_steps; - int consistent_steps = get_average_physics_steps(min_average_physics_steps, max_average_physics_steps); - if (consistent_steps > 3) { - ret.clamp_animation(min_average_physics_steps * p_frame_slice, max_average_physics_steps * p_frame_slice); - } - } - - // second clamping: keep abs(time_deficit) < jitter_fix * frame_slise - float max_clock_deviation = get_physics_jitter_fix() * p_frame_slice; - ret.clamp_animation(p_animation_step - max_clock_deviation, p_animation_step + max_clock_deviation); - - // last clamping: make sure time_accum is between 0 and p_frame_slice for consistency between physics and animation - ret.clamp_animation(animation_minus_accum, animation_minus_accum + p_frame_slice); - - // restore time_accum - time_accum = ret.animation_step - animation_minus_accum; - - // track deficit - time_deficit = p_animation_step - ret.animation_step; - - return ret; - } - - // determine wall clock step since last iteration - float get_cpu_animation_step() { - uint64_t cpu_ticks_elapsed = current_cpu_ticks_usec - last_cpu_ticks_usec; - last_cpu_ticks_usec = current_cpu_ticks_usec; - - return cpu_ticks_elapsed / 1000000.0; - } - -public: - explicit _TimerSync() : - last_cpu_ticks_usec(0), - current_cpu_ticks_usec(0), - time_accum(0), - time_deficit(0) { - for (int i = CONTROL_STEPS - 1; i >= 0; --i) { - typical_physics_steps[i] = i; - accumulated_physics_steps[i] = i; - } - } - - // start the clock - void init(uint64_t p_cpu_ticks_usec) { - current_cpu_ticks_usec = last_cpu_ticks_usec = p_cpu_ticks_usec; - } - - // set measured wall clock time - void set_cpu_ticks_usec(uint64_t p_cpu_ticks_usec) { - current_cpu_ticks_usec = p_cpu_ticks_usec; - } - - // advance one frame, return timesteps to take - _FrameTime advance(float p_frame_slice, int p_iterations_per_second) { - float cpu_animation_step = get_cpu_animation_step(); - - return advance_checked(p_frame_slice, p_iterations_per_second, cpu_animation_step); - } - - void before_start_render() { - VisualServer::get_singleton()->sync(); - } -}; -static _TimerSync _timer_sync; +static MainTimerSync main_timer_sync; bool Main::start() { @@ -1457,7 +1240,7 @@ bool Main::start() { String _export_preset; bool export_debug = false; - _timer_sync.init(OS::get_singleton()->get_ticks_usec()); + main_timer_sync.init(OS::get_singleton()->get_ticks_usec()); List<String> args = OS::get_singleton()->get_cmdline_args(); for (int i = 0; i < args.size(); i++) { @@ -1661,6 +1444,114 @@ bool Main::start() { } #endif + if (!project_manager) { // game or editor + if (game_path != "" || script != "") { + //autoload + List<PropertyInfo> props; + ProjectSettings::get_singleton()->get_property_list(&props); + + //first pass, add the constants so they exist before any script is loaded + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + String s = E->get().name; + if (!s.begins_with("autoload/")) + continue; + String name = s.get_slicec('/', 1); + String path = ProjectSettings::get_singleton()->get(s); + bool global_var = false; + if (path.begins_with("*")) { + global_var = true; + } + + if (global_var) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { +#ifdef TOOLS_ENABLED + if (editor) { + ScriptServer::get_language(i)->add_named_global_constant(name, Variant()); + } else { + ScriptServer::get_language(i)->add_global_constant(name, Variant()); + } +#else + ScriptServer::get_language(i)->add_global_constant(name, Variant()); +#endif + } + } + } + + //second pass, load into global constants + List<Node *> to_add; +#ifdef TOOLS_ENABLED + ResourceLoader::set_timestamp_on_load(editor); // Avoid problems when editing +#endif + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + + String s = E->get().name; + if (!s.begins_with("autoload/")) + continue; + String name = s.get_slicec('/', 1); + String path = ProjectSettings::get_singleton()->get(s); + bool global_var = false; + if (path.begins_with("*")) { + global_var = true; + path = path.substr(1, path.length() - 1); + } + + RES res = ResourceLoader::load(path); + ERR_EXPLAIN("Can't autoload: " + path); + ERR_CONTINUE(res.is_null()); + Node *n = NULL; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instance(); + } else if (res->is_class("Script")) { + Ref<Script> s = res; + StringName ibt = s->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_EXPLAIN("Script does not inherit a Node: " + path); + ERR_CONTINUE(!valid_type); + + Object *obj = ClassDB::instance(ibt); + + ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt)); + ERR_CONTINUE(obj == NULL); + + n = Object::cast_to<Node>(obj); + n->set_script(s.get_ref_ptr()); + } + + ERR_EXPLAIN("Path in autoload not a node or script: " + path); + ERR_CONTINUE(!n); + n->set_name(name); + + //defer so references are all valid on _ready() + to_add.push_back(n); + + if (global_var) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { +#ifdef TOOLS_ENABLED + if (editor) { + ScriptServer::get_language(i)->add_named_global_constant(name, n); + } else { + ScriptServer::get_language(i)->add_global_constant(name, n); + } +#else + ScriptServer::get_language(i)->add_global_constant(name, n); +#endif + } + } + } + +#ifdef TOOLS_ENABLED + ResourceLoader::set_timestamp_on_load(false); +#endif + + for (List<Node *>::Element *E = to_add.front(); E; E = E->next()) { + + sml->get_root()->add_child(E->get()); + } + } + } + #ifdef TOOLS_ENABLED EditorNode *editor_node = NULL; @@ -1680,9 +1571,6 @@ bool Main::start() { } #endif - { - } - if (!editor && !project_manager) { //standard helpers that can be changed from main config @@ -1795,89 +1683,6 @@ bool Main::start() { } if (!project_manager && !editor) { // game - if (game_path != "" || script != "") { - //autoload - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - - //first pass, add the constants so they exist before any script is loaded - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - String name = s.get_slicec('/', 1); - String path = ProjectSettings::get_singleton()->get(s); - bool global_var = false; - if (path.begins_with("*")) { - global_var = true; - } - - if (global_var) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(name, Variant()); - } - } - } - - //second pass, load into global constants - List<Node *> to_add; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - - String s = E->get().name; - if (!s.begins_with("autoload/")) - continue; - String name = s.get_slicec('/', 1); - String path = ProjectSettings::get_singleton()->get(s); - bool global_var = false; - if (path.begins_with("*")) { - global_var = true; - path = path.substr(1, path.length() - 1); - } - - RES res = ResourceLoader::load(path); - ERR_EXPLAIN("Can't autoload: " + path); - ERR_CONTINUE(res.is_null()); - Node *n = NULL; - if (res->is_class("PackedScene")) { - Ref<PackedScene> ps = res; - n = ps->instance(); - } else if (res->is_class("Script")) { - Ref<Script> s = res; - StringName ibt = s->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_EXPLAIN("Script does not inherit a Node: " + path); - ERR_CONTINUE(!valid_type); - - Object *obj = ClassDB::instance(ibt); - - ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt)); - ERR_CONTINUE(obj == NULL); - - n = Object::cast_to<Node>(obj); - n->set_script(s.get_ref_ptr()); - } - - ERR_EXPLAIN("Path in autoload not a node or script: " + path); - ERR_CONTINUE(!n); - n->set_name(name); - - //defer so references are all valid on _ready() - to_add.push_back(n); - - if (global_var) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(name, n); - } - } - } - - for (List<Node *>::Element *E = to_add.front(); E; E = E->next()) { - - sml->get_root()->add_child(E->get()); - } - } - if (game_path != "") { Node *scene = NULL; Ref<PackedScene> scenedata = ResourceLoader::load(local_game_path); @@ -1936,15 +1741,16 @@ bool Main::iteration() { uint64_t ticks = OS::get_singleton()->get_ticks_usec(); Engine::get_singleton()->_frame_ticks = ticks; - _timer_sync.set_cpu_ticks_usec(ticks); + main_timer_sync.set_cpu_ticks_usec(ticks); + main_timer_sync.set_fixed_fps(fixed_fps); uint64_t ticks_elapsed = ticks - last_ticks; int physics_fps = Engine::get_singleton()->get_iterations_per_second(); float frame_slice = 1.0 / physics_fps; - _FrameTime advance = _timer_sync.advance(frame_slice, physics_fps); - double step = advance.animation_step; + MainFrameTime advance = main_timer_sync.advance(frame_slice, physics_fps); + double step = advance.idle_step; Engine::get_singleton()->_frame_step = step; @@ -2008,7 +1814,7 @@ bool Main::iteration() { OS::get_singleton()->get_main_loop()->idle(step * time_scale); message_queue->flush(); - _timer_sync.before_start_render(); //sync if still drawing from previous frames. + VisualServer::get_singleton()->sync(); //sync if still drawing from previous frames. if (OS::get_singleton()->can_draw() && !disable_render_loop) { diff --git a/main/timer_sync.cpp b/main/timer_sync.cpp new file mode 100644 index 0000000000..c33cbafee8 --- /dev/null +++ b/main/timer_sync.cpp @@ -0,0 +1,193 @@ +#include "timer_sync.h" + +void MainFrameTime::clamp_idle(float min_idle_step, float max_idle_step) { + if (idle_step < min_idle_step) { + idle_step = min_idle_step; + } else if (idle_step > max_idle_step) { + idle_step = max_idle_step; + } +} + +///////////////////////////////// + +// returns the fraction of p_frame_slice required for the timer to overshoot +// before advance_core considers changing the physics_steps return from +// the typical values as defined by typical_physics_steps +float MainTimerSync::get_physics_jitter_fix() { + return Engine::get_singleton()->get_physics_jitter_fix(); +} + +// gets our best bet for the average number of physics steps per render frame +// return value: number of frames back this data is consistent +int MainTimerSync::get_average_physics_steps(float &p_min, float &p_max) { + p_min = typical_physics_steps[0]; + p_max = p_min + 1; + + for (int i = 1; i < CONTROL_STEPS; ++i) { + const float typical_lower = typical_physics_steps[i]; + const float current_min = typical_lower / (i + 1); + if (current_min > p_max) + return i; // bail out of further restrictions would void the interval + else if (current_min > p_min) + p_min = current_min; + const float current_max = (typical_lower + 1) / (i + 1); + if (current_max < p_min) + return i; + else if (current_max < p_max) + p_max = current_max; + } + + return CONTROL_STEPS; +} + +// advance physics clock by p_idle_step, return appropriate number of steps to simulate +MainFrameTime MainTimerSync::advance_core(float p_frame_slice, int p_iterations_per_second, float p_idle_step) { + MainFrameTime ret; + + ret.idle_step = p_idle_step; + + // simple determination of number of physics iteration + time_accum += ret.idle_step; + ret.physics_steps = floor(time_accum * p_iterations_per_second); + + int min_typical_steps = typical_physics_steps[0]; + int max_typical_steps = min_typical_steps + 1; + + // given the past recorded steps and typcial steps to match, calculate bounds for this + // step to be typical + bool update_typical = false; + + for (int i = 0; i < CONTROL_STEPS - 1; ++i) { + int steps_left_to_match_typical = typical_physics_steps[i + 1] - accumulated_physics_steps[i]; + if (steps_left_to_match_typical > max_typical_steps || + steps_left_to_match_typical + 1 < min_typical_steps) { + update_typical = true; + break; + } + + if (steps_left_to_match_typical > min_typical_steps) + min_typical_steps = steps_left_to_match_typical; + if (steps_left_to_match_typical + 1 < max_typical_steps) + max_typical_steps = steps_left_to_match_typical + 1; + } + + // try to keep it consistent with previous iterations + if (ret.physics_steps < min_typical_steps) { + const int max_possible_steps = floor((time_accum)*p_iterations_per_second + get_physics_jitter_fix()); + if (max_possible_steps < min_typical_steps) { + ret.physics_steps = max_possible_steps; + update_typical = true; + } else { + ret.physics_steps = min_typical_steps; + } + } else if (ret.physics_steps > max_typical_steps) { + const int min_possible_steps = floor((time_accum)*p_iterations_per_second - get_physics_jitter_fix()); + if (min_possible_steps > max_typical_steps) { + ret.physics_steps = min_possible_steps; + update_typical = true; + } else { + ret.physics_steps = max_typical_steps; + } + } + + time_accum -= ret.physics_steps * p_frame_slice; + + // keep track of accumulated step counts + for (int i = CONTROL_STEPS - 2; i >= 0; --i) { + accumulated_physics_steps[i + 1] = accumulated_physics_steps[i] + ret.physics_steps; + } + accumulated_physics_steps[0] = ret.physics_steps; + + if (update_typical) { + for (int i = CONTROL_STEPS - 1; i >= 0; --i) { + if (typical_physics_steps[i] > accumulated_physics_steps[i]) { + typical_physics_steps[i] = accumulated_physics_steps[i]; + } else if (typical_physics_steps[i] < accumulated_physics_steps[i] - 1) { + typical_physics_steps[i] = accumulated_physics_steps[i] - 1; + } + } + } + + return ret; +} + +// calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero +MainFrameTime MainTimerSync::advance_checked(float p_frame_slice, int p_iterations_per_second, float p_idle_step) { + if (fixed_fps != -1) + p_idle_step = 1.0 / fixed_fps; + + // compensate for last deficit + p_idle_step += time_deficit; + + MainFrameTime ret = advance_core(p_frame_slice, p_iterations_per_second, p_idle_step); + + // we will do some clamping on ret.idle_step and need to sync those changes to time_accum, + // that's easiest if we just remember their fixed difference now + const double idle_minus_accum = ret.idle_step - time_accum; + + // first, least important clamping: keep ret.idle_step consistent with typical_physics_steps. + // this smoothes out the idle steps and culls small but quick variations. + { + float min_average_physics_steps, max_average_physics_steps; + int consistent_steps = get_average_physics_steps(min_average_physics_steps, max_average_physics_steps); + if (consistent_steps > 3) { + ret.clamp_idle(min_average_physics_steps * p_frame_slice, max_average_physics_steps * p_frame_slice); + } + } + + // second clamping: keep abs(time_deficit) < jitter_fix * frame_slise + float max_clock_deviation = get_physics_jitter_fix() * p_frame_slice; + ret.clamp_idle(p_idle_step - max_clock_deviation, p_idle_step + max_clock_deviation); + + // last clamping: make sure time_accum is between 0 and p_frame_slice for consistency between physics and idle + ret.clamp_idle(idle_minus_accum, idle_minus_accum + p_frame_slice); + + // restore time_accum + time_accum = ret.idle_step - idle_minus_accum; + + // track deficit + time_deficit = p_idle_step - ret.idle_step; + + return ret; +} + +// determine wall clock step since last iteration +float MainTimerSync::get_cpu_idle_step() { + uint64_t cpu_ticks_elapsed = current_cpu_ticks_usec - last_cpu_ticks_usec; + last_cpu_ticks_usec = current_cpu_ticks_usec; + + return cpu_ticks_elapsed / 1000000.0; +} + +MainTimerSync::MainTimerSync() : + last_cpu_ticks_usec(0), + current_cpu_ticks_usec(0), + time_accum(0), + time_deficit(0), + fixed_fps(0) { + for (int i = CONTROL_STEPS - 1; i >= 0; --i) { + typical_physics_steps[i] = i; + accumulated_physics_steps[i] = i; + } +} + +// start the clock +void MainTimerSync::init(uint64_t p_cpu_ticks_usec) { + current_cpu_ticks_usec = last_cpu_ticks_usec = p_cpu_ticks_usec; +} + +// set measured wall clock time +void MainTimerSync::set_cpu_ticks_usec(uint64_t p_cpu_ticks_usec) { + current_cpu_ticks_usec = p_cpu_ticks_usec; +} + +void MainTimerSync::set_fixed_fps(int p_fixed_fps) { + fixed_fps = p_fixed_fps; +} + +// advance one frame, return timesteps to take +MainFrameTime MainTimerSync::advance(float p_frame_slice, int p_iterations_per_second) { + float cpu_idle_step = get_cpu_idle_step(); + + return advance_checked(p_frame_slice, p_iterations_per_second, cpu_idle_step); +} diff --git a/main/timer_sync.h b/main/timer_sync.h new file mode 100644 index 0000000000..6ef4254270 --- /dev/null +++ b/main/timer_sync.h @@ -0,0 +1,71 @@ +#ifndef TIMER_SYNC_H +#define TIMER_SYNC_H + +#include "core/engine.h" + +struct MainFrameTime { + float idle_step; // time to advance idles for (argument to process()) + int physics_steps; // number of times to iterate the physics engine + + void clamp_idle(float min_idle_step, float max_idle_step); +}; + +class MainTimerSync { + // wall clock time measured on the main thread + uint64_t last_cpu_ticks_usec; + uint64_t current_cpu_ticks_usec; + + // logical game time since last physics timestep + float time_accum; + + // current difference between wall clock time and reported sum of idle_steps + float time_deficit; + + // number of frames back for keeping accumulated physics steps roughly constant. + // value of 12 chosen because that is what is required to make 144 Hz monitors + // behave well with 60 Hz physics updates. The only worse commonly available refresh + // would be 85, requiring CONTROL_STEPS = 17. + static const int CONTROL_STEPS = 12; + + // sum of physics steps done over the last (i+1) frames + int accumulated_physics_steps[CONTROL_STEPS]; + + // typical value for accumulated_physics_steps[i] is either this or this plus one + int typical_physics_steps[CONTROL_STEPS]; + + int fixed_fps; + +protected: + // returns the fraction of p_frame_slice required for the timer to overshoot + // before advance_core considers changing the physics_steps return from + // the typical values as defined by typical_physics_steps + float get_physics_jitter_fix(); + + // gets our best bet for the average number of physics steps per render frame + // return value: number of frames back this data is consistent + int get_average_physics_steps(float &p_min, float &p_max); + + // advance physics clock by p_idle_step, return appropriate number of steps to simulate + MainFrameTime advance_core(float p_frame_slice, int p_iterations_per_second, float p_idle_step); + + // calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero + MainFrameTime advance_checked(float p_frame_slice, int p_iterations_per_second, float p_idle_step); + + // determine wall clock step since last iteration + float get_cpu_idle_step(); + +public: + MainTimerSync(); + + // start the clock + void init(uint64_t p_cpu_ticks_usec); + // set measured wall clock time + void set_cpu_ticks_usec(uint64_t p_cpu_ticks_usec); + //set fixed fps + void set_fixed_fps(int p_fixed_fps); + + // advance one frame, return timesteps to take + MainFrameTime advance(float p_frame_slice, int p_iterations_per_second); +}; + +#endif // TIMER_SYNC_H diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 4e3ee4d22c..14bdce50ec 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1333,6 +1333,15 @@ void GDScriptLanguage::add_global_constant(const StringName &p_variable, const V _add_global(p_variable, p_value); } +void GDScriptLanguage::add_named_global_constant(const StringName &p_name, const Variant &p_value) { + named_globals[p_name] = p_value; +} + +void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { + ERR_FAIL_COND(!named_globals.has(p_name)); + named_globals.erase(p_name); +} + void GDScriptLanguage::init() { //populate global constants diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 9566e3b32e..6885fbb7fe 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -262,6 +262,7 @@ class GDScriptLanguage : public ScriptLanguage { Variant *_global_array; Vector<Variant> global_array; Map<StringName, int> globals; + Map<StringName, Variant> named_globals; struct CallLevel { @@ -369,7 +370,8 @@ public: _FORCE_INLINE_ int get_global_array_size() const { return global_array.size(); } _FORCE_INLINE_ Variant *get_global_array() { return _global_array; } - _FORCE_INLINE_ const Map<StringName, int> &get_global_map() { return globals; } + _FORCE_INLINE_ const Map<StringName, int> &get_global_map() const { return globals; } + _FORCE_INLINE_ const Map<StringName, Variant> &get_named_globals_map() const { return named_globals; } _FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; } @@ -403,6 +405,8 @@ public: virtual String _get_indentation() const; virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value); + virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value); + virtual void remove_named_global_constant(const StringName &p_name); /* DEBUGGER FUNCTIONS */ diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 048948dada..9947512444 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -278,6 +278,18 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } +#ifdef TOOLS_ENABLED + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { + + int idx = codegen.named_globals.find(identifier); + if (idx == -1) { + idx = codegen.named_globals.size(); + codegen.named_globals.push_back(identifier); + } + return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + } +#endif + //not found, error _set_error("Identifier not found: " + String(identifier), p_expression); @@ -1511,6 +1523,18 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser gdfunc->_global_names_count = 0; } +#ifdef TOOLS_ENABLED + // Named globals + if (codegen.named_globals.size()) { + gdfunc->named_globals.resize(codegen.named_globals.size()); + gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); + for (int i = 0; i < codegen.named_globals.size(); i++) { + gdfunc->named_globals[i] = codegen.named_globals[i]; + } + gdfunc->_named_globals_count = gdfunc->named_globals.size(); + } +#endif + if (codegen.opcodes.size()) { gdfunc->code = codegen.opcodes; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 62aafdbe01..237b0de9e7 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -94,6 +94,9 @@ class GDScriptCompiler { HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; Map<StringName, int> name_map; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif int get_name_map_pos(const StringName &p_identifier) { int ret; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 1c5b8187ca..dac7da3a28 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -108,6 +108,21 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta #endif return &GDScriptLanguage::get_singleton()->get_global_array()[address]; } break; +#ifdef TOOLS_ENABLED + case ADDR_TYPE_NAMED_GLOBAL: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _named_globals_count, NULL); +#endif + StringName id = _named_globals_ptr[address]; + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { + return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; + } else { + r_error = "Autoload singleton '" + String(id) + "' has been removed."; + return NULL; + } + } break; +#endif case ADDR_TYPE_NIL: { return &nil; } break; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index dff4bdfaf2..ea009dcd96 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -92,7 +92,8 @@ public: ADDR_TYPE_STACK = 5, ADDR_TYPE_STACK_VARIABLE = 6, ADDR_TYPE_GLOBAL = 7, - ADDR_TYPE_NIL = 8 + ADDR_TYPE_NAMED_GLOBAL = 8, + ADDR_TYPE_NIL = 9 }; enum RPCMode { @@ -121,6 +122,10 @@ private: int _constant_count; const StringName *_global_names_ptr; int _global_names_count; +#ifdef TOOLS_ENABLED + const StringName *_named_globals_ptr; + int _named_globals_count; +#endif const int *_default_arg_ptr; int _default_arg_count; const int *_code_ptr; @@ -137,6 +142,9 @@ private: StringName name; Vector<Variant> constants; Vector<StringName> global_names; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif Vector<int> default_arguments; Vector<int> code; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index bbe245951e..161e62c81f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -782,7 +782,7 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { } if (Engine::get_singleton()->is_editor_hint()) { - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); NodeDock::singleton->update_lists(); } } diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index eb10c5e99f..dfaa873b13 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -268,7 +268,7 @@ protected: if (String(p_name) == "export") { script->set_variable_export(var, p_value); - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); return true; } diff --git a/modules/webp/SCsub b/modules/webp/SCsub index ea7af1bf9e..21ae0ce7c2 100644 --- a/modules/webp/SCsub +++ b/modules/webp/SCsub @@ -78,10 +78,12 @@ if env['builtin_libwebp']: "dsp/upsampling_msa.c", "dsp/upsampling_neon.c", "dsp/upsampling_sse2.c", + "dsp/upsampling_sse41.c", "dsp/yuv.c", "dsp/yuv_mips32.c", "dsp/yuv_mips_dsp_r2.c", "dsp/yuv_sse2.c", + "dsp/yuv_sse41.c", "enc/alpha_enc.c", "enc/analysis_enc.c", "enc/backward_references_cost_enc.c", diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp index a8be4fbc35..3b1be780d4 100644 --- a/platform/server/os_server.cpp +++ b/platform/server/os_server.cpp @@ -30,6 +30,7 @@ #include "os_server.h" #include "drivers/dummy/audio_driver_dummy.h" #include "drivers/dummy/rasterizer_dummy.h" +#include "drivers/dummy/texture_loader_dummy.h" #include "print_string.h" #include "servers/visual/visual_server_raster.h" #include <stdio.h> @@ -83,6 +84,9 @@ Error OS_Server::initialize(const VideoMode &p_desired, int p_video_driver, int _ensure_user_data_dir(); + resource_loader_dummy = memnew(ResourceFormatDummyTexture); + ResourceLoader::add_resource_format_loader(resource_loader_dummy); + return OK; } @@ -99,6 +103,8 @@ void OS_Server::finalize() { memdelete(power_manager); + memdelete(resource_loader_dummy); + args.clear(); } diff --git a/platform/server/os_server.h b/platform/server/os_server.h index 2cc6f0c47e..f1a880ecc2 100644 --- a/platform/server/os_server.h +++ b/platform/server/os_server.h @@ -32,6 +32,7 @@ #include "../x11/crash_handler_x11.h" #include "../x11/power_x11.h" +#include "drivers/dummy/texture_loader_dummy.h" #include "drivers/rtaudio/audio_driver_rtaudio.h" #include "drivers/unix/os_unix.h" #include "main/input_default.h" @@ -65,6 +66,8 @@ class OS_Server : public OS_Unix { CrashHandler crash_handler; + ResourceFormatDummyTexture *resource_loader_dummy; + protected: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; diff --git a/scene/2d/animated_sprite.cpp b/scene/2d/animated_sprite.cpp index 60a7961293..54194ff543 100644 --- a/scene/2d/animated_sprite.cpp +++ b/scene/2d/animated_sprite.cpp @@ -361,7 +361,7 @@ void AnimatedSprite::_notification(int p_what) { if (timeout <= 0) { - timeout = 1.0 / speed; + timeout = _get_frame_duration(); int fc = frames->get_frame_count(animation); if (frame >= fc - 1) { @@ -483,7 +483,13 @@ int AnimatedSprite::get_frame() const { void AnimatedSprite::set_speed_scale(float p_speed_scale) { + float elapsed = _get_frame_duration() - timeout; + speed_scale = MAX(p_speed_scale, 0.0f); + + // We adapt the timeout so that the animation speed adapts as soon as the speed scale is changed + _reset_timeout(); + timeout -= elapsed; } float AnimatedSprite::get_speed_scale() const { @@ -574,21 +580,22 @@ bool AnimatedSprite::is_playing() const { return playing; } -void AnimatedSprite::_reset_timeout() { - - if (!playing) - return; - +float AnimatedSprite::_get_frame_duration() { if (frames.is_valid() && frames->has_animation(animation)) { float speed = frames->get_animation_speed(animation) * speed_scale; if (speed > 0) { - timeout = 1.0 / speed; - } else { - timeout = 0; + return 1.0 / speed; } - } else { - timeout = 0; } + return 0.0; +} + +void AnimatedSprite::_reset_timeout() { + + if (!playing) + return; + + timeout = _get_frame_duration(); } void AnimatedSprite::set_animation(const StringName &p_animation) { diff --git a/scene/2d/animated_sprite.h b/scene/2d/animated_sprite.h index 7b91a1faef..be5b1ef6d6 100644 --- a/scene/2d/animated_sprite.h +++ b/scene/2d/animated_sprite.h @@ -141,6 +141,7 @@ class AnimatedSprite : public Node2D { void _res_changed(); + float _get_frame_duration(); void _reset_timeout(); void _set_playing(bool p_playing); bool _is_playing() const; diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index f998f23d3b..3878559b60 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -462,7 +462,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_RANGE, "1,65536,1"), "set_max_distance", "get_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation", PROPERTY_HINT_EXP_EASING), "set_attenuation", "get_attenuation"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_attenuation", "get_attenuation"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask"); diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 584c2f2c85..2ac6c76032 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -120,7 +120,6 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, float p_sc if (mirroring.x) { double den = mirroring.x * p_scale; - double before = new_ofs.x; new_ofs.x -= den * ceil(new_ofs.x / den); } diff --git a/scene/3d/light.cpp b/scene/3d/light.cpp index 240bd631a1..4d854ff578 100644 --- a/scene/3d/light.cpp +++ b/scene/3d/light.cpp @@ -375,7 +375,7 @@ void DirectionalLight::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_normal_bias", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_param", "get_param", PARAM_SHADOW_NORMAL_BIAS); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_bias_split_scale", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_BIAS_SPLIT_SCALE); ADD_PROPERTY(PropertyInfo(Variant::INT, "directional_shadow_depth_range", PROPERTY_HINT_ENUM, "Stable,Optimized"), "set_shadow_depth_range", "get_shadow_depth_range"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_max_distance", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_max_distance", PROPERTY_HINT_EXP_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_SHADOW_MAX_DISTANCE); BIND_ENUM_CONSTANT(SHADOW_ORTHOGONAL); BIND_ENUM_CONSTANT(SHADOW_PARALLEL_2_SPLITS); @@ -428,8 +428,8 @@ void OmniLight::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shadow_detail"), &OmniLight::get_shadow_detail); ADD_GROUP("Omni", "omni_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_range", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_RANGE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_range", PROPERTY_HINT_EXP_RANGE, "0,4096,0.1"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "omni_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_mode", PROPERTY_HINT_ENUM, "Dual Paraboloid,Cube"), "set_shadow_mode", "get_shadow_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "omni_shadow_detail", PROPERTY_HINT_ENUM, "Vertical,Horizontal"), "set_shadow_detail", "get_shadow_detail"); @@ -450,8 +450,8 @@ OmniLight::OmniLight() : void SpotLight::_bind_methods() { ADD_GROUP("Spot", "spot_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_range", PROPERTY_HINT_RANGE, "0,65536,0.1"), "set_param", "get_param", PARAM_RANGE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_range", PROPERTY_HINT_EXP_RANGE, "0,4096,0.1"), "set_param", "get_param", PARAM_RANGE); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_ATTENUATION); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.1"), "set_param", "get_param", PARAM_SPOT_ANGLE); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING), "set_param", "get_param", PARAM_SPOT_ATTENUATION); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION); } diff --git a/scene/3d/vehicle_body.cpp b/scene/3d/vehicle_body.cpp index b72665aa2b..a239e7e871 100644 --- a/scene/3d/vehicle_body.cpp +++ b/scene/3d/vehicle_body.cpp @@ -672,13 +672,8 @@ void VehicleBody::_update_friction(PhysicsDirectBodyState *s) { m_forwardImpulse.resize(numWheel); m_sideImpulse.resize(numWheel); - int numWheelsOnGround = 0; - //collapse all those loops into one! for (int i = 0; i < wheels.size(); i++) { - VehicleWheel &wheelInfo = *wheels[i]; - if (wheelInfo.m_raycastInfo.m_isInContact) - numWheelsOnGround++; m_sideImpulse[i] = real_t(0.); m_forwardImpulse[i] = real_t(0.); } diff --git a/scene/3d/voxel_light_baker.cpp b/scene/3d/voxel_light_baker.cpp index 13700e0bd3..670df5cc7f 100644 --- a/scene/3d/voxel_light_baker.cpp +++ b/scene/3d/voxel_light_baker.cpp @@ -2316,13 +2316,10 @@ Ref<MultiMesh> VoxelLightBaker::create_debug_multimesh(DebugMode p_mode) { PoolVector<Vector3> vertices; PoolVector<Color> colors; - - int vtx_idx = 0; #define ADD_VTX(m_idx) \ ; \ vertices.push_back(face_points[m_idx]); \ - colors.push_back(Color(1, 1, 1, 1)); \ - vtx_idx++; + colors.push_back(Color(1, 1, 1, 1)); for (int i = 0; i < 6; i++) { diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index c0d1e62e07..a0e0137863 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -507,7 +507,6 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend) { float delta = p_delta * speed_scale * cd.speed_scale; - bool backwards = delta < 0; float next_pos = cd.pos + delta; float len = cd.from->animation->get_length(); @@ -525,6 +524,8 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f if (&cd == &playback.current) { + bool backwards = delta < 0; + if (!backwards && cd.pos <= len && next_pos == len /*&& playback.blend.empty()*/) { //playback finished end_reached = true; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 6f34f3e49f..1e0db6e8cd 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -656,8 +656,9 @@ ColorPicker::ColorPicker() : void ColorPickerButton::_color_changed(const Color &p_color) { + color = p_color; update(); - emit_signal("color_changed", p_color); + emit_signal("color_changed", color); } void ColorPickerButton::_modal_closed() { @@ -667,6 +668,7 @@ void ColorPickerButton::_modal_closed() { void ColorPickerButton::pressed() { + _update_picker(); popup->set_position(get_global_position() - picker->get_combined_minimum_size()); popup->popup(); picker->set_focus_on_line_edit(); @@ -679,7 +681,7 @@ void ColorPickerButton::_notification(int p_what) { Ref<StyleBox> normal = get_stylebox("normal"); Rect2 r = Rect2(normal->get_offset(), get_size() - normal->get_minimum_size()); draw_texture_rect(Control::get_icon("bg", "ColorPickerButton"), r, true); - draw_rect(r, picker->get_pick_color()); + draw_rect(r, color); } if (p_what == MainLoop::NOTIFICATION_WM_QUIT_REQUEST) { @@ -689,27 +691,34 @@ void ColorPickerButton::_notification(int p_what) { void ColorPickerButton::set_pick_color(const Color &p_color) { - picker->set_pick_color(p_color); + color = p_color; + if (picker) { + picker->set_pick_color(p_color); + } + update(); - emit_signal("color_changed", p_color); } Color ColorPickerButton::get_pick_color() const { - return picker->get_pick_color(); + return color; } void ColorPickerButton::set_edit_alpha(bool p_show) { - picker->set_edit_alpha(p_show); + edit_alpha = p_show; + if (picker) { + picker->set_edit_alpha(p_show); + } } bool ColorPickerButton::is_editing_alpha() const { - return picker->is_editing_alpha(); + return edit_alpha; } -ColorPicker *ColorPickerButton::get_picker() const { +ColorPicker *ColorPickerButton::get_picker() { + _update_picker(); return picker; } @@ -718,6 +727,19 @@ PopupPanel *ColorPickerButton::get_popup() const { return popup; } +void ColorPickerButton::_update_picker() { + if (!picker) { + popup = memnew(PopupPanel); + picker = memnew(ColorPicker); + popup->add_child(picker); + add_child(popup); + picker->connect("color_changed", this, "_color_changed"); + popup->connect("modal_closed", this, "_modal_closed"); + picker->set_pick_color(color); + picker->set_edit_alpha(edit_alpha); + } +} + void ColorPickerButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPickerButton::set_pick_color); @@ -737,12 +759,7 @@ void ColorPickerButton::_bind_methods() { ColorPickerButton::ColorPickerButton() { - popup = memnew(PopupPanel); - picker = memnew(ColorPicker); - popup->add_child(picker); - - picker->connect("color_changed", this, "_color_changed"); - popup->connect("modal_closed", this, "_modal_closed"); - - add_child(popup); + picker = NULL; + popup = NULL; + edit_alpha = true; } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 7d1a554ada..6b63e5fe60 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -118,12 +118,16 @@ class ColorPickerButton : public Button { PopupPanel *popup; ColorPicker *picker; + Color color; + bool edit_alpha; void _color_changed(const Color &p_color); void _modal_closed(); virtual void pressed(); + void _update_picker(); + protected: void _notification(int); static void _bind_methods(); @@ -135,7 +139,7 @@ public: void set_edit_alpha(bool p_show); bool is_editing_alpha() const; - ColorPicker *get_picker() const; + ColorPicker *get_picker(); PopupPanel *get_popup() const; ColorPickerButton(); diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 7cb0ad5707..177582c87c 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -34,9 +34,9 @@ void Container::_child_minsize_changed() { - Size2 ms = get_combined_minimum_size(); - if (ms.width > get_size().width || ms.height > get_size().height) - minimum_size_changed(); + //Size2 ms = get_combined_minimum_size(); + //if (ms.width > get_size().width || ms.height > get_size().height) { + minimum_size_changed(); queue_sort(); } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index b7c1d35fd7..3097ecaf16 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -155,12 +155,21 @@ Size2 Control::get_custom_minimum_size() const { return data.custom_minimum_size; } -Size2 Control::get_combined_minimum_size() const { +void Control::_update_minimum_size_cache() { Size2 minsize = get_minimum_size(); minsize.x = MAX(minsize.x, data.custom_minimum_size.x); minsize.y = MAX(minsize.y, data.custom_minimum_size.y); - return minsize; + data.minimum_size_cache = minsize; + data.minimum_size_valid = true; +} + +Size2 Control::get_combined_minimum_size() const { + + if (!data.minimum_size_valid) { + const_cast<Control *>(this)->_update_minimum_size_cache(); + } + return data.minimum_size_cache; } Size2 Control::_edit_get_minimum_size() const { @@ -259,14 +268,17 @@ void Control::_update_minimum_size() { if (!is_inside_tree()) return; - data.pending_min_size_update = false; Size2 minsize = get_combined_minimum_size(); if (minsize.x > data.size_cache.x || minsize.y > data.size_cache.y) { _size_changed(); } - emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + data.updating_last_minimum_size = false; + + if (minsize != data.last_minimum_size) { + emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + } } bool Control::_get(const StringName &p_name, Variant &r_ret) const { @@ -437,8 +449,12 @@ void Control::_notification(int p_notification) { case NOTIFICATION_ENTER_TREE: { - _size_changed(); - + } break; + case NOTIFICATION_POST_ENTER_TREE: { + if (is_visible_in_tree()) { + data.minimum_size_valid = false; + _size_changed(); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -620,13 +636,12 @@ void Control::_notification(int p_notification) { if (is_inside_tree()) { _modal_stack_remove(); - minimum_size_changed(); } //remove key focus //remove modalness } else { - + data.minimum_size_valid = false; _size_changed(); } @@ -2464,17 +2479,25 @@ void Control::minimum_size_changed() { if (!is_inside_tree() || data.block_minimum_size_adjust) return; - if (data.pending_min_size_update) + Control *invalidate = this; + + //invalidate cache upwards + while (invalidate && invalidate->data.minimum_size_valid) { + invalidate->data.minimum_size_valid = false; + if (invalidate->is_set_as_toplevel()) + break; // do not go further up + invalidate = invalidate->data.parent; + } + + if (!is_visible_in_tree()) + return; + + if (data.updating_last_minimum_size) return; - data.pending_min_size_update = true; - MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); + data.updating_last_minimum_size = true; - if (!is_toplevel_control()) { - Control *pc = get_parent_control(); - if (pc) - pc->minimum_size_changed(); - } + MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); } int Control::get_v_size_flags() const { @@ -2985,7 +3008,6 @@ Control::Control() { data.h_size_flags = SIZE_FILL; data.v_size_flags = SIZE_FILL; data.expand = 1; - data.pending_min_size_update = false; data.rotation = 0; data.parent_canvas_item = NULL; data.scale = Vector2(1, 1); @@ -2995,6 +3017,8 @@ Control::Control() { data.disable_visibility_clip = false; data.h_grow = GROW_DIRECTION_END; data.v_grow = GROW_DIRECTION_END; + data.minimum_size_valid = false; + data.updating_last_minimum_size = false; data.clip_contents = false; for (int i = 0; i < 4; i++) { diff --git a/scene/gui/control.h b/scene/gui/control.h index b5453e60f5..9124256624 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -148,6 +148,11 @@ private: Point2 pos_cache; Size2 size_cache; + Size2 minimum_size_cache; + bool minimum_size_valid; + + Size2 last_minimum_size; + bool updating_last_minimum_size; float margin[4]; float anchor[4]; @@ -164,7 +169,6 @@ private: int h_size_flags; int v_size_flags; float expand; - bool pending_min_size_update; Point2 custom_minimum_size; bool pass_on_modal_close_click; @@ -244,6 +248,8 @@ private: void _modal_stack_remove(); void _modal_set_prev_focus_owner(ObjectID p_prev); + void _update_minimum_size_cache(); + protected: virtual void add_child_notify(Node *p_child); virtual void remove_child_notify(Node *p_child); diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index cd6c6bb65c..caafda2c65 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -136,9 +136,9 @@ void Range::set_as_ratio(double p_value) { double v; - if (shared->exp_ratio && get_min() > 0) { + if (shared->exp_ratio && get_min() >= 0) { - double exp_min = Math::log(get_min()) / Math::log((double)2); + double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2); double exp_max = Math::log(get_max()) / Math::log((double)2); v = Math::pow(2, exp_min + (exp_max - exp_min) * p_value); } else { @@ -155,9 +155,9 @@ void Range::set_as_ratio(double p_value) { } double Range::get_as_ratio() const { - if (shared->exp_ratio && get_min() > 0) { + if (shared->exp_ratio && get_min() >= 0) { - double exp_min = Math::log(get_min()) / Math::log((double)2); + double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2); double exp_max = Math::log(get_max()) / Math::log((double)2); double v = Math::log(get_value()) / Math::log((double)2); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index d4ef735107..af368af46a 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -190,7 +190,6 @@ private: struct ItemNewline : public Item { - int line; // FIXME: Overriding base's line ? ItemNewline() { type = ITEM_NEWLINE; } }; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index a1dcf3b002..2dd5c64378 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -37,6 +37,7 @@ bool ScrollContainer::clips_input() const { Size2 ScrollContainer::get_minimum_size() const { + Ref<StyleBox> sb = get_stylebox("bg"); Size2 min_size; for (int i = 0; i < get_child_count(); i++) { @@ -64,8 +65,9 @@ Size2 ScrollContainer::get_minimum_size() const { if (v_scroll->is_visible_in_tree()) { min_size.x += v_scroll->get_minimum_size().x; } + min_size += sb->get_minimum_size(); return min_size; -}; +} void ScrollContainer::_cancel_drag() { set_physics_process_internal(false); @@ -233,6 +235,12 @@ void ScrollContainer::_notification(int p_what) { child_max_size = Size2(0, 0); Size2 size = get_size(); + Point2 ofs; + + Ref<StyleBox> sb = get_stylebox("bg"); + size -= sb->get_minimum_size(); + ofs += sb->get_offset(); + if (h_scroll->is_visible_in_tree()) size.y -= h_scroll->get_minimum_size().y; @@ -268,6 +276,7 @@ void ScrollContainer::_notification(int p_what) { else r.size.height = minsize.height; } + r.position += ofs; fit_child_in_rect(c, r); } update(); @@ -275,6 +284,9 @@ void ScrollContainer::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { + Ref<StyleBox> sb = get_stylebox("bg"); + draw_style_box(sb, Rect2(Vector2(), get_size())); + update_scrollbars(); } @@ -353,6 +365,8 @@ void ScrollContainer::_notification(int p_what) { void ScrollContainer::update_scrollbars() { Size2 size = get_size(); + Ref<StyleBox> sb = get_stylebox("bg"); + size -= sb->get_minimum_size(); Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index bf7033e8ba..c38c411333 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -33,13 +33,6 @@ #include "label.h" #include "margin_container.h" -struct _MinSizeCache { - - int min_size; - bool will_stretch; - int final_size; -}; - Control *SplitContainer::_getch(int p_idx) const { int idx = 0; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 4ff74dbb3f..c6ff8489c0 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -117,7 +117,6 @@ void TextEdit::Text::set_indent_size(int p_indent_size) { void TextEdit::Text::_update_line_cache(int p_line) const { int w = 0; - int tab_w = font->get_char_size(' ').width * indent_size; int len = text[p_line].data.length(); const CharType *str = text[p_line].data.c_str(); @@ -125,22 +124,13 @@ void TextEdit::Text::_update_line_cache(int p_line) const { //update width for (int i = 0; i < len; i++) { - if (str[i] == '\t') { - - int left = w % tab_w; - if (left == 0) - w += tab_w; - else - w += tab_w - w % tab_w; // is right... - - } else { - - w += font->get_char_size(str[i], str[i + 1]).width; - } + w += get_char_width(str[i], str[i + 1], w); } text[p_line].width_cache = w; + text[p_line].wrap_amount_cache = -1; + //update regions text[p_line].region_info.clear(); @@ -242,10 +232,32 @@ int TextEdit::Text::get_line_width(int p_line) const { return text[p_line].width_cache; } -void TextEdit::Text::clear_caches() { +void TextEdit::Text::set_line_wrap_amount(int p_line, int p_wrap_amount) const { + + ERR_FAIL_INDEX(p_line, text.size()); + + text[p_line].wrap_amount_cache = p_wrap_amount; +} + +int TextEdit::Text::get_line_wrap_amount(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), -1); + + return text[p_line].wrap_amount_cache; +} + +void TextEdit::Text::clear_width_cache() { - for (int i = 0; i < text.size(); i++) + for (int i = 0; i < text.size(); i++) { text[i].width_cache = -1; + } +} + +void TextEdit::Text::clear_wrap_cache() { + + for (int i = 0; i < text.size(); i++) { + text[i].wrap_amount_cache = -1; + } } void TextEdit::Text::clear() { @@ -270,6 +282,7 @@ void TextEdit::Text::set(int p_line, const String &p_text) { ERR_FAIL_INDEX(p_line, text.size()); text[p_line].width_cache = -1; + text[p_line].wrap_amount_cache = -1; text[p_line].data = p_text; } @@ -280,6 +293,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) { line.breakpoint = false; line.hidden = false; line.width_cache = -1; + line.wrap_amount_cache = -1; line.data = p_text; text.insert(p_at, line); } @@ -288,6 +302,25 @@ void TextEdit::Text::remove(int p_at) { text.remove(p_at); } +int TextEdit::Text::get_char_width(CharType c, CharType next_c, int px) const { + + int tab_w = font->get_char_size(' ').width * indent_size; + int w = 0; + + if (c == '\t') { + + int left = px % tab_w; + if (left == 0) + w = tab_w; + else + w = tab_w - px % tab_w; // is right... + } else { + + w = font->get_char_size(c, next_c).width; + } + return w; +} + void TextEdit::_update_scrollbars() { Size2 size = get_size(); @@ -302,9 +335,12 @@ void TextEdit::_update_scrollbars() { int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1; int visible_rows = get_visible_rows(); - int num_rows = MAX(visible_rows, num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs))); - int total_rows = (is_hiding_enabled() ? get_total_unhidden_rows() : text.size()); + int first_vis_line = get_first_visible_line(); + int wi; + int num_rows = MAX(visible_rows, num_lines_from_rows(first_vis_line, cursor.wrap_ofs, visible_rows, wi)); + + int total_rows = get_total_visible_rows(); if (scroll_past_end_of_file_enabled) { total_rows += visible_rows - 1; } @@ -350,28 +386,24 @@ void TextEdit::_update_scrollbars() { if (use_vscroll) { v_scroll->show(); - v_scroll->set_max(total_rows); - v_scroll->set_page(visible_rows); + v_scroll->set_max(total_rows + get_visible_rows_offset()); + v_scroll->set_page(visible_rows + get_visible_rows_offset()); if (smooth_scroll_enabled) { v_scroll->set_step(0.25); } else { v_scroll->set_step(1); } - - update_line_scroll_pos(); - if (fabs(v_scroll->get_value() - get_line_scroll_pos()) >= 1) { - cursor.line_ofs += v_scroll->get_value() - get_line_scroll_pos(); - } + set_v_scroll(get_v_scroll()); } else { cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; v_scroll->set_value(0); v_scroll->hide(); } - if (use_hscroll) { + if (use_hscroll && !is_wrap_enabled()) { h_scroll->show(); h_scroll->set_max(total_width); @@ -422,8 +454,8 @@ void TextEdit::_update_selection_mode_pointer() { select(selection.selecting_line, selection.selecting_column, row, col); - cursor_set_line(row); - cursor_set_column(col); + cursor_set_line(row, false); + cursor_set_column(col, false); update(); click_select_held->start(); @@ -476,7 +508,7 @@ void TextEdit::_update_selection_mode_word() { cursor_set_column(selection.to_column); } } - cursor_set_line(row); + cursor_set_line(row, false); update(); click_select_held->start(); @@ -491,15 +523,15 @@ void TextEdit::_update_selection_mode_line() { col = 0; if (row < selection.selecting_line) { // cursor is above us - cursor_set_line(row - 1); + cursor_set_line(row - 1, false); selection.selecting_column = text[selection.selecting_line].length(); } else { // cursor is below us - cursor_set_line(row + 1); + cursor_set_line(row + 1, false); selection.selecting_column = 0; col = text[row].length(); } - cursor_set_column(0); + cursor_set_column(0, false); select(selection.selecting_line, selection.selecting_column, row, col); update(); @@ -517,13 +549,13 @@ void TextEdit::_notification(int p_what) { MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit"); if (text_changed_dirty) MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); - + update_wrap_at(); } break; case NOTIFICATION_RESIZED: { cache.size = get_size(); - adjust_viewport_to_cursor(); - + _update_scrollbars(); + update_wrap_at(); } break; case NOTIFICATION_THEME_CHANGED: { @@ -540,17 +572,17 @@ void TextEdit::_notification(int p_what) { update(); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (scrolling && v_scroll->get_value() != target_v_scroll) { - double target_y = target_v_scroll - v_scroll->get_value(); + if (scrolling && get_v_scroll() != target_v_scroll) { + double target_y = target_v_scroll - get_v_scroll(); double dist = sqrt(target_y * target_y); double vel = ((target_y / dist) * v_scroll_speed) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); scrolling = false; set_physics_process_internal(false); } else { - v_scroll->set_value(v_scroll->get_value() + vel); + set_v_scroll(get_v_scroll() + vel); } } else { scrolling = false; @@ -777,12 +809,14 @@ void TextEdit::_notification(int p_what) { String highlighted_text = get_selection_text(); String line_num_padding = line_numbers_zero_padded ? "0" : " "; - update_line_scroll_pos(); - int line = cursor.line_ofs - 1; - // another row may be visible during smooth scrolling - int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 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 draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + draw_amount += times_line_wraps(line + 1); for (int i = 0; i < draw_amount; i++) { line++; @@ -800,269 +834,360 @@ void TextEdit::_notification(int p_what) { if (line < 0 || line >= (int)text.size()) continue; - const String &str = text[line]; + const String &fullstr = text[line]; - int char_margin = xmargin_beg - cursor.x_ofs; - int char_ofs = 0; - - int ofs_readonly = 0; - int ofs_x = 0; + Map<int, HighlighterInfo> color_map; + if (syntax_coloring) { + color_map = _get_line_syntax_highlighting(line); + } + // ensure we at least use the font color + Color current_color = cache.font_color; if (readonly) { - ofs_readonly = cache.style_readonly->get_offset().y / 2; - ofs_x = cache.style_readonly->get_offset().x / 2; + current_color.a *= readonly_alpha; } - int ofs_y = (i * get_row_height() + cache.line_spacing / 2) + ofs_readonly; - if (smooth_scroll_enabled) - ofs_y -= ((v_scroll->get_value() - get_line_scroll_pos()) * get_row_height()); bool underlined = false; - // check if line contains highlighted word - int highlighted_text_col = -1; - int search_text_col = -1; - int highlighted_word_col = -1; + int line_wrap_amount = times_line_wraps(line); + int last_wrap_column = 0; + Vector<String> wrap_rows = get_wrap_rows_text(line); - if (!search_text.empty()) - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); + for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) { + if (line_wrap_index != 0) { + i++; + if (i >= draw_amount) + break; + } - if (highlighted_text.length() != 0 && highlighted_text != search_text) - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + 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 (select_identifiers_enabled && highlighted_word.length() != 0) { - if (_is_char(highlighted_word[0])) { - highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + if (line_wrap_index > 0) + last_wrap_column += wrap_rows[line_wrap_index - 1].length(); + + int char_margin = xmargin_beg - cursor.x_ofs; + char_margin += indent_px; + int char_ofs = 0; + + int ofs_readonly = 0; + int ofs_x = 0; + if (readonly) { + ofs_readonly = cache.style_readonly->get_offset().y / 2; + ofs_x = cache.style_readonly->get_offset().x / 2; } - } - if (text.is_marked(line)) { + int ofs_y = (i * get_row_height() + cache.line_spacing / 2) + ofs_readonly; + ofs_y -= cursor.wrap_ofs * get_row_height(); + if (smooth_scroll_enabled) + ofs_y += (-get_v_scroll_offset()) * get_row_height(); - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.mark_color); - } + // check if line contains highlighted word + int highlighted_text_col = -1; + int search_text_col = -1; + int highlighted_word_col = -1; + + if (!search_text.empty()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); - if (str.length() == 0) { - // draw line background if empty as we won't loop at at all - if (line == cursor.line && highlight_current_line) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color); + if (highlighted_text.length() != 0 && highlighted_text != search_text) + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + + if (select_identifiers_enabled && highlighted_word.length() != 0) { + if (_is_char(highlighted_word[0])) { + highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + } } - // 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); + if (text.is_marked(line)) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.mark_color); } - } else { - // if it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. - if (line == cursor.line && highlight_current_line) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg + ofs_x, get_row_height()), cache.current_line_color); + + if (str.length() == 0) { + // draw line background if empty as we won't loop at at all + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color); + } + + // give visual indication of empty selected line + if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { + int char_w = cache.font->get_char_size(' ').width; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, get_row_height()), cache.selection_color); + } + } else { + // if it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg, get_row_height()), cache.current_line_color); + } } - } - if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { + if (line_wrap_index == 0) { + // 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 - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); #else - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.breakpoint_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.breakpoint_color); #endif - } + } - // draw breakpoint marker - if (text.is_breakpoint(line)) { - if (draw_breakpoint_gutter) { - int vertical_gap = (get_row_height() * 40) / 100; - int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; - int marker_height = get_row_height() - (vertical_gap * 2); - int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); - // no transparency on marker - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); - } - } + // draw breakpoint marker + if (text.is_breakpoint(line)) { + if (draw_breakpoint_gutter) { + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; + int marker_height = get_row_height() - (vertical_gap * 2); + int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); + // no transparency on marker + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); + } + } - // draw fold markers - if (draw_fold_gutter) { - int horizontal_gap = (cache.fold_gutter_width * 30) / 100; - int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w; - if (is_folded(line)) { - int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; - int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; - cache.folded_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } else if (can_fold(line)) { - int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; - int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; - cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } - } + // draw fold markers + if (draw_fold_gutter) { + int horizontal_gap = (cache.fold_gutter_width * 30) / 100; + int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w; + if (is_folded(line)) { + int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; + int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; + cache.folded_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); + } else if (can_fold(line)) { + int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; + int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; + cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); + } + } + + // draw line numbers + if (cache.line_number_w) { + String fc = String::num(line + 1); + while (fc.length() < line_number_char_count) { + fc = line_num_padding + fc; + } - if (cache.line_number_w) { - String fc = String::num(line + 1); - while (fc.length() < line_number_char_count) { - fc = line_num_padding + fc; + cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color); + } } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color); - } + //loop through characters in one line + for (int j = 0; j < str.length(); j++) { - //loop through characters in one line - Map<int, HighlighterInfo> color_map; - if (syntax_coloring) { - color_map = _get_line_syntax_highlighting(line); - } + if (syntax_coloring) { + if (color_map.has(last_wrap_column + j)) { + current_color = color_map[last_wrap_column + j].color; + if (readonly) { + current_color.a *= readonly_alpha; + } + } + color = current_color; + } - // ensure we at least use the font color - Color current_color = cache.font_color; - if (readonly) { - current_color.a *= readonly_alpha; - } - for (int j = 0; j < str.length(); j++) { + int char_w; + + //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; - if (syntax_coloring) { - if (color_map.has(j)) { - current_color = color_map[j].color; - if (readonly) { - current_color.a *= readonly_alpha; + // 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 + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + } else if ((char_ofs + char_margin) > xmargin_beg) { + // char next to margin is skipped + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color); + } } + continue; } - color = current_color; - } - int char_w; - //handle tabulator + if ((char_ofs + char_margin + char_w) >= xmargin_end) { + if (syntax_coloring) + continue; + else + break; + } - if (str[j] == '\t') { - int left = char_ofs % tab_w; - if (left == 0) - char_w = tab_w; - else - char_w = tab_w - char_ofs % tab_w; // is right... + bool in_search_result = false; - } else { - char_w = cache.font->get_char_size(str[j], str[j + 1]).width; - } + if (search_text_col != -1) { + // if we are at the end check for new search result on same line + if (j >= search_text_col + search_text.length()) + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); - if ((char_ofs + char_margin) < xmargin_beg) { - char_ofs += char_w; + in_search_result = j >= search_text_col && j < search_text_col + search_text.length(); - // line highlighting handle horizontal clipping - if (line == cursor.line && highlight_current_line) { + if (in_search_result) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin, ofs_y), Size2i(char_w, get_row_height())), cache.search_result_color); + } + } - if (j == str.length() - 1) { - // end of line when last char is skipped - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color); + //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)); - } else if ((char_ofs + char_margin) > xmargin_beg) { - // char next to margin is skipped - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - xmargin_beg, get_row_height()), cache.current_line_color); + if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + // draw the wrap indent offset highlight + if (line_wrap_index != 0 && j == 0) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin - indent_px, ofs_y, (char_ofs + char_margin), get_row_height()), cache.current_line_color); + } + // if its the last char draw to end of the line + if (j == str.length() - 1) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + } + // actual text + if (!in_selection) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color); } } - continue; - } - - if ((char_ofs + char_margin + char_w) >= xmargin_end) { - if (syntax_coloring) - continue; - else - break; - } - bool in_search_result = false; + if (in_selection) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.selection_color); + } - if (search_text_col != -1) { - // if we are at the end check for new search result on same line - if (j >= search_text_col + search_text.length()) - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); + if (in_search_result) { + Color border_color = (line == search_result_line && j >= search_result_col && j < search_result_col + search_text.length()) ? cache.font_color : cache.search_result_border_color; - in_search_result = j >= search_text_col && j < search_text_col + search_text.length(); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, 1)), border_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y + get_row_height() - 1), Size2i(char_w, 1)), border_color); - if (in_search_result) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin, ofs_y), Size2i(char_w, get_row_height())), cache.search_result_color); + if (j == search_text_col) + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(1, get_row_height())), border_color); + if (j == search_text_col + search_text.length() - 1) + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color); } - } - //current line highlighting - bool in_selection = (selection.active && line >= selection.from_line && line <= selection.to_line && (line > selection.from_line || j >= selection.from_column) && (line < selection.to_line || j < selection.to_column)); + if (highlight_all_occurrences) { + if (highlighted_text_col != -1) { - if (line == cursor.line && highlight_current_line) { - // if its the last char draw to end of the line - if (j == str.length() - 1) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + // 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 (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; + } + + if (in_highlighted_word) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.word_highlighted_color); + } + } } - // actual text - if (!in_selection) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color); + + if (highlighted_word_col != -1) { + if (j + last_wrap_column > highlighted_word_col + highlighted_word.length()) { + highlighted_word_col = _get_column_pos_of_word(highlighted_word, fullstr, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j + last_wrap_column); + } + underlined = (j + last_wrap_column >= highlighted_word_col && j + last_wrap_column < highlighted_word_col + highlighted_word.length()); } - } - if (in_selection) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.selection_color); - } + if (brace_matching_enabled) { + if ((brace_open_match_line == line && brace_open_match_column == last_wrap_column + j) || + (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { - if (in_search_result) { - Color border_color = (line == search_result_line && j >= search_result_col && j < search_result_col + search_text.length()) ? cache.font_color : cache.search_result_border_color; + if (brace_open_mismatch) + color = cache.brace_mismatch_color; + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + } - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, 1)), border_color); - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y + get_row_height() - 1), Size2i(char_w, 1)), border_color); + if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) || + (cursor.column == last_wrap_column + j + 1 && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_close_matching || brace_close_mismatch))) { - if (j == search_text_col) - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(1, get_row_height())), border_color); - if (j == search_text_col + search_text.length() - 1) - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color); - } + if (brace_close_mismatch) + color = cache.brace_mismatch_color; + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + } + } - if (highlight_all_occurrences) { - if (highlighted_text_col != -1) { + if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index) { - // 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); + cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); + + if (insert_mode) { + cursor_pos.y += (get_row_height() - 3); } - bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length()); + int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w; + if (ime_text.length() > 0) { + int ofs = 0; + while (true) { + if (ofs >= ime_text.length()) + break; - /* if this is the original highlighted text we don't want to highlight it again */ - if (cursor.line == line && (cursor.column >= highlighted_text_col && cursor.column <= highlighted_text_col + highlighted_text.length())) { - in_highlighted_word = false; - } + CharType cchar = ime_text[ofs]; + CharType next = ime_text[ofs + 1]; + int im_char_width = cache.font->get_char_size(cchar, next).width; + + if ((char_ofs + char_margin + im_char_width) >= xmargin_end) + break; + + bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; + if (selected) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); + } else { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); + } + + drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); - if (in_highlighted_word) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.word_highlighted_color); + char_ofs += im_char_width; + ofs++; + } + } + if (ime_text.length() == 0) { + if (draw_caret) { + if (insert_mode) { + int caret_h = (block_caret) ? 4 : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); + } else { + caret_w = (block_caret) ? caret_w : 1; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); + } + } } } - } - if (highlighted_word_col != -1) { - if (j > highlighted_word_col + highlighted_word.length()) { - highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j); + 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; } - underlined = (j >= highlighted_word_col && j < highlighted_word_col + highlighted_word.length()); - } - if (brace_matching_enabled) { - if ((brace_open_match_line == line && brace_open_match_column == j) || - (cursor.column == j && cursor.line == line && (brace_open_matching || brace_open_mismatch))) { - - if (brace_open_mismatch) - color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (str[j] >= 32) { + int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (underlined) { + draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); + } + } else if (draw_tabs && str[j] == '\t') { + int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; + cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_selected_color : color); } - if ( - (brace_close_match_line == line && brace_close_match_column == j) || - (cursor.column == j + 1 && cursor.line == line && (brace_close_matching || brace_close_mismatch))) { + char_ofs += char_w; - if (brace_close_mismatch) - color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + if (line_wrap_index == line_wrap_amount && j == str.length() - 1 && is_folded(line)) { + int yofs = (get_row_height() - cache.folded_eol_icon->get_height()) / 2; + int xofs = cache.folded_eol_icon->get_width() / 2; + Color eol_color = cache.code_folding_color; + eol_color.a = 1; + cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color); } } - if (cursor.column == j && cursor.line == line) { + if (cursor.column == last_wrap_column + str.length() && cursor.line == line && cursor_wrap_index == line_wrap_index && (char_ofs + char_margin) >= xmargin_beg) { cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); if (insert_mode) { cursor_pos.y += (get_row_height() - 3); } - - int caret_w = (str[j] == '\t') ? cache.font->get_char_size(' ').width : char_w; if (ime_text.length() > 0) { int ofs = 0; while (true) { @@ -1092,92 +1217,17 @@ void TextEdit::_notification(int p_what) { if (ime_text.length() == 0) { if (draw_caret) { if (insert_mode) { + int char_w = cache.font->get_char_size(' ').width; int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, caret_h)), cache.caret_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); } else { - caret_w = (block_caret) ? caret_w : 1; + int char_w = cache.font->get_char_size(' ').width; + int caret_w = (block_caret) ? char_w : 1; VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); } } } } - - if (cursor.column == j && cursor.line == line && block_caret && draw_caret && !insert_mode) { - color = cache.caret_background_color; - } else if (!syntax_coloring && block_caret) { - color = cache.font_color; - color.a *= readonly_alpha; - } - - if (str[j] >= 32) { - int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); - if (underlined) { - draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color); - } - } - - else if (draw_tabs && str[j] == '\t') { - int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; - cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_selected_color : color); - } - - char_ofs += char_w; - - if (j == str.length() - 1 && is_folded(line)) { - int yofs = (get_row_height() - cache.folded_eol_icon->get_height()) / 2; - int xofs = cache.folded_eol_icon->get_width() / 2; - Color eol_color = cache.code_folding_color; - eol_color.a = 1; - cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin + xofs + ofs_x, ofs_y + yofs), eol_color); - } - } - - if (cursor.column == str.length() && cursor.line == line && (char_ofs + char_margin) >= xmargin_beg) { - - cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); - - if (insert_mode) { - cursor_pos.y += (get_row_height() - 3); - } - if (ime_text.length() > 0) { - int ofs = 0; - while (true) { - if (ofs >= ime_text.length()) - break; - - CharType cchar = ime_text[ofs]; - CharType next = ime_text[ofs + 1]; - int im_char_width = cache.font->get_char_size(cchar, next).width; - - if ((char_ofs + char_margin + im_char_width) >= xmargin_end) - break; - - bool selected = ofs >= ime_selection.x && ofs < ime_selection.x + ime_selection.y; - if (selected) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 3)), color); - } else { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color); - } - - drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color); - - char_ofs += im_char_width; - ofs++; - } - } - if (ime_text.length() == 0) { - if (draw_caret) { - if (insert_mode) { - int char_w = cache.font->get_char_size(' ').width; - int caret_h = (block_caret) ? 4 : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(char_w, caret_h)), cache.caret_color); - } else { - int char_w = cache.font->get_char_size(' ').width; - int caret_w = (block_caret) ? char_w : 1; - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cursor_pos, Size2i(caret_w, get_row_height())), cache.caret_color); - } - } - } } } @@ -1604,16 +1654,17 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co float rows = p_mouse.y; rows -= cache.style_normal->get_margin(MARGIN_TOP); - rows += (CLAMP(v_scroll->get_value() - get_line_scroll_pos(true), 0, 1) * get_row_height()); rows /= get_row_height(); - int first_vis_line = CLAMP(cursor.line_ofs, 0, text.size() - 1); + rows += get_v_scroll_offset(); + int first_vis_line = get_first_visible_line(); int row = first_vis_line + Math::floor(rows); + int wrap_index = 0; + + if (is_wrap_enabled() || is_hiding_enabled()) { - if (is_hiding_enabled()) { - // row will be offset by the hidden rows - int f_ofs = num_lines_from(first_vis_line, rows + 1) - 1; + int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + 1, wrap_index) - 1; row = first_vis_line + f_ofs; - row = CLAMP(row, 0, text.size() - num_lines_from(text.size() - 1, -1)); + row = CLAMP(row, 0, get_last_visible_line() + 1); } if (row < 0) @@ -1627,9 +1678,19 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co col = text[row].size(); } else { - col = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); - col += cursor.x_ofs; - col = get_char_pos_for(col, get_line(row)); + int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); + colx += cursor.x_ofs; + col = get_char_pos_for_line(colx, row, wrap_index); + if (is_wrap_enabled() && wrap_index < times_line_wraps(row)) { + // move back one if we are at the end of the row + Vector<String> rows = get_wrap_rows_text(row); + int row_end_col = 0; + for (int i = 0; i < wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (col >= row_end_col) + col -= 1; + } } r_row = row; @@ -1704,7 +1765,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _reset_caret_blink_timer(); int row, col; - update_line_scroll_pos(); _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); if (mb->get_command() && highlighted_word != String()) { @@ -1836,7 +1896,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _reset_caret_blink_timer(); int row, col; - update_line_scroll_pos(); _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); if (is_right_click_moving_caret()) { @@ -2453,13 +2512,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } else if (k->get_command()) { #endif - bool prev_char = false; int cc = cursor.column; if (cc == 0 && cursor.line > 0) { cursor_set_line(cursor.line - 1); cursor_set_column(text[cursor.line].length()); } else { + bool prev_char = false; + while (cc > 0) { bool ischar = _is_text_char(text[cursor.line][cc - 1]); @@ -2514,13 +2574,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } else if (k->get_command()) { #endif - bool prev_char = false; int cc = cursor.column; if (cc == text[cursor.line].length() && cursor.line < text.size() - 1) { cursor_set_line(cursor.line + 1); cursor_set_column(0); } else { + bool prev_char = false; + while (cc < text[cursor.line].length()) { bool ischar = _is_text_char(text[cursor.line][cc]); @@ -2572,11 +2633,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } - if (k->get_command()) + if (k->get_command()) { cursor_set_line(0); - else + } else #endif - cursor_set_line(cursor_get_line() - num_lines_from(CLAMP(cursor.line - 1, 0, text.size() - 1), -1)); + { + int cur_wrap_index = get_cursor_wrap_index(); + if (cur_wrap_index > 0) { + cursor_set_line(cursor.line, true, false, cur_wrap_index - 1); + } else if (cursor.line == 0) { + cursor_set_column(0); + } else { + int new_line = cursor.line - num_lines_from(cursor.line - 1, -1); + if (line_wraps(new_line)) { + cursor_set_line(new_line, true, false, times_line_wraps(new_line)); + } else { + cursor_set_line(new_line, true, false); + } + } + } if (k->get_shift()) _post_shift_selection(); @@ -2604,22 +2679,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { break; } - { #else if (k->get_command() && k->get_alt()) { _scroll_lines_down(); break; } - if (k->get_command()) - cursor_set_line(text.size() - 1, true, false); - else { + if (k->get_command()) { + cursor_set_line(get_last_unhidden_line(), true, false, 9999); + } else #endif - if (!is_last_visible_line(cursor.line)) { - cursor_set_line(cursor_get_line() + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1), true, false); + { + int cur_wrap_index = get_cursor_wrap_index(); + if (cur_wrap_index < times_line_wraps(cursor.line)) { + cursor_set_line(cursor.line, true, false, cur_wrap_index + 1); + } else if (cursor.line == get_last_unhidden_line()) { + cursor_set_column(text[cursor.line].length()); } else { - cursor_set_line(text.size() - 1); - cursor_set_column(get_line(cursor.line).length(), true); + int new_line = cursor.line + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1); + cursor_set_line(new_line, true, false, 0); } } @@ -2628,7 +2706,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_code_hint(); } break; - case KEY_DELETE: { if (readonly) @@ -2735,19 +2812,31 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_line(0); cursor_set_column(0); } else { - // compute whitespace symbols seq length - int current_line_whitespace_len = 0; - while (current_line_whitespace_len < text[cursor.line].length()) { - CharType c = text[cursor.line][current_line_whitespace_len]; - if (c != '\t' && c != ' ') - break; - current_line_whitespace_len++; + + // move cursor column to start of wrapped row and then to start of text + Vector<String> rows = get_wrap_rows_text(cursor.line); + int wi = get_cursor_wrap_index(); + int row_start_col = 0; + for (int i = 0; i < wi; i++) { + row_start_col += rows[i].length(); } + if (cursor.column == row_start_col || wi == 0) { + // compute whitespace symbols seq length + int current_line_whitespace_len = 0; + while (current_line_whitespace_len < text[cursor.line].length()) { + CharType c = text[cursor.line][current_line_whitespace_len]; + if (c != '\t' && c != ' ') + break; + current_line_whitespace_len++; + } - if (cursor_get_column() == current_line_whitespace_len) - cursor_set_column(0); - else - cursor_set_column(current_line_whitespace_len); + if (cursor_get_column() == current_line_whitespace_len) + cursor_set_column(0); + else + cursor_set_column(current_line_whitespace_len); + } else { + cursor_set_column(row_start_col); + } } if (k->get_shift()) @@ -2772,7 +2861,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(text.size() - 1, true, false); + cursor_set_line(get_last_unhidden_line(), true, false, 9999); if (k->get_shift()) _post_shift_selection(); @@ -2787,8 +2876,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _pre_shift_selection(); if (k->get_command()) - cursor_set_line(text.size() - 1, true, false); - cursor_set_column(text[cursor.line].length()); + cursor_set_line(get_last_unhidden_line(), true, false, 9999); + + // move cursor column to end of wrapped row and then to end of text + Vector<String> rows = get_wrap_rows_text(cursor.line); + int wi = get_cursor_wrap_index(); + int row_end_col = -1; + for (int i = 0; i < wi + 1; i++) { + row_end_col += rows[i].length(); + } + if (wi == rows.size() - 1 || cursor.column == row_end_col) { + cursor_set_column(text[cursor.line].length()); + } else { + cursor_set_column(row_end_col); + } if (k->get_shift()) _post_shift_selection(); @@ -2812,7 +2913,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() - num_lines_from(cursor.line, -get_visible_rows()), true, false); + int wi; + int n_line = cursor.line - num_lines_from_rows(cursor.line, get_cursor_wrap_index(), -get_visible_rows(), wi) + 1; + cursor_set_line(n_line, true, false, wi); if (k->get_shift()) _post_shift_selection(); @@ -2826,14 +2929,16 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_pageup + // numlock disabled. fallthrough to key_pagedown } case KEY_PAGEDOWN: { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() + num_lines_from(cursor.line, get_visible_rows()), true, false); + int wi; + int n_line = cursor.line + num_lines_from_rows(cursor.line, get_cursor_wrap_index(), get_visible_rows(), wi) - 1; + cursor_set_line(n_line, true, false, wi); if (k->get_shift()) _post_shift_selection(); @@ -2851,7 +2956,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } select_all(); #else - if (k->get_alt()) { + if (k->get_alt() || (!k->get_shift() && !k->get_command() && !k->get_control())) { scancode_handled = false; break; } @@ -3078,7 +3183,7 @@ void TextEdit::_scroll_up(real_t p_delta) { if (scrolling) { target_v_scroll = (target_v_scroll - p_delta); } else { - target_v_scroll = (v_scroll->get_value() - p_delta); + target_v_scroll = (get_v_scroll() - p_delta); } if (smooth_scroll_enabled) { @@ -3092,7 +3197,7 @@ void TextEdit::_scroll_up(real_t p_delta) { set_physics_process_internal(true); } } else { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); } } @@ -3104,20 +3209,15 @@ void TextEdit::_scroll_down(real_t p_delta) { if (scrolling) { target_v_scroll = (target_v_scroll + p_delta); } else { - target_v_scroll = (v_scroll->get_value() + p_delta); + target_v_scroll = (get_v_scroll() + p_delta); } if (smooth_scroll_enabled) { - int max_v_scroll = get_total_unhidden_rows(); - if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows(); - max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); - } - + int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); if (target_v_scroll > max_v_scroll) { target_v_scroll = max_v_scroll; + v_scroll->set_value(target_v_scroll); } - if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) { v_scroll->set_value(target_v_scroll); } else { @@ -3125,7 +3225,7 @@ void TextEdit::_scroll_down(real_t p_delta) { set_physics_process_internal(true); } } else { - v_scroll->set_value(target_v_scroll); + set_v_scroll(target_v_scroll); } } @@ -3156,35 +3256,37 @@ void TextEdit::_scroll_lines_up() { scrolling = false; // adjust the vertical scroll - if (get_v_scroll() >= 0) { - set_v_scroll(get_v_scroll() - 1); - } + set_v_scroll(get_v_scroll() - 1); + + // adjust the cursor to viewport + if (!selection.active) { + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); + int last_vis_line = get_last_visible_line(); + int last_vis_wrap = get_last_visible_line_wrap_index(); - // adjust the cursor - int num_lines = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), get_visible_rows()); - if (cursor.line >= cursor.line_ofs + num_lines && !selection.active) { - cursor_set_line(cursor.line_ofs + num_lines, false, false); + if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + cursor_set_line(last_vis_line, false, false, last_vis_wrap); + } } } void TextEdit::_scroll_lines_down() { scrolling = false; - // calculate the maximum vertical scroll position - int max_v_scroll = get_total_unhidden_rows(); - if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows(); - max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); - } - // adjust the vertical scroll - if (get_v_scroll() < max_v_scroll) { - set_v_scroll(get_v_scroll() + 1); - } + set_v_scroll(get_v_scroll() + 1); - // adjust the cursor - if (cursor.line <= cursor.line_ofs - 1 && !selection.active) { - cursor_set_line(cursor.line_ofs, false, false); + // adjust the cursor to viewport + if (!selection.active) { + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = cursor.wrap_ofs; + + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + cursor_set_line(first_vis_line, false, false, first_vis_wrap); + } } } @@ -3238,6 +3340,8 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i text.set_hidden(p_line, false); } + text.set_line_wrap_amount(p_line, -1); + r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -3292,6 +3396,8 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li text.set(p_from_line, pre_text + post_text); + text.set_line_wrap_amount(p_from_line, -1); + if (!text_changed_dirty && !setting_text) { if (is_inside_tree()) MessageQueue::get_singleton()->push_call(this, "_text_changed_emit"); @@ -3450,61 +3556,63 @@ int TextEdit::get_visible_rows() const { int total = cache.size.height; total -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) + total -= h_scroll->get_size().height; total /= get_row_height(); return total; } -int TextEdit::get_total_unhidden_rows() const { - if (!is_hiding_enabled()) +int TextEdit::get_total_visible_rows() const { + + // returns the total amount of rows we need in the editor. + // This skips hidden lines and counts each wrapping of a line. + if (!is_hiding_enabled() && !is_wrap_enabled()) return text.size(); - int total_unhidden = 0; + int total_rows = 0; for (int i = 0; i < text.size(); i++) { - if (!text.is_hidden(i)) - total_unhidden++; + if (!text.is_hidden(i)) { + total_rows++; + total_rows += times_line_wraps(i); + } } - return total_unhidden; + return total_rows; } -double TextEdit::get_line_scroll_pos(bool p_recalculate) const { +void TextEdit::update_wrap_at() { - if (!is_hiding_enabled()) - return cursor.line_ofs; - if (!p_recalculate) - return line_scroll_pos; + wrap_at = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - wrap_right_offset; + update_cursor_wrap_offset(); + text.clear_wrap_cache(); - // count num unhidden lines to the cursor line ofs - double new_line_scroll_pos = 0; - int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) - new_line_scroll_pos++; + for (int i = 0; i < text.size(); i++) { + // update all values that wrap + if (!line_wraps(i)) + continue; + Vector<String> rows = get_wrap_rows_text(i); + text.set_line_wrap_amount(i, rows.size() - 1); } - return new_line_scroll_pos; } -void TextEdit::update_line_scroll_pos() { +void TextEdit::adjust_viewport_to_cursor() { - if (!is_hiding_enabled()) { - line_scroll_pos = cursor.line_ofs; - return; - } + // make sure cursor is visible on the screen + scrolling = false; - // count num unhidden lines to the cursor line ofs - double new_line_scroll_pos = 0; - int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) - new_line_scroll_pos++; - } - line_scroll_pos = new_line_scroll_pos; -} + int cur_line = cursor.line; + int cur_wrap = get_cursor_wrap_index(); -void TextEdit::adjust_viewport_to_cursor() { - scrolling = false; + int first_vis_line = get_first_visible_line(); + int first_vis_wrap = cursor.wrap_ofs; + int last_vis_line = get_last_visible_line(); + int last_vis_wrap = get_last_visible_line_wrap_index(); - if (cursor.line_ofs > cursor.line) { - cursor.line_ofs = cursor.line; + if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { + // cursor is above screen + set_line_as_first_visible(cur_line, cur_wrap); + } else if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { + // cursor is below screen + set_line_as_last_visible(cur_line, cur_wrap); } int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; @@ -3512,91 +3620,174 @@ void TextEdit::adjust_viewport_to_cursor() { visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // give it a little more space - int visible_rows = get_visible_rows(); - if (h_scroll->is_visible_in_tree() && !scroll_past_end_of_file_enabled) - visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); - int num_rows = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs)); - - // make sure the cursor is on the screen - // above the caret - if (cursor.line > (cursor.line_ofs + MAX(num_rows, visible_rows))) { - cursor.line_ofs = cursor.line - num_lines_from(cursor.line, -visible_rows) + 1; - } - // below the caret - if (cursor.line_ofs == cursor.line) { - cursor.line_ofs = cursor.line - 2; - } - int line_ofs_max = text.size() - 1; - if (!scroll_past_end_of_file_enabled) { - line_ofs_max -= num_lines_from(text.size() - 1, -visible_rows) - 1; - line_ofs_max += (h_scroll->is_visible_in_tree() ? 1 : 0); - line_ofs_max += (cursor.line == text.size() - 1 ? 1 : 0); - } - line_ofs_max = MAX(line_ofs_max, 0); - cursor.line_ofs = CLAMP(cursor.line_ofs, 0, line_ofs_max); - - // adjust x offset - int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); + if (!is_wrap_enabled()) { + // adjust x offset + int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); - if (cursor_x > (cursor.x_ofs + visible_width)) - cursor.x_ofs = cursor_x - visible_width + 1; + if (cursor_x > (cursor.x_ofs + visible_width)) + cursor.x_ofs = cursor_x - visible_width + 1; - if (cursor_x < cursor.x_ofs) - cursor.x_ofs = cursor_x; - - updating_scrolls = true; - h_scroll->set_value(cursor.x_ofs); - update_line_scroll_pos(); - double new_v_scroll = get_line_scroll_pos(); - // keep offset if smooth scroll is enabled - if (smooth_scroll_enabled) { - new_v_scroll += fmod(v_scroll->get_value(), 1.0); + if (cursor_x < cursor.x_ofs) + cursor.x_ofs = cursor_x; + } else { + cursor.x_ofs = 0; } - v_scroll->set_value(new_v_scroll); - updating_scrolls = false; + h_scroll->set_value(cursor.x_ofs); + update(); } void TextEdit::center_viewport_to_cursor() { - scrolling = false; - if (cursor.line_ofs > cursor.line) - cursor.line_ofs = cursor.line; + // move viewport so the cursor is in the center of the screen + scrolling = false; if (is_line_hidden(cursor.line)) unfold_line(cursor.line); + set_line_as_center_visible(cursor.line, get_cursor_wrap_index()); int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; if (v_scroll->is_visible_in_tree()) visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // give it a little more space - int visible_rows = get_visible_rows(); - if (h_scroll->is_visible_in_tree()) - visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); - if (text.size() >= visible_rows) { - int max_ofs = text.size() - (scroll_past_end_of_file_enabled ? 1 : MAX(num_lines_from(text.size() - 1, -visible_rows), 0)); - cursor.line_ofs = CLAMP(cursor.line - num_lines_from(MAX(cursor.line - visible_rows / 2, 0), -visible_rows / 2), 0, max_ofs); + if (is_wrap_enabled()) { + // center x offset + int cursor_x = get_column_x_offset_for_line(cursor.column, cursor.line); + + if (cursor_x > (cursor.x_ofs + visible_width)) + cursor.x_ofs = cursor_x - visible_width + 1; + + if (cursor_x < cursor.x_ofs) + cursor.x_ofs = cursor_x; + } else { + cursor.x_ofs = 0; } - int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); + h_scroll->set_value(cursor.x_ofs); - if (cursor_x > (cursor.x_ofs + visible_width)) - cursor.x_ofs = cursor_x - visible_width + 1; + update(); +} - if (cursor_x < cursor.x_ofs) - cursor.x_ofs = cursor_x; +void TextEdit::update_cursor_wrap_offset() { + int first_vis_line = get_first_visible_line(); + if (line_wraps(first_vis_line)) { + cursor.wrap_ofs = MIN(cursor.wrap_ofs, times_line_wraps(first_vis_line)); + } else { + cursor.wrap_ofs = 0; + } + set_line_as_first_visible(cursor.line_ofs, cursor.wrap_ofs); +} - updating_scrolls = true; - h_scroll->set_value(cursor.x_ofs); - update_line_scroll_pos(); - double new_v_scroll = get_line_scroll_pos(); - // keep offset if smooth scroll is enabled - if (smooth_scroll_enabled) { - new_v_scroll += fmod(v_scroll->get_value(), 1.0); +bool TextEdit::line_wraps(int line) const { + + ERR_FAIL_INDEX_V(line, text.size(), 0); + if (!is_wrap_enabled()) + return false; + return text.get_line_width(line) > wrap_at; +} + +int TextEdit::times_line_wraps(int line) const { + + ERR_FAIL_INDEX_V(line, text.size(), 0); + if (!line_wraps(line)) + return 0; + + int wrap_amount = text.get_line_wrap_amount(line); + if (wrap_amount == -1) { + // update the value + Vector<String> rows = get_wrap_rows_text(line); + wrap_amount = rows.size() - 1; + text.set_line_wrap_amount(line, wrap_amount); } - v_scroll->set_value(new_v_scroll); - updating_scrolls = false; - update(); + + return wrap_amount; +} + +Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), Vector<String>()); + + Vector<String> lines; + if (!line_wraps(p_line)) { + lines.push_back(text[p_line]); + return lines; + } + + int px = 0; + int col = 0; + String line_text = text[p_line]; + String wrap_substring = ""; + + int word_px = 0; + String word_str = ""; + int cur_wrap_index = 0; + + int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + + while (col < line_text.length()) { + char c = line_text[col]; + int w = text.get_char_width(c, line_text[col + 1], px + word_px); + + int indent_ofs = (cur_wrap_index != 0 ? tab_offset_px : 0); + + word_str += c; + word_px += w; + if (c == ' ') { + // end of a word; add this word to the substring + wrap_substring += word_str; + px += word_px; + word_str = ""; + word_px = 0; + } + + if ((indent_ofs + px + word_px) > wrap_at) { + // do not want to add this word + if (indent_ofs + word_px > wrap_at) { + // not enough space; add it anyway + wrap_substring += word_str; + px += word_px; + word_str = ""; + word_px = 0; + } + lines.push_back(wrap_substring); + // reset for next wrap + cur_wrap_index++; + wrap_substring = ""; + px = 0; + } + col++; + } + // line ends before hit wrap_at; add this word to the substring + wrap_substring += word_str; + px += word_px; + lines.push_back(wrap_substring); + return lines; +} + +int TextEdit::get_cursor_wrap_index() const { + + return get_line_wrap_index_at_col(cursor.line, cursor.column); +} + +int TextEdit::get_line_wrap_index_at_col(int p_line, int p_column) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + if (!line_wraps(p_line)) + return 0; + + // loop through wraps in the line text until we get to the column + int wrap_index = 0; + int col = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + for (int i = 0; i < rows.size(); i++) { + wrap_index = i; + String s = rows[wrap_index]; + col += s.length(); + if (col > p_column) + break; + } + return wrap_index; } void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { @@ -3608,7 +3799,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { if (cursor.column > get_line(cursor.line).length()) cursor.column = get_line(cursor.line).length(); - cursor.last_fit_x = get_column_x_offset(cursor.column, get_line(cursor.line)); + cursor.last_fit_x = get_column_x_offset_for_line(cursor.column, cursor.line); if (p_adjust_viewport) adjust_viewport_to_cursor(); @@ -3620,7 +3811,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { } } -void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden) { +void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index) { if (setting_row) return; @@ -3629,8 +3820,8 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ if (p_row < 0) p_row = 0; - if (p_row >= (int)text.size()) - p_row = (int)text.size() - 1; + if (p_row >= text.size()) + p_row = text.size() - 1; if (!p_can_be_hidden) { if (is_line_hidden(CLAMP(p_row, 0, text.size() - 1))) { @@ -3648,7 +3839,18 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ } } cursor.line = p_row; - cursor.column = get_char_pos_for(cursor.last_fit_x, get_line(cursor.line)); + + int n_col = get_char_pos_for_line(cursor.last_fit_x, p_row, p_wrap_index); + if (is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) { + Vector<String> rows = get_wrap_rows_text(p_row); + int row_end_col = 0; + for (int i = 0; i < p_wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (n_col >= row_end_col) + n_col -= 1; + } + cursor.column = n_col; if (p_adjust_viewport) adjust_viewport_to_cursor(); @@ -3725,9 +3927,25 @@ void TextEdit::_scroll_moved(double p_to_val) { if (h_scroll->is_visible_in_tree()) cursor.x_ofs = h_scroll->get_value(); if (v_scroll->is_visible_in_tree()) { - double val = v_scroll->get_value(); - cursor.line_ofs = num_lines_from(0, (int)floor(val)); - line_scroll_pos = (int)floor(val); + + // set line ofs and wrap ofs + int v_scroll_i = floor(get_v_scroll()); + int sc = 0; + int n_line; + for (n_line = 0; n_line < text.size(); n_line++) { + if (!is_line_hidden(n_line)) { + sc++; + sc += times_line_wraps(n_line); + if (sc > v_scroll_i) + break; + } + } + int line_wrap_amount = times_line_wraps(n_line); + int wi = line_wrap_amount - (sc - v_scroll_i - 1); + wi = CLAMP(wi, 0, line_wrap_amount); + + cursor.line_ofs = n_line; + cursor.wrap_ofs = wi; } update(); } @@ -3737,29 +3955,73 @@ int TextEdit::get_row_height() const { return cache.font->get_height() + cache.line_spacing; } -int TextEdit::get_char_pos_for(int p_px, String p_str) const { +int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const { - int px = 0; - int c = 0; + ERR_FAIL_INDEX_V(p_line, text.size(), 0); - int tab_w = cache.font->get_char_size(' ').width * indent_size; + if (line_wraps(p_line)) { - while (c < p_str.length()) { + int line_wrap_amount = times_line_wraps(p_line); + int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (p_wrap_index > line_wrap_amount) + p_wrap_index = line_wrap_amount; + if (p_wrap_index > 0) + p_px -= wrap_offset_px; + else + p_wrap_index = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + int c_pos = get_char_pos_for(p_px, rows[p_wrap_index]); + for (int i = 0; i < p_wrap_index; i++) { + String s = rows[i]; + c_pos += s.length(); + } - int w = 0; + return c_pos; + } else { - if (p_str[c] == '\t') { + return get_char_pos_for(p_px, text[p_line]); + } +} - int left = px % tab_w; - if (left == 0) - w = tab_w; - else - w = tab_w - px % tab_w; // is right... +int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { - } else { + ERR_FAIL_INDEX_V(p_line, text.size(), 0); - w = cache.font->get_char_size(p_str[c], p_str[c + 1]).width; + if (line_wraps(p_line)) { + + int n_char = p_char; + int col = 0; + Vector<String> rows = get_wrap_rows_text(p_line); + int wrap_index = 0; + for (int i = 0; i < rows.size(); i++) { + wrap_index = i; + String s = rows[wrap_index]; + col += s.length(); + if (col > p_char) + break; + n_char -= s.length(); } + int px = get_column_x_offset(n_char, rows[wrap_index]); + + int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (wrap_index != 0) + px += wrap_offset_px; + + return px; + } else { + + return get_column_x_offset(p_char, text[p_line]); + } +} + +int TextEdit::get_char_pos_for(int p_px, String p_str) const { + + int px = 0; + int c = 0; + + while (c < p_str.length()) { + + int w = text.get_char_width(p_str[c], p_str[c + 1], px); if (p_px < (px + w / 2)) break; @@ -3770,28 +4032,16 @@ int TextEdit::get_char_pos_for(int p_px, String p_str) const { return c; } -int TextEdit::get_column_x_offset(int p_char, String p_str) { +int TextEdit::get_column_x_offset(int p_char, String p_str) const { int px = 0; - int tab_w = cache.font->get_char_size(' ').width * indent_size; - for (int i = 0; i < p_char; i++) { if (i >= p_str.length()) break; - if (p_str[i] == '\t') { - - int left = px % tab_w; - if (left == 0) - px += tab_w; - else - px += tab_w - px % tab_w; // is right... - - } else { - px += cache.font->get_char_size(p_str[i], p_str[i + 1]).width; - } + px += text.get_char_width(p_str[i], p_str[i + 1], px); } return px; @@ -3867,7 +4117,7 @@ void TextEdit::set_text(String p_text) { cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; cursor.last_fit_x = 0; cursor_set_line(0); cursor_set_column(0); @@ -3953,7 +4203,7 @@ void TextEdit::_clear() { cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; - line_scroll_pos = 0; + cursor.wrap_ofs = 0; cursor.last_fit_x = 0; } @@ -3975,14 +4225,14 @@ bool TextEdit::is_readonly() const { return readonly; } -void TextEdit::set_wrap(bool p_wrap) { +void TextEdit::set_wrap_enabled(bool p_wrap_enabled) { - wrap = p_wrap; + wrap_enabled = p_wrap_enabled; } -bool TextEdit::is_wrapping() const { +bool TextEdit::is_wrap_enabled() const { - return wrap; + return wrap_enabled; } void TextEdit::set_max_chars(int p_max_chars) { @@ -4131,7 +4381,7 @@ void TextEdit::clear_colors() { keywords.clear(); color_regions.clear(); color_region_cache.clear(); - text.clear_caches(); + text.clear_width_cache(); } void TextEdit::add_keyword_color(const String &p_keyword, const Color &p_color) { @@ -4151,7 +4401,7 @@ Color TextEdit::get_keyword_color(String p_keyword) const { void TextEdit::add_color_region(const String &p_begin_key, const String &p_end_key, const Color &p_color, bool p_line_only) { color_regions.push_back(ColorRegion(p_begin_key, p_end_key, p_color, p_line_only)); - text.clear_caches(); + text.clear_width_cache(); update(); } @@ -4648,52 +4898,99 @@ void TextEdit::unhide_all_lines() { update(); } -int TextEdit::num_lines_from(int p_line_from, int unhidden_amount) const { +int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { - // returns the number of hidden and unhidden lines from p_line_from to p_line_from + amount of visible lines - ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(unhidden_amount)); + // returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines) + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); if (!is_hiding_enabled()) - return ABS(unhidden_amount); + return ABS(visible_amount); + int num_visible = 0; int num_total = 0; - if (unhidden_amount >= 0) { + if (visible_amount >= 0) { for (int i = p_line_from; i < text.size(); i++) { num_total++; - if (!is_line_hidden(i)) + if (!is_line_hidden(i)) { num_visible++; - if (num_visible >= unhidden_amount) + } + if (num_visible >= visible_amount) break; } } else { - unhidden_amount = ABS(unhidden_amount); + visible_amount = ABS(visible_amount); for (int i = p_line_from; i >= 0; i--) { num_total++; - if (!is_line_hidden(i)) + if (!is_line_hidden(i)) { num_visible++; - if (num_visible >= unhidden_amount) + } + if (num_visible >= visible_amount) break; } } return num_total; } -bool TextEdit::is_last_visible_line(int p_line) const { +int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const { - ERR_FAIL_INDEX_V(p_line, text.size(), false); + // returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows) + // wrap index is set to the wrap index of the last line + wrap_index = 0; + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); - if (p_line == text.size() - 1) - return true; + if (!is_hiding_enabled() && !is_wrap_enabled()) + return ABS(visible_amount); + + int num_visible = 0; + int num_total = 0; + if (visible_amount == 0) { + num_total = 0; + wrap_index = 0; + } else if (visible_amount > 0) { + int i; + num_visible -= p_wrap_index_from; + for (i = p_line_from; i < text.size(); i++) { + num_total++; + if (!is_line_hidden(i)) { + num_visible++; + num_visible += times_line_wraps(i); + } + if (num_visible >= visible_amount) + break; + } + wrap_index = times_line_wraps(MIN(i, text.size() - 1)) - (num_visible - visible_amount); + } else { + visible_amount = ABS(visible_amount); + int i; + num_visible -= times_line_wraps(p_line_from) - p_wrap_index_from; + for (i = p_line_from; i >= 0; i--) { + num_total++; + if (!is_line_hidden(i)) { + num_visible++; + num_visible += times_line_wraps(i); + } + if (num_visible >= visible_amount) + break; + } + wrap_index = (num_visible - visible_amount); + } + wrap_index = MAX(wrap_index, 0); + return num_total; +} + +int TextEdit::get_last_unhidden_line() const { + // returns the last line in the text that is not hidden if (!is_hiding_enabled()) - return false; + return text.size() - 1; - for (int i = p_line + 1; i < text.size(); i++) { - if (!is_line_hidden(i)) - return false; + int last_line; + for (last_line = text.size() - 1; last_line > 0; last_line--) { + if (!is_line_hidden(last_line)) { + break; + } } - - return true; + return last_line; } int TextEdit::get_indent_level(int p_line) const { @@ -4713,7 +5010,7 @@ int TextEdit::get_indent_level(int p_line) const { break; } } - return tab_count + whitespace_count / indent_size; + return tab_count * indent_size + whitespace_count; } bool TextEdit::is_line_comment(int p_line) const { @@ -5061,6 +5358,7 @@ bool TextEdit::is_drawing_tabs() const { void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { override_selected_font_color = p_override_selected_font_color; } + bool TextEdit::is_overriding_selected_font_color() const { return override_selected_font_color; } @@ -5081,58 +5379,143 @@ bool TextEdit::is_insert_text_operation() { uint32_t TextEdit::get_version() const { return current_op.version; } + uint32_t TextEdit::get_saved_version() const { return saved_version; } + void TextEdit::tag_saved_version() { saved_version = get_version(); } -int TextEdit::get_v_scroll() const { +double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { - return v_scroll->get_value(); -} -void TextEdit::set_v_scroll(int p_scroll) { + if (!is_wrap_enabled() && !is_hiding_enabled()) + return p_line; - if (p_scroll < 0) { - p_scroll = 0; - } - if (!scroll_past_end_of_file_enabled) { - if (p_scroll + get_visible_rows() > get_total_unhidden_rows()) { - int num_rows = num_lines_from(CLAMP(p_scroll, 0, text.size() - 1), MIN(get_visible_rows(), text.size() - 1 - p_scroll)); - p_scroll = text.size() - num_rows; + // count the number of visible lines up to this line + double new_line_scroll_pos = 0; + int to = CLAMP(p_line, 0, text.size() - 1); + for (int i = 0; i < to; i++) { + if (!text.is_hidden(i)) { + new_line_scroll_pos++; + new_line_scroll_pos += times_line_wraps(i); } } + new_line_scroll_pos += p_wrap_index; + return new_line_scroll_pos; +} + +void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) { + + set_v_scroll(get_scroll_pos_for_line(p_line, p_wrap_index)); +} + +void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { + + int visible_rows = get_visible_rows(); + int wi; + int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -visible_rows / 2, wi) + 1; + + set_v_scroll(get_scroll_pos_for_line(first_line, wi)); +} + +void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { + + int wi; + int first_line = p_line - num_lines_from_rows(p_line, p_wrap_index, -get_visible_rows() - 1, wi) + 1; + + set_v_scroll(get_scroll_pos_for_line(first_line, wi) + get_visible_rows_offset()); +} + +int TextEdit::get_first_visible_line() const { + + return CLAMP(cursor.line_ofs, 0, text.size() - 1); +} + +int TextEdit::get_last_visible_line() const { + + int first_vis_line = get_first_visible_line(); + int last_vis_line = 0; + int wi; + last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1; + last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1); + return last_vis_line; +} + +int TextEdit::get_last_visible_line_wrap_index() const { + + int first_vis_line = get_first_visible_line(); + int last_vis_line = 0; + int wi; + last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1; + return wi; +} + +double TextEdit::get_visible_rows_offset() const { + + double total = cache.size.height; + total -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) + total -= h_scroll->get_size().height; + total /= (double)get_row_height(); + total = total - floor(total); + total = -CLAMP(total, 0.001, 1) + 1; + return total; +} + +double TextEdit::get_v_scroll_offset() const { + + double val = get_v_scroll() - floor(get_v_scroll()); + return CLAMP(val, 0, 1); +} + +double TextEdit::get_v_scroll() const { + + return v_scroll->get_value(); +} + +void TextEdit::set_v_scroll(double p_scroll) { + v_scroll->set_value(p_scroll); - cursor.line_ofs = num_lines_from(0, p_scroll); - line_scroll_pos = p_scroll; + int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); + if (p_scroll >= max_v_scroll - 1.0) + _scroll_moved(v_scroll->get_value()); } int TextEdit::get_h_scroll() const { return h_scroll->get_value(); } + void TextEdit::set_h_scroll(int p_scroll) { + if (p_scroll < 0) { + p_scroll = 0; + } h_scroll->set_value(p_scroll); } void TextEdit::set_smooth_scroll_enabled(bool p_enable) { + v_scroll->set_smooth_scroll_enabled(p_enable); smooth_scroll_enabled = p_enable; } bool TextEdit::is_smooth_scroll_enabled() const { + return smooth_scroll_enabled; } void TextEdit::set_v_scroll_speed(float p_speed) { + v_scroll_speed = p_speed; } float TextEdit::get_v_scroll_speed() const { + return v_scroll_speed; } @@ -5612,7 +5995,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column); ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line); @@ -5629,8 +6012,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly); ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly); - ClassDB::bind_method(D_METHOD("set_wrap", "enable"), &TextEdit::set_wrap); - ClassDB::bind_method(D_METHOD("is_wrapping"), &TextEdit::is_wrapping); + ClassDB::bind_method(D_METHOD("set_wrap_enabled", "enable"), &TextEdit::set_wrap_enabled); + ClassDB::bind_method(D_METHOD("is_wrap_enabled"), &TextEdit::is_wrap_enabled); // ClassDB::bind_method(D_METHOD("set_max_chars", "amount"), &TextEdit::set_max_chars); // ClassDB::bind_method(D_METHOD("get_max_char"), &TextEdit::get_max_chars); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &TextEdit::set_context_menu_enabled); @@ -5708,7 +6091,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_lines"), "set_wrap", "is_wrapping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled"); // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "max_chars"), "set_max_chars", "get_max_chars"); ADD_GROUP("Caret", "caret_"); @@ -5743,7 +6126,8 @@ TextEdit::TextEdit() { draw_caret = true; max_chars = 0; clear(); - wrap = false; + wrap_enabled = false; + wrap_right_offset = 10; set_focus_mode(FOCUS_ALL); syntax_highlighter = NULL; _update_caches(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 60c6ab4929..5c82d1ac20 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -76,6 +76,7 @@ public: bool marked : 1; bool breakpoint : 1; bool hidden : 1; + int wrap_amount_cache : 24; Map<int, ColorRegionInfo> region_info; String data; }; @@ -94,6 +95,9 @@ public: void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; } int get_line_width(int p_line) const; int get_max_width(bool p_exclude_hidden = false) const; + int get_char_width(CharType c, CharType next_c, int px) const; + void set_line_wrap_amount(int p_line, int p_wrap_amount) const; + int get_line_wrap_amount(int p_line) const; const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const; void set(int p_line, const String &p_text); void set_marked(int p_line, bool p_marked) { text[p_line].marked = p_marked; } @@ -106,7 +110,8 @@ public: void remove(int p_at); int size() const { return text.size(); } void clear(); - void clear_caches(); + void clear_width_cache(); + void clear_wrap_cache(); _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; } Text() { indent_size = 4; } }; @@ -115,7 +120,7 @@ private: struct Cursor { int last_fit_x; int line, column; ///< cursor - int x_ofs, line_ofs; + int x_ofs, line_ofs, wrap_ofs; } cursor; struct Selection { @@ -263,8 +268,11 @@ private: bool block_caret; bool right_click_moves_caret; + bool wrap_enabled; + int wrap_at; + int wrap_right_offset; + bool setting_row; - bool wrap; bool draw_tabs; bool override_selected_font_color; bool cursor_changed_dirty; @@ -321,19 +329,34 @@ private: int search_result_line; int search_result_col; - double line_scroll_pos; - bool context_menu_enabled; int get_visible_rows() const; - int get_total_unhidden_rows() const; - double get_line_scroll_pos(bool p_recalculate = false) const; - void update_line_scroll_pos(); - + int get_total_visible_rows() const; + + void update_cursor_wrap_offset(); + void update_wrap_at(); + bool line_wraps(int line) const; + int times_line_wraps(int line) const; + Vector<String> get_wrap_rows_text(int p_line) const; + int get_cursor_wrap_index() const; + int get_line_wrap_index_at_col(int p_line, int p_column) const; int get_char_count(); + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; + void set_line_as_first_visible(int p_line, int p_wrap_index = 0); + void set_line_as_center_visible(int p_line, int p_wrap_index = 0); + void set_line_as_last_visible(int p_line, int p_wrap_index = 0); + int get_first_visible_line() const; + int get_last_visible_line() const; + int get_last_visible_line_wrap_index() const; + double get_visible_rows_offset() const; + double get_v_scroll_offset() const; + + int get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; + int get_column_x_offset_for_line(int p_char, int p_line) const; int get_char_pos_for(int p_px, String p_str) const; - int get_column_x_offset(int p_char, String p_str); + int get_column_x_offset(int p_char, String p_str) const; void adjust_viewport_to_cursor(); double get_scroll_line_diff() const; @@ -455,8 +478,10 @@ public: bool is_line_hidden(int p_line) const; void fold_all_lines(); void unhide_all_lines(); - int num_lines_from(int p_line_from, int unhidden_amount) const; - bool is_last_visible_line(int p_line) const; + int num_lines_from(int p_line_from, int visible_amount) const; + int num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const; + int get_last_unhidden_line() const; + bool can_fold(int p_line) const; bool is_folded(int p_line) const; void fold_line(int p_line); @@ -493,7 +518,7 @@ public: void center_viewport_to_cursor(); void cursor_set_column(int p_col, bool p_adjust_viewport = true); - void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true); + void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); int cursor_get_column() const; int cursor_get_line() const; @@ -516,8 +541,8 @@ public: void set_max_chars(int p_max_chars); int get_max_chars() const; - void set_wrap(bool p_wrap); - bool is_wrapping() const; + void set_wrap_enabled(bool p_wrap_enabled); + bool is_wrap_enabled() const; void clear(); @@ -578,8 +603,8 @@ public: Color get_member_color(String p_member) const; void clear_member_keywords(); - int get_v_scroll() const; - void set_v_scroll(int p_scroll); + double get_v_scroll() const; + void set_v_scroll(double p_scroll); int get_h_scroll() const; void set_h_scroll(int p_scroll); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index e6ea4e4b4a..3643aedb85 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -176,6 +176,9 @@ void Node::_propagate_ready() { data.children[i]->_propagate_ready(); } data.blocked--; + + notification(NOTIFICATION_POST_ENTER_TREE); + if (data.ready_first) { data.ready_first = false; notification(NOTIFICATION_READY); diff --git a/scene/main/node.h b/scene/main/node.h index 4ff1247e14..540f34cba7 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -237,6 +237,7 @@ public: NOTIFICATION_TRANSLATION_CHANGED = 24, NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, + NOTIFICATION_POST_ENTER_TREE = 27, }; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 295f131db3..5a3447bec0 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1345,7 +1345,7 @@ void Viewport::_gui_show_tooltip() { gui.tooltip_label->set_anchor_and_margin(MARGIN_RIGHT, Control::ANCHOR_END, -ttp->get_margin(MARGIN_RIGHT)); gui.tooltip_label->set_anchor_and_margin(MARGIN_BOTTOM, Control::ANCHOR_END, -ttp->get_margin(MARGIN_BOTTOM)); gui.tooltip_label->set_text(tooltip.strip_edges()); - Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_label->get_combined_minimum_size() + ttp->get_minimum_size()); + Rect2 r(gui.tooltip_pos + Point2(10, 10), gui.tooltip_label->get_minimum_size() + ttp->get_minimum_size()); Rect2 vr = gui.tooltip_label->get_viewport_rect(); if (r.size.x + r.position.x > vr.size.x) r.position.x = vr.size.x - r.size.x; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 5ac9344f31..3ea856541e 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -544,6 +544,11 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("updown", "SpinBox", make_icon(spinbox_updown_png)); + //scroll container + Ref<StyleBoxEmpty> empty; + empty.instance(); + theme->set_stylebox("bg", "ScrollContainer", empty); + // WindowDialog theme->set_stylebox("panel", "WindowDialog", sb_expand(make_stylebox(popup_window_png, 10, 26, 10, 8), 8, 24, 8, 6)); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index d87644381c..b0620d3363 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -222,7 +222,6 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { continue; Array a = surface_get_arrays(i); - int vcount = 0; if (i == 0) { arrays = a; @@ -230,6 +229,7 @@ Ref<Mesh> Mesh::create_outline(float p_margin) const { index_accum += v.size(); } else { + int vcount = 0; for (int j = 0; j < arrays.size(); j++) { if (arrays[j].get_type() == Variant::NIL || a[j].get_type() == Variant::NIL) { @@ -1194,8 +1194,6 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe for (int j = 0; j < 3; j++) { - int vertex_idx = gen_vertices[gen_indices[i + j]]; - SurfaceTool::Vertex v = surfaces[surface].vertices[uv_index[gen_vertices[gen_indices[i + j]]].second]; if (surfaces[surface].format & ARRAY_FORMAT_COLOR) { diff --git a/thirdparty/README.md b/thirdparty/README.md index 1d4e56d42e..0848be285b 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -218,7 +218,7 @@ Godot-made change marked with `// -- GODOT --` comments. ## libwebp - Upstream: https://chromium.googlesource.com/webm/libwebp/ -- Version: 0.6.1 +- Version: 1.0.0 - License: BSD-3-Clause Files extracted from upstream source: diff --git a/thirdparty/libwebp/AUTHORS b/thirdparty/libwebp/AUTHORS index 70423cb4dd..83c7b9c5eb 100644 --- a/thirdparty/libwebp/AUTHORS +++ b/thirdparty/libwebp/AUTHORS @@ -2,25 +2,38 @@ Contributors: - Charles Munger (clm at google dot com) - Christian Duvivier (cduvivier at google dot com) - Djordje Pesut (djordje dot pesut at imgtec dot com) +- Hui Su (huisu at google dot com) - James Zern (jzern at google dot com) - Jan Engelhardt (jengelh at medozas dot de) +- Jehan (jehan at girinstud dot io) - Johann (johann dot koenig at duck dot com) - Jovan Zelincevic (jovan dot zelincevic at imgtec dot com) - Jyrki Alakuijala (jyrki at google dot com) -- levytamar82 (tamar dot levy at intel dot com) +- Lode Vandevenne (lode at google dot com) - Lou Quillio (louquillio at google dot com) - Mans Rullgard (mans at mansr dot com) +- Marcin Kowalczyk (qrczak at google dot com) - Martin Olsson (mnemo at minimum dot se) - Mikołaj Zalewski (mikolajz at google dot com) +- Mislav Bradac (mislavm at google dot com) +- Nico Weber (thakis at chromium dot org) - Noel Chromium (noel at chromium dot org) +- Owen Rodley (orodley at google dot com) +- Parag Salasakar (img dot mips1 at gmail dot com) - Pascal Massimino (pascal dot massimino at gmail dot com) - Paweł Hajdan, Jr (phajdan dot jr at chromium dot org) - Pierre Joye (pierre dot php at gmail dot com) - Sam Clegg (sbc at chromium dot org) +- Scott Hancher (seh at google dot com) - Scott LaVarnway (slavarnway at google dot com) - Scott Talbot (s at chikachow dot org) - Slobodan Prijic (slobodan dot prijic at imgtec dot com) - Somnath Banerjee (somnath dot banerjee at gmail dot com) +- Sriraman Tallam (tmsriram at google dot com) +- Tamar Levy (tamar dot levy at intel dot com) - Timothy Gu (timothygu99 at gmail dot com) - Urvang Joshi (urvang at google dot com) - Vikas Arora (vikasa at google dot com) +- Vincent Rabaud (vrabaud at google dot com) +- Vlad Tsyrklevich (vtsyrklevich at chromium dot org) +- Yang Zhang (yang dot zhang at arm dot com) diff --git a/thirdparty/libwebp/src/dec/frame_dec.c b/thirdparty/libwebp/src/dec/frame_dec.c index 517d0f5850..a9d5430d00 100644 --- a/thirdparty/libwebp/src/dec/frame_dec.c +++ b/thirdparty/libwebp/src/dec/frame_dec.c @@ -400,7 +400,9 @@ static void DitherRow(VP8Decoder* const dec) { #define MACROBLOCK_VPOS(mb_y) ((mb_y) * 16) // vertical position of a MB // Finalize and transmit a complete row. Return false in case of user-abort. -static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { +static int FinishRow(void* arg1, void* arg2) { + VP8Decoder* const dec = (VP8Decoder*)arg1; + VP8Io* const io = (VP8Io*)arg2; int ok = 1; const VP8ThreadContext* const ctx = &dec->thread_ctx_; const int cache_id = ctx->id_; @@ -448,10 +450,9 @@ static int FinishRow(VP8Decoder* const dec, VP8Io* const io) { if (y_end > io->crop_bottom) { y_end = io->crop_bottom; // make sure we don't overflow on last row. } + // If dec->alpha_data_ is not NULL, we have some alpha plane present. io->a = NULL; if (dec->alpha_data_ != NULL && y_start < y_end) { - // TODO(skal): testing presence of alpha with dec->alpha_data_ is not a - // good idea. io->a = VP8DecompressAlphaRows(dec, io, y_start, y_end - y_start); if (io->a == NULL) { return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, @@ -558,7 +559,6 @@ VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { if (io->bypass_filtering) { dec->filter_type_ = 0; } - // TODO(skal): filter type / strength / sharpness forcing // Define the area where we can skip in-loop filtering, in case of cropping. // @@ -569,8 +569,6 @@ VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { // Means: there's a dependency chain that goes all the way up to the // top-left corner of the picture (MB #0). We must filter all the previous // macroblocks. - // TODO(skal): add an 'approximate_decoding' option, that won't produce - // a 1:1 bit-exactness for complex filtering? { const int extra_pixels = kFilterExtraRows[dec->filter_type_]; if (dec->filter_type_ == 2) { @@ -651,7 +649,7 @@ static int InitThreadContext(VP8Decoder* const dec) { } worker->data1 = dec; worker->data2 = (void*)&dec->thread_ctx_.io_; - worker->hook = (WebPWorkerHook)FinishRow; + worker->hook = FinishRow; dec->num_caches_ = (dec->filter_type_ > 0) ? MT_CACHE_LINES : MT_CACHE_LINES - 1; } else { diff --git a/thirdparty/libwebp/src/dec/vp8_dec.c b/thirdparty/libwebp/src/dec/vp8_dec.c index 6212efd179..c904b529f6 100644 --- a/thirdparty/libwebp/src/dec/vp8_dec.c +++ b/thirdparty/libwebp/src/dec/vp8_dec.c @@ -491,7 +491,7 @@ static int GetCoeffsAlt(VP8BitReader* const br, return 16; } -WEBP_TSAN_IGNORE_FUNCTION static void InitGetCoeffs(void) { +static WEBP_TSAN_IGNORE_FUNCTION void InitGetCoeffs(void) { if (GetCoeffs == NULL) { if (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kSlowSSSE3)) { GetCoeffs = GetCoeffsAlt; diff --git a/thirdparty/libwebp/src/dec/vp8i_dec.h b/thirdparty/libwebp/src/dec/vp8i_dec.h index 28244d9d7a..c929933e1c 100644 --- a/thirdparty/libwebp/src/dec/vp8i_dec.h +++ b/thirdparty/libwebp/src/dec/vp8i_dec.h @@ -30,9 +30,9 @@ extern "C" { // Various defines and enums // version numbers -#define DEC_MAJ_VERSION 0 -#define DEC_MIN_VERSION 6 -#define DEC_REV_VERSION 1 +#define DEC_MAJ_VERSION 1 +#define DEC_MIN_VERSION 0 +#define DEC_REV_VERSION 0 // YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). // Constraints are: We need to store one 16x16 block of luma samples (y), diff --git a/thirdparty/libwebp/src/dec/vp8l_dec.c b/thirdparty/libwebp/src/dec/vp8l_dec.c index 42ea3b5e4c..0570f53a77 100644 --- a/thirdparty/libwebp/src/dec/vp8l_dec.c +++ b/thirdparty/libwebp/src/dec/vp8l_dec.c @@ -1643,17 +1643,17 @@ int VP8LDecodeImage(VP8LDecoder* const dec) { #if !defined(WEBP_REDUCE_SIZE) if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err; - - if (io->use_scaling || WebPIsPremultipliedMode(dec->output_->colorspace)) { - // need the alpha-multiply functions for premultiplied output or rescaling - WebPInitAlphaProcessing(); - } #else if (io->use_scaling) { dec->status_ = VP8_STATUS_INVALID_PARAM; goto Err; } #endif + if (io->use_scaling || WebPIsPremultipliedMode(dec->output_->colorspace)) { + // need the alpha-multiply functions for premultiplied output or rescaling + WebPInitAlphaProcessing(); + } + if (!WebPIsRGBMode(dec->output_->colorspace)) { WebPInitConvertARGBToYUV(); if (dec->output_->u.YUVA.a != NULL) WebPInitAlphaProcessing(); diff --git a/thirdparty/libwebp/src/demux/demux.c b/thirdparty/libwebp/src/demux/demux.c index 79c24a5a7f..684215e3de 100644 --- a/thirdparty/libwebp/src/demux/demux.c +++ b/thirdparty/libwebp/src/demux/demux.c @@ -23,9 +23,9 @@ #include "src/webp/demux.h" #include "src/webp/format_constants.h" -#define DMUX_MAJ_VERSION 0 -#define DMUX_MIN_VERSION 3 -#define DMUX_REV_VERSION 3 +#define DMUX_MAJ_VERSION 1 +#define DMUX_MIN_VERSION 0 +#define DMUX_REV_VERSION 0 typedef struct { size_t start_; // start location of the data diff --git a/thirdparty/libwebp/src/dsp/alpha_processing.c b/thirdparty/libwebp/src/dsp/alpha_processing.c index 590e3bc312..819d1391f2 100644 --- a/thirdparty/libwebp/src/dsp/alpha_processing.c +++ b/thirdparty/libwebp/src/dsp/alpha_processing.c @@ -366,6 +366,16 @@ static WEBP_INLINE uint32_t MakeARGB32(int a, int r, int g, int b) { return (((uint32_t)a << 24) | (r << 16) | (g << 8) | b); } +#ifdef WORDS_BIGENDIAN +static void PackARGB_C(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int len, uint32_t* out) { + int i; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(a[4 * i], r[4 * i], g[4 * i], b[4 * i]); + } +} +#endif + static void PackRGB_C(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out) { int i, offset = 0; @@ -381,6 +391,10 @@ int (*WebPDispatchAlpha)(const uint8_t*, int, int, int, uint8_t*, int); void (*WebPDispatchAlphaToGreen)(const uint8_t*, int, int, int, uint32_t*, int); int (*WebPExtractAlpha)(const uint8_t*, int, int, int, uint8_t*, int); void (*WebPExtractGreen)(const uint32_t* argb, uint8_t* alpha, int size); +#ifdef WORDS_BIGENDIAN +void (*WebPPackARGB)(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int, uint32_t*); +#endif void (*WebPPackRGB)(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out); @@ -395,16 +409,14 @@ extern void WebPInitAlphaProcessingSSE2(void); extern void WebPInitAlphaProcessingSSE41(void); extern void WebPInitAlphaProcessingNEON(void); -static volatile VP8CPUInfo alpha_processing_last_cpuinfo_used = - (VP8CPUInfo)&alpha_processing_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessing(void) { - if (alpha_processing_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitAlphaProcessing) { WebPMultARGBRow = WebPMultARGBRow_C; WebPMultRow = WebPMultRow_C; WebPApplyAlphaMultiply4444 = ApplyAlphaMultiply_16b_C; +#ifdef WORDS_BIGENDIAN + WebPPackARGB = PackARGB_C; +#endif WebPPackRGB = PackRGB_C; #if !WEBP_NEON_OMIT_C_CODE WebPApplyAlphaMultiply = ApplyAlphaMultiply_C; @@ -451,9 +463,10 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessing(void) { assert(WebPDispatchAlphaToGreen != NULL); assert(WebPExtractAlpha != NULL); assert(WebPExtractGreen != NULL); +#ifdef WORDS_BIGENDIAN + assert(WebPPackARGB != NULL); +#endif assert(WebPPackRGB != NULL); assert(WebPHasAlpha8b != NULL); assert(WebPHasAlpha32b != NULL); - - alpha_processing_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c b/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c index e0dc91bab9..0090e87cd1 100644 --- a/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c +++ b/thirdparty/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c @@ -125,6 +125,49 @@ static void MultARGBRow_MIPSdspR2(uint32_t* const ptr, int width, } } +#ifdef WORDS_BIGENDIAN +static void PackARGB_MIPSdspR2(const uint8_t* a, const uint8_t* r, + const uint8_t* g, const uint8_t* b, int len, + uint32_t* out) { + int temp0, temp1, temp2, temp3, offset; + const int rest = len & 1; + const uint32_t* const loop_end = out + len - rest; + const int step = 4; + __asm__ volatile ( + "xor %[offset], %[offset], %[offset] \n\t" + "beq %[loop_end], %[out], 0f \n\t" + "2: \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "addiu %[out], %[out], 4 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], -4(%[out]) \n\t" + "addu %[offset], %[offset], %[step] \n\t" + "bne %[loop_end], %[out], 2b \n\t" + "0: \n\t" + "beq %[rest], $zero, 1f \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], 0(%[out]) \n\t" + "1: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [offset]"=&r"(offset), [out]"+&r"(out) + : [a]"r"(a), [r]"r"(r), [g]"r"(g), [b]"r"(b), [step]"r"(step), + [loop_end]"r"(loop_end), [rest]"r"(rest) + : "memory" + ); +} +#endif // WORDS_BIGENDIAN + static void PackRGB_MIPSdspR2(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out) { @@ -172,6 +215,9 @@ extern void WebPInitAlphaProcessingMIPSdspR2(void); WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingMIPSdspR2(void) { WebPDispatchAlpha = DispatchAlpha_MIPSdspR2; WebPMultARGBRow = MultARGBRow_MIPSdspR2; +#ifdef WORDS_BIGENDIAN + WebPPackARGB = PackARGB_MIPSdspR2; +#endif WebPPackRGB = PackRGB_MIPSdspR2; } diff --git a/thirdparty/libwebp/src/dsp/common_sse2.h b/thirdparty/libwebp/src/dsp/common_sse2.h index 995d7cf4ea..e9f1ebff44 100644 --- a/thirdparty/libwebp/src/dsp/common_sse2.h +++ b/thirdparty/libwebp/src/dsp/common_sse2.h @@ -128,9 +128,9 @@ static WEBP_INLINE void VP8Transpose_2_4x4_16b( // Pack the planar buffers // rrrr... rrrr... gggg... gggg... bbbb... bbbb.... // triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... -static WEBP_INLINE void VP8PlanarTo24b(__m128i* const in0, __m128i* const in1, - __m128i* const in2, __m128i* const in3, - __m128i* const in4, __m128i* const in5) { +static WEBP_INLINE void VP8PlanarTo24b_SSE2( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { // The input is 6 registers of sixteen 8b but for the sake of explanation, // let's take 6 registers of four 8b values. // To pack, we will keep taking one every two 8b integer and move it @@ -159,10 +159,10 @@ static WEBP_INLINE void VP8PlanarTo24b(__m128i* const in0, __m128i* const in1, // Convert four packed four-channel buffers like argbargbargbargb... into the // split channels aaaaa ... rrrr ... gggg .... bbbbb ...... -static WEBP_INLINE void VP8L32bToPlanar(__m128i* const in0, - __m128i* const in1, - __m128i* const in2, - __m128i* const in3) { +static WEBP_INLINE void VP8L32bToPlanar_SSE2(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { // Column-wise transpose. const __m128i A0 = _mm_unpacklo_epi8(*in0, *in1); const __m128i A1 = _mm_unpackhi_epi8(*in0, *in1); diff --git a/thirdparty/libwebp/src/dsp/common_sse41.h b/thirdparty/libwebp/src/dsp/common_sse41.h new file mode 100644 index 0000000000..2f173c024a --- /dev/null +++ b/thirdparty/libwebp/src/dsp/common_sse41.h @@ -0,0 +1,132 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 code common to several files. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_COMMON_SSE41_H_ +#define WEBP_DSP_COMMON_SSE41_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WEBP_USE_SSE41) +#include <smmintrin.h> + +//------------------------------------------------------------------------------ +// Channel mixing. +// Shuffles the input buffer as A0 0 0 A1 0 0 A2 ... +#define WEBP_SSE41_SHUFF(OUT, IN0, IN1) \ + OUT##0 = _mm_shuffle_epi8(*IN0, shuff0); \ + OUT##1 = _mm_shuffle_epi8(*IN0, shuff1); \ + OUT##2 = _mm_shuffle_epi8(*IN0, shuff2); \ + OUT##3 = _mm_shuffle_epi8(*IN1, shuff0); \ + OUT##4 = _mm_shuffle_epi8(*IN1, shuff1); \ + OUT##5 = _mm_shuffle_epi8(*IN1, shuff2); + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void VP8PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { + __m128i R0, R1, R2, R3, R4, R5; + __m128i G0, G1, G2, G3, G4, G5; + __m128i B0, B1, B2, B3, B4, B5; + + // Process R. + { + const __m128i shuff0 = _mm_set_epi8( + 5, -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + -1, -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1); + WEBP_SSE41_SHUFF(R, in0, in1) + } + + // Process G. + { + // Same as before, just shifted to the left by one and including the right + // padding. + const __m128i shuff0 = _mm_set_epi8( + -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1); + const __m128i shuff1 = _mm_set_epi8( + 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5); + const __m128i shuff2 = _mm_set_epi8( + -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1); + WEBP_SSE41_SHUFF(G, in2, in3) + } + + // Process B. + { + const __m128i shuff0 = _mm_set_epi8( + -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1, -1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1, 10); + WEBP_SSE41_SHUFF(B, in4, in5) + } + + // OR the different channels. + { + const __m128i RG0 = _mm_or_si128(R0, G0); + const __m128i RG1 = _mm_or_si128(R1, G1); + const __m128i RG2 = _mm_or_si128(R2, G2); + const __m128i RG3 = _mm_or_si128(R3, G3); + const __m128i RG4 = _mm_or_si128(R4, G4); + const __m128i RG5 = _mm_or_si128(R5, G5); + *in0 = _mm_or_si128(RG0, B0); + *in1 = _mm_or_si128(RG1, B1); + *in2 = _mm_or_si128(RG2, B2); + *in3 = _mm_or_si128(RG3, B3); + *in4 = _mm_or_si128(RG4, B4); + *in5 = _mm_or_si128(RG5, B5); + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert four packed four-channel buffers like argbargbargbargb... into the +// split channels aaaaa ... rrrr ... gggg .... bbbbb ...... +static WEBP_INLINE void VP8L32bToPlanar_SSE41(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { + // aaaarrrrggggbbbb + const __m128i shuff0 = + _mm_set_epi8(15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0); + const __m128i A0 = _mm_shuffle_epi8(*in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(*in1, shuff0); + const __m128i A2 = _mm_shuffle_epi8(*in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(*in3, shuff0); + // A0A1R0R1 + // G0G1B0B1 + // A2A3R2R3 + // G0G1B0B1 + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_unpackhi_epi32(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_unpackhi_epi32(A2, A3); + *in3 = _mm_unpacklo_epi64(B0, B2); + *in2 = _mm_unpackhi_epi64(B0, B2); + *in1 = _mm_unpacklo_epi64(B1, B3); + *in0 = _mm_unpackhi_epi64(B1, B3); +} + +#endif // WEBP_USE_SSE41 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_COMMON_SSE41_H_ diff --git a/thirdparty/libwebp/src/dsp/cost.c b/thirdparty/libwebp/src/dsp/cost.c index a732389d58..634ccc2085 100644 --- a/thirdparty/libwebp/src/dsp/cost.c +++ b/thirdparty/libwebp/src/dsp/cost.c @@ -378,12 +378,7 @@ extern void VP8EncDspCostInitMIPS32(void); extern void VP8EncDspCostInitMIPSdspR2(void); extern void VP8EncDspCostInitSSE2(void); -static volatile VP8CPUInfo cost_last_cpuinfo_used = - (VP8CPUInfo)&cost_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInit(void) { - if (cost_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8EncDspCostInit) { VP8GetResidualCost = GetResidualCost_C; VP8SetResidualCoeffs = SetResidualCoeffs_C; @@ -405,8 +400,6 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInit(void) { } #endif } - - cost_last_cpuinfo_used = VP8GetCPUInfo; } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/dec.c b/thirdparty/libwebp/src/dsp/dec.c index 7e82407567..1119842dd3 100644 --- a/thirdparty/libwebp/src/dsp/dec.c +++ b/thirdparty/libwebp/src/dsp/dec.c @@ -741,12 +741,7 @@ extern void VP8DspInitMIPS32(void); extern void VP8DspInitMIPSdspR2(void); extern void VP8DspInitMSA(void); -static volatile VP8CPUInfo dec_last_cpuinfo_used = - (VP8CPUInfo)&dec_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8DspInit(void) { - if (dec_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8DspInit) { VP8InitClipTables(); #if !WEBP_NEON_OMIT_C_CODE @@ -889,6 +884,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8DspInit(void) { assert(VP8PredChroma8[5] != NULL); assert(VP8PredChroma8[6] != NULL); assert(VP8DitherCombine8x8 != NULL); - - dec_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/dsp.h b/thirdparty/libwebp/src/dsp/dsp.h index 99eefe092f..4ab77a5130 100644 --- a/thirdparty/libwebp/src/dsp/dsp.h +++ b/thirdparty/libwebp/src/dsp/dsp.h @@ -141,6 +141,42 @@ extern "C" { #endif #endif +#if defined(WEBP_USE_THREAD) && !defined(_WIN32) +#include <pthread.h> // NOLINT + +#define WEBP_DSP_INIT(func) do { \ + static volatile VP8CPUInfo func ## _last_cpuinfo_used = \ + (VP8CPUInfo)&func ## _last_cpuinfo_used; \ + static pthread_mutex_t func ## _lock = PTHREAD_MUTEX_INITIALIZER; \ + if (pthread_mutex_lock(&func ## _lock)) break; \ + if (func ## _last_cpuinfo_used != VP8GetCPUInfo) func(); \ + func ## _last_cpuinfo_used = VP8GetCPUInfo; \ + (void)pthread_mutex_unlock(&func ## _lock); \ +} while (0) +#else // !(defined(WEBP_USE_THREAD) && !defined(_WIN32)) +#define WEBP_DSP_INIT(func) do { \ + static volatile VP8CPUInfo func ## _last_cpuinfo_used = \ + (VP8CPUInfo)&func ## _last_cpuinfo_used; \ + if (func ## _last_cpuinfo_used == VP8GetCPUInfo) break; \ + func(); \ + func ## _last_cpuinfo_used = VP8GetCPUInfo; \ +} while (0) +#endif // defined(WEBP_USE_THREAD) && !defined(_WIN32) + +// Defines an Init + helper function that control multiple initialization of +// function pointers / tables. +/* Usage: + WEBP_DSP_INIT_FUNC(InitFunc) { + ...function body + } +*/ +#define WEBP_DSP_INIT_FUNC(name) \ + static WEBP_TSAN_IGNORE_FUNCTION void name ## _body(void); \ + WEBP_TSAN_IGNORE_FUNCTION void name(void) { \ + WEBP_DSP_INIT(name ## _body); \ + } \ + static WEBP_TSAN_IGNORE_FUNCTION void name ## _body(void) + #define WEBP_UBSAN_IGNORE_UNDEF #define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW #if defined(__clang__) && defined(__has_attribute) @@ -166,6 +202,13 @@ extern "C" { #define WEBP_SWAP_16BIT_CSP 0 #endif +// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__) +#if !defined(WORDS_BIGENDIAN) && \ + (defined(__BIG_ENDIAN__) || defined(_M_PPC) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))) +#define WORDS_BIGENDIAN +#endif + typedef enum { kSSE2, kSSE3, @@ -189,7 +232,7 @@ WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; // avoiding a compiler warning. #define WEBP_DSP_INIT_STUB(func) \ extern void func(void); \ - WEBP_TSAN_IGNORE_FUNCTION void func(void) {} + void func(void) {} //------------------------------------------------------------------------------ // Encoding @@ -578,6 +621,13 @@ void WebPMultRow_C(uint8_t* const ptr, const uint8_t* const alpha, int width, int inverse); void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse); +#ifdef WORDS_BIGENDIAN +// ARGB packing function: a/r/g/b input is rgba or bgra order. +extern void (*WebPPackARGB)(const uint8_t* a, const uint8_t* r, + const uint8_t* g, const uint8_t* b, int len, + uint32_t* out); +#endif + // RGB packing function. 'step' can be 3 or 4. r/g/b input is rgb or bgr order. extern void (*WebPPackRGB)(const uint8_t* r, const uint8_t* g, const uint8_t* b, int len, int step, uint32_t* out); diff --git a/thirdparty/libwebp/src/dsp/enc.c b/thirdparty/libwebp/src/dsp/enc.c index 1c807f1df7..fa23b40a30 100644 --- a/thirdparty/libwebp/src/dsp/enc.c +++ b/thirdparty/libwebp/src/dsp/enc.c @@ -740,12 +740,7 @@ extern void VP8EncDspInitMIPS32(void); extern void VP8EncDspInitMIPSdspR2(void); extern void VP8EncDspInitMSA(void); -static volatile VP8CPUInfo enc_last_cpuinfo_used = - (VP8CPUInfo)&enc_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInit(void) { - if (enc_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8EncDspInit) { VP8DspInit(); // common inverse transforms InitTables(); @@ -838,6 +833,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInit(void) { assert(VP8EncQuantizeBlockWHT != NULL); assert(VP8Copy4x4 != NULL); assert(VP8Copy16x8 != NULL); - - enc_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/filters.c b/thirdparty/libwebp/src/dsp/filters.c index ca5f877da7..069a22eaef 100644 --- a/thirdparty/libwebp/src/dsp/filters.c +++ b/thirdparty/libwebp/src/dsp/filters.c @@ -238,12 +238,7 @@ extern void VP8FiltersInitMSA(void); extern void VP8FiltersInitNEON(void); extern void VP8FiltersInitSSE2(void); -static volatile VP8CPUInfo filters_last_cpuinfo_used = - (VP8CPUInfo)&filters_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInit(void) { - if (filters_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8FiltersInit) { WebPUnfilters[WEBP_FILTER_NONE] = NULL; #if !WEBP_NEON_OMIT_C_CODE WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_C; @@ -289,6 +284,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInit(void) { assert(WebPFilters[WEBP_FILTER_HORIZONTAL] != NULL); assert(WebPFilters[WEBP_FILTER_VERTICAL] != NULL); assert(WebPFilters[WEBP_FILTER_GRADIENT] != NULL); - - filters_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/lossless.c b/thirdparty/libwebp/src/dsp/lossless.c index 83f553d9ad..f9b3c182d3 100644 --- a/thirdparty/libwebp/src/dsp/lossless.c +++ b/thirdparty/libwebp/src/dsp/lossless.c @@ -577,9 +577,6 @@ extern void VP8LDspInitNEON(void); extern void VP8LDspInitMIPSdspR2(void); extern void VP8LDspInitMSA(void); -static volatile VP8CPUInfo lossless_last_cpuinfo_used = - (VP8CPUInfo)&lossless_last_cpuinfo_used; - #define COPY_PREDICTOR_ARRAY(IN, OUT) do { \ (OUT)[0] = IN##0_C; \ (OUT)[1] = IN##1_C; \ @@ -599,9 +596,7 @@ static volatile VP8CPUInfo lossless_last_cpuinfo_used = (OUT)[15] = IN##0_C; \ } while (0); -WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInit(void) { - if (lossless_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8LDspInit) { COPY_PREDICTOR_ARRAY(Predictor, VP8LPredictors) COPY_PREDICTOR_ARRAY(Predictor, VP8LPredictors_C) COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd) @@ -658,8 +653,6 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInit(void) { assert(VP8LConvertBGRAToRGB565 != NULL); assert(VP8LMapColor32b != NULL); assert(VP8LMapColor8b != NULL); - - lossless_last_cpuinfo_used = VP8GetCPUInfo; } #undef COPY_PREDICTOR_ARRAY diff --git a/thirdparty/libwebp/src/dsp/lossless.h b/thirdparty/libwebp/src/dsp/lossless.h index a99dbda686..b2bbdfc93c 100644 --- a/thirdparty/libwebp/src/dsp/lossless.h +++ b/thirdparty/libwebp/src/dsp/lossless.h @@ -25,10 +25,6 @@ extern "C" { #endif -#ifdef WEBP_EXPERIMENTAL_FEATURES -#include "src/enc/delta_palettization_enc.h" -#endif // WEBP_EXPERIMENTAL_FEATURES - //------------------------------------------------------------------------------ // Decoding diff --git a/thirdparty/libwebp/src/dsp/lossless_enc.c b/thirdparty/libwebp/src/dsp/lossless_enc.c index 92ca3c0542..d608326fef 100644 --- a/thirdparty/libwebp/src/dsp/lossless_enc.c +++ b/thirdparty/libwebp/src/dsp/lossless_enc.c @@ -863,12 +863,7 @@ extern void VP8LEncDspInitMIPS32(void); extern void VP8LEncDspInitMIPSdspR2(void); extern void VP8LEncDspInitMSA(void); -static volatile VP8CPUInfo lossless_enc_last_cpuinfo_used = - (VP8CPUInfo)&lossless_enc_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInit(void) { - if (lossless_enc_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8LEncDspInit) { VP8LDspInit(); #if !WEBP_NEON_OMIT_C_CODE @@ -1011,8 +1006,6 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInit(void) { assert(VP8LPredictorsSub_C[13] != NULL); assert(VP8LPredictorsSub_C[14] != NULL); assert(VP8LPredictorsSub_C[15] != NULL); - - lossless_enc_last_cpuinfo_used = VP8GetCPUInfo; } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c b/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c index 1eaf35ca8e..f84a9909e1 100644 --- a/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c +++ b/thirdparty/libwebp/src/dsp/lossless_enc_sse2.c @@ -46,16 +46,14 @@ static void SubtractGreenFromBlueAndRed_SSE2(uint32_t* argb_data, //------------------------------------------------------------------------------ // Color Transform +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + static void TransformColor_SSE2(const VP8LMultipliers* const m, uint32_t* argb_data, int num_pixels) { - const __m128i mults_rb = _mm_set_epi16( - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_), - CST_5b(m->green_to_red_), CST_5b(m->green_to_blue_)); - const __m128i mults_b2 = _mm_set_epi16( - CST_5b(m->red_to_blue_), 0, CST_5b(m->red_to_blue_), 0, - CST_5b(m->red_to_blue_), 0, CST_5b(m->red_to_blue_), 0); + const __m128i mults_rb = MK_CST_16(CST_5b(m->green_to_red_), + CST_5b(m->green_to_blue_)); + const __m128i mults_b2 = MK_CST_16(CST_5b(m->red_to_blue_), 0); const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks const __m128i mask_rb = _mm_set1_epi32(0x00ff00ff); // red-blue masks int i; @@ -85,12 +83,8 @@ static void CollectColorBlueTransforms_SSE2(const uint32_t* argb, int stride, int tile_width, int tile_height, int green_to_blue, int red_to_blue, int histo[]) { - const __m128i mults_r = _mm_set_epi16( - CST_5b(red_to_blue), 0, CST_5b(red_to_blue), 0, - CST_5b(red_to_blue), 0, CST_5b(red_to_blue), 0); - const __m128i mults_g = _mm_set_epi16( - 0, CST_5b(green_to_blue), 0, CST_5b(green_to_blue), - 0, CST_5b(green_to_blue), 0, CST_5b(green_to_blue)); + const __m128i mults_r = MK_CST_16(CST_5b(red_to_blue), 0); + const __m128i mults_g = MK_CST_16(0, CST_5b(green_to_blue)); const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask const __m128i mask_b = _mm_set1_epi32(0x0000ff); // blue mask int y; @@ -135,9 +129,7 @@ static void CollectColorBlueTransforms_SSE2(const uint32_t* argb, int stride, static void CollectColorRedTransforms_SSE2(const uint32_t* argb, int stride, int tile_width, int tile_height, int green_to_red, int histo[]) { - const __m128i mults_g = _mm_set_epi16( - 0, CST_5b(green_to_red), 0, CST_5b(green_to_red), - 0, CST_5b(green_to_red), 0, CST_5b(green_to_red)); + const __m128i mults_g = MK_CST_16(0, CST_5b(green_to_red)); const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask const __m128i mask = _mm_set1_epi32(0xff); @@ -174,6 +166,7 @@ static void CollectColorRedTransforms_SSE2(const uint32_t* argb, int stride, } } #undef SPAN +#undef MK_CST_16 //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c b/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c index 3526a342d3..2e12a712eb 100644 --- a/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c +++ b/thirdparty/libwebp/src/dsp/lossless_enc_sse41.c @@ -18,6 +18,9 @@ #include <smmintrin.h> #include "src/dsp/lossless.h" +// For sign-extended multiplying constants, pre-shifted by 5: +#define CST_5b(X) (((int16_t)((uint16_t)(X) << 8)) >> 5) + //------------------------------------------------------------------------------ // Subtract-Green Transform @@ -39,12 +42,103 @@ static void SubtractGreenFromBlueAndRed_SSE41(uint32_t* argb_data, } //------------------------------------------------------------------------------ +// Color Transform + +#define SPAN 8 +static void CollectColorBlueTransforms_SSE41(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + const __m128i mults_r = _mm_set1_epi16(CST_5b(red_to_blue)); + const __m128i mults_g = _mm_set1_epi16(CST_5b(green_to_blue)); + const __m128i mask_g = _mm_set1_epi16(0xff00); // green mask + const __m128i mask_gb = _mm_set1_epi32(0xffff); // green/blue mask + const __m128i mask_b = _mm_set1_epi16(0x00ff); // blue mask + const __m128i shuffler_lo = _mm_setr_epi8(-1, 2, -1, 6, -1, 10, -1, 14, -1, + -1, -1, -1, -1, -1, -1, -1); + const __m128i shuffler_hi = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, + 2, -1, 6, -1, 10, -1, 14); + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i r0 = _mm_shuffle_epi8(in0, shuffler_lo); + const __m128i r1 = _mm_shuffle_epi8(in1, shuffler_hi); + const __m128i r = _mm_or_si128(r0, r1); // r 0 + const __m128i gb0 = _mm_and_si128(in0, mask_gb); + const __m128i gb1 = _mm_and_si128(in1, mask_gb); + const __m128i gb = _mm_packus_epi32(gb0, gb1); // g b + const __m128i g = _mm_and_si128(gb, mask_g); // g 0 + const __m128i A = _mm_mulhi_epi16(r, mults_r); // x dbr + const __m128i B = _mm_mulhi_epi16(g, mults_g); // x dbg + const __m128i C = _mm_sub_epi8(gb, B); // x b' + const __m128i D = _mm_sub_epi8(C, A); // x b'' + const __m128i E = _mm_and_si128(D, mask_b); // 0 b'' + _mm_storeu_si128((__m128i*)values, E); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorBlueTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, + green_to_blue, red_to_blue, histo); + } + } +} + +static void CollectColorRedTransforms_SSE41(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + const __m128i mults_g = _mm_set1_epi16(CST_5b(green_to_red)); + const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask + const __m128i mask = _mm_set1_epi16(0xff); + + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i g0 = _mm_and_si128(in0, mask_g); // 0 0 | g 0 + const __m128i g1 = _mm_and_si128(in1, mask_g); + const __m128i g = _mm_packus_epi32(g0, g1); // g 0 + const __m128i A0 = _mm_srli_epi32(in0, 16); // 0 0 | x r + const __m128i A1 = _mm_srli_epi32(in1, 16); + const __m128i A = _mm_packus_epi32(A0, A1); // x r + const __m128i B = _mm_mulhi_epi16(g, mults_g); // x dr + const __m128i C = _mm_sub_epi8(A, B); // x r' + const __m128i D = _mm_and_si128(C, mask); // 0 r' + _mm_storeu_si128((__m128i*)values, D); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorRedTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, green_to_red, + histo); + } + } +} + +//------------------------------------------------------------------------------ // Entry point extern void VP8LEncDspInitSSE41(void); WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitSSE41(void) { VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed_SSE41; + VP8LCollectColorBlueTransforms = CollectColorBlueTransforms_SSE41; + VP8LCollectColorRedTransforms = CollectColorRedTransforms_SSE41; } #else // !WEBP_USE_SSE41 diff --git a/thirdparty/libwebp/src/dsp/lossless_sse2.c b/thirdparty/libwebp/src/dsp/lossless_sse2.c index 653b466cd6..17d7576419 100644 --- a/thirdparty/libwebp/src/dsp/lossless_sse2.c +++ b/thirdparty/libwebp/src/dsp/lossless_sse2.c @@ -453,14 +453,11 @@ static void TransformColorInverse_SSE2(const VP8LMultipliers* const m, int num_pixels, uint32_t* dst) { // sign-extended multiplying constants, pre-shifted by 5. #define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend - const __m128i mults_rb = _mm_set_epi16( - CST(green_to_red_), CST(green_to_blue_), - CST(green_to_red_), CST(green_to_blue_), - CST(green_to_red_), CST(green_to_blue_), - CST(green_to_red_), CST(green_to_blue_)); - const __m128i mults_b2 = _mm_set_epi16( - CST(red_to_blue_), 0, CST(red_to_blue_), 0, - CST(red_to_blue_), 0, CST(red_to_blue_), 0); +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + const __m128i mults_rb = MK_CST_16(CST(green_to_red_), CST(green_to_blue_)); + const __m128i mults_b2 = MK_CST_16(CST(red_to_blue_), 0); +#undef MK_CST_16 #undef CST const __m128i mask_ag = _mm_set1_epi32(0xff00ff00); // alpha-green masks int i; @@ -503,11 +500,11 @@ static void ConvertBGRAToRGB_SSE2(const uint32_t* src, int num_pixels, __m128i in5 = _mm_loadu_si128(in + 5); __m128i in6 = _mm_loadu_si128(in + 6); __m128i in7 = _mm_loadu_si128(in + 7); - VP8L32bToPlanar(&in0, &in1, &in2, &in3); - VP8L32bToPlanar(&in4, &in5, &in6, &in7); + VP8L32bToPlanar_SSE2(&in0, &in1, &in2, &in3); + VP8L32bToPlanar_SSE2(&in4, &in5, &in6, &in7); // At this points, in1/in5 contains red only, in2/in6 green only ... // Pack the colors in 24b RGB. - VP8PlanarTo24b(&in1, &in5, &in2, &in6, &in3, &in7); + VP8PlanarTo24b_SSE2(&in1, &in5, &in2, &in6, &in3, &in7); _mm_storeu_si128(out + 0, in1); _mm_storeu_si128(out + 1, in5); _mm_storeu_si128(out + 2, in2); diff --git a/thirdparty/libwebp/src/dsp/rescaler.c b/thirdparty/libwebp/src/dsp/rescaler.c index 4b6b7834e5..f307d35056 100644 --- a/thirdparty/libwebp/src/dsp/rescaler.c +++ b/thirdparty/libwebp/src/dsp/rescaler.c @@ -204,11 +204,7 @@ extern void WebPRescalerDspInitMIPSdspR2(void); extern void WebPRescalerDspInitMSA(void); extern void WebPRescalerDspInitNEON(void); -static volatile VP8CPUInfo rescaler_last_cpuinfo_used = - (VP8CPUInfo)&rescaler_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInit(void) { - if (rescaler_last_cpuinfo_used == VP8GetCPUInfo) return; +WEBP_DSP_INIT_FUNC(WebPRescalerDspInit) { #if !defined(WEBP_REDUCE_SIZE) #if !WEBP_NEON_OMIT_C_CODE WebPRescalerExportRowExpand = WebPRescalerExportRowExpand_C; @@ -253,5 +249,4 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInit(void) { assert(WebPRescalerImportRowExpand != NULL); assert(WebPRescalerImportRowShrink != NULL); #endif // WEBP_REDUCE_SIZE - rescaler_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/rescaler_sse2.c b/thirdparty/libwebp/src/dsp/rescaler_sse2.c index f93b204fe1..64c50deab5 100644 --- a/thirdparty/libwebp/src/dsp/rescaler_sse2.c +++ b/thirdparty/libwebp/src/dsp/rescaler_sse2.c @@ -36,7 +36,7 @@ static void LoadTwoPixels_SSE2(const uint8_t* const src, __m128i* out) { } // input: 8 bytes ABCDEFGH -> output: A0B0C0D0E0F0G0H0 -static void LoadHeightPixels_SSE2(const uint8_t* const src, __m128i* out) { +static void LoadEightPixels_SSE2(const uint8_t* const src, __m128i* out) { const __m128i zero = _mm_setzero_si128(); const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH *out = _mm_unpacklo_epi8(A, zero); @@ -50,13 +50,15 @@ static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, int accum = x_add; __m128i cur_pixels; + // SSE2 implementation only works with 16b signed arithmetic at max. + if (wrk->src_width < 8 || accum >= (1 << 15)) { + WebPRescalerImportRowExpand_C(wrk, src); + return; + } + assert(!WebPRescalerInputDone(wrk)); assert(wrk->x_expand); if (wrk->num_channels == 4) { - if (wrk->src_width < 2) { - WebPRescalerImportRowExpand_C(wrk, src); - return; - } LoadTwoPixels_SSE2(src, &cur_pixels); src += 4; while (1) { @@ -75,11 +77,7 @@ static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, } else { int left; const uint8_t* const src_limit = src + wrk->src_width - 8; - if (wrk->src_width < 8) { - WebPRescalerImportRowExpand_C(wrk, src); - return; - } - LoadHeightPixels_SSE2(src, &cur_pixels); + LoadEightPixels_SSE2(src, &cur_pixels); src += 7; left = 7; while (1) { @@ -94,7 +92,7 @@ static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, if (--left) { cur_pixels = _mm_srli_si128(cur_pixels, 2); } else if (src <= src_limit) { - LoadHeightPixels_SSE2(src, &cur_pixels); + LoadEightPixels_SSE2(src, &cur_pixels); src += 7; left = 7; } else { // tail diff --git a/thirdparty/libwebp/src/dsp/ssim.c b/thirdparty/libwebp/src/dsp/ssim.c index dc1b518a33..989ce8254c 100644 --- a/thirdparty/libwebp/src/dsp/ssim.c +++ b/thirdparty/libwebp/src/dsp/ssim.c @@ -139,12 +139,7 @@ VP8AccumulateSSEFunc VP8AccumulateSSE; extern void VP8SSIMDspInitSSE2(void); -static volatile VP8CPUInfo ssim_last_cpuinfo_used = - (VP8CPUInfo)&ssim_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void VP8SSIMDspInit(void) { - if (ssim_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(VP8SSIMDspInit) { #if !defined(WEBP_REDUCE_SIZE) VP8SSIMGetClipped = SSIMGetClipped_C; VP8SSIMGet = SSIMGet_C; @@ -161,6 +156,4 @@ WEBP_TSAN_IGNORE_FUNCTION void VP8SSIMDspInit(void) { } #endif } - - ssim_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/upsampling.c b/thirdparty/libwebp/src/dsp/upsampling.c index e72626a82a..9b60da5bbb 100644 --- a/thirdparty/libwebp/src/dsp/upsampling.c +++ b/thirdparty/libwebp/src/dsp/upsampling.c @@ -217,13 +217,9 @@ WebPYUV444Converter WebPYUV444Converters[MODE_LAST]; extern void WebPInitYUV444ConvertersMIPSdspR2(void); extern void WebPInitYUV444ConvertersSSE2(void); +extern void WebPInitYUV444ConvertersSSE41(void); -static volatile VP8CPUInfo upsampling_last_cpuinfo_used1 = - (VP8CPUInfo)&upsampling_last_cpuinfo_used1; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444Converters(void) { - if (upsampling_last_cpuinfo_used1 == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitYUV444Converters) { WebPYUV444Converters[MODE_RGBA] = WebPYuv444ToRgba_C; WebPYUV444Converters[MODE_BGRA] = WebPYuv444ToBgra_C; WebPYUV444Converters[MODE_RGB] = WebPYuv444ToRgb_C; @@ -242,29 +238,29 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444Converters(void) { WebPInitYUV444ConvertersSSE2(); } #endif +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitYUV444ConvertersSSE41(); + } +#endif #if defined(WEBP_USE_MIPS_DSP_R2) if (VP8GetCPUInfo(kMIPSdspR2)) { WebPInitYUV444ConvertersMIPSdspR2(); } #endif } - upsampling_last_cpuinfo_used1 = VP8GetCPUInfo; } //------------------------------------------------------------------------------ // Main calls extern void WebPInitUpsamplersSSE2(void); +extern void WebPInitUpsamplersSSE41(void); extern void WebPInitUpsamplersNEON(void); extern void WebPInitUpsamplersMIPSdspR2(void); extern void WebPInitUpsamplersMSA(void); -static volatile VP8CPUInfo upsampling_last_cpuinfo_used2 = - (VP8CPUInfo)&upsampling_last_cpuinfo_used2; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { - if (upsampling_last_cpuinfo_used2 == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitUpsamplers) { #ifdef FANCY_UPSAMPLING #if !WEBP_NEON_OMIT_C_CODE WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_C; @@ -287,6 +283,11 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { WebPInitUpsamplersSSE2(); } #endif +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitUpsamplersSSE41(); + } +#endif #if defined(WEBP_USE_MIPS_DSP_R2) if (VP8GetCPUInfo(kMIPSdspR2)) { WebPInitUpsamplersMIPSdspR2(); @@ -310,6 +311,7 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { assert(WebPUpsamplers[MODE_BGRA] != NULL); assert(WebPUpsamplers[MODE_rgbA] != NULL); assert(WebPUpsamplers[MODE_bgrA] != NULL); +#if !defined(WEBP_REDUCE_CSP) || !WEBP_NEON_OMIT_C_CODE assert(WebPUpsamplers[MODE_RGB] != NULL); assert(WebPUpsamplers[MODE_BGR] != NULL); assert(WebPUpsamplers[MODE_ARGB] != NULL); @@ -317,9 +319,9 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplers(void) { assert(WebPUpsamplers[MODE_RGB_565] != NULL); assert(WebPUpsamplers[MODE_Argb] != NULL); assert(WebPUpsamplers[MODE_rgbA_4444] != NULL); +#endif #endif // FANCY_UPSAMPLING - upsampling_last_cpuinfo_used2 = VP8GetCPUInfo; } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/dsp/upsampling_msa.c b/thirdparty/libwebp/src/dsp/upsampling_msa.c index 535ffb772c..99eea70e7d 100644 --- a/thirdparty/libwebp/src/dsp/upsampling_msa.c +++ b/thirdparty/libwebp/src/dsp/upsampling_msa.c @@ -264,6 +264,7 @@ static void YuvToBgr(int y, int u, int v, uint8_t* const bgr) { bgr[2] = Clip8(r1 >> 6); } +#if !defined(WEBP_REDUCE_CSP) static void YuvToRgb565(int y, int u, int v, uint8_t* const rgb) { const int y1 = MultHi(y, 19077); const int r1 = y1 + MultHi(v, 26149) - 14234; @@ -306,6 +307,7 @@ static void YuvToArgb(uint8_t y, uint8_t u, uint8_t v, uint8_t* const argb) { argb[0] = 0xff; YuvToRgb(y, u, v, argb + 1); } +#endif // WEBP_REDUCE_CSP static void YuvToBgra(uint8_t y, uint8_t u, uint8_t v, uint8_t* const bgra) { YuvToBgr(y, u, v, bgra); @@ -317,6 +319,7 @@ static void YuvToRgba(uint8_t y, uint8_t u, uint8_t v, uint8_t* const rgba) { rgba[3] = 0xff; } +#if !defined(WEBP_REDUCE_CSP) static void YuvToRgbLine(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int length) { v16u8 R, G, B; @@ -370,6 +373,7 @@ static void YuvToBgrLine(const uint8_t* y, const uint8_t* u, memcpy(dst, temp, length * 3 * sizeof(*dst)); } } +#endif // WEBP_REDUCE_CSP static void YuvToRgbaLine(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int length) { @@ -427,6 +431,7 @@ static void YuvToBgraLine(const uint8_t* y, const uint8_t* u, } } +#if !defined(WEBP_REDUCE_CSP) static void YuvToArgbLine(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int length) { v16u8 R, G, B; @@ -526,6 +531,7 @@ static void YuvToRgb565Line(const uint8_t* y, const uint8_t* u, memcpy(dst, temp, length * 2 * sizeof(*dst)); } } +#endif // WEBP_REDUCE_CSP #define UPSAMPLE_32PIXELS(a, b, c, d) do { \ v16u8 s = __msa_aver_u_b(a, d); \ diff --git a/thirdparty/libwebp/src/dsp/upsampling_sse2.c b/thirdparty/libwebp/src/dsp/upsampling_sse2.c index fd5d303982..340f1e2ac2 100644 --- a/thirdparty/libwebp/src/dsp/upsampling_sse2.c +++ b/thirdparty/libwebp/src/dsp/upsampling_sse2.c @@ -104,21 +104,6 @@ static void Upsample32Pixels_SSE2(const uint8_t r1[], const uint8_t r2[], Upsample32Pixels_SSE2(r1, r2, out); \ } -#define CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, \ - top_dst, bottom_dst, cur_x, num_pixels) { \ - int n; \ - for (n = 0; n < (num_pixels); ++n) { \ - FUNC((top_y)[(cur_x) + n], r_u[n], r_v[n], \ - (top_dst) + ((cur_x) + n) * (XSTEP)); \ - } \ - if ((bottom_y) != NULL) { \ - for (n = 0; n < (num_pixels); ++n) { \ - FUNC((bottom_y)[(cur_x) + n], r_u[64 + n], r_v[64 + n], \ - (bottom_dst) + ((cur_x) + n) * (XSTEP)); \ - } \ - } \ -} - #define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ top_dst, bottom_dst, cur_x) do { \ FUNC##32_SSE2((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ @@ -135,7 +120,7 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ int uv_pos, pos; \ /* 16byte-aligned array to cache reconstructed u and v */ \ - uint8_t uv_buf[4 * 32 + 15]; \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ uint8_t* const r_v = r_u + 32; \ \ @@ -160,11 +145,22 @@ static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ } \ if (len > 1) { \ const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ assert(left_over > 0); \ UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ - CONVERT2RGB(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, \ - pos, len - pos); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ } \ } diff --git a/thirdparty/libwebp/src/dsp/upsampling_sse41.c b/thirdparty/libwebp/src/dsp/upsampling_sse41.c new file mode 100644 index 0000000000..648d456027 --- /dev/null +++ b/thirdparty/libwebp/src/dsp/upsampling_sse41.c @@ -0,0 +1,239 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE41 version of YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include <assert.h> +#include <smmintrin.h> +#include <string.h> +#include "src/dsp/yuv.h" + +#ifdef FANCY_UPSAMPLING + +#if !defined(WEBP_REDUCE_CSP) + +// We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows +// u = (9*a + 3*b + 3*c + d + 8) / 16 +// = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 +// = (a + m + 1) / 2 +// where m = (a + 3*b + 3*c + d) / 8 +// = ((a + b + c + d) / 2 + b + c) / 4 +// +// Let's say k = (a + b + c + d) / 4. +// We can compute k as +// k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 +// where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 +// +// Then m can be written as +// m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + +// Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 +#define GET_M(ij, in, out) do { \ + const __m128i tmp0 = _mm_avg_epu8(k, (in)); /* (k + in + 1) / 2 */ \ + const __m128i tmp1 = _mm_and_si128((ij), st); /* (ij) & (s^t) */ \ + const __m128i tmp2 = _mm_xor_si128(k, (in)); /* (k^in) */ \ + const __m128i tmp3 = _mm_or_si128(tmp1, tmp2); /* ((ij) & (s^t)) | (k^in) */\ + const __m128i tmp4 = _mm_and_si128(tmp3, one); /* & 1 -> lsb_correction */ \ + (out) = _mm_sub_epi8(tmp0, tmp4); /* (k + in + 1) / 2 - lsb_correction */ \ +} while (0) + +// pack and store two alternating pixel rows +#define PACK_AND_STORE(a, b, da, db, out) do { \ + const __m128i t_a = _mm_avg_epu8(a, da); /* (9a + 3b + 3c + d + 8) / 16 */ \ + const __m128i t_b = _mm_avg_epu8(b, db); /* (3a + 9b + c + 3d + 8) / 16 */ \ + const __m128i t_1 = _mm_unpacklo_epi8(t_a, t_b); \ + const __m128i t_2 = _mm_unpackhi_epi8(t_a, t_b); \ + _mm_store_si128(((__m128i*)(out)) + 0, t_1); \ + _mm_store_si128(((__m128i*)(out)) + 1, t_2); \ +} while (0) + +// Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. +#define UPSAMPLE_32PIXELS(r1, r2, out) { \ + const __m128i one = _mm_set1_epi8(1); \ + const __m128i a = _mm_loadu_si128((const __m128i*)&(r1)[0]); \ + const __m128i b = _mm_loadu_si128((const __m128i*)&(r1)[1]); \ + const __m128i c = _mm_loadu_si128((const __m128i*)&(r2)[0]); \ + const __m128i d = _mm_loadu_si128((const __m128i*)&(r2)[1]); \ + \ + const __m128i s = _mm_avg_epu8(a, d); /* s = (a + d + 1) / 2 */ \ + const __m128i t = _mm_avg_epu8(b, c); /* t = (b + c + 1) / 2 */ \ + const __m128i st = _mm_xor_si128(s, t); /* st = s^t */ \ + \ + const __m128i ad = _mm_xor_si128(a, d); /* ad = a^d */ \ + const __m128i bc = _mm_xor_si128(b, c); /* bc = b^c */ \ + \ + const __m128i t1 = _mm_or_si128(ad, bc); /* (a^d) | (b^c) */ \ + const __m128i t2 = _mm_or_si128(t1, st); /* (a^d) | (b^c) | (s^t) */ \ + const __m128i t3 = _mm_and_si128(t2, one); /* (a^d) | (b^c) | (s^t) & 1 */ \ + const __m128i t4 = _mm_avg_epu8(s, t); \ + const __m128i k = _mm_sub_epi8(t4, t3); /* k = (a + b + c + d) / 4 */ \ + __m128i diag1, diag2; \ + \ + GET_M(bc, t, diag1); /* diag1 = (a + 3b + 3c + d) / 8 */ \ + GET_M(ad, s, diag2); /* diag2 = (3a + b + c + 3d) / 8 */ \ + \ + /* pack the alternate pixels */ \ + PACK_AND_STORE(a, b, diag1, diag2, (out) + 0); /* store top */ \ + PACK_AND_STORE(c, d, diag2, diag1, (out) + 2 * 32); /* store bottom */ \ +} + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample32Pixels_SSE41(const uint8_t r1[], const uint8_t r2[], + uint8_t* const out) { + UPSAMPLE_32PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[17], r2[17]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 17 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 17 - (num_pixels)); \ + /* using the shared function instead of the macro saves ~3k code size */ \ + Upsample32Pixels_SSE41(r1, r2, out); \ +} + +#define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ + top_dst, bottom_dst, cur_x) do { \ + FUNC##32_SSE41((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ + if ((bottom_y) != NULL) { \ + FUNC##32_SSE41((bottom_y) + (cur_x), r_u + 64, r_v + 64, \ + (bottom_dst) + (cur_x) * (XSTEP)); \ + } \ +} while (0) + +#define SSE4_UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int uv_pos, pos; \ + /* 16byte-aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ + uint8_t* const r_v = r_u + 32; \ + \ + assert(top_y != NULL); \ + { /* Treat the first pixel in regular way */ \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + const int u0_t = (top_u[0] + u_diag) >> 1; \ + const int v0_t = (top_v[0] + v_diag) >> 1; \ + FUNC(top_y[0], u0_t, v0_t, top_dst); \ + if (bottom_y != NULL) { \ + const int u0_b = (cur_u[0] + u_diag) >> 1; \ + const int v0_b = (cur_v[0] + v_diag) >> 1; \ + FUNC(bottom_y[0], u0_b, v0_b, bottom_dst); \ + } \ + } \ + /* For UPSAMPLE_32PIXELS, 17 u/v values must be read-able for each block */ \ + for (pos = 1, uv_pos = 0; pos + 32 + 1 <= len; pos += 32, uv_pos += 16) { \ + UPSAMPLE_32PIXELS(top_u + uv_pos, cur_u + uv_pos, r_u); \ + UPSAMPLE_32PIXELS(top_v + uv_pos, cur_v + uv_pos, r_v); \ + CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, pos); \ + } \ + if (len > 1) { \ + const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ + assert(left_over > 0); \ + UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ + UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ + } \ +} + +// SSE4 variants of the fancy upsampler. +SSE4_UPSAMPLE_FUNC(UpsampleRgbLinePair_SSE41, VP8YuvToRgb, 3) +SSE4_UPSAMPLE_FUNC(UpsampleBgrLinePair_SSE41, VP8YuvToBgr, 3) + +#undef GET_M +#undef PACK_AND_STORE +#undef UPSAMPLE_32PIXELS +#undef UPSAMPLE_LAST_BLOCK +#undef CONVERT2RGB +#undef CONVERT2RGB_32 +#undef SSE4_UPSAMPLE_FUNC + +#endif // WEBP_REDUCE_CSP + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_SSE41; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern void WebPInitYUV444ConvertersSSE41(void); + +#define YUV444_FUNC(FUNC_NAME, CALL, CALL_C, XSTEP) \ +extern void CALL_C(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len); \ +static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + const int max_len = len & ~31; \ + for (i = 0; i < max_len; i += 32) { \ + CALL(y + i, u + i, v + i, dst + i * (XSTEP)); \ + } \ + if (i < len) { /* C-fallback */ \ + CALL_C(y + i, u + i, v + i, dst + i * (XSTEP), len - i); \ + } \ +} + +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(Yuv444ToRgb_SSE41, VP8YuvToRgb32_SSE41, WebPYuv444ToRgb_C, 3); +YUV444_FUNC(Yuv444ToBgr_SSE41, VP8YuvToBgr32_SSE41, WebPYuv444ToBgr_C, 3); +#endif // WEBP_REDUCE_CSP + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb_SSE41; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#else + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersSSE41) + +#endif // WEBP_USE_SSE41 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_SSE41)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersSSE41) +#endif diff --git a/thirdparty/libwebp/src/dsp/yuv.c b/thirdparty/libwebp/src/dsp/yuv.c index bddf81fe09..14e67fc28e 100644 --- a/thirdparty/libwebp/src/dsp/yuv.c +++ b/thirdparty/libwebp/src/dsp/yuv.c @@ -71,15 +71,11 @@ void WebPSamplerProcessPlane(const uint8_t* y, int y_stride, WebPSamplerRowFunc WebPSamplers[MODE_LAST]; extern void WebPInitSamplersSSE2(void); +extern void WebPInitSamplersSSE41(void); extern void WebPInitSamplersMIPS32(void); extern void WebPInitSamplersMIPSdspR2(void); -static volatile VP8CPUInfo yuv_last_cpuinfo_used = - (VP8CPUInfo)&yuv_last_cpuinfo_used; - -WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplers(void) { - if (yuv_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitSamplers) { WebPSamplers[MODE_RGB] = YuvToRgbRow; WebPSamplers[MODE_RGBA] = YuvToRgbaRow; WebPSamplers[MODE_BGR] = YuvToBgrRow; @@ -99,6 +95,11 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplers(void) { WebPInitSamplersSSE2(); } #endif // WEBP_USE_SSE2 +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitSamplersSSE41(); + } +#endif // WEBP_USE_SSE41 #if defined(WEBP_USE_MIPS32) if (VP8GetCPUInfo(kMIPS32)) { WebPInitSamplersMIPS32(); @@ -110,7 +111,6 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplers(void) { } #endif // WEBP_USE_MIPS_DSP_R2 } - yuv_last_cpuinfo_used = VP8GetCPUInfo; } //----------------------------------------------------------------------------- @@ -254,17 +254,13 @@ void (*WebPSharpYUVUpdateRGB)(const int16_t* ref, const int16_t* src, void (*WebPSharpYUVFilterRow)(const int16_t* A, const int16_t* B, int len, const uint16_t* best_y, uint16_t* out); -static volatile VP8CPUInfo rgba_to_yuv_last_cpuinfo_used = - (VP8CPUInfo)&rgba_to_yuv_last_cpuinfo_used; - extern void WebPInitConvertARGBToYUVSSE2(void); +extern void WebPInitConvertARGBToYUVSSE41(void); extern void WebPInitConvertARGBToYUVNEON(void); extern void WebPInitSharpYUVSSE2(void); extern void WebPInitSharpYUVNEON(void); -WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUV(void) { - if (rgba_to_yuv_last_cpuinfo_used == VP8GetCPUInfo) return; - +WEBP_DSP_INIT_FUNC(WebPInitConvertARGBToYUV) { WebPConvertARGBToY = ConvertARGBToY_C; WebPConvertARGBToUV = WebPConvertARGBToUV_C; @@ -286,6 +282,11 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUV(void) { WebPInitSharpYUVSSE2(); } #endif // WEBP_USE_SSE2 +#if defined(WEBP_USE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitConvertARGBToYUVSSE41(); + } +#endif // WEBP_USE_SSE41 } #if defined(WEBP_USE_NEON) @@ -304,6 +305,4 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUV(void) { assert(WebPSharpYUVUpdateY != NULL); assert(WebPSharpYUVUpdateRGB != NULL); assert(WebPSharpYUVFilterRow != NULL); - - rgba_to_yuv_last_cpuinfo_used = VP8GetCPUInfo; } diff --git a/thirdparty/libwebp/src/dsp/yuv.h b/thirdparty/libwebp/src/dsp/yuv.h index c8a55832d4..eb787270d2 100644 --- a/thirdparty/libwebp/src/dsp/yuv.h +++ b/thirdparty/libwebp/src/dsp/yuv.h @@ -166,6 +166,19 @@ void VP8YuvToRgb56532_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, #endif // WEBP_USE_SSE2 +//----------------------------------------------------------------------------- +// SSE41 extra functions (mostly for upsampling_sse41.c) + +#if defined(WEBP_USE_SSE41) + +// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst. +void VP8YuvToRgb32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToBgr32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); + +#endif // WEBP_USE_SSE41 + //------------------------------------------------------------------------------ // RGB -> YUV conversion diff --git a/thirdparty/libwebp/src/dsp/yuv_sse2.c b/thirdparty/libwebp/src/dsp/yuv_sse2.c index 6810bf8d15..baa48d5371 100644 --- a/thirdparty/libwebp/src/dsp/yuv_sse2.c +++ b/thirdparty/libwebp/src/dsp/yuv_sse2.c @@ -180,7 +180,7 @@ static WEBP_INLINE void PlanarTo24b_SSE2(__m128i* const in0, __m128i* const in1, // Repeat the same permutations twice more: // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 - VP8PlanarTo24b(in0, in1, in2, in3, in4, in5); + VP8PlanarTo24b_SSE2(in0, in1, in2, in3, in4, in5); _mm_storeu_si128((__m128i*)(rgb + 0), *in0); _mm_storeu_si128((__m128i*)(rgb + 16), *in1); @@ -492,7 +492,7 @@ static WEBP_INLINE void RGB32PackedToPlanar_SSE2(const uint32_t* const argb, __m128i a1 = LOAD_16(argb + 4); __m128i a2 = LOAD_16(argb + 8); __m128i a3 = LOAD_16(argb + 12); - VP8L32bToPlanar(&a0, &a1, &a2, &a3); + VP8L32bToPlanar_SSE2(&a0, &a1, &a2, &a3); rgb[0] = _mm_unpacklo_epi8(a1, zero); rgb[1] = _mm_unpackhi_epi8(a1, zero); rgb[2] = _mm_unpacklo_epi8(a2, zero); diff --git a/thirdparty/libwebp/src/dsp/yuv_sse41.c b/thirdparty/libwebp/src/dsp/yuv_sse41.c new file mode 100644 index 0000000000..579d1f7402 --- /dev/null +++ b/thirdparty/libwebp/src/dsp/yuv_sse41.c @@ -0,0 +1,613 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_SSE41) + +#include "src/dsp/common_sse41.h" +#include <stdlib.h> +#include <smmintrin.h> + +//----------------------------------------------------------------------------- +// Convert spans of 32 pixels to various RGB formats for the fancy upsampler. + +// These constants are 14b fixed-point version of ITU-R BT.601 constants. +// R = (19077 * y + 26149 * v - 14234) >> 6 +// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 +// B = (19077 * y + 33050 * u - 17685) >> 6 +static void ConvertYUV444ToRGB_SSE41(const __m128i* const Y0, + const __m128i* const U0, + const __m128i* const V0, + __m128i* const R, + __m128i* const G, + __m128i* const B) { + const __m128i k19077 = _mm_set1_epi16(19077); + const __m128i k26149 = _mm_set1_epi16(26149); + const __m128i k14234 = _mm_set1_epi16(14234); + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + const __m128i k33050 = _mm_set1_epi16((short)33050); + const __m128i k17685 = _mm_set1_epi16(17685); + const __m128i k6419 = _mm_set1_epi16(6419); + const __m128i k13320 = _mm_set1_epi16(13320); + const __m128i k8708 = _mm_set1_epi16(8708); + + const __m128i Y1 = _mm_mulhi_epu16(*Y0, k19077); + + const __m128i R0 = _mm_mulhi_epu16(*V0, k26149); + const __m128i R1 = _mm_sub_epi16(Y1, k14234); + const __m128i R2 = _mm_add_epi16(R1, R0); + + const __m128i G0 = _mm_mulhi_epu16(*U0, k6419); + const __m128i G1 = _mm_mulhi_epu16(*V0, k13320); + const __m128i G2 = _mm_add_epi16(Y1, k8708); + const __m128i G3 = _mm_add_epi16(G0, G1); + const __m128i G4 = _mm_sub_epi16(G2, G3); + + // be careful with the saturated *unsigned* arithmetic here! + const __m128i B0 = _mm_mulhi_epu16(*U0, k33050); + const __m128i B1 = _mm_adds_epu16(B0, Y1); + const __m128i B2 = _mm_subs_epu16(B1, k17685); + + // use logical shift for B2, which can be larger than 32767 + *R = _mm_srai_epi16(R2, 6); // range: [-14234, 30815] + *G = _mm_srai_epi16(G4, 6); // range: [-10953, 27710] + *B = _mm_srli_epi16(B2, 6); // range: [0, 34238] +} + +// Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. +static WEBP_INLINE __m128i Load_HI_16_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + return _mm_unpacklo_epi8(zero, _mm_loadl_epi64((const __m128i*)src)); +} + +// Load and replicate the U/V samples +static WEBP_INLINE __m128i Load_UV_HI_8_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + const __m128i tmp0 = _mm_cvtsi32_si128(*(const uint32_t*)src); + const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); + return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples +} + +// Convert 32 samples of YUV444 to R/G/B +static void YUV444ToRGB_SSE41(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_HI_16_SSE41(u), + V0 = Load_HI_16_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Convert 32 samples of YUV420 to R/G/B +static void YUV420ToRGB_SSE41(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_UV_HI_8_SSE41(u), + V0 = Load_UV_HI_8_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5, + uint8_t* const rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + VP8PlanarTo24b_SSE41(in0, in1, in2, in3, in4, in5); + + _mm_storeu_si128((__m128i*)(rgb + 0), *in0); + _mm_storeu_si128((__m128i*)(rgb + 16), *in1); + _mm_storeu_si128((__m128i*)(rgb + 32), *in2); + _mm_storeu_si128((__m128i*)(rgb + 48), *in3); + _mm_storeu_si128((__m128i*)(rgb + 64), *in4); + _mm_storeu_si128((__m128i*)(rgb + 80), *in5); +} + +void VP8YuvToRgb32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); +} + +void VP8YuvToBgr32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5= _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); +} + +//----------------------------------------------------------------------------- +// Arbitrary-length row conversion functions + +static void YuvToRgbRow_SSE41(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgb(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgrRow_SSE41(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5 = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgr(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE41(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_SSE41; + WebPSamplers[MODE_BGR] = YuvToBgrRow_SSE41; +} + +//------------------------------------------------------------------------------ +// RGB24/32 -> YUV converters + +// Load eight 16b-words from *src. +#define LOAD_16(src) _mm_loadu_si128((const __m128i*)(src)) +// Store either 16b-words into *dst +#define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) + +#define WEBP_SSE41_SHUFF(OUT) do { \ + const __m128i tmp0 = _mm_shuffle_epi8(A0, shuff0); \ + const __m128i tmp1 = _mm_shuffle_epi8(A1, shuff1); \ + const __m128i tmp2 = _mm_shuffle_epi8(A2, shuff2); \ + const __m128i tmp3 = _mm_shuffle_epi8(A3, shuff0); \ + const __m128i tmp4 = _mm_shuffle_epi8(A4, shuff1); \ + const __m128i tmp5 = _mm_shuffle_epi8(A5, shuff2); \ + \ + /* OR everything to get one channel */ \ + const __m128i tmp6 = _mm_or_si128(tmp0, tmp1); \ + const __m128i tmp7 = _mm_or_si128(tmp3, tmp4); \ + out[OUT + 0] = _mm_or_si128(tmp6, tmp2); \ + out[OUT + 1] = _mm_or_si128(tmp7, tmp5); \ +} while (0); + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. +static WEBP_INLINE void RGB24PackedToPlanar_SSE41( + const uint8_t* const rgb, __m128i* const out /*out[6]*/) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(rgb + 0)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(rgb + 16)); + const __m128i A2 = _mm_loadu_si128((const __m128i*)(rgb + 32)); + const __m128i A3 = _mm_loadu_si128((const __m128i*)(rgb + 48)); + const __m128i A4 = _mm_loadu_si128((const __m128i*)(rgb + 64)); + const __m128i A5 = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + // Compute RR. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 13, 10, 7, 4, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(0) + } + // Compute GG. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(2) + } + // Compute BB. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 14, 11, 8, 5, 2); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(4) + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert 8 packed ARGB to r[], g[], b[] +static WEBP_INLINE void RGB32PackedToPlanar_SSE41( + const uint32_t* const argb, __m128i* const rgb /*in[6]*/) { + const __m128i zero = _mm_setzero_si128(); + __m128i a0 = LOAD_16(argb + 0); + __m128i a1 = LOAD_16(argb + 4); + __m128i a2 = LOAD_16(argb + 8); + __m128i a3 = LOAD_16(argb + 12); + VP8L32bToPlanar_SSE41(&a0, &a1, &a2, &a3); + rgb[0] = _mm_unpacklo_epi8(a1, zero); + rgb[1] = _mm_unpackhi_epi8(a1, zero); + rgb[2] = _mm_unpacklo_epi8(a2, zero); + rgb[3] = _mm_unpackhi_epi8(a2, zero); + rgb[4] = _mm_unpacklo_epi8(a3, zero); + rgb[5] = _mm_unpackhi_epi8(a3, zero); +} + +// This macro computes (RG * MULT_RG + GB * MULT_GB + ROUNDER) >> DESCALE_FIX +// It's a macro and not a function because we need to use immediate values with +// srai_epi32, e.g. +#define TRANSFORM(RG_LO, RG_HI, GB_LO, GB_HI, MULT_RG, MULT_GB, \ + ROUNDER, DESCALE_FIX, OUT) do { \ + const __m128i V0_lo = _mm_madd_epi16(RG_LO, MULT_RG); \ + const __m128i V0_hi = _mm_madd_epi16(RG_HI, MULT_RG); \ + const __m128i V1_lo = _mm_madd_epi16(GB_LO, MULT_GB); \ + const __m128i V1_hi = _mm_madd_epi16(GB_HI, MULT_GB); \ + const __m128i V2_lo = _mm_add_epi32(V0_lo, V1_lo); \ + const __m128i V2_hi = _mm_add_epi32(V0_hi, V1_hi); \ + const __m128i V3_lo = _mm_add_epi32(V2_lo, ROUNDER); \ + const __m128i V3_hi = _mm_add_epi32(V2_hi, ROUNDER); \ + const __m128i V5_lo = _mm_srai_epi32(V3_lo, DESCALE_FIX); \ + const __m128i V5_hi = _mm_srai_epi32(V3_hi, DESCALE_FIX); \ + (OUT) = _mm_packs_epi32(V5_lo, V5_hi); \ +} while (0) + +#define MK_CST_16(A, B) _mm_set_epi16((B), (A), (B), (A), (B), (A), (B), (A)) +static WEBP_INLINE void ConvertRGBToY_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const Y) { + const __m128i kRG_y = MK_CST_16(16839, 33059 - 16384); + const __m128i kGB_y = MK_CST_16(16384, 6420); + const __m128i kHALF_Y = _mm_set1_epi32((16 << YUV_FIX) + YUV_HALF); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_y, kGB_y, kHALF_Y, YUV_FIX, *Y); +} + +static WEBP_INLINE void ConvertRGBToUV_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const U, + __m128i* const V) { + const __m128i kRG_u = MK_CST_16(-9719, -19081); + const __m128i kGB_u = MK_CST_16(0, 28800); + const __m128i kRG_v = MK_CST_16(28800, 0); + const __m128i kGB_v = MK_CST_16(-24116, -4684); + const __m128i kHALF_UV = _mm_set1_epi32(((128 << YUV_FIX) + YUV_HALF) << 2); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_u, kGB_u, + kHALF_UV, YUV_FIX + 2, *U); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_v, kGB_v, + kHALF_UV, YUV_FIX + 2, *V); +} + +#undef MK_CST_16 +#undef TRANSFORM + +static void ConvertRGB24ToY_SSE41(const uint8_t* rgb, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_SSE41(const uint8_t* bgr, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_SSE41(const uint32_t* argb, uint8_t* y, int width) { + const int max_width = width & ~15; + int i; + for (i = 0; i < max_width; i += 16) { + __m128i Y0, Y1, rgb[6]; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + ConvertRGBToY_SSE41(&rgb[0], &rgb[2], &rgb[4], &Y0); + ConvertRGBToY_SSE41(&rgb[1], &rgb[3], &rgb[5], &Y1); + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +// Horizontal add (doubled) of two 16b values, result is 16b. +// in: A | B | C | D | ... -> out: 2*(A+B) | 2*(C+D) | ... +static void HorizontalAddPack_SSE41(const __m128i* const A, + const __m128i* const B, + __m128i* const out) { + const __m128i k2 = _mm_set1_epi16(2); + const __m128i C = _mm_madd_epi16(*A, k2); + const __m128i D = _mm_madd_epi16(*B, k2); + *out = _mm_packs_epi32(C, D); +} + +static void ConvertARGBToUV_SSE41(const uint32_t* argb, + uint8_t* u, uint8_t* v, + int src_width, int do_store) { + const int max_width = src_width & ~31; + int i; + for (i = 0; i < max_width; i += 32, u += 16, v += 16) { + __m128i rgb[6], U0, V0, U1, V1; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U0, &V0); + + RGB32PackedToPlanar_SSE41(&argb[i + 16], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U1, &V1); + + U0 = _mm_packus_epi16(U0, U1); + V0 = _mm_packus_epi16(V0, V1); + if (!do_store) { + const __m128i prev_u = LOAD_16(u); + const __m128i prev_v = LOAD_16(v); + U0 = _mm_avg_epu8(U0, prev_u); + V0 = _mm_avg_epu8(V0, prev_v); + } + STORE_16(U0, u); + STORE_16(V0, v); + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + +// Convert 16 packed ARGB 16b-values to r[], g[], b[] +static WEBP_INLINE void RGBA32PackedToPlanar_16b_SSE41( + const uint16_t* const rgbx, + __m128i* const r, __m128i* const g, __m128i* const b) { + const __m128i in0 = LOAD_16(rgbx + 0); // r0 | g0 | b0 |x| r1 | g1 | b1 |x + const __m128i in1 = LOAD_16(rgbx + 8); // r2 | g2 | b2 |x| r3 | g3 | b3 |x + const __m128i in2 = LOAD_16(rgbx + 16); // r4 | ... + const __m128i in3 = LOAD_16(rgbx + 24); // r6 | ... + // aarrggbb as 16-bit. + const __m128i shuff0 = + _mm_set_epi8(-1, -1, -1, -1, 13, 12, 5, 4, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i shuff1 = + _mm_set_epi8(13, 12, 5, 4, -1, -1, -1, -1, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i A0 = _mm_shuffle_epi8(in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(in1, shuff1); + const __m128i A2 = _mm_shuffle_epi8(in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(in3, shuff1); + // R0R1G0G1 + // B0B1**** + // R2R3G2G3 + // B2B3**** + // (OR is used to free port 5 for the unpack) + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_or_si128(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_or_si128(A2, A3); + // Gather the channels. + *r = _mm_unpacklo_epi64(B0, B2); + *g = _mm_unpackhi_epi64(B0, B2); + *b = _mm_unpackhi_epi64(B1, B3); +} + +static void ConvertRGBA32ToUV_SSE41(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + const int max_width = width & ~15; + const uint16_t* const last_rgb = rgb + 4 * max_width; + while (rgb < last_rgb) { + __m128i r, g, b, U0, V0, U1, V1; + RGBA32PackedToPlanar_16b_SSE41(rgb + 0, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U0, &V0); + RGBA32PackedToPlanar_16b_SSE41(rgb + 32, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U1, &V1); + STORE_16(_mm_packus_epi16(U0, U1), u); + STORE_16(_mm_packus_epi16(V0, V1), v); + u += 16; + v += 16; + rgb += 2 * 32; + } + if (max_width < width) { // left-over + WebPConvertRGBA32ToUV_C(rgb, u, v, width - max_width); + } +} + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVSSE41(void) { + WebPConvertARGBToY = ConvertARGBToY_SSE41; + WebPConvertARGBToUV = ConvertARGBToUV_SSE41; + + WebPConvertRGB24ToY = ConvertRGB24ToY_SSE41; + WebPConvertBGR24ToY = ConvertBGR24ToY_SSE41; + + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_SSE41; +} + +//------------------------------------------------------------------------------ + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(WebPInitSamplersSSE41) +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/thirdparty/libwebp/src/enc/alpha_enc.c b/thirdparty/libwebp/src/enc/alpha_enc.c index 7e8d87f22e..dce9ca957d 100644 --- a/thirdparty/libwebp/src/enc/alpha_enc.c +++ b/thirdparty/libwebp/src/enc/alpha_enc.c @@ -361,7 +361,8 @@ static int EncodeAlpha(VP8Encoder* const enc, //------------------------------------------------------------------------------ // Main calls -static int CompressAlphaJob(VP8Encoder* const enc, void* dummy) { +static int CompressAlphaJob(void* arg1, void* dummy) { + VP8Encoder* const enc = (VP8Encoder*)arg1; const WebPConfig* config = enc->config_; uint8_t* alpha_data = NULL; size_t alpha_size = 0; @@ -394,7 +395,7 @@ void VP8EncInitAlpha(VP8Encoder* const enc) { WebPGetWorkerInterface()->Init(worker); worker->data1 = enc; worker->data2 = NULL; - worker->hook = (WebPWorkerHook)CompressAlphaJob; + worker->hook = CompressAlphaJob; } } diff --git a/thirdparty/libwebp/src/enc/analysis_enc.c b/thirdparty/libwebp/src/enc/analysis_enc.c index 08f471f5f8..a47ff7d4e8 100644 --- a/thirdparty/libwebp/src/enc/analysis_enc.c +++ b/thirdparty/libwebp/src/enc/analysis_enc.c @@ -434,7 +434,9 @@ typedef struct { } SegmentJob; // main work call -static int DoSegmentsJob(SegmentJob* const job, VP8EncIterator* const it) { +static int DoSegmentsJob(void* arg1, void* arg2) { + SegmentJob* const job = (SegmentJob*)arg1; + VP8EncIterator* const it = (VP8EncIterator*)arg2; int ok = 1; if (!VP8IteratorIsDone(it)) { uint8_t tmp[32 + WEBP_ALIGN_CST]; @@ -462,7 +464,7 @@ static void InitSegmentJob(VP8Encoder* const enc, SegmentJob* const job, WebPGetWorkerInterface()->Init(&job->worker); job->worker.data1 = job; job->worker.data2 = &job->it; - job->worker.hook = (WebPWorkerHook)DoSegmentsJob; + job->worker.hook = DoSegmentsJob; VP8IteratorInit(enc, &job->it); VP8IteratorSetRow(&job->it, start_row); VP8IteratorSetCountDown(&job->it, (end_row - start_row) * enc->mb_w_); diff --git a/thirdparty/libwebp/src/enc/frame_enc.c b/thirdparty/libwebp/src/enc/frame_enc.c index 2b0dc66410..1aec376e44 100644 --- a/thirdparty/libwebp/src/enc/frame_enc.c +++ b/thirdparty/libwebp/src/enc/frame_enc.c @@ -198,7 +198,7 @@ static void SetSegmentProbas(VP8Encoder* const enc) { for (n = 0; n < enc->mb_w_ * enc->mb_h_; ++n) { const VP8MBInfo* const mb = &enc->mb_info_[n]; - p[mb->segment_]++; + ++p[mb->segment_]; } #if !defined(WEBP_DISABLE_STATS) if (enc->pic_->stats != NULL) { @@ -520,6 +520,14 @@ static void StoreSideInfo(const VP8EncIterator* const it) { #endif } +static void ResetSideInfo(const VP8EncIterator* const it) { + VP8Encoder* const enc = it->enc_; + WebPPicture* const pic = enc->pic_; + if (pic->stats != NULL) { + memset(enc->block_count_, 0, sizeof(enc->block_count_)); + } + ResetSSE(enc); +} #else // defined(WEBP_DISABLE_STATS) static void ResetSSE(VP8Encoder* const enc) { (void)enc; @@ -528,10 +536,16 @@ static void StoreSideInfo(const VP8EncIterator* const it) { VP8Encoder* const enc = it->enc_; WebPPicture* const pic = enc->pic_; if (pic->extra_info != NULL) { - memset(pic->extra_info, 0, - enc->mb_w_ * enc->mb_h_ * sizeof(*pic->extra_info)); + if (it->x_ == 0 && it->y_ == 0) { // only do it once, at start + memset(pic->extra_info, 0, + enc->mb_w_ * enc->mb_h_ * sizeof(*pic->extra_info)); + } } } + +static void ResetSideInfo(const VP8EncIterator* const it) { + (void)it; +} #endif // !defined(WEBP_DISABLE_STATS) static double GetPSNR(uint64_t mse, uint64_t size) { @@ -570,7 +584,7 @@ static uint64_t OneStatPass(VP8Encoder* const enc, VP8RDLevel rd_opt, VP8IteratorImport(&it, NULL); if (VP8Decimate(&it, &info, rd_opt)) { // Just record the number of skips and act like skip_proba is not used. - enc->proba_.nb_skip_++; + ++enc->proba_.nb_skip_; } RecordResiduals(&it, &info); size += info.R + info.H; @@ -841,6 +855,9 @@ int VP8EncTokenLoop(VP8Encoder* const enc) { if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) { ++num_pass_left; enc->max_i4_header_bits_ >>= 1; // strengthen header bit limitation... + if (is_last_pass) { + ResetSideInfo(&it); + } continue; // ...and start over } if (is_last_pass) { @@ -871,4 +888,3 @@ int VP8EncTokenLoop(VP8Encoder* const enc) { #endif // DISABLE_TOKEN_BUFFER //------------------------------------------------------------------------------ - diff --git a/thirdparty/libwebp/src/enc/histogram_enc.c b/thirdparty/libwebp/src/enc/histogram_enc.c index 056a972dda..9fdbc627a1 100644 --- a/thirdparty/libwebp/src/enc/histogram_enc.c +++ b/thirdparty/libwebp/src/enc/histogram_enc.c @@ -200,14 +200,9 @@ static WEBP_INLINE double BitsEntropyRefine(const VP8LBitEntropy* entropy) { } } -double VP8LBitsEntropy(const uint32_t* const array, int n, - uint32_t* const trivial_symbol) { +double VP8LBitsEntropy(const uint32_t* const array, int n) { VP8LBitEntropy entropy; VP8LBitsEntropyUnrefined(array, n, &entropy); - if (trivial_symbol != NULL) { - *trivial_symbol = - (entropy.nonzeros == 1) ? entropy.nonzero_code : VP8L_NON_TRIVIAL_SYM; - } return BitsEntropyRefine(&entropy); } @@ -605,7 +600,7 @@ static void HistogramCombineEntropyBin(VP8LHistogramSet* const image_histo, } // Implement a Lehmer random number generator with a multiplicative constant of -// 48271 and a modulo constant of 2^31 − 1. +// 48271 and a modulo constant of 2^31 - 1. static uint32_t MyRand(uint32_t* const seed) { *seed = (uint32_t)(((uint64_t)(*seed) * 48271u) % 2147483647u); assert(*seed > 0); @@ -1031,7 +1026,7 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize, } } - // TODO(vikasa): Optimize HistogramRemap for low-effort compression mode also. + // TODO(vrabaud): Optimize HistogramRemap for low-effort compression mode. // Find the optimal map from original histograms to the final ones. HistogramRemap(orig_histo, image_histo, histogram_symbols); diff --git a/thirdparty/libwebp/src/enc/histogram_enc.h b/thirdparty/libwebp/src/enc/histogram_enc.h index 15b1fbda34..e8c4c83f6f 100644 --- a/thirdparty/libwebp/src/enc/histogram_enc.h +++ b/thirdparty/libwebp/src/enc/histogram_enc.h @@ -109,10 +109,7 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize, uint16_t* const histogram_symbols); // Returns the entropy for the symbols in the input array. -// Also sets trivial_symbol to the code value, if the array has only one code -// value. Otherwise, set it to VP8L_NON_TRIVIAL_SYM. -double VP8LBitsEntropy(const uint32_t* const array, int n, - uint32_t* const trivial_symbol); +double VP8LBitsEntropy(const uint32_t* const array, int n); // Estimate how many bits the combined entropy of literals and distance // approximately maps to. diff --git a/thirdparty/libwebp/src/enc/iterator_enc.c b/thirdparty/libwebp/src/enc/iterator_enc.c index cfacfd2401..7c47d51272 100644 --- a/thirdparty/libwebp/src/enc/iterator_enc.c +++ b/thirdparty/libwebp/src/enc/iterator_enc.c @@ -26,6 +26,9 @@ static void InitLeft(VP8EncIterator* const it) { memset(it->u_left_, 129, 8); memset(it->v_left_, 129, 8); it->left_nz_[8] = 0; + if (it->top_derr_ != NULL) { + memset(&it->left_derr_, 0, sizeof(it->left_derr_)); + } } static void InitTop(VP8EncIterator* const it) { @@ -33,6 +36,9 @@ static void InitTop(VP8EncIterator* const it) { const size_t top_size = enc->mb_w_ * 16; memset(enc->y_top_, 127, 2 * top_size); memset(enc->nz_, 0, enc->mb_w_ * sizeof(*enc->nz_)); + if (enc->top_derr_ != NULL) { + memset(enc->top_derr_, 0, enc->mb_w_ * sizeof(*enc->top_derr_)); + } } void VP8IteratorSetRow(VP8EncIterator* const it, int y) { @@ -76,6 +82,7 @@ void VP8IteratorInit(VP8Encoder* const enc, VP8EncIterator* const it) { it->y_left_ = (uint8_t*)WEBP_ALIGN(it->yuv_left_mem_ + 1); it->u_left_ = it->y_left_ + 16 + 16; it->v_left_ = it->u_left_ + 16; + it->top_derr_ = enc->top_derr_; VP8IteratorReset(it); } @@ -450,4 +457,3 @@ int VP8IteratorRotateI4(VP8EncIterator* const it, } //------------------------------------------------------------------------------ - diff --git a/thirdparty/libwebp/src/enc/near_lossless_enc.c b/thirdparty/libwebp/src/enc/near_lossless_enc.c index cadd14c664..5517a7e271 100644 --- a/thirdparty/libwebp/src/enc/near_lossless_enc.c +++ b/thirdparty/libwebp/src/enc/near_lossless_enc.c @@ -146,6 +146,6 @@ int VP8ApplyNearLossless(const WebPPicture* const picture, int quality, // Define a stub to suppress compiler warnings. extern void VP8LNearLosslessStub(void); -WEBP_TSAN_IGNORE_FUNCTION void VP8LNearLosslessStub(void) {} +void VP8LNearLosslessStub(void) {} #endif // (WEBP_NEAR_LOSSLESS == 1) diff --git a/thirdparty/libwebp/src/enc/picture_csp_enc.c b/thirdparty/libwebp/src/enc/picture_csp_enc.c index d531dd0282..02d9df76d5 100644 --- a/thirdparty/libwebp/src/enc/picture_csp_enc.c +++ b/thirdparty/libwebp/src/enc/picture_csp_enc.c @@ -28,11 +28,11 @@ // If defined, use table to compute x / alpha. #define USE_INVERSE_ALPHA_TABLE -static const union { - uint32_t argb; - uint8_t bytes[4]; -} test_endian = { 0xff000000u }; -#define ALPHA_IS_LAST (test_endian.bytes[3] == 0xff) +#ifdef WORDS_BIGENDIAN +#define ALPHA_OFFSET 0 // uint32_t 0xff000000 is 0xff,00,00,00 in memory +#else +#define ALPHA_OFFSET 3 // uint32_t 0xff000000 is 0x00,00,00,ff in memory +#endif //------------------------------------------------------------------------------ // Detection of non-trivial transparency @@ -61,7 +61,7 @@ int WebPPictureHasTransparency(const WebPPicture* picture) { return CheckNonOpaque(picture->a, picture->width, picture->height, 1, picture->a_stride); } else { - const int alpha_offset = ALPHA_IS_LAST ? 3 : 0; + const int alpha_offset = ALPHA_OFFSET; return CheckNonOpaque((const uint8_t*)picture->argb + alpha_offset, picture->width, picture->height, 4, picture->argb_stride * sizeof(*picture->argb)); @@ -126,7 +126,7 @@ static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) { #else -static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTables(void) {} +static void InitGammaTables(void) {} static WEBP_INLINE uint32_t GammaToLinear(uint8_t v) { return v; } static WEBP_INLINE int LinearToGamma(uint32_t base_value, int shift) { return (int)(base_value << shift); @@ -170,29 +170,33 @@ typedef uint16_t fixed_y_t; // unsigned type with extra SFIX precision for W #if defined(USE_GAMMA_COMPRESSION) -// float variant of gamma-correction // We use tables of different size and precision for the Rec709 / BT2020 // transfer function. #define kGammaF (1./0.45) -static float kGammaToLinearTabF[MAX_Y_T + 1]; // size scales with Y_FIX -static float kLinearToGammaTabF[kGammaTabSize + 2]; -static volatile int kGammaTablesFOk = 0; - -static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) { - if (!kGammaTablesFOk) { +static uint32_t kLinearToGammaTabS[kGammaTabSize + 2]; +#define GAMMA_TO_LINEAR_BITS 14 +static uint32_t kGammaToLinearTabS[MAX_Y_T + 1]; // size scales with Y_FIX +static volatile int kGammaTablesSOk = 0; + +static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesS(void) { + assert(2 * GAMMA_TO_LINEAR_BITS < 32); // we use uint32_t intermediate values + if (!kGammaTablesSOk) { int v; const double norm = 1. / MAX_Y_T; const double scale = 1. / kGammaTabSize; const double a = 0.09929682680944; const double thresh = 0.018053968510807; + const double final_scale = 1 << GAMMA_TO_LINEAR_BITS; for (v = 0; v <= MAX_Y_T; ++v) { const double g = norm * v; + double value; if (g <= thresh * 4.5) { - kGammaToLinearTabF[v] = (float)(g / 4.5); + value = g / 4.5; } else { const double a_rec = 1. / (1. + a); - kGammaToLinearTabF[v] = (float)pow(a_rec * (g + a), kGammaF); + value = pow(a_rec * (g + a), kGammaF); } + kGammaToLinearTabS[v] = (uint32_t)(value * final_scale + .5); } for (v = 0; v <= kGammaTabSize; ++v) { const double g = scale * v; @@ -202,37 +206,44 @@ static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) { } else { value = (1. + a) * pow(g, 1. / kGammaF) - a; } - kLinearToGammaTabF[v] = (float)(MAX_Y_T * value); + // we already incorporate the 1/2 rounding constant here + kLinearToGammaTabS[v] = + (uint32_t)(MAX_Y_T * value) + (1 << GAMMA_TO_LINEAR_BITS >> 1); } // to prevent small rounding errors to cause read-overflow: - kLinearToGammaTabF[kGammaTabSize + 1] = kLinearToGammaTabF[kGammaTabSize]; - kGammaTablesFOk = 1; + kLinearToGammaTabS[kGammaTabSize + 1] = kLinearToGammaTabS[kGammaTabSize]; + kGammaTablesSOk = 1; } } -static WEBP_INLINE float GammaToLinearF(int v) { - return kGammaToLinearTabF[v]; +// return value has a fixed-point precision of GAMMA_TO_LINEAR_BITS +static WEBP_INLINE uint32_t GammaToLinearS(int v) { + return kGammaToLinearTabS[v]; } -static WEBP_INLINE int LinearToGammaF(float value) { - const float v = value * kGammaTabSize; - const int tab_pos = (int)v; - const float x = v - (float)tab_pos; // fractional part - const float v0 = kLinearToGammaTabF[tab_pos + 0]; - const float v1 = kLinearToGammaTabF[tab_pos + 1]; - const float y = v1 * x + v0 * (1.f - x); // interpolate - return (int)(y + .5); +static WEBP_INLINE uint32_t LinearToGammaS(uint32_t value) { + // 'value' is in GAMMA_TO_LINEAR_BITS fractional precision + const uint32_t v = value * kGammaTabSize; + const uint32_t tab_pos = v >> GAMMA_TO_LINEAR_BITS; + // fractional part, in GAMMA_TO_LINEAR_BITS fixed-point precision + const uint32_t x = v - (tab_pos << GAMMA_TO_LINEAR_BITS); // fractional part + // v0 / v1 are in GAMMA_TO_LINEAR_BITS fixed-point precision (range [0..1]) + const uint32_t v0 = kLinearToGammaTabS[tab_pos + 0]; + const uint32_t v1 = kLinearToGammaTabS[tab_pos + 1]; + // Final interpolation. Note that rounding is already included. + const uint32_t v2 = (v1 - v0) * x; // note: v1 >= v0. + const uint32_t result = v0 + (v2 >> GAMMA_TO_LINEAR_BITS); + return result; } #else -static WEBP_TSAN_IGNORE_FUNCTION void InitGammaTablesF(void) {} -static WEBP_INLINE float GammaToLinearF(int v) { - const float norm = 1.f / MAX_Y_T; - return norm * v; +static void InitGammaTablesS(void) {} +static WEBP_INLINE uint32_t GammaToLinearS(int v) { + return (v << GAMMA_TO_LINEAR_BITS) / MAX_Y_T; } -static WEBP_INLINE int LinearToGammaF(float value) { - return (int)(MAX_Y_T * value + .5); +static WEBP_INLINE uint32_t LinearToGammaS(uint32_t value) { + return (MAX_Y_T * value) >> GAMMA_TO_LINEAR_BITS; } #endif // USE_GAMMA_COMPRESSION @@ -254,26 +265,22 @@ static int RGBToGray(int r, int g, int b) { return (luma >> YUV_FIX); } -static float RGBToGrayF(float r, float g, float b) { - return (float)(0.2126 * r + 0.7152 * g + 0.0722 * b); -} - -static int ScaleDown(int a, int b, int c, int d) { - const float A = GammaToLinearF(a); - const float B = GammaToLinearF(b); - const float C = GammaToLinearF(c); - const float D = GammaToLinearF(d); - return LinearToGammaF(0.25f * (A + B + C + D)); +static uint32_t ScaleDown(int a, int b, int c, int d) { + const uint32_t A = GammaToLinearS(a); + const uint32_t B = GammaToLinearS(b); + const uint32_t C = GammaToLinearS(c); + const uint32_t D = GammaToLinearS(d); + return LinearToGammaS((A + B + C + D + 2) >> 2); } static WEBP_INLINE void UpdateW(const fixed_y_t* src, fixed_y_t* dst, int w) { int i; for (i = 0; i < w; ++i) { - const float R = GammaToLinearF(src[0 * w + i]); - const float G = GammaToLinearF(src[1 * w + i]); - const float B = GammaToLinearF(src[2 * w + i]); - const float Y = RGBToGrayF(R, G, B); - dst[i] = (fixed_y_t)LinearToGammaF(Y); + const uint32_t R = GammaToLinearS(src[0 * w + i]); + const uint32_t G = GammaToLinearS(src[1 * w + i]); + const uint32_t B = GammaToLinearS(src[2 * w + i]); + const uint32_t Y = RGBToGray(R, G, B); + dst[i] = (fixed_y_t)LinearToGammaS(Y); } } @@ -863,7 +870,7 @@ static int ImportYUVAFromRGBA(const uint8_t* r_ptr, } if (use_iterative_conversion) { - InitGammaTablesF(); + InitGammaTablesS(); if (!PreprocessARGB(r_ptr, g_ptr, b_ptr, step, rgb_stride, picture)) { return 0; } @@ -990,10 +997,10 @@ static int PictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace, return WebPEncodingSetError(picture, VP8_ENC_ERROR_INVALID_CONFIGURATION); } else { const uint8_t* const argb = (const uint8_t*)picture->argb; - const uint8_t* const r = ALPHA_IS_LAST ? argb + 2 : argb + 1; - const uint8_t* const g = ALPHA_IS_LAST ? argb + 1 : argb + 2; - const uint8_t* const b = ALPHA_IS_LAST ? argb + 0 : argb + 3; - const uint8_t* const a = ALPHA_IS_LAST ? argb + 3 : argb + 0; + const uint8_t* const a = argb + (0 ^ ALPHA_OFFSET); + const uint8_t* const r = argb + (1 ^ ALPHA_OFFSET); + const uint8_t* const g = argb + (2 ^ ALPHA_OFFSET); + const uint8_t* const b = argb + (3 ^ ALPHA_OFFSET); picture->colorspace = WEBP_YUV420; return ImportYUVAFromRGBA(r, g, b, a, 4, 4 * picture->argb_stride, @@ -1044,7 +1051,8 @@ int WebPPictureYUVAToARGB(WebPPicture* picture) { const int argb_stride = 4 * picture->argb_stride; uint8_t* dst = (uint8_t*)picture->argb; const uint8_t *cur_u = picture->u, *cur_v = picture->v, *cur_y = picture->y; - WebPUpsampleLinePairFunc upsample = WebPGetLinePairConverter(ALPHA_IS_LAST); + WebPUpsampleLinePairFunc upsample = + WebPGetLinePairConverter(ALPHA_OFFSET > 0); // First row, with replicated top samples. upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, width); @@ -1087,6 +1095,7 @@ static int Import(WebPPicture* const picture, const uint8_t* rgb, int rgb_stride, int step, int swap_rb, int import_alpha) { int y; + // swap_rb -> b,g,r,a , !swap_rb -> r,g,b,a const uint8_t* r_ptr = rgb + (swap_rb ? 2 : 0); const uint8_t* g_ptr = rgb + 1; const uint8_t* b_ptr = rgb + (swap_rb ? 0 : 2); @@ -1104,19 +1113,32 @@ static int Import(WebPPicture* const picture, WebPInitAlphaProcessing(); if (import_alpha) { + // dst[] byte order is {a,r,g,b} for big-endian, {b,g,r,a} for little endian uint32_t* dst = picture->argb; - const int do_copy = - (!swap_rb && !ALPHA_IS_LAST) || (swap_rb && ALPHA_IS_LAST); + const int do_copy = (ALPHA_OFFSET == 3) && swap_rb; assert(step == 4); - for (y = 0; y < height; ++y) { - if (do_copy) { + if (do_copy) { + for (y = 0; y < height; ++y) { memcpy(dst, rgb, width * 4); - } else { + rgb += rgb_stride; + dst += picture->argb_stride; + } + } else { + for (y = 0; y < height; ++y) { +#ifdef WORDS_BIGENDIAN + // BGRA or RGBA input order. + const uint8_t* a_ptr = rgb + 3; + WebPPackARGB(a_ptr, r_ptr, g_ptr, b_ptr, width, dst); + r_ptr += rgb_stride; + g_ptr += rgb_stride; + b_ptr += rgb_stride; +#else // RGBA input order. Need to swap R and B. VP8LConvertBGRAToRGBA((const uint32_t*)rgb, width, (uint8_t*)dst); +#endif + rgb += rgb_stride; + dst += picture->argb_stride; } - rgb += rgb_stride; - dst += picture->argb_stride; } } else { uint32_t* dst = picture->argb; diff --git a/thirdparty/libwebp/src/enc/picture_psnr_enc.c b/thirdparty/libwebp/src/enc/picture_psnr_enc.c index 362a7c79be..1a2f0bef3e 100644 --- a/thirdparty/libwebp/src/enc/picture_psnr_enc.c +++ b/thirdparty/libwebp/src/enc/picture_psnr_enc.c @@ -18,6 +18,7 @@ #include <math.h> #include <stdlib.h> +#include "src/dsp/dsp.h" #include "src/enc/vp8i_enc.h" #include "src/utils/utils.h" @@ -169,6 +170,12 @@ int WebPPlaneDistortion(const uint8_t* src, size_t src_stride, return 1; } +#ifdef WORDS_BIGENDIAN +#define BLUE_OFFSET 3 // uint32_t 0x000000ff is 0x00,00,00,ff in memory +#else +#define BLUE_OFFSET 0 // uint32_t 0x000000ff is 0xff,00,00,00 in memory +#endif + int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, int type, float results[5]) { int w, h, c; @@ -195,8 +202,10 @@ int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, float distortion; const size_t stride0 = 4 * (size_t)p0.argb_stride; const size_t stride1 = 4 * (size_t)p1.argb_stride; - if (!WebPPlaneDistortion((const uint8_t*)p0.argb + c, stride0, - (const uint8_t*)p1.argb + c, stride1, + // results are reported as BGRA + const int offset = c ^ BLUE_OFFSET; + if (!WebPPlaneDistortion((const uint8_t*)p0.argb + offset, stride0, + (const uint8_t*)p1.argb + offset, stride1, w, h, 4, type, &distortion, results + c)) { goto Error; } @@ -214,6 +223,8 @@ int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref, return ok; } +#undef BLUE_OFFSET + #else // defined(WEBP_DISABLE_STATS) int WebPPlaneDistortion(const uint8_t* src, size_t src_stride, const uint8_t* ref, size_t ref_stride, diff --git a/thirdparty/libwebp/src/enc/quant_enc.c b/thirdparty/libwebp/src/enc/quant_enc.c index 3b1a3129b5..35bfaf21ef 100644 --- a/thirdparty/libwebp/src/enc/quant_enc.c +++ b/thirdparty/libwebp/src/enc/quant_enc.c @@ -826,6 +826,85 @@ static int ReconstructIntra4(VP8EncIterator* const it, return nz; } +//------------------------------------------------------------------------------ +// DC-error diffusion + +// Diffusion weights. We under-correct a bit (15/16th of the error is actually +// diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. +#define C1 7 // fraction of error sent to the 4x4 block below +#define C2 8 // fraction of error sent to the 4x4 block on the right +#define DSHIFT 4 +#define DSCALE 1 // storage descaling, needed to make the error fit int8_t + +// Quantize as usual, but also compute and return the quantization error. +// Error is already divided by DSHIFT. +static int QuantizeSingle(int16_t* const v, const VP8Matrix* const mtx) { + int V = *v; + const int sign = (V < 0); + if (sign) V = -V; + if (V > (int)mtx->zthresh_[0]) { + const int qV = QUANTDIV(V, mtx->iq_[0], mtx->bias_[0]) * mtx->q_[0]; + const int err = (V - qV); + *v = sign ? -qV : qV; + return (sign ? -err : err) >> DSCALE; + } + *v = 0; + return (sign ? -V : V) >> DSCALE; +} + +static void CorrectDCValues(const VP8EncIterator* const it, + const VP8Matrix* const mtx, + int16_t tmp[][16], VP8ModeScore* const rd) { + // | top[0] | top[1] + // --------+--------+--------- + // left[0] | tmp[0] tmp[1] <-> err0 err1 + // left[1] | tmp[2] tmp[3] err2 err3 + // + // Final errors {err1,err2,err3} are preserved and later restored + // as top[]/left[] on the next block. + int ch; + for (ch = 0; ch <= 1; ++ch) { + const int8_t* const top = it->top_derr_[it->x_][ch]; + const int8_t* const left = it->left_derr_[ch]; + int16_t (* const c)[16] = &tmp[ch * 4]; + int err0, err1, err2, err3; + c[0][0] += (C1 * top[0] + C2 * left[0]) >> (DSHIFT - DSCALE); + err0 = QuantizeSingle(&c[0][0], mtx); + c[1][0] += (C1 * top[1] + C2 * err0) >> (DSHIFT - DSCALE); + err1 = QuantizeSingle(&c[1][0], mtx); + c[2][0] += (C1 * err0 + C2 * left[1]) >> (DSHIFT - DSCALE); + err2 = QuantizeSingle(&c[2][0], mtx); + c[3][0] += (C1 * err1 + C2 * err2) >> (DSHIFT - DSCALE); + err3 = QuantizeSingle(&c[3][0], mtx); + // error 'err' is bounded by mtx->q_[0] which is 132 at max. Hence + // err >> DSCALE will fit in an int8_t type if DSCALE>=1. + assert(abs(err1) <= 127 && abs(err2) <= 127 && abs(err3) <= 127); + rd->derr[ch][0] = (int8_t)err1; + rd->derr[ch][1] = (int8_t)err2; + rd->derr[ch][2] = (int8_t)err3; + } +} + +static void StoreDiffusionErrors(VP8EncIterator* const it, + const VP8ModeScore* const rd) { + int ch; + for (ch = 0; ch <= 1; ++ch) { + int8_t* const top = it->top_derr_[it->x_][ch]; + int8_t* const left = it->left_derr_[ch]; + left[0] = rd->derr[ch][0]; // restore err1 + left[1] = 3 * rd->derr[ch][2] >> 2; // ... 3/4th of err3 + top[0] = rd->derr[ch][1]; // ... err2 + top[1] = rd->derr[ch][2] - left[1]; // ... 1/4th of err3. + } +} + +#undef C1 +#undef C2 +#undef DSHIFT +#undef DSCALE + +//------------------------------------------------------------------------------ + static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd, uint8_t* const yuv_out, int mode) { const VP8Encoder* const enc = it->enc_; @@ -839,6 +918,8 @@ static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd, for (n = 0; n < 8; n += 2) { VP8FTransform2(src + VP8ScanUV[n], ref + VP8ScanUV[n], tmp[n]); } + if (it->top_derr_ != NULL) CorrectDCValues(it, &dqm->uv_, tmp, rd); + if (DO_TRELLIS_UV && it->do_trellis_) { int ch, x, y; for (ch = 0, n = 0; ch <= 2; ch += 2) { @@ -1101,6 +1182,9 @@ static void PickBestUV(VP8EncIterator* const it, VP8ModeScore* const rd) { CopyScore(&rd_best, &rd_uv); rd->mode_uv = mode; memcpy(rd->uv_levels, rd_uv.uv_levels, sizeof(rd->uv_levels)); + if (it->top_derr_ != NULL) { + memcpy(rd->derr, rd_uv.derr, sizeof(rd_uv.derr)); + } SwapPtr(&dst, &tmp_dst); } } @@ -1109,6 +1193,9 @@ static void PickBestUV(VP8EncIterator* const it, VP8ModeScore* const rd) { if (dst != dst0) { // copy 16x8 block if needed VP8Copy16x8(dst, dst0); } + if (it->top_derr_ != NULL) { // store diffusion errors for next block + StoreDiffusionErrors(it, rd); + } } //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/enc/vp8i_enc.h b/thirdparty/libwebp/src/enc/vp8i_enc.h index 3463491e9d..624e8f8e66 100644 --- a/thirdparty/libwebp/src/enc/vp8i_enc.h +++ b/thirdparty/libwebp/src/enc/vp8i_enc.h @@ -30,9 +30,9 @@ extern "C" { // Various defines and enums // version numbers -#define ENC_MAJ_VERSION 0 -#define ENC_MIN_VERSION 6 -#define ENC_REV_VERSION 1 +#define ENC_MAJ_VERSION 1 +#define ENC_MIN_VERSION 0 +#define ENC_REV_VERSION 0 enum { MAX_LF_LEVELS = 64, // Maximum loop filter level MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost @@ -120,6 +120,9 @@ static WEBP_INLINE int QUANTDIV(uint32_t n, uint32_t iQ, uint32_t B) { // Uncomment the following to remove token-buffer code: // #define DISABLE_TOKEN_BUFFER +// quality below which error-diffusion is enabled +#define ERROR_DIFFUSION_QUALITY 98 + //------------------------------------------------------------------------------ // Headers @@ -201,6 +204,8 @@ typedef struct { score_t i4_penalty_; // penalty for using Intra4 } VP8SegmentInfo; +typedef int8_t DError[2 /* u/v */][2 /* top or left */]; + // Handy transient struct to accumulate score and info during RD-optimization // and mode evaluation. typedef struct { @@ -213,6 +218,7 @@ typedef struct { uint8_t modes_i4[16]; // mode numbers for intra4 predictions int mode_uv; // mode number of chroma prediction uint32_t nz; // non-zero blocks + int8_t derr[2][3]; // DC diffusion errors for U/V for blocks #1/2/3 } VP8ModeScore; // Iterator structure to iterate through macroblocks, pointing to the @@ -242,6 +248,9 @@ typedef struct { int count_down0_; // starting counter value (for progress) int percent0_; // saved initial progress percent + DError left_derr_; // left error diffusion (u/v) + DError *top_derr_; // top diffusion error - NULL if disabled + uint8_t* y_left_; // left luma samples (addressable from index -1 to 15). uint8_t* u_left_; // left u samples (addressable from index -1 to 7) uint8_t* v_left_; // left v samples (addressable from index -1 to 7) @@ -401,6 +410,7 @@ struct VP8Encoder { uint8_t* uv_top_; // top u/v samples. // U and V are packed into 16 bytes (8 U + 8 V) LFStats* lf_stats_; // autofilter stats (if NULL, autofilter is off) + DError* top_derr_; // diffusion error (NULL if disabled) }; //------------------------------------------------------------------------------ diff --git a/thirdparty/libwebp/src/enc/vp8l_enc.c b/thirdparty/libwebp/src/enc/vp8l_enc.c index 312e521906..a89184eb08 100644 --- a/thirdparty/libwebp/src/enc/vp8l_enc.c +++ b/thirdparty/libwebp/src/enc/vp8l_enc.c @@ -26,8 +26,6 @@ #include "src/utils/utils.h" #include "src/webp/format_constants.h" -#include "src/enc/delta_palettization_enc.h" - // Maximum number of histogram images (sub-blocks). #define MAX_HUFF_IMAGE_SIZE 2600 @@ -259,7 +257,7 @@ static int AnalyzeEntropy(const uint32_t* argb, ++histo[kHistoAlphaPred * 256]; for (j = 0; j < kHistoTotal; ++j) { - entropy_comp[j] = VP8LBitsEntropy(&histo[j * 256], 256, NULL); + entropy_comp[j] = VP8LBitsEntropy(&histo[j * 256], 256); } entropy[kDirect] = entropy_comp[kHistoAlpha] + entropy_comp[kHistoRed] + @@ -384,8 +382,7 @@ static int EncoderAnalyze(VP8LEncoder* const enc, AnalyzeAndCreatePalette(pic, low_effort, enc->palette_, &enc->palette_size_); - // TODO(jyrki): replace the decision to be based on an actual estimate - // of entropy, or even spatial variance of entropy. + // Empirical bit sizes. enc->histo_bits_ = GetHistoBits(method, use_palette, pic->width, pic->height); enc->transform_bits_ = GetTransformBits(method, enc->histo_bits_); @@ -756,7 +753,6 @@ static WebPEncodingError StoreImageToBitMask( // Don't write the distance with the extra bits code since // the distance can be up to 18 bits of extra bits, and the prefix // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. - // TODO(jyrki): optimize this further. VP8LPrefixEncode(distance, &code, &n_bits, &bits); WriteHuffmanCode(bw, codes + 4, code); VP8LPutBits(bw, bits, n_bits); @@ -1464,49 +1460,6 @@ static WebPEncodingError EncodePalette(VP8LBitWriter* const bw, int low_effort, 20 /* quality */, low_effort); } -#ifdef WEBP_EXPERIMENTAL_FEATURES - -static WebPEncodingError EncodeDeltaPalettePredictorImage( - VP8LBitWriter* const bw, VP8LEncoder* const enc, int quality, - int low_effort) { - const WebPPicture* const pic = enc->pic_; - const int width = pic->width; - const int height = pic->height; - - const int pred_bits = 5; - const int transform_width = VP8LSubSampleSize(width, pred_bits); - const int transform_height = VP8LSubSampleSize(height, pred_bits); - const int pred = 7; // default is Predictor7 (Top/Left Average) - const int tiles_per_row = VP8LSubSampleSize(width, pred_bits); - const int tiles_per_col = VP8LSubSampleSize(height, pred_bits); - uint32_t* predictors; - int tile_x, tile_y; - WebPEncodingError err = VP8_ENC_OK; - - predictors = (uint32_t*)WebPSafeMalloc(tiles_per_col * tiles_per_row, - sizeof(*predictors)); - if (predictors == NULL) return VP8_ENC_ERROR_OUT_OF_MEMORY; - - for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) { - for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) { - predictors[tile_y * tiles_per_row + tile_x] = 0xff000000u | (pred << 8); - } - } - - VP8LPutBits(bw, TRANSFORM_PRESENT, 1); - VP8LPutBits(bw, PREDICTOR_TRANSFORM, 2); - VP8LPutBits(bw, pred_bits - 2, 3); - err = EncodeImageNoHuffman( - bw, predictors, &enc->hash_chain_, - (VP8LBackwardRefs*)&enc->refs_[0], // cast const away - (VP8LBackwardRefs*)&enc->refs_[1], - transform_width, transform_height, quality, low_effort); - WebPSafeFree(predictors); - return err; -} - -#endif // WEBP_EXPERIMENTAL_FEATURES - // ----------------------------------------------------------------------------- // VP8LEncoder @@ -1568,7 +1521,7 @@ static int EncodeStreamHook(void* input, void* data2) { WebPEncodingError err = VP8_ENC_OK; const int quality = (int)config->quality; const int low_effort = (config->method == 0); -#if (WEBP_NEAR_LOSSLESS == 1) || defined(WEBP_EXPERIMENTAL_FEATURES) +#if (WEBP_NEAR_LOSSLESS == 1) const int width = picture->width; #endif const int height = picture->height; @@ -1627,29 +1580,6 @@ static int EncodeStreamHook(void* input, void* data2) { enc->argb_content_ = kEncoderNone; #endif -#ifdef WEBP_EXPERIMENTAL_FEATURES - if (config->use_delta_palette) { - enc->use_predict_ = 1; - enc->use_cross_color_ = 0; - enc->use_subtract_green_ = 0; - enc->use_palette_ = 1; - if (enc->argb_content_ != kEncoderNearLossless && - enc->argb_content_ != kEncoderPalette) { - err = MakeInputImageCopy(enc); - if (err != VP8_ENC_OK) goto Error; - } - err = WebPSearchOptimalDeltaPalette(enc); - if (err != VP8_ENC_OK) goto Error; - if (enc->use_palette_) { - err = AllocateTransformBuffer(enc, width, height); - if (err != VP8_ENC_OK) goto Error; - err = EncodeDeltaPalettePredictorImage(bw, enc, quality, low_effort); - if (err != VP8_ENC_OK) goto Error; - use_delta_palette = 1; - } - } -#endif // WEBP_EXPERIMENTAL_FEATURES - // Encode palette if (enc->use_palette_) { err = EncodePalette(bw, low_effort, enc); @@ -1822,7 +1752,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, worker_interface->Init(worker); worker->data1 = param; worker->data2 = NULL; - worker->hook = (WebPWorkerHook)EncodeStreamHook; + worker->hook = EncodeStreamHook; } } @@ -1944,7 +1874,6 @@ int VP8LEncodeImage(const WebPConfig* const config, err = VP8LEncodeStream(config, picture, &bw, 1 /*use_cache*/); if (err != VP8_ENC_OK) goto Error; - // TODO(skal): have a fine-grained progress report in VP8LEncodeStream(). if (!WebPReportProgress(picture, 90, &percent)) goto UserAbort; // Finish the RIFF chunk. diff --git a/thirdparty/libwebp/src/enc/webp_enc.c b/thirdparty/libwebp/src/enc/webp_enc.c index 283cda8e7b..9f4b10c26c 100644 --- a/thirdparty/libwebp/src/enc/webp_enc.c +++ b/thirdparty/libwebp/src/enc/webp_enc.c @@ -159,12 +159,16 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, + WEBP_ALIGN_CST; // align all const size_t lf_stats_size = config->autofilter ? sizeof(*enc->lf_stats_) + WEBP_ALIGN_CST : 0; + const size_t top_derr_size = + (config->quality <= ERROR_DIFFUSION_QUALITY || config->pass > 1) ? + mb_w * sizeof(*enc->top_derr_) : 0; uint8_t* mem; const uint64_t size = (uint64_t)sizeof(*enc) // main struct + WEBP_ALIGN_CST // cache alignment + info_size // modes info + preds_size // prediction modes + samples_size // top/left samples + + top_derr_size // top diffusion error + nz_size // coeff context bits + lf_stats_size; // autofilter stats @@ -175,11 +179,12 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, " info: %ld\n" " preds: %ld\n" " top samples: %ld\n" + " top diffusion: %ld\n" " non-zero: %ld\n" " lf-stats: %ld\n" " total: %ld\n", sizeof(*enc) + WEBP_ALIGN_CST, info_size, - preds_size, samples_size, nz_size, lf_stats_size, size); + preds_size, samples_size, top_derr_size, nz_size, lf_stats_size, size); printf("Transient object sizes:\n" " VP8EncIterator: %ld\n" " VP8ModeScore: %ld\n" @@ -219,6 +224,8 @@ static VP8Encoder* InitVP8Encoder(const WebPConfig* const config, enc->y_top_ = mem; enc->uv_top_ = enc->y_top_ + top_stride; mem += 2 * top_stride; + enc->top_derr_ = top_derr_size ? (DError*)mem : NULL; + mem += top_derr_size; assert(mem <= (uint8_t*)enc + size); enc->config_ = config; diff --git a/thirdparty/libwebp/src/mux/muxi.h b/thirdparty/libwebp/src/mux/muxi.h index b73e3fbd7a..6b57eea30f 100644 --- a/thirdparty/libwebp/src/mux/muxi.h +++ b/thirdparty/libwebp/src/mux/muxi.h @@ -26,9 +26,9 @@ extern "C" { //------------------------------------------------------------------------------ // Defines and constants. -#define MUX_MAJ_VERSION 0 -#define MUX_MIN_VERSION 4 -#define MUX_REV_VERSION 1 +#define MUX_MAJ_VERSION 1 +#define MUX_MIN_VERSION 0 +#define MUX_REV_VERSION 0 // Chunk object. typedef struct WebPChunk WebPChunk; diff --git a/thirdparty/libwebp/src/utils/endian_inl_utils.h b/thirdparty/libwebp/src/utils/endian_inl_utils.h index 4b2f91dfb8..3630a293bf 100644 --- a/thirdparty/libwebp/src/utils/endian_inl_utils.h +++ b/thirdparty/libwebp/src/utils/endian_inl_utils.h @@ -19,13 +19,6 @@ #include "src/dsp/dsp.h" #include "src/webp/types.h" -// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__) -#if !defined(WORDS_BIGENDIAN) && \ - (defined(__BIG_ENDIAN__) || defined(_M_PPC) || \ - (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))) -#define WORDS_BIGENDIAN -#endif - #if defined(WORDS_BIGENDIAN) #define HToLE32 BSwap32 #define HToLE16 BSwap16 |