diff options
119 files changed, 2952 insertions, 699 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 399fca03e8..3bbe47af2a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -30,7 +30,6 @@ doc_classes/* @godotengine/documentation /modules/bullet/ @AndreaCatania /modules/csg/ @BastiaanOlij /modules/enet/ @godotengine/network -/modules/gdnative/ @karroffel /modules/gdnative/*arvr/ @BastiaanOlij /modules/gdscript/ @vnen @bojidar-bg /modules/mbedtls/ @godotengine/network @@ -45,6 +44,6 @@ doc_classes/* @godotengine/documentation /platform/uwp/ @vnen /server/physics*/ @reduz @AndreaCatania -/server/visual*/ @reduz @karroffel +/server/visual*/ @reduz /thirdparty/ @akien-mga diff --git a/core/SCsub b/core/SCsub index 85e5f1b089..ed9a0a231d 100644 --- a/core/SCsub +++ b/core/SCsub @@ -159,6 +159,7 @@ env.CommandNoCache('#core/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"] # Chain load SCsubs SConscript('os/SCsub') SConscript('math/SCsub') +SConscript('crypto/SCsub') SConscript('io/SCsub') SConscript('bind/SCsub') diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 56369a3cc6..8e0d156438 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -30,11 +30,11 @@ #include "core_bind.h" +#include "core/crypto/crypto_core.h" #include "core/io/file_access_compressed.h" #include "core/io/file_access_encrypted.h" #include "core/io/json.h" #include "core/io/marshalls.h" -#include "core/math/crypto_core.h" #include "core/math/geometry.h" #include "core/os/keyboard.h" #include "core/os/os.h" diff --git a/core/crypto/SCsub b/core/crypto/SCsub new file mode 100644 index 0000000000..0a3f05d87a --- /dev/null +++ b/core/crypto/SCsub @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +Import('env') + +env_crypto = env.Clone() + +is_builtin = env["builtin_mbedtls"] +has_module = env["module_mbedtls_enabled"] + +if is_builtin or not has_module: + # Use our headers for builtin or if the module is not going to be compiled. + # We decided not to depend on system mbedtls just for these few files that can + # be easily extracted. + env_crypto.Prepend(CPPPATH=["#thirdparty/mbedtls/include"]) + +# MbedTLS core functions (for CryptoCore). +# If the mbedtls module is compiled we don't need to add the .c files with our +# custom config since they will be built by the module itself. +# Only if the module is not enabled, we must compile here the required sources +# to make a "light" build with only the necessary mbedtls files. +if not has_module: + env_thirdparty = env_crypto.Clone() + env_thirdparty.disable_warnings() + # Custom config file + env_thirdparty.Append(CPPDEFINES=[('MBEDTLS_CONFIG_FILE', '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')]) + thirdparty_mbedtls_dir = "#thirdparty/mbedtls/library/" + thirdparty_mbedtls_sources = [ + "aes.c", + "base64.c", + "md5.c", + "sha1.c", + "sha256.c", + "godot_core_mbedtls_platform.c" + ] + thirdparty_mbedtls_sources = [thirdparty_mbedtls_dir + file for file in thirdparty_mbedtls_sources] + env_thirdparty.add_source_files(env.core_sources, thirdparty_mbedtls_sources) + +env_crypto.add_source_files(env.core_sources, "*.cpp") diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp new file mode 100644 index 0000000000..925a01b36a --- /dev/null +++ b/core/crypto/crypto.cpp @@ -0,0 +1,170 @@ +/*************************************************************************/ +/* crypto.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "crypto.h" + +#include "core/engine.h" +#include "core/io/certs_compressed.gen.h" +#include "core/io/compression.h" + +/// Resources + +CryptoKey *(*CryptoKey::_create)() = NULL; +CryptoKey *CryptoKey::create() { + if (_create) + return _create(); + return NULL; +} + +void CryptoKey::_bind_methods() { + ClassDB::bind_method(D_METHOD("save", "path"), &CryptoKey::save); + ClassDB::bind_method(D_METHOD("load", "path"), &CryptoKey::load); +} + +X509Certificate *(*X509Certificate::_create)() = NULL; +X509Certificate *X509Certificate::create() { + if (_create) + return _create(); + return NULL; +} + +void X509Certificate::_bind_methods() { + ClassDB::bind_method(D_METHOD("save", "path"), &X509Certificate::save); + ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load); +} + +/// Crypto + +void (*Crypto::_load_default_certificates)(String p_path) = NULL; +Crypto *(*Crypto::_create)() = NULL; +Crypto *Crypto::create() { + if (_create) + return _create(); + return memnew(Crypto); +} + +void Crypto::load_default_certificates(String p_path) { + + if (_load_default_certificates) + _load_default_certificates(p_path); +} + +void Crypto::_bind_methods() { + ClassDB::bind_method(D_METHOD("generate_random_bytes", "size"), &Crypto::generate_random_bytes); + ClassDB::bind_method(D_METHOD("generate_rsa", "size"), &Crypto::generate_rsa); + ClassDB::bind_method(D_METHOD("generate_self_signed_certificate", "key", "issuer_name", "not_before", "not_after"), &Crypto::generate_self_signed_certificate, DEFVAL("CN=myserver,O=myorganisation,C=IT"), DEFVAL("20140101000000"), DEFVAL("20340101000000")); +} + +PoolByteArray Crypto::generate_random_bytes(int p_bytes) { + ERR_FAIL_V_MSG(PoolByteArray(), "generate_random_bytes is not available when mbedtls module is disabled."); +} + +Ref<CryptoKey> Crypto::generate_rsa(int p_bytes) { + ERR_FAIL_V_MSG(NULL, "generate_rsa is not available when mbedtls module is disabled."); +} + +Ref<X509Certificate> Crypto::generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) { + ERR_FAIL_V_MSG(NULL, "generate_self_signed_certificate is not available when mbedtls module is disabled."); +} + +Crypto::Crypto() { +} + +/// Resource loader/saver + +RES ResourceFormatLoaderCrypto::load(const String &p_path, const String &p_original_path, Error *r_error) { + + String el = p_path.get_extension().to_lower(); + if (el == "crt") { + X509Certificate *cert = X509Certificate::create(); + if (cert) + cert->load(p_path); + return cert; + } else if (el == "key") { + CryptoKey *key = CryptoKey::create(); + if (key) + key->load(p_path); + return key; + } + return NULL; +} + +void ResourceFormatLoaderCrypto::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("crt"); + p_extensions->push_back("key"); +} + +bool ResourceFormatLoaderCrypto::handles_type(const String &p_type) const { + + return p_type == "X509Certificate" || p_type == "CryptoKey"; +} + +String ResourceFormatLoaderCrypto::get_resource_type(const String &p_path) const { + + String el = p_path.get_extension().to_lower(); + if (el == "crt") + return "X509Certificate"; + else if (el == "key") + return "CryptoKey"; + return ""; +} + +Error ResourceFormatSaverCrypto::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { + + Error err; + Ref<X509Certificate> cert = p_resource; + Ref<CryptoKey> key = p_resource; + if (cert.is_valid()) { + err = cert->save(p_path); + } else if (key.is_valid()) { + err = key->save(p_path); + } else { + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + ERR_FAIL_COND_V(err != OK, err); + return OK; +} + +void ResourceFormatSaverCrypto::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { + + const X509Certificate *cert = Object::cast_to<X509Certificate>(*p_resource); + const CryptoKey *key = Object::cast_to<CryptoKey>(*p_resource); + if (cert) { + p_extensions->push_back("crt"); + } + if (key) { + p_extensions->push_back("key"); + } +} +bool ResourceFormatSaverCrypto::recognize(const RES &p_resource) const { + + return Object::cast_to<X509Certificate>(*p_resource) || Object::cast_to<CryptoKey>(*p_resource); +} diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h new file mode 100644 index 0000000000..2de81f5b57 --- /dev/null +++ b/core/crypto/crypto.h @@ -0,0 +1,105 @@ +/*************************************************************************/ +/* crypto.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CRYPTO_H +#define CRYPTO_H + +#include "core/reference.h" +#include "core/resource.h" + +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" + +class CryptoKey : public Resource { + GDCLASS(CryptoKey, Resource); + +protected: + static void _bind_methods(); + static CryptoKey *(*_create)(); + +public: + static CryptoKey *create(); + virtual Error load(String p_path) = 0; + virtual Error save(String p_path) = 0; +}; + +class X509Certificate : public Resource { + GDCLASS(X509Certificate, Resource); + +protected: + static void _bind_methods(); + static X509Certificate *(*_create)(); + +public: + static X509Certificate *create(); + virtual Error load(String p_path) = 0; + virtual Error load_from_memory(const uint8_t *p_buffer, int p_len) = 0; + virtual Error save(String p_path) = 0; +}; + +class Crypto : public Reference { + GDCLASS(Crypto, Reference); + +protected: + static void _bind_methods(); + static Crypto *(*_create)(); + static void (*_load_default_certificates)(String p_path); + +public: + static Crypto *create(); + static void load_default_certificates(String p_path); + + virtual PoolByteArray generate_random_bytes(int p_bytes); + virtual Ref<CryptoKey> generate_rsa(int p_bytes); + virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after); + + Crypto(); +}; + +class ResourceFormatLoaderCrypto : public ResourceFormatLoader { + GDCLASS(ResourceFormatLoaderCrypto, 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; +}; + +class ResourceFormatSaverCrypto : public ResourceFormatSaver { + GDCLASS(ResourceFormatSaverCrypto, ResourceFormatSaver); + +public: + virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); + virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const; + virtual bool recognize(const RES &p_resource) const; +}; + +#endif // CRYPTO_H diff --git a/core/math/crypto_core.cpp b/core/crypto/crypto_core.cpp index d7ba54e469..51c2e3c9e5 100644 --- a/core/math/crypto_core.cpp +++ b/core/crypto/crypto_core.cpp @@ -52,7 +52,7 @@ Error CryptoCore::MD5Context::start() { return ret ? FAILED : OK; } -Error CryptoCore::MD5Context::update(uint8_t *p_src, size_t p_len) { +Error CryptoCore::MD5Context::update(const uint8_t *p_src, size_t p_len) { int ret = mbedtls_md5_update_ret((mbedtls_md5_context *)ctx, p_src, p_len); return ret ? FAILED : OK; } @@ -62,6 +62,32 @@ Error CryptoCore::MD5Context::finish(unsigned char r_hash[16]) { return ret ? FAILED : OK; } +// SHA1 +CryptoCore::SHA1Context::SHA1Context() { + ctx = memalloc(sizeof(mbedtls_sha1_context)); + mbedtls_sha1_init((mbedtls_sha1_context *)ctx); +} + +CryptoCore::SHA1Context::~SHA1Context() { + mbedtls_sha1_free((mbedtls_sha1_context *)ctx); + memfree((mbedtls_sha1_context *)ctx); +} + +Error CryptoCore::SHA1Context::start() { + int ret = mbedtls_sha1_starts_ret((mbedtls_sha1_context *)ctx); + return ret ? FAILED : OK; +} + +Error CryptoCore::SHA1Context::update(const uint8_t *p_src, size_t p_len) { + int ret = mbedtls_sha1_update_ret((mbedtls_sha1_context *)ctx, p_src, p_len); + return ret ? FAILED : OK; +} + +Error CryptoCore::SHA1Context::finish(unsigned char r_hash[20]) { + int ret = mbedtls_sha1_finish_ret((mbedtls_sha1_context *)ctx, r_hash); + return ret ? FAILED : OK; +} + // SHA256 CryptoCore::SHA256Context::SHA256Context() { ctx = memalloc(sizeof(mbedtls_sha256_context)); @@ -78,12 +104,12 @@ Error CryptoCore::SHA256Context::start() { return ret ? FAILED : OK; } -Error CryptoCore::SHA256Context::update(uint8_t *p_src, size_t p_len) { +Error CryptoCore::SHA256Context::update(const uint8_t *p_src, size_t p_len) { int ret = mbedtls_sha256_update_ret((mbedtls_sha256_context *)ctx, p_src, p_len); return ret ? FAILED : OK; } -Error CryptoCore::SHA256Context::finish(unsigned char r_hash[16]) { +Error CryptoCore::SHA256Context::finish(unsigned char r_hash[32]) { int ret = mbedtls_sha256_finish_ret((mbedtls_sha256_context *)ctx, r_hash); return ret ? FAILED : OK; } diff --git a/core/math/crypto_core.h b/core/crypto/crypto_core.h index e28cb5a792..c859d612d4 100644 --- a/core/math/crypto_core.h +++ b/core/crypto/crypto_core.h @@ -46,10 +46,24 @@ public: ~MD5Context(); Error start(); - Error update(uint8_t *p_src, size_t p_len); + Error update(const uint8_t *p_src, size_t p_len); Error finish(unsigned char r_hash[16]); }; + class SHA1Context { + + private: + void *ctx; // To include, or not to include... + + public: + SHA1Context(); + ~SHA1Context(); + + Error start(); + Error update(const uint8_t *p_src, size_t p_len); + Error finish(unsigned char r_hash[20]); + }; + class SHA256Context { private: @@ -60,8 +74,8 @@ public: ~SHA256Context(); Error start(); - Error update(uint8_t *p_src, size_t p_len); - Error finish(unsigned char r_hash[16]); + Error update(const uint8_t *p_src, size_t p_len); + Error finish(unsigned char r_hash[32]); }; class AESContext { diff --git a/core/crypto/hashing_context.cpp b/core/crypto/hashing_context.cpp new file mode 100644 index 0000000000..bdccb258dd --- /dev/null +++ b/core/crypto/hashing_context.cpp @@ -0,0 +1,137 @@ +/*************************************************************************/ +/* hashing_context.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "hashing_context.h" + +#include "core/crypto/crypto_core.h" + +Error HashingContext::start(HashType p_type) { + ERR_FAIL_COND_V(ctx != NULL, ERR_ALREADY_IN_USE); + _create_ctx(p_type); + ERR_FAIL_COND_V(ctx == NULL, ERR_UNAVAILABLE); + switch (type) { + case HASH_MD5: + return ((CryptoCore::MD5Context *)ctx)->start(); + case HASH_SHA1: + return ((CryptoCore::SHA1Context *)ctx)->start(); + case HASH_SHA256: + return ((CryptoCore::SHA256Context *)ctx)->start(); + } + return ERR_UNAVAILABLE; +} + +Error HashingContext::update(PoolByteArray p_chunk) { + ERR_FAIL_COND_V(ctx == NULL, ERR_UNCONFIGURED); + size_t len = p_chunk.size(); + PoolByteArray::Read r = p_chunk.read(); + switch (type) { + case HASH_MD5: + return ((CryptoCore::MD5Context *)ctx)->update(&r[0], len); + case HASH_SHA1: + return ((CryptoCore::SHA1Context *)ctx)->update(&r[0], len); + case HASH_SHA256: + return ((CryptoCore::SHA256Context *)ctx)->update(&r[0], len); + } + return ERR_UNAVAILABLE; +} + +PoolByteArray HashingContext::finish() { + ERR_FAIL_COND_V(ctx == NULL, PoolByteArray()); + PoolByteArray out; + Error err = FAILED; + switch (type) { + case HASH_MD5: + out.resize(16); + err = ((CryptoCore::MD5Context *)ctx)->finish(out.write().ptr()); + break; + case HASH_SHA1: + out.resize(20); + err = ((CryptoCore::SHA1Context *)ctx)->finish(out.write().ptr()); + break; + case HASH_SHA256: + out.resize(32); + err = ((CryptoCore::SHA256Context *)ctx)->finish(out.write().ptr()); + break; + } + _delete_ctx(); + ERR_FAIL_COND_V(err != OK, PoolByteArray()); + return out; +} + +void HashingContext::_create_ctx(HashType p_type) { + type = p_type; + switch (type) { + case HASH_MD5: + ctx = memnew(CryptoCore::MD5Context); + break; + case HASH_SHA1: + ctx = memnew(CryptoCore::SHA1Context); + break; + case HASH_SHA256: + ctx = memnew(CryptoCore::SHA256Context); + break; + default: + ctx = NULL; + } +} + +void HashingContext::_delete_ctx() { + return; + switch (type) { + case HASH_MD5: + memdelete((CryptoCore::MD5Context *)ctx); + break; + case HASH_SHA1: + memdelete((CryptoCore::SHA1Context *)ctx); + break; + case HASH_SHA256: + memdelete((CryptoCore::SHA256Context *)ctx); + break; + } + ctx = NULL; +} + +void HashingContext::_bind_methods() { + ClassDB::bind_method(D_METHOD("start", "type"), &HashingContext::start); + ClassDB::bind_method(D_METHOD("update", "chunk"), &HashingContext::update); + ClassDB::bind_method(D_METHOD("finish"), &HashingContext::finish); + BIND_ENUM_CONSTANT(HASH_MD5); + BIND_ENUM_CONSTANT(HASH_SHA1); + BIND_ENUM_CONSTANT(HASH_SHA256); +} + +HashingContext::HashingContext() { + ctx = NULL; +} + +HashingContext::~HashingContext() { + if (ctx != NULL) + _delete_ctx(); +} diff --git a/core/crypto/hashing_context.h b/core/crypto/hashing_context.h new file mode 100644 index 0000000000..aa69636f2c --- /dev/null +++ b/core/crypto/hashing_context.h @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* hashing_context.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef HASHING_CONTEXT_H +#define HASHING_CONTEXT_H + +#include "core/reference.h" + +class HashingContext : public Reference { + GDCLASS(HashingContext, Reference); + +public: + enum HashType { + HASH_MD5, + HASH_SHA1, + HASH_SHA256 + }; + +private: + void *ctx; + HashType type; + +protected: + static void _bind_methods(); + void _create_ctx(HashType p_type); + void _delete_ctx(); + +public: + Error start(HashType p_type); + Error update(PoolByteArray p_chunk); + PoolByteArray finish(); + + HashingContext(); + ~HashingContext(); +}; + +VARIANT_ENUM_CAST(HashingContext::HashType); + +#endif // HASHING_CONTEXT_H diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp index 1452c61d1a..77decc107d 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -30,7 +30,7 @@ #include "file_access_encrypted.h" -#include "core/math/crypto_core.h" +#include "core/crypto/crypto_core.h" #include "core/os/copymem.h" #include "core/print_string.h" #include "core/variant.h" diff --git a/core/io/stream_peer_ssl.cpp b/core/io/stream_peer_ssl.cpp index ccce48ccd7..f2eaf57acc 100644 --- a/core/io/stream_peer_ssl.cpp +++ b/core/io/stream_peer_ssl.cpp @@ -30,10 +30,7 @@ #include "stream_peer_ssl.h" -#include "core/io/certs_compressed.gen.h" -#include "core/io/compression.h" -#include "core/os/file_access.h" -#include "core/project_settings.h" +#include "core/engine.h" StreamPeerSSL *(*StreamPeerSSL::_create)() = NULL; @@ -44,22 +41,8 @@ StreamPeerSSL *StreamPeerSSL::create() { return NULL; } -StreamPeerSSL::LoadCertsFromMemory StreamPeerSSL::load_certs_func = NULL; bool StreamPeerSSL::available = false; -void StreamPeerSSL::load_certs_from_memory(const PoolByteArray &p_memory) { - if (load_certs_func) - load_certs_func(p_memory); -} - -void StreamPeerSSL::load_certs_from_file(String p_path) { - if (p_path != "") { - PoolByteArray certs = get_cert_file_as_array(p_path); - if (certs.size() > 0) - load_certs_func(certs); - } -} - bool StreamPeerSSL::is_available() { return available; } @@ -72,56 +55,11 @@ bool StreamPeerSSL::is_blocking_handshake_enabled() const { return blocking_handshake; } -PoolByteArray StreamPeerSSL::get_cert_file_as_array(String p_path) { - - PoolByteArray out; - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); - if (f) { - int flen = f->get_len(); - out.resize(flen + 1); - PoolByteArray::Write w = out.write(); - f->get_buffer(w.ptr(), flen); - w[flen] = 0; // Make sure it ends with string terminator - memdelete(f); -#ifdef DEBUG_ENABLED - print_verbose(vformat("Loaded certs from '%s'.", p_path)); -#endif - } - - return out; -} - -PoolByteArray StreamPeerSSL::get_project_cert_array() { - - PoolByteArray out; - String certs_path = GLOBAL_DEF("network/ssl/certificates", ""); - ProjectSettings::get_singleton()->set_custom_property_info("network/ssl/certificates", PropertyInfo(Variant::STRING, "network/ssl/certificates", PROPERTY_HINT_FILE, "*.crt")); - - if (certs_path != "") { - // Use certs defined in project settings. - return get_cert_file_as_array(certs_path); - } -#ifdef BUILTIN_CERTS_ENABLED - else { - // Use builtin certs only if user did not override it in project settings. - out.resize(_certs_uncompressed_size + 1); - PoolByteArray::Write w = out.write(); - Compression::decompress(w.ptr(), _certs_uncompressed_size, _certs_compressed, _certs_compressed_size, Compression::MODE_DEFLATE); - w[_certs_uncompressed_size] = 0; // Make sure it ends with string terminator -#ifdef DEBUG_ENABLED - print_verbose("Loaded builtin certs"); -#endif - } -#endif - - return out; -} - void StreamPeerSSL::_bind_methods() { ClassDB::bind_method(D_METHOD("poll"), &StreamPeerSSL::poll); - ClassDB::bind_method(D_METHOD("accept_stream", "base"), &StreamPeerSSL::accept_stream); - ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "validate_certs", "for_hostname"), &StreamPeerSSL::connect_to_stream, DEFVAL(false), DEFVAL(String())); + ClassDB::bind_method(D_METHOD("accept_stream", "stream", "private_key", "certificate", "chain"), &StreamPeerSSL::accept_stream, DEFVAL(Ref<X509Certificate>())); + ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "validate_certs", "for_hostname", "valid_certificate"), &StreamPeerSSL::connect_to_stream, DEFVAL(false), DEFVAL(String()), DEFVAL(Ref<X509Certificate>())); ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerSSL::get_status); ClassDB::bind_method(D_METHOD("disconnect_from_stream"), &StreamPeerSSL::disconnect_from_stream); ClassDB::bind_method(D_METHOD("set_blocking_handshake_enabled", "enabled"), &StreamPeerSSL::set_blocking_handshake_enabled); diff --git a/core/io/stream_peer_ssl.h b/core/io/stream_peer_ssl.h index 482576c4c6..dedc35b9ac 100644 --- a/core/io/stream_peer_ssl.h +++ b/core/io/stream_peer_ssl.h @@ -31,19 +31,16 @@ #ifndef STREAM_PEER_SSL_H #define STREAM_PEER_SSL_H +#include "core/crypto/crypto.h" #include "core/io/stream_peer.h" class StreamPeerSSL : public StreamPeer { GDCLASS(StreamPeerSSL, StreamPeer); -public: - typedef void (*LoadCertsFromMemory)(const PoolByteArray &p_certs); - protected: static StreamPeerSSL *(*_create)(); static void _bind_methods(); - static LoadCertsFromMemory load_certs_func; static bool available; bool blocking_handshake; @@ -61,18 +58,14 @@ public: bool is_blocking_handshake_enabled() const; virtual void poll() = 0; - virtual Error accept_stream(Ref<StreamPeer> p_base) = 0; - virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String()) = 0; + virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()) = 0; + virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>()) = 0; virtual Status get_status() const = 0; virtual void disconnect_from_stream() = 0; static StreamPeerSSL *create(); - static PoolByteArray get_cert_file_as_array(String p_path); - static PoolByteArray get_project_cert_array(); - static void load_certs_from_file(String p_path); - static void load_certs_from_memory(const PoolByteArray &p_memory); static bool is_available(); StreamPeerSSL(); diff --git a/core/math/SCsub b/core/math/SCsub index 0995298a4b..be438fcfbe 100644 --- a/core/math/SCsub +++ b/core/math/SCsub @@ -2,37 +2,6 @@ Import('env') -env_math = env.Clone() # Maybe make one specific for crypto? - -is_builtin = env["builtin_mbedtls"] -has_module = env["module_mbedtls_enabled"] - -if is_builtin or not has_module: - # Use our headers for builtin or if the module is not going to be compiled. - # We decided not to depend on system mbedtls just for these few files that can - # be easily extracted. - env_math.Prepend(CPPPATH=["#thirdparty/mbedtls/include"]) - -# MbedTLS core functions (for CryptoCore). -# If the mbedtls module is compiled we don't need to add the .c files with our -# custom config since they will be built by the module itself. -# Only if the module is not enabled, we must compile here the required sources -# to make a "light" build with only the necessary mbedtls files. -if not has_module: - env_thirdparty = env_math.Clone() - env_thirdparty.disable_warnings() - # Custom config file - env_thirdparty.Append(CPPDEFINES=[('MBEDTLS_CONFIG_FILE', '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')]) - thirdparty_mbedtls_dir = "#thirdparty/mbedtls/library/" - thirdparty_mbedtls_sources = [ - "aes.c", - "base64.c", - "md5.c", - "sha1.c", - "sha256.c", - "godot_core_mbedtls_platform.c" - ] - thirdparty_mbedtls_sources = [thirdparty_mbedtls_dir + file for file in thirdparty_mbedtls_sources] - env_thirdparty.add_source_files(env.core_sources, thirdparty_mbedtls_sources) +env_math = env.Clone() env_math.add_source_files(env.core_sources, "*.cpp") diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp index 779a28be66..972bccc0ac 100644 --- a/core/math/vector2.cpp +++ b/core/math/vector2.cpp @@ -98,6 +98,11 @@ real_t Vector2::cross(const Vector2 &p_other) const { return x * p_other.y - y * p_other.x; } +Vector2 Vector2::sign() const { + + return Vector2(SGN(x), SGN(y)); +} + Vector2 Vector2::floor() const { return Vector2(Math::floor(x), Math::floor(y)); @@ -121,6 +126,14 @@ Vector2 Vector2::rotated(real_t p_by) const { return v; } +Vector2 Vector2::posmod(const real_t p_mod) const { + return Vector2(Math::fposmod(x, p_mod), Math::fposmod(y, p_mod)); +} + +Vector2 Vector2::posmodv(const Vector2 &p_modv) const { + return Vector2(Math::fposmod(x, p_modv.x), Math::fposmod(y, p_modv.y)); +} + Vector2 Vector2::project(const Vector2 &p_b) const { return p_b * (dot(p_b) / p_b.length_squared()); } diff --git a/core/math/vector2.h b/core/math/vector2.h index 78a1641c1e..1a73831891 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -38,6 +38,11 @@ struct Vector2i; struct Vector2 { + enum Axis { + AXIS_X, + AXIS_Y, + }; + union { real_t x; real_t width; @@ -69,6 +74,8 @@ struct Vector2 { real_t dot(const Vector2 &p_other) const; real_t cross(const Vector2 &p_other) const; + Vector2 posmod(const real_t p_mod) const; + Vector2 posmodv(const Vector2 &p_modv) const; Vector2 project(const Vector2 &p_b) const; Vector2 plane_project(real_t p_d, const Vector2 &p_vec) const; @@ -107,8 +114,10 @@ struct Vector2 { bool operator==(const Vector2 &p_vec2) const; bool operator!=(const Vector2 &p_vec2) const; - bool operator<(const Vector2 &p_vec2) const { return (Math::is_equal_approx(x, p_vec2.x)) ? (y < p_vec2.y) : (x < p_vec2.x); } - bool operator<=(const Vector2 &p_vec2) const { return (Math::is_equal_approx(x, p_vec2.x)) ? (y <= p_vec2.y) : (x < p_vec2.x); } + bool operator<(const Vector2 &p_vec2) const { return Math::is_equal_approx(x, p_vec2.x) ? (y < p_vec2.y) : (x < p_vec2.x); } + bool operator>(const Vector2 &p_vec2) const { return Math::is_equal_approx(x, p_vec2.x) ? (y > p_vec2.y) : (x > p_vec2.x); } + bool operator<=(const Vector2 &p_vec2) const { return Math::is_equal_approx(x, p_vec2.x) ? (y <= p_vec2.y) : (x < p_vec2.x); } + bool operator>=(const Vector2 &p_vec2) const { return Math::is_equal_approx(x, p_vec2.x) ? (y >= p_vec2.y) : (x > p_vec2.x); } real_t angle() const; @@ -129,6 +138,7 @@ struct Vector2 { return Vector2(y, -x); } + Vector2 sign() const; Vector2 floor() const; Vector2 ceil() const; Vector2 round() const; @@ -141,10 +151,7 @@ struct Vector2 { x = p_x; y = p_y; } - _FORCE_INLINE_ Vector2() { - x = 0; - y = 0; - } + _FORCE_INLINE_ Vector2() { x = y = 0; } }; _FORCE_INLINE_ Vector2 Vector2::plane_project(real_t p_d, const Vector2 &p_vec) const { @@ -262,6 +269,11 @@ typedef Vector2 Point2; struct Vector2i { + enum Axis { + AXIS_X, + AXIS_Y, + }; + union { int x; int width; diff --git a/core/math/vector3.h b/core/math/vector3.h index 45bdfee487..597d3c22a8 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -31,9 +31,7 @@ #ifndef VECTOR3_H #define VECTOR3_H -#include "core/math/math_defs.h" #include "core/math/math_funcs.h" -#include "core/typedefs.h" #include "core/ustring.h" class Basis; @@ -110,6 +108,8 @@ struct Vector3 { _FORCE_INLINE_ real_t distance_to(const Vector3 &p_b) const; _FORCE_INLINE_ real_t distance_squared_to(const Vector3 &p_b) const; + _FORCE_INLINE_ Vector3 posmod(const real_t p_mod) const; + _FORCE_INLINE_ Vector3 posmodv(const Vector3 &p_modv) const; _FORCE_INLINE_ Vector3 project(const Vector3 &p_b) const; _FORCE_INLINE_ real_t angle_to(const Vector3 &p_b) const; @@ -141,15 +141,17 @@ struct Vector3 { _FORCE_INLINE_ bool operator!=(const Vector3 &p_v) const; _FORCE_INLINE_ bool operator<(const Vector3 &p_v) const; _FORCE_INLINE_ bool operator<=(const Vector3 &p_v) const; + _FORCE_INLINE_ bool operator>(const Vector3 &p_v) const; + _FORCE_INLINE_ bool operator>=(const Vector3 &p_v) const; operator String() const; - _FORCE_INLINE_ Vector3() { x = y = z = 0; } _FORCE_INLINE_ Vector3(real_t p_x, real_t p_y, real_t p_z) { x = p_x; y = p_y; z = p_z; } + _FORCE_INLINE_ Vector3() { x = y = z = 0; } }; // Should be included after class definition, otherwise we get circular refs @@ -233,6 +235,14 @@ real_t Vector3::distance_squared_to(const Vector3 &p_b) const { return (p_b - *this).length_squared(); } +Vector3 Vector3::posmod(const real_t p_mod) const { + return Vector3(Math::fposmod(x, p_mod), Math::fposmod(y, p_mod), Math::fposmod(z, p_mod)); +} + +Vector3 Vector3::posmodv(const Vector3 &p_modv) const { + return Vector3(Math::fposmod(x, p_modv.x), Math::fposmod(y, p_modv.y), Math::fposmod(z, p_modv.z)); +} + Vector3 Vector3::project(const Vector3 &p_b) const { return p_b * (dot(p_b) / p_b.length_squared()); } @@ -357,6 +367,18 @@ bool Vector3::operator<(const Vector3 &p_v) const { } } +bool Vector3::operator>(const Vector3 &p_v) const { + + if (x == p_v.x) { + if (y == p_v.y) + return z > p_v.z; + else + return y > p_v.y; + } else { + return x > p_v.x; + } +} + bool Vector3::operator<=(const Vector3 &p_v) const { if (Math::is_equal_approx(x, p_v.x)) { @@ -369,6 +391,18 @@ bool Vector3::operator<=(const Vector3 &p_v) const { } } +bool Vector3::operator>=(const Vector3 &p_v) const { + + if (x == p_v.x) { + if (y == p_v.y) + return z >= p_v.z; + else + return y > p_v.y; + } else { + return x > p_v.x; + } +} + _FORCE_INLINE_ Vector3 vec3_cross(const Vector3 &p_a, const Vector3 &p_b) { return p_a.cross(p_b); diff --git a/core/os/file_access.cpp b/core/os/file_access.cpp index ba94e87da6..9a8315a3bb 100644 --- a/core/os/file_access.cpp +++ b/core/os/file_access.cpp @@ -30,9 +30,9 @@ #include "file_access.h" +#include "core/crypto/crypto_core.h" #include "core/io/file_access_pack.h" #include "core/io/marshalls.h" -#include "core/math/crypto_core.h" #include "core/os/os.h" #include "core/project_settings.h" diff --git a/core/os/input.cpp b/core/os/input.cpp index f04d4a1b3e..51cb41b184 100644 --- a/core/os/input.cpp +++ b/core/os/input.cpp @@ -80,6 +80,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("get_joy_axis_index_from_string", "axis"), &Input::get_joy_axis_index_from_string); ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0)); ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration); + ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms"), &Input::vibrate_handheld, DEFVAL(500)); ClassDB::bind_method(D_METHOD("get_gravity"), &Input::get_gravity); ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer); ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer); diff --git a/core/os/input.h b/core/os/input.h index de04f239e6..a12ded176b 100644 --- a/core/os/input.h +++ b/core/os/input.h @@ -100,6 +100,7 @@ public: virtual uint64_t get_joy_vibration_timestamp(int p_device) = 0; virtual void start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration = 0) = 0; virtual void stop_joy_vibration(int p_device) = 0; + virtual void vibrate_handheld(int p_duration_ms = 500) = 0; virtual Point2 get_mouse_position() const = 0; virtual Point2 get_last_mouse_speed() const = 0; diff --git a/core/os/os.cpp b/core/os/os.cpp index 0d7d150914..7531900480 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -186,6 +186,11 @@ int OS::get_process_id() const { return -1; }; +void OS::vibrate_handheld(int p_duration_ms) { + + WARN_PRINTS("vibrate_handheld() only works with Android and iOS"); +} + bool OS::is_stdout_verbose() const { return _verbose_stdout; diff --git a/core/os/os.h b/core/os/os.h index c0c346e9aa..e627773d88 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -265,6 +265,7 @@ public: virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL) = 0; virtual Error kill(const ProcessID &p_pid) = 0; virtual int get_process_id() const; + virtual void vibrate_handheld(int p_duration_ms = 500); virtual Error shell_open(String p_uri); virtual Error set_cwd(const String &p_cwd); diff --git a/core/project_settings.cpp b/core/project_settings.cpp index 0a673fb638..eb88143db3 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -1019,6 +1019,9 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("editor/search_in_file_extensions", extensions); custom_prop_info["editor/search_in_file_extensions"] = PropertyInfo(Variant::POOL_STRING_ARRAY, "editor/search_in_file_extensions"); + GLOBAL_DEF("editor/script_templates_search_path", "res://script_templates"); + custom_prop_info["editor/script_templates_search_path"] = PropertyInfo(Variant::STRING, "editor/script_templates_search_path", PROPERTY_HINT_DIR); + action = Dictionary(); action["deadzone"] = Variant(0.5f); events = Array(); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index e442546124..efc77bde48 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -34,6 +34,8 @@ #include "core/class_db.h" #include "core/compressed_translation.h" #include "core/core_string_names.h" +#include "core/crypto/crypto.h" +#include "core/crypto/hashing_context.h" #include "core/engine.h" #include "core/func_ref.h" #include "core/input_map.h" @@ -70,6 +72,8 @@ static Ref<ResourceFormatLoaderBinary> resource_loader_binary; static Ref<ResourceFormatImporter> resource_format_importer; static Ref<ResourceFormatLoaderImage> resource_format_image; static Ref<TranslationLoaderPO> resource_format_po; +static Ref<ResourceFormatSaverCrypto> resource_format_saver_crypto; +static Ref<ResourceFormatLoaderCrypto> resource_format_loader_crypto; static _ResourceLoader *_resource_loader = NULL; static _ResourceSaver *_resource_saver = NULL; @@ -151,7 +155,19 @@ void register_core_types() { ClassDB::register_class<StreamPeerTCP>(); ClassDB::register_class<TCP_Server>(); ClassDB::register_class<PacketPeerUDP>(); + + // Crypto + ClassDB::register_class<HashingContext>(); + ClassDB::register_custom_instance_class<X509Certificate>(); + ClassDB::register_custom_instance_class<CryptoKey>(); + ClassDB::register_custom_instance_class<Crypto>(); ClassDB::register_custom_instance_class<StreamPeerSSL>(); + + resource_format_saver_crypto.instance(); + ResourceSaver::add_resource_format_saver(resource_format_saver_crypto); + resource_format_loader_crypto.instance(); + ResourceLoader::add_resource_format_loader(resource_format_loader_crypto); + ClassDB::register_virtual_class<IP>(); ClassDB::register_virtual_class<PacketPeer>(); ClassDB::register_class<PacketPeerStream>(); @@ -211,6 +227,9 @@ void register_core_settings() { ProjectSettings::get_singleton()->set_custom_property_info("network/limits/tcp/connect_timeout_seconds", PropertyInfo(Variant::INT, "network/limits/tcp/connect_timeout_seconds", PROPERTY_HINT_RANGE, "1,1800,1")); GLOBAL_DEF_RST("network/limits/packet_peer_stream/max_buffer_po2", (16)); ProjectSettings::get_singleton()->set_custom_property_info("network/limits/packet_peer_stream/max_buffer_po2", PropertyInfo(Variant::INT, "network/limits/packet_peer_stream/max_buffer_po2", PROPERTY_HINT_RANGE, "0,64,1,or_greater")); + + GLOBAL_DEF("network/ssl/certificates", ""); + ProjectSettings::get_singleton()->set_custom_property_info("network/ssl/certificates", PropertyInfo(Variant::STRING, "network/ssl/certificates", PROPERTY_HINT_FILE, "*.crt")); } void register_core_singletons() { @@ -272,6 +291,11 @@ void unregister_core_types() { ResourceLoader::remove_resource_format_loader(resource_format_po); resource_format_po.unref(); + ResourceSaver::remove_resource_format_saver(resource_format_saver_crypto); + resource_format_saver_crypto.unref(); + ResourceLoader::remove_resource_format_loader(resource_format_loader_crypto); + resource_format_loader_crypto.unref(); + if (ip) memdelete(ip); diff --git a/core/ustring.cpp b/core/ustring.cpp index ed401c3763..21696e2a55 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -31,7 +31,7 @@ #include "ustring.h" #include "core/color.h" -#include "core/math/crypto_core.h" +#include "core/crypto/crypto_core.h" #include "core/math/math_funcs.h" #include "core/os/memory.h" #include "core/print_string.h" @@ -40,6 +40,7 @@ #include "core/variant.h" #include <wchar.h> +#include <cstdint> #ifndef NO_USE_STDLIB #include <stdio.h> @@ -1668,6 +1669,7 @@ int String::hex_to_int(bool p_with_prefix) const { return 0; } + ERR_FAIL_COND_V_MSG(hex > INT32_MAX / 16, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + *this + " as integer, provided value is " + (sign == 1 ? "too big." : "too small.")); hex *= 16; hex += n; s++; @@ -1709,6 +1711,7 @@ int64_t String::hex_to_int64(bool p_with_prefix) const { return 0; } + ERR_FAIL_COND_V_MSG(hex > INT64_MAX / 16, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small.")); hex *= 16; hex += n; s++; @@ -1748,6 +1751,7 @@ int64_t String::bin_to_int64(bool p_with_prefix) const { return 0; } + ERR_FAIL_COND_V_MSG(binary > INT64_MAX / 2, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small.")); binary *= 2; binary += n; s++; @@ -1771,6 +1775,7 @@ int String::to_int() const { CharType c = operator[](i); if (c >= '0' && c <= '9') { + ERR_FAIL_COND_V_MSG(integer > INT32_MAX / 10, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + *this + " as integer, provided value is " + (sign == 1 ? "too big." : "too small.")); integer *= 10; integer += c - '0'; @@ -1798,6 +1803,7 @@ int64_t String::to_int64() const { CharType c = operator[](i); if (c >= '0' && c <= '9') { + ERR_FAIL_COND_V_MSG(integer > INT64_MAX / 10, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small.")); integer *= 10; integer += c - '0'; @@ -1828,6 +1834,7 @@ int String::to_int(const char *p_str, int p_len) { char c = p_str[i]; if (c >= '0' && c <= '9') { + ERR_FAIL_COND_V_MSG(integer > INT32_MAX / 10, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as integer, provided value is " + (sign == 1 ? "too big." : "too small.")); integer *= 10; integer += c - '0'; @@ -2140,6 +2147,14 @@ int64_t String::to_int(const CharType *p_str, int p_len) { if (c >= '0' && c <= '9') { + if (integer > INT32_MAX / 10) { + String number(""); + str = p_str; + while (str != limit) { + number += *(str++); + } + ERR_FAIL_V_MSG(sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + number + " as integer, provided value is " + (sign == 1 ? "too big." : "too small.")); + } integer *= 10; integer += c - '0'; } else { diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 9ea2fed5ae..05ef51cecd 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -32,8 +32,8 @@ #include "core/color_names.inc" #include "core/core_string_names.h" +#include "core/crypto/crypto_core.h" #include "core/io/compression.h" -#include "core/math/crypto_core.h" #include "core/object.h" #include "core/os/os.h" #include "core/script_language.h" @@ -347,6 +347,8 @@ struct _VariantCall { VCALL_LOCALMEM0R(Vector2, is_normalized); VCALL_LOCALMEM1R(Vector2, distance_to); VCALL_LOCALMEM1R(Vector2, distance_squared_to); + VCALL_LOCALMEM1R(Vector2, posmod); + VCALL_LOCALMEM1R(Vector2, posmodv); VCALL_LOCALMEM1R(Vector2, project); VCALL_LOCALMEM1R(Vector2, angle_to); VCALL_LOCALMEM1R(Vector2, angle_to_point); @@ -370,6 +372,7 @@ struct _VariantCall { VCALL_LOCALMEM1R(Vector2, cross); VCALL_LOCALMEM0R(Vector2, abs); VCALL_LOCALMEM1R(Vector2, clamped); + VCALL_LOCALMEM0R(Vector2, sign); VCALL_LOCALMEM0R(Rect2, get_area); VCALL_LOCALMEM1R(Rect2, intersects); @@ -407,12 +410,15 @@ struct _VariantCall { VCALL_LOCALMEM0R(Vector3, round); VCALL_LOCALMEM1R(Vector3, distance_to); VCALL_LOCALMEM1R(Vector3, distance_squared_to); + VCALL_LOCALMEM1R(Vector3, posmod); + VCALL_LOCALMEM1R(Vector3, posmodv); VCALL_LOCALMEM1R(Vector3, project); VCALL_LOCALMEM1R(Vector3, angle_to); VCALL_LOCALMEM1R(Vector3, direction_to); VCALL_LOCALMEM1R(Vector3, slide); VCALL_LOCALMEM1R(Vector3, bounce); VCALL_LOCALMEM1R(Vector3, reflect); + VCALL_LOCALMEM0R(Vector3, sign); VCALL_LOCALMEM0R(Plane, normalized); VCALL_LOCALMEM0R(Plane, center); @@ -596,13 +602,10 @@ struct _VariantCall { r_ret = decompressed; } - static void _call_PoolByteArray_sha256_string(Variant &r_ret, Variant &p_self, const Variant **p_args) { + static void _call_PoolByteArray_hex_encode(Variant &r_ret, Variant &p_self, const Variant **p_args) { PoolByteArray *ba = reinterpret_cast<PoolByteArray *>(p_self._data._mem); PoolByteArray::Read r = ba->read(); - String s; - unsigned char hash[32]; - CryptoCore::sha256((unsigned char *)r.ptr(), ba->size(), hash); - s = String::hex_encode_buffer(hash, 32); + String s = String::hex_encode_buffer(&r[0], ba->size()); r_ret = s; } @@ -1590,6 +1593,8 @@ void register_variant_methods() { ADDFUNC1R(VECTOR2, VECTOR2, Vector2, direction_to, VECTOR2, "b", varray()); ADDFUNC1R(VECTOR2, REAL, Vector2, distance_to, VECTOR2, "to", varray()); ADDFUNC1R(VECTOR2, REAL, Vector2, distance_squared_to, VECTOR2, "to", varray()); + ADDFUNC1R(VECTOR2, VECTOR2, Vector2, posmod, REAL, "mod", varray()); + ADDFUNC1R(VECTOR2, VECTOR2, Vector2, posmodv, VECTOR2, "modv", varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, project, VECTOR2, "b", varray()); ADDFUNC1R(VECTOR2, REAL, Vector2, angle_to, VECTOR2, "to", varray()); ADDFUNC1R(VECTOR2, REAL, Vector2, angle_to_point, VECTOR2, "to", varray()); @@ -1611,6 +1616,7 @@ void register_variant_methods() { ADDFUNC1R(VECTOR2, REAL, Vector2, cross, VECTOR2, "with", varray()); ADDFUNC0R(VECTOR2, VECTOR2, Vector2, abs, varray()); ADDFUNC1R(VECTOR2, VECTOR2, Vector2, clamped, REAL, "length", varray()); + ADDFUNC0R(VECTOR2, VECTOR2, Vector2, sign, varray()); ADDFUNC0R(RECT2, REAL, Rect2, get_area, varray()); ADDFUNC1R(RECT2, BOOL, Rect2, intersects, RECT2, "b", varray()); @@ -1649,11 +1655,14 @@ void register_variant_methods() { ADDFUNC0R(VECTOR3, VECTOR3, Vector3, round, varray()); ADDFUNC1R(VECTOR3, REAL, Vector3, distance_to, VECTOR3, "b", varray()); ADDFUNC1R(VECTOR3, REAL, Vector3, distance_squared_to, VECTOR3, "b", varray()); + ADDFUNC1R(VECTOR3, VECTOR3, Vector3, posmod, REAL, "mod", varray()); + ADDFUNC1R(VECTOR3, VECTOR3, Vector3, posmodv, VECTOR3, "modv", varray()); ADDFUNC1R(VECTOR3, VECTOR3, Vector3, project, VECTOR3, "b", varray()); ADDFUNC1R(VECTOR3, REAL, Vector3, angle_to, VECTOR3, "to", varray()); ADDFUNC1R(VECTOR3, VECTOR3, Vector3, slide, VECTOR3, "n", varray()); ADDFUNC1R(VECTOR3, VECTOR3, Vector3, bounce, VECTOR3, "n", varray()); ADDFUNC1R(VECTOR3, VECTOR3, Vector3, reflect, VECTOR3, "n", varray()); + ADDFUNC0R(VECTOR3, VECTOR3, Vector3, sign, varray()); ADDFUNC0R(PLANE, PLANE, Plane, normalized, varray()); ADDFUNC0R(PLANE, VECTOR3, Plane, center, varray()); @@ -1762,7 +1771,7 @@ void register_variant_methods() { ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, get_string_from_ascii, varray()); ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, get_string_from_utf8, varray()); - ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, sha256_string, varray()); + ADDFUNC0R(POOL_BYTE_ARRAY, STRING, PoolByteArray, hex_encode, varray()); ADDFUNC1R(POOL_BYTE_ARRAY, POOL_BYTE_ARRAY, PoolByteArray, compress, INT, "compression_mode", varray(0)); ADDFUNC2R(POOL_BYTE_ARRAY, POOL_BYTE_ARRAY, PoolByteArray, decompress, INT, "buffer_size", INT, "compression_mode", varray(0)); @@ -1946,6 +1955,9 @@ void register_variant_methods() { _VariantCall::add_variant_constant(Variant::VECTOR3, "FORWARD", Vector3(0, 0, -1)); _VariantCall::add_variant_constant(Variant::VECTOR3, "BACK", Vector3(0, 0, 1)); + _VariantCall::add_constant(Variant::VECTOR2, "AXIS_X", Vector2::AXIS_X); + _VariantCall::add_constant(Variant::VECTOR2, "AXIS_Y", Vector2::AXIS_Y); + _VariantCall::add_variant_constant(Variant::VECTOR2, "ZERO", Vector2(0, 0)); _VariantCall::add_variant_constant(Variant::VECTOR2, "ONE", Vector2(1, 1)); _VariantCall::add_variant_constant(Variant::VECTOR2, "INF", Vector2(Math_INF, Math_INF)); diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index acceffb3bf..f263c12821 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Control" inherits="CanvasItem" category="Core" version="3.2"> <brief_description> - All User Interface nodes inherit from Control. A control's anchors and margins adapt its position and size relative to its parent. + All user interface nodes inherit from Control. A control's anchors and margins adapt its position and size relative to its parent. </brief_description> <description> Base class for all UI-related nodes. [Control] features a bounding rectangle that defines its extents, an anchor position relative to its parent control or the current viewport, and margins that represent an offset to the anchor. The margins update automatically when the node, any of its parents, or the screen size change. @@ -49,10 +49,10 @@ [/codeblock] The event won't trigger if: * clicking outside the control (see [method has_point]); - * control has [member mouse_filter] set to [member MOUSE_FILTER_IGNORE]; - * control is obstructed by another [Control] on top of it, which doesn't have [member mouse_filter] set to [member MOUSE_FILTER_IGNORE]; - * control's parent has [member mouse_filter] set to [member MOUSE_FILTER_STOP] or has accepted the event; - * it happens outside parent's rectangle and the parent has either [member rect_clip_content] or [member _clips_input] enabled. + * control has [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE]; + * control is obstructed by another [Control] on top of it, which doesn't have [member mouse_filter] set to [constant MOUSE_FILTER_IGNORE]; + * control's parent has [member mouse_filter] set to [constant MOUSE_FILTER_STOP] or has accepted the event; + * it happens outside parent's rectangle and the parent has either [member rect_clip_content] or [method _clips_input] enabled. </description> </method> <method name="_make_custom_tooltip" qualifiers="virtual"> @@ -63,7 +63,7 @@ <description> Virtual method to be implemented by the user. Returns a [Control] node that should be used as a tooltip instead of the default one. Use [code]for_text[/code] parameter to determine what text the tooltip should contain (likely the contents of [member hint_tooltip]). The returned node must be of type [Control] or Control-derieved. It can have child nodes of any type. It is freed when the tooltip disappears, so make sure you always provide a new instance, not e.g. a node from scene. When null or non-Control node is returned, the default tooltip will be used instead. - [b]Note:[/b] The tooltip is shrunk to miminal size. If you want to ensure it's fully visible, you might want to set its [member rect_min_size] to some non-zero value. + [b]Note:[/b] The tooltip is shrunk to minimal size. If you want to ensure it's fully visible, you might want to set its [member rect_min_size] to some non-zero value. Example of usage with custom-constructed node: [codeblock] func _make_custom_tooltip(for_text): @@ -388,7 +388,7 @@ <argument index="0" name="at_position" type="Vector2" default="Vector2( 0, 0 )"> </argument> <description> - Returns the tooltip, which will appear when the cursor is resting over this control. See [member]hint_tooltip[/member]. + Returns the tooltip, which will appear when the cursor is resting over this control. See [member hint_tooltip]. </description> </method> <method name="grab_click_focus"> diff --git a/doc/classes/Crypto.xml b/doc/classes/Crypto.xml new file mode 100644 index 0000000000..bb852f5fff --- /dev/null +++ b/doc/classes/Crypto.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="Crypto" inherits="Reference" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + <method name="generate_random_bytes"> + <return type="PoolByteArray"> + </return> + <argument index="0" name="size" type="int"> + </argument> + <description> + </description> + </method> + <method name="generate_rsa"> + <return type="CryptoKey"> + </return> + <argument index="0" name="size" type="int"> + </argument> + <description> + </description> + </method> + <method name="generate_self_signed_certificate"> + <return type="X509Certificate"> + </return> + <argument index="0" name="key" type="CryptoKey"> + </argument> + <argument index="1" name="issuer_name" type="String" default=""CN=myserver,O=myorganisation,C=IT""> + </argument> + <argument index="2" name="not_before" type="String" default=""20140101000000""> + </argument> + <argument index="3" name="not_after" type="String" default=""20340101000000""> + </argument> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/CryptoKey.xml b/doc/classes/CryptoKey.xml new file mode 100644 index 0000000000..d3cd485a5f --- /dev/null +++ b/doc/classes/CryptoKey.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CryptoKey" inherits="Resource" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + <method name="load"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + </description> + </method> + <method name="save"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index e78f21b274..4f1e8cc309 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -134,6 +134,8 @@ </theme_item> <theme_item name="folder" type="Texture"> </theme_item> + <theme_item name="folder_icon_modulate" type="Color" default="Color( 1, 1, 1, 1 )"> + </theme_item> <theme_item name="parent_folder" type="Texture"> </theme_item> <theme_item name="reload" type="Texture"> diff --git a/doc/classes/GeometryInstance.xml b/doc/classes/GeometryInstance.xml index b108e1be7c..02f2c27043 100644 --- a/doc/classes/GeometryInstance.xml +++ b/doc/classes/GeometryInstance.xml @@ -46,22 +46,26 @@ </member> <member name="lod_max_distance" type="float" setter="set_lod_max_distance" getter="get_lod_max_distance" default="0.0"> The GeometryInstance's max LOD distance. + [b]Note:[/b] This property currently has no effect. </member> <member name="lod_max_hysteresis" type="float" setter="set_lod_max_hysteresis" getter="get_lod_max_hysteresis" default="0.0"> The GeometryInstance's max LOD margin. + [b]Note:[/b] This property currently has no effect. </member> <member name="lod_min_distance" type="float" setter="set_lod_min_distance" getter="get_lod_min_distance" default="0.0"> The GeometryInstance's min LOD distance. + [b]Note:[/b] This property currently has no effect. </member> <member name="lod_min_hysteresis" type="float" setter="set_lod_min_hysteresis" getter="get_lod_min_hysteresis" default="0.0"> The GeometryInstance's min LOD margin. + [b]Note:[/b] This property currently has no effect. </member> <member name="material_override" type="Material" setter="set_material_override" getter="get_material_override"> The material override for the whole geometry. - If there is a material in [code]material_override[/code], it will be used instead of any material set in any material slot of the mesh. + If a material is assigned to this property, it will be used instead of any material set in any material slot of the mesh. </member> <member name="use_in_baked_light" type="bool" setter="set_flag" getter="get_flag" default="false"> - If [code]true[/code], this GeometryInstance will be used when baking lights using a [GIProbe] and/or any other form of baked lighting. + If [code]true[/code], this GeometryInstance will be used when baking lights using a [GIProbe] or [BakedLightmap]. </member> </members> <constants> @@ -78,10 +82,10 @@ </constant> <constant name="SHADOW_CASTING_SETTING_SHADOWS_ONLY" value="3" enum="ShadowCastingSetting"> Will only show the shadows casted from this object. - In other words: The actual mesh will not be visible, only the shadows casted from the mesh. + In other words, the actual mesh will not be visible, only the shadows casted from the mesh will be. </constant> <constant name="FLAG_USE_BAKED_LIGHT" value="0" enum="Flags"> - Will allow the GeometryInstance to be used when baking lights using a [GIProbe] and/or any other form of baked lighting. + Will allow the GeometryInstance to be used when baking lights using a [GIProbe] or [BakedLightmap]. </constant> <constant name="FLAG_DRAW_NEXT_FRAME_IF_VISIBLE" value="1" enum="Flags"> Unused in this class, exposed for consistency with [enum VisualServer.InstanceFlags]. diff --git a/doc/classes/HashingContext.xml b/doc/classes/HashingContext.xml new file mode 100644 index 0000000000..552a74eba4 --- /dev/null +++ b/doc/classes/HashingContext.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="HashingContext" inherits="Reference" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + <method name="finish"> + <return type="PoolByteArray"> + </return> + <description> + </description> + </method> + <method name="start"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="type" type="int" enum="HashingContext.HashType"> + </argument> + <description> + </description> + </method> + <method name="update"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="chunk" type="PoolByteArray"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + <constant name="HASH_MD5" value="0" enum="HashType"> + </constant> + <constant name="HASH_SHA1" value="1" enum="HashType"> + </constant> + <constant name="HASH_SHA256" value="2" enum="HashType"> + </constant> + </constants> +</class> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 33b9da6fdf..5fd5e8c3c0 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -372,6 +372,16 @@ Stops the vibration of the joypad. </description> </method> + <method name="vibrate_handheld"> + <return type="void"> + </return> + <argument index="0" name="duration_ms" type="int" default="500"> + </argument> + <description> + Vibrate Android and iOS devices. + [b]Note:[/b] It needs VIBRATE permission for Android at export settings. iOS does not support duration. + </description> + </method> <method name="warp_mouse_position"> <return type="void"> </return> diff --git a/doc/classes/PoolByteArray.xml b/doc/classes/PoolByteArray.xml index 08848e789b..21bf078017 100644 --- a/doc/classes/PoolByteArray.xml +++ b/doc/classes/PoolByteArray.xml @@ -67,6 +67,12 @@ Returns a copy of the array's contents as [String]. Slower than [method get_string_from_ascii] but supports UTF-8 encoded data. Use this function if you are unsure about the source of the data. For user input this function should always be preferred. </description> </method> + <method name="hex_encode"> + <return type="String"> + </return> + <description> + </description> + </method> <method name="insert"> <return type="int"> </return> @@ -113,13 +119,6 @@ Changes the byte at the given index. </description> </method> - <method name="sha256_string"> - <return type="String"> - </return> - <description> - Returns SHA-256 string of the PoolByteArray. - </description> - </method> <method name="size"> <return type="int"> </return> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 9f18eea0d4..bf1835594b 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -664,6 +664,8 @@ </member> <member name="network/limits/tcp/connect_timeout_seconds" type="int" setter="" getter="" default="30"> </member> + <member name="network/limits/webrtc/max_channel_in_buffer_kb" type="int" setter="" getter="" default="64"> + </member> <member name="network/limits/websocket_client/max_in_buffer_kb" type="int" setter="" getter="" default="64"> </member> <member name="network/limits/websocket_client/max_in_packets" type="int" setter="" getter="" default="1024"> @@ -686,6 +688,8 @@ <member name="network/remote_fs/page_size" type="int" setter="" getter="" default="65536"> Page size used by remote filesystem (in bytes). </member> + <member name="network/ssl/certificates" type="String" setter="" getter="" default=""""> + </member> <member name="node/name_casing" type="int" setter="" getter="" default="0"> When creating node names automatically, set the type of casing in this project. This is mostly an editor setting. </member> diff --git a/doc/classes/ResourceFormatLoaderCrypto.xml b/doc/classes/ResourceFormatLoaderCrypto.xml new file mode 100644 index 0000000000..8bc7d50c75 --- /dev/null +++ b/doc/classes/ResourceFormatLoaderCrypto.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="ResourceFormatLoaderCrypto" inherits="ResourceFormatLoader" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/ResourceFormatSaverCrypto.xml b/doc/classes/ResourceFormatSaverCrypto.xml new file mode 100644 index 0000000000..2f7d224dab --- /dev/null +++ b/doc/classes/ResourceFormatSaverCrypto.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="ResourceFormatSaverCrypto" inherits="ResourceFormatSaver" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index f280dc81c2..32a1634f77 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -154,7 +154,7 @@ Multiplies the gravity applied to the body. The body's gravity is calculated from the [b]Default Gravity[/b] value in [b]Project > Project Settings > Physics > 2d[/b] and/or any additional gravity vector applied by [Area2D]s. </member> <member name="inertia" type="float" setter="set_inertia" getter="get_inertia"> - The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body. The moment of inertia is usually computed automatically from the mass and the shapes, but this function allows you to set a custom value. Set 0 (or negative) inertia to return to automatically computing it. + The body's moment of inertia. This is like mass, but for rotation: it determines how much torque it takes to rotate the body. The moment of inertia is usually computed automatically from the mass and the shapes, but this function allows you to set a custom value. Set 0 inertia to return to automatically computing it. </member> <member name="linear_damp" type="float" setter="set_linear_damp" getter="get_linear_damp" default="-1.0"> Damps the body's [member linear_velocity]. If [code]-1[/code], the body will use the [b]Default Linear Damp[/b] in [b]Project > Project Settings > Physics > 2d[/b]. diff --git a/doc/classes/StreamPeerSSL.xml b/doc/classes/StreamPeerSSL.xml index 9b5f4e7580..c960a794e2 100644 --- a/doc/classes/StreamPeerSSL.xml +++ b/doc/classes/StreamPeerSSL.xml @@ -13,7 +13,13 @@ <method name="accept_stream"> <return type="int" enum="Error"> </return> - <argument index="0" name="base" type="StreamPeer"> + <argument index="0" name="stream" type="StreamPeer"> + </argument> + <argument index="1" name="private_key" type="CryptoKey"> + </argument> + <argument index="2" name="certificate" type="X509Certificate"> + </argument> + <argument index="3" name="chain" type="X509Certificate" default="null"> </argument> <description> </description> @@ -27,6 +33,8 @@ </argument> <argument index="2" name="for_hostname" type="String" default=""""> </argument> + <argument index="3" name="valid_certificate" type="X509Certificate" default="null"> + </argument> <description> Connects to a peer using an underlying [StreamPeer] [code]stream[/code]. If [code]validate_certs[/code] is [code]true[/code], [StreamPeerSSL] will validate that the certificate presented by the peer matches the [code]for_hostname[/code]. </description> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 22c769330d..fb5f20361b 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -418,6 +418,10 @@ <member name="highlight_current_line" type="bool" setter="set_highlight_current_line" getter="is_highlight_current_line_enabled" default="false"> If [code]true[/code], the line containing the cursor is highlighted. </member> + <member name="minimap_draw" type="bool" setter="draw_minimap" getter="is_drawing_minimap" default="false"> + </member> + <member name="minimap_width" type="int" setter="set_minimap_width" getter="get_minimap_width" default="80"> + </member> <member name="override_selected_font_color" type="bool" setter="set_override_selected_font_color" getter="is_overriding_selected_font_color" default="false"> </member> <member name="readonly" type="bool" setter="set_readonly" getter="is_readonly" default="false"> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 62a7147e08..51d56c758e 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -16,7 +16,7 @@ var subchild1 = tree.create_item(child1) subchild1.set_text(0, "Subchild1") [/codeblock] - To iterate over all the [TreeItem] objects in a [Tree] object, use [method TreeItem.get_next] and [method TreeItem.get_children] after getting the root through [get_root]. + To iterate over all the [TreeItem] objects in a [Tree] object, use [method TreeItem.get_next] and [method TreeItem.get_children] after getting the root through [method get_root]. </description> <tutorials> </tutorials> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 237b596fad..66c4849358 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -203,6 +203,24 @@ Returns the vector scaled to unit length. Equivalent to [code]v / v.length()[/code]. </description> </method> + <method name="posmod"> + <return type="Vector2"> + </return> + <argument index="0" name="mod" type="float"> + </argument> + <description> + Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]mod[/code]. + </description> + </method> + <method name="posmodv"> + <return type="Vector2"> + </return> + <argument index="0" name="mod" type="float"> + </argument> + <description> + Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]modv[/code]'s components. + </description> + </method> <method name="project"> <return type="Vector2"> </return> @@ -237,6 +255,13 @@ Returns the vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. </description> </method> + <method name="sign"> + <return type="Vector2"> + </return> + <description> + Returns the vector with each component set to one or negative one, depending on the signs of the components. + </description> + </method> <method name="slerp"> <return type="Vector2"> </return> @@ -284,6 +309,12 @@ </member> </members> <constants> + <constant name="AXIS_X" value="0"> + Enumerated value for the X axis. Returned by [method max_axis] and [method min_axis]. + </constant> + <constant name="AXIS_Y" value="1"> + Enumerated value for the Y axis. + </constant> <constant name="ZERO" value="Vector2( 0, 0 )"> Zero vector. </constant> @@ -291,7 +322,7 @@ One vector. </constant> <constant name="INF" value="Vector2( inf, inf )"> - Infinite vector. + Infinity vector. </constant> <constant name="LEFT" value="Vector2( -1, 0 )"> Left unit vector. diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index 3e1083ab69..4631eb7300 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Vector3" category="Built-In Types" version="3.2"> <brief_description> - Vector class, which performs basic 3D vector math operations. + Vector used for 3D math. </brief_description> <description> - Vector3 is one of the core classes of the engine, and includes several built-in helper functions to perform basic vector math operations. + 3-element structure that can be used to represent positions in 3D space or any other pair of numeric values. </description> <tutorials> <link>https://docs.godotengine.org/en/latest/tutorials/math/index.html</link> @@ -202,6 +202,24 @@ Returns the outer product with [code]b[/code]. </description> </method> + <method name="posmod"> + <return type="Vector3"> + </return> + <argument index="0" name="mod" type="float"> + </argument> + <description> + Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]mod[/code]. + </description> + </method> + <method name="posmodv"> + <return type="Vector3"> + </return> + <argument index="0" name="mod" type="float"> + </argument> + <description> + Returns a vector composed of the [code]fposmod[/code] of this vector's components and [code]modv[/code]'s components. + </description> + </method> <method name="project"> <return type="Vector3"> </return> @@ -238,6 +256,13 @@ Returns the vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. </description> </method> + <method name="sign"> + <return type="Vector3"> + </return> + <description> + Returns the vector with each component set to one or negative one, depending on the signs of the components. + </description> + </method> <method name="slerp"> <return type="Vector3"> </return> @@ -304,7 +329,7 @@ One vector. </constant> <constant name="INF" value="Vector3( inf, inf, inf )"> - Infinite vector. + Infinity vector. </constant> <constant name="LEFT" value="Vector3( -1, 0, 0 )"> Left unit vector. diff --git a/doc/classes/VisualShaderNodeCustom.xml b/doc/classes/VisualShaderNodeCustom.xml index d4a1732364..9067097f0b 100644 --- a/doc/classes/VisualShaderNodeCustom.xml +++ b/doc/classes/VisualShaderNodeCustom.xml @@ -22,14 +22,6 @@ Override this method to define the category of the associated custom node in the Visual Shader Editor's members dialog. Defining this method is [b]optional[/b]. If not overridden, the node will be filed under the "Custom" category. </description> - </method> - <method name="_get_description" qualifiers="virtual"> - <return type="String"> - </return> - <description> - Override this method to define the description of the associated custom node in the Visual Shader Editor's members dialog. - Defining this method is [b]optional[/b]. - </description> </method> <method name="_get_code" qualifiers="virtual"> <return type="String"> @@ -38,9 +30,9 @@ </argument> <argument index="1" name="output_vars" type="Array"> </argument> - <argument index="2" name="mode" type="int" enum="Shader.Mode"> - </argument> - <argument index="3" name="type" type="int" enum="VisualShader.Type"> + <argument index="2" name="mode" type="int"> + </argument> + <argument index="3" name="type" type="int"> </argument> <description> Override this method to define the actual shader code of the associated custom node. The shader code should be returned as a string, which can have multiple lines (the [code]"""[/code] multiline string construct can be used for convenience). @@ -50,10 +42,18 @@ Defining this method is [b]required[/b]. </description> </method> + <method name="_get_description" qualifiers="virtual"> + <return type="String"> + </return> + <description> + Override this method to define the description of the associated custom node in the Visual Shader Editor's members dialog. + Defining this method is [b]optional[/b]. + </description> + </method> <method name="_get_global_code" qualifiers="virtual"> <return type="String"> </return> - <argument index="0" name="mode" type="int" enum="Shader.Mode"> + <argument index="0" name="mode" type="int"> </argument> <description> Override this method to add shader code on top of the global shader, to define your own standard library of reusable methods, varyings, constants, uniforms, etc. The shader code should be returned as a string, which can have multiple lines (the [code]"""[/code] multiline string construct can be used for convenience). @@ -81,7 +81,7 @@ </description> </method> <method name="_get_input_port_type" qualifiers="virtual"> - <return type="int" enum="VisualShaderNode.PortType"> + <return type="int"> </return> <argument index="0" name="port" type="int"> </argument> @@ -117,7 +117,7 @@ </description> </method> <method name="_get_output_port_type" qualifiers="virtual"> - <return type="int" enum="VisualShaderNode.PortType"> + <return type="int"> </return> <argument index="0" name="port" type="int"> </argument> @@ -127,7 +127,7 @@ </description> </method> <method name="_get_return_icon_type" qualifiers="virtual"> - <return type="int" enum="VisualShaderNode.PortType"> + <return type="int"> </return> <description> Override this method to define the return icon of the associated custom node in the Visual Shader Editor's members dialog. diff --git a/doc/classes/VisualShaderNodeGlobalExpression.xml b/doc/classes/VisualShaderNodeGlobalExpression.xml new file mode 100644 index 0000000000..3c5a26bf47 --- /dev/null +++ b/doc/classes/VisualShaderNodeGlobalExpression.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeGlobalExpression" inherits="VisualShaderNodeExpression" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/VisualShaderNodeGroupBase.xml b/doc/classes/VisualShaderNodeGroupBase.xml index c2e9b9503b..d32a63d605 100644 --- a/doc/classes/VisualShaderNodeGroupBase.xml +++ b/doc/classes/VisualShaderNodeGroupBase.xml @@ -208,6 +208,10 @@ </description> </method> </methods> + <members> + <member name="editable" type="bool" setter="set_editable" getter="is_editable" default="false"> + </member> + </members> <constants> </constants> </class> diff --git a/doc/classes/X509Certificate.xml b/doc/classes/X509Certificate.xml new file mode 100644 index 0000000000..013f768843 --- /dev/null +++ b/doc/classes/X509Certificate.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="X509Certificate" inherits="Resource" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + <method name="load"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + </description> + </method> + <method name="save"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index 7db847d6ed..fb21c0c5a1 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -38,6 +38,7 @@ #include <shlwapi.h> #include <windows.h> +#include <errno.h> #include <sys/stat.h> #include <sys/types.h> #include <tchar.h> diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 33af5927b3..61adff7c9c 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -908,6 +908,8 @@ void CodeTextEditor::update_editor_settings() { 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->set_draw_minimap(EditorSettings::get_singleton()->get("text_editor/line_numbers/draw_minimap")); + text_editor->set_minimap_width(EditorSettings::get_singleton()->get("text_editor/line_numbers/minimap_width")); text_editor->set_draw_info_gutter(EditorSettings::get_singleton()->get("text_editor/line_numbers/show_info_gutter")); 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")); diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index bea1980b09..b2567249d8 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -181,14 +181,14 @@ EditorAbout::EditorAbout() { // Thirdparty License VBoxContainer *license_thirdparty = memnew(VBoxContainer); - license_thirdparty->set_name(TTR("Thirdparty License")); + license_thirdparty->set_name(TTR("Third-party Licenses")); license_thirdparty->set_h_size_flags(Control::SIZE_EXPAND_FILL); tc->add_child(license_thirdparty); Label *tpl_label = memnew(Label); tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); tpl_label->set_autowrap(true); - tpl_label->set_text(TTR("Godot Engine relies on a number of thirdparty free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such thirdparty components with their respective copyright statements and license terms.")); + tpl_label->set_text(TTR("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms.")); tpl_label->set_size(Size2(630, 1) * EDSCALE); license_thirdparty->add_child(tpl_label); diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp index b706f2cae6..98e670f952 100644 --- a/editor/editor_asset_installer.cpp +++ b/editor/editor_asset_installer.cpp @@ -90,7 +90,7 @@ void EditorAssetInstaller::open(const String &p_path, int p_depth) { unzFile pkg = unzOpen2(p_path.utf8().get_data(), &io); if (!pkg) { - error->set_text(TTR("Error opening package file, not in zip format.")); + error->set_text(TTR("Error opening package file, not in ZIP format.")); return; } @@ -217,7 +217,7 @@ void EditorAssetInstaller::ok_pressed() { unzFile pkg = unzOpen2(package_path.utf8().get_data(), &io); if (!pkg) { - error->set_text(TTR("Error opening package file, not in zip format.")); + error->set_text(TTR("Error opening package file, not in ZIP format.")); return; } diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index cb1754bb10..e58c7c992a 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -30,11 +30,11 @@ #include "editor_export.h" +#include "core/crypto/crypto_core.h" #include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/io/zip_io.h" -#include "core/math/crypto_core.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/project_settings.h" @@ -148,6 +148,12 @@ String EditorExportPreset::get_include_filter() const { void EditorExportPreset::set_export_path(const String &p_path) { export_path = p_path; + /* NOTE(SonerSound): if there is a need to implement a PropertyHint that specifically indicates a relative path, + * this should be removed. */ + if (export_path.is_abs_path()) { + String res_path = OS::get_singleton()->get_resource_dir(); + export_path = res_path.path_to_file(export_path); + } EditorExport::singleton->save_presets(); } diff --git a/editor/editor_file_dialog.cpp b/editor/editor_file_dialog.cpp index 8c9aedc7b7..80aeeef868 100644 --- a/editor/editor_file_dialog.cpp +++ b/editor/editor_file_dialog.cpp @@ -257,7 +257,7 @@ void EditorFileDialog::_post_popup() { if (is_visible_in_tree()) { Ref<Texture> folder = get_icon("folder", "FileDialog"); - const Color folder_color = get_color("folder", "FileDialog"); + const Color folder_color = get_color("folder_icon_modulate", "FileDialog"); recent->clear(); bool res = access == ACCESS_RESOURCES; @@ -736,7 +736,7 @@ void EditorFileDialog::update_file_list() { dir_access->list_dir_begin(); Ref<Texture> folder = get_icon("folder", "FileDialog"); - const Color folder_color = get_color("folder", "FileDialog"); + const Color folder_color = get_color("folder_icon_modulate", "FileDialog"); List<String> files; List<String> dirs; @@ -1204,7 +1204,7 @@ void EditorFileDialog::_update_favorites() { String current = get_current_dir(); Ref<Texture> folder_icon = get_icon("Folder", "EditorIcons"); - const Color folder_color = get_color("folder", "FileDialog"); + const Color folder_color = get_color("folder_icon_modulate", "FileDialog"); favorites->clear(); favorite->set_pressed(false); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 385b6924df..2e8f8ec646 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -164,6 +164,17 @@ void EditorHelp::_class_desc_select(const String &p_select) { void EditorHelp::_class_desc_input(const Ref<InputEvent> &p_input) { } +void EditorHelp::_class_desc_resized() { + // Add extra horizontal margins for better readability. + // The margins increase as the width of the editor help container increases. + const int display_margin = MAX(30 * EDSCALE, get_parent_anchorable_rect().size.width - 900 * EDSCALE) * 0.5; + + Ref<StyleBox> class_desc_stylebox = EditorNode::get_singleton()->get_theme_base()->get_stylebox("normal", "RichTextLabel")->duplicate(); + class_desc_stylebox->set_default_margin(MARGIN_LEFT, display_margin); + class_desc_stylebox->set_default_margin(MARGIN_RIGHT, display_margin); + class_desc->add_style_override("normal", class_desc_stylebox); +} + void EditorHelp::_add_type(const String &p_type, const String &p_enum) { String t = p_type; @@ -1488,6 +1499,7 @@ void EditorHelp::_bind_methods() { ClassDB::bind_method("_class_list_select", &EditorHelp::_class_list_select); ClassDB::bind_method("_class_desc_select", &EditorHelp::_class_desc_select); ClassDB::bind_method("_class_desc_input", &EditorHelp::_class_desc_input); + ClassDB::bind_method("_class_desc_resized", &EditorHelp::_class_desc_resized); ClassDB::bind_method("_request_help", &EditorHelp::_request_help); ClassDB::bind_method("_unhandled_key_input", &EditorHelp::_unhandled_key_input); ClassDB::bind_method("_search", &EditorHelp::_search); @@ -1506,8 +1518,11 @@ EditorHelp::EditorHelp() { add_child(class_desc); class_desc->set_v_size_flags(SIZE_EXPAND_FILL); class_desc->add_color_override("selection_color", get_color("accent_color", "Editor") * Color(1, 1, 1, 0.4)); + class_desc->connect("meta_clicked", this, "_class_desc_select"); class_desc->connect("gui_input", this, "_class_desc_input"); + class_desc->connect("resized", this, "_class_desc_resized"); + _class_desc_resized(); // Added second so it opens at the bottom so it won't offset the entire widget. find_bar = memnew(FindBar); diff --git a/editor/editor_help.h b/editor/editor_help.h index 3824ba231e..1019cafffc 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -151,6 +151,7 @@ class EditorHelp : public VBoxContainer { void _class_list_select(const String &p_select); void _class_desc_select(const String &p_select); void _class_desc_input(const Ref<InputEvent> &p_input); + void _class_desc_resized(); Error _goto_desc(const String &p_class, int p_vscr = -1); //void _update_history_buttons(); diff --git a/editor/editor_path.cpp b/editor/editor_path.cpp index 12510e27de..23d28261d1 100644 --- a/editor/editor_path.cpp +++ b/editor/editor_path.cpp @@ -78,6 +78,9 @@ void EditorPath::_about_to_show() { } void EditorPath::update_path() { + set_text(""); + set_tooltip(""); + set_icon(NULL); for (int i = 0; i < history->get_path_size(); i++) { diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 20ba07c102..378dd34e39 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -209,13 +209,7 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() { void EditorPropertyPath::_path_selected(const String &p_path) { - String final_path = p_path; - if (final_path.is_abs_path()) { - String res_path = OS::get_singleton()->get_resource_dir() + "/"; - final_path = res_path.path_to_file(final_path); - } - - emit_changed(get_edited_property(), final_path); + emit_changed(get_edited_property(), p_path); update_property(); } void EditorPropertyPath::_path_pressed() { @@ -228,13 +222,6 @@ void EditorPropertyPath::_path_pressed() { } String full_path = get_edited_object()->get(get_edited_property()); - if (full_path.is_rel_path()) { - - if (!DirAccess::exists(full_path.get_base_dir())) { - DirAccessRef da(DirAccess::create(DirAccess::ACCESS_FILESYSTEM)); - da->make_dir_recursive(full_path.get_base_dir()); - } - } dialog->clear_filters(); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 3bd333692f..ea6361665c 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -448,6 +448,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/line_numbers/show_info_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/draw_minimap", true); + _initial_set("text_editor/line_numbers/minimap_width", 80); + hints["text_editor/line_numbers/minimap_width"] = PropertyInfo(Variant::INT, "text_editor/line_numbers/minimap_width", PROPERTY_HINT_RANGE, "50,250,1"); _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"); @@ -561,6 +564,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // 2D _initial_set("editors/2d/grid_color", Color(1.0, 1.0, 1.0, 0.07)); _initial_set("editors/2d/guides_color", Color(0.6, 0.0, 0.8)); + _initial_set("editors/2d/smart_snapping_line_color", Color(0.9, 0.1, 0.1)); _initial_set("editors/2d/bone_width", 5); _initial_set("editors/2d/bone_color1", Color(1.0, 1.0, 1.0, 0.9)); _initial_set("editors/2d/bone_color2", Color(0.6, 0.6, 0.6, 0.9)); @@ -608,6 +612,18 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("run/output/always_open_output_on_play", true); _initial_set("run/output/always_close_output_on_stop", false); + /* Network */ + + // Debug + _initial_set("network/debug/remote_host", "127.0.0.1"); // Hints provided in setup_network + + _initial_set("network/debug/remote_port", 6007); + hints["network/debug/remote_port"] = PropertyInfo(Variant::INT, "network/debug/remote_port", PROPERTY_HINT_RANGE, "1,65535,1"); + + // SSL + _initial_set("network/ssl/editor_ssl_certificates", _SYSTEM_CERTS_PATH); + hints["network/ssl/editor_ssl_certificates"] = PropertyInfo(Variant::STRING, "network/ssl/editor_ssl_certificates", PROPERTY_HINT_GLOBAL_FILE, "*.crt,*.pem"); + /* Extra config */ _initial_set("project_manager/sorting_order", 0); @@ -993,11 +1009,11 @@ void EditorSettings::setup_network() { List<IP_Address> local_ip; IP::get_singleton()->get_local_addresses(&local_ip); - String lip = "127.0.0.1"; String hint; String current = has_setting("network/debug/remote_host") ? get("network/debug/remote_host") : ""; - int port = has_setting("network/debug/remote_port") ? (int)get("network/debug/remote_port") : 6007; + String selected = "127.0.0.1"; + // Check that current remote_host is a valid interface address and populate hints. for (List<IP_Address>::Element *E = local_ip.front(); E; E = E->next()) { String ip = E->get(); @@ -1008,22 +1024,18 @@ void EditorSettings::setup_network() { // Same goes for IPv4 link-local (APIPA) addresses. if (ip.begins_with("169.254.")) // 169.254.0.0/16 continue; + // Select current IP (found) if (ip == current) - lip = current; //so it saves + selected = ip; if (hint != "") hint += ","; hint += ip; } - _initial_set("network/debug/remote_host", lip); + // Add hints with valid IP addresses to remote_host property. add_property_hint(PropertyInfo(Variant::STRING, "network/debug/remote_host", PROPERTY_HINT_ENUM, hint)); - - _initial_set("network/debug/remote_port", port); - add_property_hint(PropertyInfo(Variant::INT, "network/debug/remote_port", PROPERTY_HINT_RANGE, "1,65535,1")); - - // Editor SSL certificates override - _initial_set("network/ssl/editor_ssl_certificates", _SYSTEM_CERTS_PATH); - add_property_hint(PropertyInfo(Variant::STRING, "network/ssl/editor_ssl_certificates", PROPERTY_HINT_GLOBAL_FILE, "*.crt,*.pem")); + // Fix potentially invalid remote_host due to network change. + set("network/debug/remote_host", selected); } void EditorSettings::save() { @@ -1197,6 +1209,11 @@ String EditorSettings::get_script_templates_dir() const { return get_settings_dir().plus_file("script_templates"); } +String EditorSettings::get_project_script_templates_dir() const { + + return ProjectSettings::get_singleton()->get("editor/script_templates_search_path"); +} + // Cache directory String EditorSettings::get_cache_dir() const { @@ -1417,10 +1434,14 @@ bool EditorSettings::is_default_text_editor_theme() { return _is_default_text_editor_theme(p_file.get_file().to_lower()); } -Vector<String> EditorSettings::get_script_templates(const String &p_extension) { +Vector<String> EditorSettings::get_script_templates(const String &p_extension, const String &p_custom_path) { Vector<String> templates; - DirAccess *d = DirAccess::open(get_script_templates_dir()); + String template_dir = get_script_templates_dir(); + if (!p_custom_path.empty()) { + template_dir = p_custom_path; + } + DirAccess *d = DirAccess::open(template_dir); if (d) { d->list_dir_begin(); String file = d->get_next(); diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 890850629e..0738185e95 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -166,6 +166,7 @@ public: String get_project_settings_dir() const; String get_text_editor_themes_dir() const; String get_script_templates_dir() const; + String get_project_script_templates_dir() const; String get_cache_dir() const; String get_feature_profiles_dir() const; @@ -187,7 +188,7 @@ public: bool save_text_editor_theme_as(String p_file); bool is_default_text_editor_theme(); - Vector<String> get_script_templates(const String &p_extension); + Vector<String> get_script_templates(const String &p_extension, const String &p_custom_path = String()); String get_editor_layouts_config() const; void add_shortcut(const String &p_name, Ref<ShortCut> &p_shortcut); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index d962001b38..56eed96e31 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1068,7 +1068,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("folder", "FileDialog", theme->get_icon("Folder", "EditorIcons")); // Use a different color for folder icons to make them easier to distinguish from files. // On a light theme, the icon will be dark, so we need to lighten it before blending it with the accent color. - theme->set_color("folder", "FileDialog", (dark_theme ? Color(1, 1, 1) : Color(5, 5, 5)).linear_interpolate(accent_color, 0.7)); + theme->set_color("folder_icon_modulate", "FileDialog", (dark_theme ? Color(1, 1, 1) : Color(5, 5, 5)).linear_interpolate(accent_color, 0.7)); theme->set_color("files_disabled", "FileDialog", font_color_disabled); // color picker diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index 68e859966c..1447a143d4 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -590,7 +590,7 @@ Error ExportTemplateManager::install_android_template() { zlib_filefunc_def io = zipio_create_io_from_file(&src_f); unzFile pkg = unzOpen2(source_zip.utf8().get_data(), &io); - ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android sources not in zip format."); + ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android sources not in ZIP format."); int ret = unzGoToFirstFile(pkg); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 83904800e9..84631f3e38 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -53,10 +53,9 @@ Ref<Texture> FileSystemDock::_get_tree_item_icon(EditorFileSystemDirectory *p_di } bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory *p_dir, Vector<String> &uncollapsed_paths, bool p_select_in_favorites) { - bool parent_should_expand = false; - // Create a tree item for the subdirectory + // Create a tree item for the subdirectory. TreeItem *subdirectory_item = tree->create_item(p_parent); String dname = p_dir->get_name(); if (dname == "") @@ -64,7 +63,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory subdirectory_item->set_text(0, dname); subdirectory_item->set_icon(0, get_icon("Folder", "EditorIcons")); - subdirectory_item->set_icon_color(0, get_color("folder", "FileDialog")); + subdirectory_item->set_icon_color(0, get_color("folder_icon_modulate", "FileDialog")); subdirectory_item->set_selectable(0, true); String lpath = p_dir->get_path(); subdirectory_item->set_metadata(0, lpath); @@ -81,28 +80,28 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory parent_should_expand = true; } - // Create items for all subdirectories + // Create items for all subdirectories. for (int i = 0; i < p_dir->get_subdir_count(); i++) parent_should_expand = (_create_tree(subdirectory_item, p_dir->get_subdir(i), uncollapsed_paths, p_select_in_favorites) || parent_should_expand); - // Create all items for the files in the subdirectory + // Create all items for the files in the subdirectory. if (display_mode == DISPLAY_MODE_TREE_ONLY) { for (int i = 0; i < p_dir->get_file_count(); i++) { String file_type = p_dir->get_file_type(i); if (_is_file_type_disabled_by_feature_profile(file_type)) { - //if type is disabled, file won't be displayed. + // If type is disabled, file won't be displayed. continue; } String file_name = p_dir->get_file(i); if (searched_string.length() > 0) { if (file_name.to_lower().find(searched_string) < 0) { - // The searched string is not in the file name, we skip it + // The searched string is not in the file name, we skip it. continue; } else { - // We expand all parents + // We expand all parents. parent_should_expand = true; } } @@ -135,7 +134,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } Vector<String> FileSystemDock::_compute_uncollapsed_paths() { - // Register currently collapsed paths + // Register currently collapsed paths. Vector<String> uncollapsed_paths; TreeItem *root = tree->get_root(); if (root) { @@ -166,14 +165,13 @@ Vector<String> FileSystemDock::_compute_uncollapsed_paths() { } void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, bool p_uncollapse_root, bool p_select_in_favorites) { - - // Recreate the tree + // Recreate the tree. tree->clear(); tree_update_id++; updating_tree = true; TreeItem *root = tree->create_item(); - // Handles the favorites + // Handles the favorites. TreeItem *favorites = tree->create_item(root); favorites->set_icon(0, get_icon("Favorites", "EditorIcons")); favorites->set_text(0, TTR("Favorites:")); @@ -187,7 +185,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo continue; Ref<Texture> folder_icon = get_icon("Folder", "EditorIcons"); - const Color folder_color = get_color("folder", "FileDialog"); + const Color folder_color = get_color("folder_icon_modulate", "FileDialog"); String text; Ref<Texture> icon; @@ -238,7 +236,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo uncollapsed_paths.push_back("res://"); } - // Create the remaining of the tree + // Create the remaining of the tree. _create_tree(root, EditorFileSystem::get_singleton()->get_filesystem(), uncollapsed_paths, p_select_in_favorites); tree->ensure_cursor_is_visible(); updating_tree = false; @@ -250,7 +248,7 @@ void FileSystemDock::set_display_mode(DisplayMode p_display_mode) { } void FileSystemDock::_update_display_mode(bool p_force) { - // Compute the new display mode + // Compute the new display mode. if (p_force || old_display_mode != display_mode) { button_toggle_display_mode->set_pressed(display_mode == DISPLAY_MODE_SPLIT); switch (display_mode) { @@ -283,11 +281,8 @@ void FileSystemDock::_update_display_mode(bool p_force) { } void FileSystemDock::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - if (initialized) return; initialized = true; @@ -327,18 +322,15 @@ void FileSystemDock::_notification(int p_what) { } else { _update_tree(Vector<String>(), true); } - } break; + case NOTIFICATION_PROCESS: { if (EditorFileSystem::get_singleton()->is_scanning()) { scanning_progress->set_value(EditorFileSystem::get_singleton()->get_scanning_progress() * 100); } } break; - case NOTIFICATION_EXIT_TREE: { - } break; case NOTIFICATION_DRAG_BEGIN: { - Dictionary dd = get_viewport()->gui_get_drag_data(); if (tree->is_visible_in_tree() && dd.has("type")) { if ((String(dd["type"]) == "files") || (String(dd["type"]) == "files_and_dirs") || (String(dd["type"]) == "resource")) { @@ -347,20 +339,20 @@ void FileSystemDock::_notification(int p_what) { tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN); } } - } break; - case NOTIFICATION_DRAG_END: { + case NOTIFICATION_DRAG_END: { tree->set_drop_mode_flags(0); - } break; + case NOTIFICATION_THEME_CHANGED: { if (is_visible_in_tree()) { _update_display_mode(true); } } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - // Update icons + // Update icons. String ei = "EditorIcons"; button_reload->set_icon(get_icon("Reload", ei)); button_toggle_display_mode->set_icon(get_icon("Panels2", ei)); @@ -377,48 +369,47 @@ void FileSystemDock::_notification(int p_what) { file_list_search_box->set_right_icon(get_icon("Search", ei)); file_list_search_box->set_clear_button_enabled(true); - // Update always showfolders + // Update always show folders. bool new_always_show_folders = bool(EditorSettings::get_singleton()->get("docks/filesystem/always_show_folders")); if (new_always_show_folders != always_show_folders) { always_show_folders = new_always_show_folders; _update_file_list(true); } - // Change full tree mode + // Change full tree mode. _update_display_mode(); - } break; } } void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_selected) { - // Update the import dock + // Update the import dock. import_dock_needs_update = true; call_deferred("_update_import_dock"); - // Return if we don't select something new + // Return if we don't select something new. if (!p_selected) return; - // Tree item selected + // Tree item selected. TreeItem *selected = tree->get_selected(); if (!selected) return; TreeItem *favorites_item = tree->get_root()->get_children(); if (selected->get_parent() == favorites_item && !String(selected->get_metadata(0)).ends_with("/")) { - // Go to the favorites if we click in the favorites and the path has changed + // Go to the favorites if we click in the favorites and the path has changed. path = "Favorites"; } else { path = selected->get_metadata(0); - // Note: the "Favorites" item also leads to this path + // Note: the "Favorites" item also leads to this path. } - // Set the current path + // Set the current path. _set_current_path_text(path); _push_to_history(); - // Update the file list + // Update the file list. if (!updating_tree && display_mode == DISPLAY_MODE_SPLIT) { _update_file_list(false); } @@ -432,7 +423,6 @@ String FileSystemDock::get_selected_path() const { } String FileSystemDock::get_current_path() const { - return path; } @@ -491,7 +481,6 @@ void FileSystemDock::navigate_to_path(const String &p_path) { } void FileSystemDock::_file_list_thumbnail_done(const String &p_path, const Ref<Texture> &p_preview, const Ref<Texture> &p_small_preview, const Variant &p_udata) { - if ((file_list_vb->is_visible_in_tree() || path == p_path.get_base_dir()) && p_preview.is_valid()) { Array uarr = p_udata; int idx = uarr[0]; @@ -539,7 +528,6 @@ void FileSystemDock::_set_file_display(bool p_active) { } bool FileSystemDock::_is_file_type_disabled_by_feature_profile(const StringName &p_class) { - Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); if (profile.is_null()) { return false; @@ -559,7 +547,6 @@ bool FileSystemDock::_is_file_type_disabled_by_feature_profile(const StringName } void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> *matches, int p_max_items) { - if (matches->size() > p_max_items) return; @@ -577,10 +564,9 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> * fi.type = p_path->get_file_type(i); fi.path = p_path->get_file_path(i); fi.import_broken = !p_path->get_file_import_is_valid(i); - fi.import_status = 0; if (_is_file_type_disabled_by_feature_profile(fi.type)) { - //this type is disabled, will not appear here + // This type is disabled, will not appear here. continue; } @@ -592,8 +578,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> * } void FileSystemDock::_update_file_list(bool p_keep_selection) { - - // Register the previously selected items + // Register the previously selected items. Set<String> cselection; if (p_keep_selection) { for (int i = 0; i < files->get_item_count(); i++) { @@ -619,7 +604,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { bool use_thumbnails = (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS); if (use_thumbnails) { - // Thumbnails mode + // Thumbnails mode. files->set_max_columns(0); files->set_icon_mode(ItemList::ICON_MODE_TOP); files->set_fixed_column_width(thumbnail_size * 3 / 2); @@ -636,8 +621,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { file_thumbnail_broken = get_icon("FileDeadBigThumb", ei); } } else { - - // No thumbnails + // No thumbnails. files->set_icon_mode(ItemList::ICON_MODE_LEFT); files->set_max_columns(1); files->set_max_text_lines(1); @@ -646,12 +630,12 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } Ref<Texture> folder_icon = (use_thumbnails) ? folder_thumbnail : get_icon("folder", "FileDialog"); - const Color folder_color = get_color("folder", "FileDialog"); + const Color folder_color = get_color("folder_icon_modulate", "FileDialog"); - // Build the FileInfo list + // Build the FileInfo list. List<FileInfo> filelist; if (path == "Favorites") { - // Display the favorites + // Display the favorites. Vector<String> favorites = EditorSettings::get_singleton()->get_favorites(); for (int i = 0; i < favorites.size(); i++) { String favorite = favorites[i]; @@ -685,7 +669,6 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { fi.type = ""; fi.import_broken = true; } - fi.import_status = 0; if (searched_string.length() == 0 || fi.name.to_lower().find(searched_string) >= 0) { filelist.push_back(fi); @@ -693,8 +676,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } } } else { - - // Get infos on the directory + file + // Get infos on the directory + file. if (directory.ends_with("/") && directory != "res://") { directory = directory.substr(0, directory.length() - 1); } @@ -708,13 +690,11 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { return; if (searched_string.length() > 0) { - // Display the search results + // Display the search results. _search(EditorFileSystem::get_singleton()->get_filesystem(), &filelist, 128); } else { - if (display_mode == DISPLAY_MODE_TREE_ONLY || always_show_folders) { - // Display folders in the list - + // Display folders in the list. if (directory != "res://") { files->add_item("..", folder_icon, true); @@ -728,7 +708,6 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } for (int i = 0; i < efd->get_subdir_count(); i++) { - String dname = efd->get_subdir(i)->get_name(); files->add_item(dname, folder_icon, true); @@ -741,15 +720,13 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } } - // Display the folder content + // Display the folder content. for (int i = 0; i < efd->get_file_count(); i++) { - FileInfo fi; fi.name = efd->get_file(i); fi.path = directory.plus_file(fi.name); fi.type = efd->get_file_type(i); fi.import_broken = !efd->get_file_import_is_valid(i); - fi.import_status = 0; filelist.push_back(fi); } @@ -757,7 +734,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { filelist.sort(); } - // Fills the ItemList control node from the FileInfos + // Fills the ItemList control node from the FileInfos. String oi = "Object"; for (List<FileInfo>::Element *E = filelist.front(); E; E = E->next()) { FileInfo *finfo = &(E->get()); @@ -770,7 +747,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { String tooltip = fpath; - // Select the icons + // Select the icons. if (!finfo->import_broken) { type_icon = (has_icon(ftype, ei)) ? get_icon(ftype, ei) : get_icon(oi, ei); big_icon = file_thumbnail; @@ -780,7 +757,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { tooltip += "\n" + TTR("Status: Import of file failed. Please fix file and reimport manually."); } - // Add the item to the ItemList + // Add the item to the ItemList. int item_index; if (use_thumbnails) { files->add_item(fname, big_icon, true); @@ -794,7 +771,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_item_metadata(item_index, fpath); } - // Generate the preview + // Generate the preview. if (!finfo->import_broken) { Array udata; udata.resize(2); @@ -803,7 +780,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { EditorResourcePreview::get_singleton()->queue_resource_preview(fpath, this, "_file_list_thumbnail_done", udata); } - // Select the items + // Select the items. if (cselection.has(fname)) files->select(item_index, false); @@ -812,7 +789,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->ensure_current_is_visible(); } - // Tooltip + // Tooltip. if (finfo->sources.size()) { for (int j = 0; j < finfo->sources.size(); j++) { tooltip += "\nSource: " + finfo->sources[j]; @@ -859,13 +836,12 @@ void FileSystemDock::_file_list_activate_file(int p_idx) { } void FileSystemDock::_preview_invalidated(const String &p_path) { - if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) { for (int i = 0; i < files->get_item_count(); i++) { if (files->get_item_metadata(i) == p_path) { - //re-request preview + // Re-request preview. Array udata; udata.resize(2); udata[0] = i; @@ -878,7 +854,6 @@ void FileSystemDock::_preview_invalidated(const String &p_path) { } void FileSystemDock::_fs_changed() { - button_hist_prev->set_disabled(history_pos == 0); button_hist_next->set_disabled(history_pos == history.size() - 1); scanning_vb->hide(); @@ -896,7 +871,6 @@ void FileSystemDock::_fs_changed() { } void FileSystemDock::_set_scanning_mode() { - button_hist_prev->set_disabled(true); button_hist_next->set_disabled(true); split_box->hide(); @@ -910,7 +884,6 @@ void FileSystemDock::_set_scanning_mode() { } void FileSystemDock::_fw_history() { - if (history_pos < history.size() - 1) history_pos++; @@ -988,7 +961,7 @@ void FileSystemDock::_find_remaps(EditorFileSystemDirectory *efsd, const Map<Str void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_new_path, Map<String, String> &p_file_renames, Map<String, String> &p_folder_renames) { - //Ensure folder paths end with "/" + // Ensure folder paths end with "/". String old_path = (p_item.is_file || p_item.path.ends_with("/")) ? p_item.path : (p_item.path + "/"); String new_path = (p_item.is_file || p_new_path.ends_with("/")) ? p_new_path : (p_new_path + "/"); @@ -998,12 +971,12 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ EditorNode::get_singleton()->add_io_error(TTR("Cannot move/rename resources root.")); return; } else if (!p_item.is_file && new_path.begins_with(old_path)) { - //This check doesn't erroneously catch renaming to a longer name as folder paths always end with "/" + // This check doesn't erroneously catch renaming to a longer name as folder paths always end with "/". EditorNode::get_singleton()->add_io_error(TTR("Cannot move a folder into itself.") + "\n" + old_path + "\n"); return; } - //Build a list of files which will have new paths as a result of this operation + // Build a list of files which will have new paths as a result of this operation. Vector<String> file_changed_paths; Vector<String> folder_changed_paths; if (p_item.is_file) { @@ -1017,8 +990,7 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ print_verbose("Moving " + old_path + " -> " + new_path); Error err = da->rename(old_path, new_path); if (err == OK) { - - //Move/Rename any corresponding import settings too + // Move/Rename any corresponding import settings too. if (p_item.is_file && FileAccess::exists(old_path + ".import")) { err = da->rename(old_path + ".import", new_path + ".import"); if (err != OK) { @@ -1026,7 +998,7 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ } } - // update scene if it is open + // Update scene if it is open. for (int i = 0; i < file_changed_paths.size(); ++i) { String new_item_path = p_item.is_file ? new_path : file_changed_paths[i].replace_first(old_path, new_path); if (ResourceLoader::get_resource_type(new_item_path) == "PackedScene" && editor->is_scene_open(file_changed_paths[i])) { @@ -1041,7 +1013,7 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ } } - //Only treat as a changed dependency if it was successfully moved + // Only treat as a changed dependency if it was successfully moved. for (int i = 0; i < file_changed_paths.size(); ++i) { p_file_renames[file_changed_paths[i]] = file_changed_paths[i].replace_first(old_path, new_path); print_verbose(" Remap: " + file_changed_paths[i] + " -> " + p_file_renames[file_changed_paths[i]]); @@ -1058,7 +1030,7 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ } void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const String &p_new_path) const { - //Ensure folder paths end with "/" + // Ensure folder paths end with "/". String old_path = (p_item.is_file || p_item.path.ends_with("/")) ? p_item.path : (p_item.path + "/"); String new_path = (p_item.is_file || p_new_path.ends_with("/")) ? p_new_path : (p_new_path + "/"); @@ -1068,7 +1040,7 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin EditorNode::get_singleton()->add_io_error(TTR("Cannot move/rename resources root.")); return; } else if (!p_item.is_file && new_path.begins_with(old_path)) { - //This check doesn't erroneously catch renaming to a longer name as folder paths always end with "/" + // This check doesn't erroneously catch renaming to a longer name as folder paths always end with "/". EditorNode::get_singleton()->add_io_error(TTR("Cannot move a folder into itself.") + "\n" + old_path + "\n"); return; } @@ -1077,7 +1049,7 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin print_verbose("Duplicating " + old_path + " -> " + new_path); Error err = p_item.is_file ? da->copy(old_path, new_path) : da->copy_dir(old_path, new_path); if (err == OK) { - //Move/Rename any corresponding import settings too + // Move/Rename any corresponding import settings too. if (p_item.is_file && FileAccess::exists(old_path + ".import")) { err = da->copy(old_path + ".import", new_path + ".import"); if (err != OK) { @@ -1091,13 +1063,11 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin } void FileSystemDock::_update_resource_paths_after_move(const Map<String, String> &p_renames) const { - - //Rename all resources loaded, be it subresources or actual resources + // Rename all resources loaded, be it subresources or actual resources. List<Ref<Resource> > cached; ResourceCache::get_cached_resources(&cached); for (List<Ref<Resource> >::Element *E = cached.front(); E; E = E->next()) { - Ref<Resource> r = E->get(); String base_path = r->get_path(); @@ -1116,7 +1086,6 @@ void FileSystemDock::_update_resource_paths_after_move(const Map<String, String> } for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { - String path; if (i == EditorNode::get_editor_data().get_edited_scene()) { if (!get_tree()->get_edited_scene_root()) @@ -1124,7 +1093,6 @@ void FileSystemDock::_update_resource_paths_after_move(const Map<String, String> path = get_tree()->get_edited_scene_root()->get_filename(); } else { - path = EditorNode::get_editor_data().get_scene_path(i); } @@ -1133,23 +1101,21 @@ void FileSystemDock::_update_resource_paths_after_move(const Map<String, String> } if (i == EditorNode::get_editor_data().get_edited_scene()) { - get_tree()->get_edited_scene_root()->set_filename(path); } else { - EditorNode::get_editor_data().set_scene_path(i, path); } } } void FileSystemDock::_update_dependencies_after_move(const Map<String, String> &p_renames) const { - //The following code assumes that the following holds: + // The following code assumes that the following holds: // 1) EditorFileSystem contains the old paths/folder structure from before the rename/move. // 2) ResourceLoader can use the new paths without needing to call rescan. Vector<String> remaps; _find_remaps(EditorFileSystem::get_singleton()->get_filesystem(), p_renames, remaps); for (int i = 0; i < remaps.size(); ++i) { - //Because we haven't called a rescan yet the found remap might still be an old path itself. + // Because we haven't called a rescan yet the found remap might still be an old path itself. String file = p_renames.has(remaps[i]) ? p_renames[remaps[i]] : remaps[i]; print_verbose("Remapping dependencies for: " + file); Error err = ResourceLoader::rename_dependencies(file, p_renames); @@ -1163,8 +1129,7 @@ void FileSystemDock::_update_dependencies_after_move(const Map<String, String> & } void FileSystemDock::_update_project_settings_after_move(const Map<String, String> &p_renames) const { - - // Find all project settings of type FILE and replace them if needed + // Find all project settings of type FILE and replace them if needed. const Map<StringName, PropertyInfo> prop_info = ProjectSettings::get_singleton()->get_custom_property_info(); for (const Map<StringName, PropertyInfo>::Element *E = prop_info.front(); E; E = E->next()) { if (E->get().hint == PROPERTY_HINT_FILE) { @@ -1195,7 +1160,6 @@ void FileSystemDock::_update_project_settings_after_move(const Map<String, Strin } void FileSystemDock::_update_favorites_list_after_move(const Map<String, String> &p_files_renames, const Map<String, String> &p_folders_renames) const { - Vector<String> favorites = EditorSettings::get_singleton()->get_favorites(); Vector<String> new_favorites; @@ -1311,7 +1275,6 @@ void FileSystemDock::_folder_deleted(String p_folder) { } void FileSystemDock::_rename_operation_confirm() { - String new_name = rename_dialog_text->get_text().strip_edges(); if (new_name.length() == 0) { EditorNode::get_singleton()->show_warning(TTR("No name provided.")); @@ -1331,10 +1294,10 @@ void FileSystemDock::_rename_operation_confirm() { EditorFileSystem::get_singleton()->move_group_file(old_path, new_path); } - //Present a more user friendly warning for name conflict + // Present a more user friendly warning for name conflict. DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES); #if defined(WINDOWS_ENABLED) || defined(UWP_ENABLED) - // Workaround case insensitivity on Windows + // Workaround case insensitivity on Windows. if ((da->file_exists(new_path) || da->dir_exists(new_path)) && new_path.to_lower() != old_path.to_lower()) { #else if (da->file_exists(new_path) || da->dir_exists(new_path)) { @@ -1366,7 +1329,6 @@ void FileSystemDock::_rename_operation_confirm() { } void FileSystemDock::_duplicate_operation_confirm() { - String new_name = duplicate_dialog_text->get_text().strip_edges(); if (new_name.length() == 0) { EditorNode::get_singleton()->show_warning(TTR("No name provided.")); @@ -1384,7 +1346,7 @@ void FileSystemDock::_duplicate_operation_confirm() { String new_path = base_dir.plus_file(new_name); - //Present a more user friendly warning for name conflict + // Present a more user friendly warning for name conflict DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (da->file_exists(new_path) || da->dir_exists(new_path)) { EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); @@ -1395,7 +1357,7 @@ void FileSystemDock::_duplicate_operation_confirm() { _try_duplicate_item(to_duplicate, new_path); - //Rescan everything + // Rescan everything. print_verbose("FileSystem: calling rescan."); _rescan(); } @@ -1428,14 +1390,14 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool overw to_move_path = p_to_path; bool can_move = _check_existing(); if (!can_move) { - //ask to do something + // Ask to do something. overwrite_dialog->popup_centered_minsize(); overwrite_dialog->grab_focus(); return; } } - //check groups + // Check groups. for (int i = 0; i < to_move.size(); i++) { print_line("is group: " + to_move[i].path + ": " + itos(EditorFileSystem::get_singleton()->is_group_file(to_move[i].path))); @@ -1476,7 +1438,7 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool overw } Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion) { - // Build a list of selected items with the active one at the first position + // Build a list of selected items with the active one at the first position. Vector<String> selected_strings; TreeItem *favorites_item = tree->get_root()->get_children(); @@ -1494,8 +1456,15 @@ Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion) { selected = tree->get_next_selected(selected); } - // Remove paths or files that are included into another - if (remove_self_inclusion && selected_strings.size() > 1) { + if (remove_self_inclusion) { + selected_strings = _remove_self_included_paths(selected_strings); + } + return selected_strings; +} + +Vector<String> FileSystemDock::_remove_self_included_paths(Vector<String> selected_strings) { + // Remove paths or files that are included into another. + if (selected_strings.size() > 1) { selected_strings.sort_custom<NaturalNoCaseComparator>(); String last_path = ""; for (int i = 0; i < selected_strings.size(); i++) { @@ -1512,10 +1481,9 @@ Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion) { } void FileSystemDock::_tree_rmb_option(int p_option) { + Vector<String> selected_strings = _tree_get_selected(false); - Vector<String> selected_strings = _tree_get_selected(); - - // Execute the current option + // Execute the current option. switch (p_option) { case FOLDER_EXPAND_ALL: case FOLDER_COLLAPSE_ALL: { @@ -1555,11 +1523,11 @@ void FileSystemDock::_file_list_rmb_option(int p_option) { } void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected) { - // The first one should be the active item + // The first one should be the active item. switch (p_option) { case FILE_SHOW_IN_EXPLORER: { - // Show the file / folder in the OS explorer + // Show the file/folder in the OS explorer. String fpath = path; if (path == "Favorites") { fpath = p_selected[0]; @@ -1573,7 +1541,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_OPEN: { - // Open folders + // Open folders. TreeItem *selected = tree->get_root(); selected = tree->get_next_selected(selected); while (selected) { @@ -1582,21 +1550,21 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } selected = tree->get_next_selected(selected); } - // Open the file + // Open the file. for (int i = 0; i < p_selected.size(); i++) { _select_file(p_selected[i]); } } break; case FILE_INHERIT: { - // Create a new scene inherited from the selected one + // Create a new scene inherited from the selected one. if (p_selected.size() == 1) { emit_signal("inherit", p_selected[0]); } } break; case FILE_INSTANCE: { - // Instance all selected scenes + // Instance all selected scenes. Vector<String> paths; for (int i = 0; i < p_selected.size(); i++) { String fpath = p_selected[i]; @@ -1610,7 +1578,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_ADD_FAVORITE: { - // Add the files from favorites + // Add the files from favorites. Vector<String> favorites = EditorSettings::get_singleton()->get_favorites(); for (int i = 0; i < p_selected.size(); i++) { if (favorites.find(p_selected[i]) == -1) { @@ -1622,7 +1590,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_REMOVE_FAVORITE: { - // Remove the files from favorites + // Remove the files from favorites. Vector<String> favorites = EditorSettings::get_singleton()->get_favorites(); for (int i = 0; i < p_selected.size(); i++) { favorites.erase(p_selected[i]); @@ -1634,7 +1602,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_DEPENDENCIES: { - // Checkout the file dependencies + // Checkout the file dependencies. if (!p_selected.empty()) { String fpath = p_selected[0]; deps_editor->edit(fpath); @@ -1642,7 +1610,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_OWNERS: { - // Checkout the file owners + // Checkout the file owners. if (!p_selected.empty()) { String fpath = p_selected[0]; owners_editor->show(fpath); @@ -1650,10 +1618,11 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_MOVE: { - // Move the files to a given location + // Move the files to a given location. to_move.clear(); - for (int i = 0; i < p_selected.size(); i++) { - String fpath = p_selected[i]; + Vector<String> collapsed_paths = _remove_self_included_paths(p_selected); + for (int i = collapsed_paths.size() - 1; i >= 0; i--) { + String fpath = collapsed_paths[i]; if (fpath != "res://") { to_move.push_back(FileOrFolder(fpath, !fpath.ends_with("/"))); } @@ -1664,7 +1633,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_RENAME: { - // Rename the active file + // Rename the active file. if (!p_selected.empty()) { to_rename.path = p_selected[0]; if (to_rename.path != "res://") { @@ -1687,12 +1656,13 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_REMOVE: { - // Remove the selected files + // Remove the selected files. Vector<String> remove_files; Vector<String> remove_folders; + Vector<String> collapsed_paths = _remove_self_included_paths(p_selected); - for (int i = 0; i < p_selected.size(); i++) { - String fpath = p_selected[i]; + for (int i = 0; i < collapsed_paths.size(); i++) { + String fpath = collapsed_paths[i]; if (fpath != "res://") { if (fpath.ends_with("/")) { remove_folders.push_back(fpath); @@ -1708,7 +1678,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_DUPLICATE: { - // Duplicate the selected files + // Duplicate the selected files. for (int i = 0; i < p_selected.size(); i++) { to_duplicate.path = p_selected[i]; to_duplicate.is_file = !to_duplicate.path.ends_with("/"); @@ -1733,7 +1703,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_REIMPORT: { - // Reimport all selected files + // Reimport all selected files. Vector<String> reimport; for (int i = 0; i < p_selected.size(); i++) { reimport.push_back(p_selected[i]); @@ -1743,7 +1713,6 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_FOLDER: { - // Create a new folder make_dir_dialog_text->set_text("new folder"); make_dir_dialog_text->select_all(); make_dir_dialog->popup_centered_minsize(Size2(250, 80) * EDSCALE); @@ -1758,7 +1727,6 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_SCRIPT: { - // Create a new script String fpath = path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); @@ -1768,7 +1736,6 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_COPY_PATH: { - // Copy the file path if (!p_selected.empty()) { String fpath = p_selected[0]; OS::get_singleton()->set_clipboard(fpath); @@ -1776,7 +1743,6 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_RESOURCE: { - // Create a new resource new_resource_dialog->popup_create(true); } break; } @@ -1804,7 +1770,7 @@ void FileSystemDock::_resource_created() const { void FileSystemDock::_search_changed(const String &p_text, const Control *p_from) { if (searched_string.length() == 0 && p_text.length() > 0) { - // Register the uncollapsed paths before they change + // Register the uncollapsed paths before they change. uncollapsed_paths_before_search = _compute_uncollapsed_paths(); } @@ -1812,7 +1778,7 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from if (p_from == tree_search_box) file_list_search_box->set_text(searched_string); - else // file_list_search_box + else // File_list_search_box. tree_search_box->set_text(searched_string); switch (display_mode) { @@ -1827,7 +1793,6 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from } void FileSystemDock::_rescan() { - _set_scanning_mode(); EditorFileSystem::get_singleton()->scan(); } @@ -1842,12 +1807,10 @@ void FileSystemDock::fix_dependencies(const String &p_for_file) { } void FileSystemDock::focus_on_filter() { - file_list_search_box->grab_focus(); } void FileSystemDock::set_file_list_display_mode(FileListDisplayMode p_mode) { - if (p_mode == file_list_display_mode) return; @@ -1861,12 +1824,12 @@ Variant FileSystemDock::get_drag_data_fw(const Point2 &p_point, Control *p_from) Vector<String> paths; if (p_from == tree) { - // Check if the first selected is in favorite + // Check if the first selected is in favorite. TreeItem *selected = tree->get_next_selected(tree->get_root()); while (selected) { TreeItem *favorites_item = tree->get_root()->get_children(); if (selected == favorites_item) { - // The "Favorites" item is not draggable + // The "Favorites" item is not draggable. return Variant(); } @@ -1904,12 +1867,11 @@ Variant FileSystemDock::get_drag_data_fw(const Point2 &p_point, Control *p_from) } bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { - Dictionary drag_data = p_data; if (drag_data.has("type") && String(drag_data["type"]) == "favorite") { - // Moving favorite around + // Moving favorite around. TreeItem *ti = tree->get_item_at_position(p_point); if (!ti) return false; @@ -1920,20 +1882,20 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da TreeItem *resources_item = favorites_item->get_next(); if (ti == favorites_item) { - return (drop_section == 1); // The parent, first fav + return (drop_section == 1); // The parent, first fav. } if (ti->get_parent() && favorites_item == ti->get_parent()) { return true; // A favorite } if (ti == resources_item) { - return (drop_section == -1); // The tree, last fav + return (drop_section == -1); // The tree, last fav. } return false; } if (drag_data.has("type") && String(drag_data["type"]) == "resource") { - // Move resources + // Move resources. String to_dir; bool favorite; _get_drag_target_folder(to_dir, favorite, p_point, p_from); @@ -1941,7 +1903,7 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da } if (drag_data.has("type") && (String(drag_data["type"]) == "files" || String(drag_data["type"]) == "files_and_dirs")) { - // Move files or dir + // Move files or dir. String to_dir; bool favorite; _get_drag_target_folder(to_dir, favorite, p_point, p_from); @@ -1952,8 +1914,8 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da if (to_dir.empty()) return false; - //Attempting to move a folder into itself will fail later - //Rather than bring up a message don't try to do it in the first place + // Attempting to move a folder into itself will fail later, + // rather than bring up a message don't try to do it in the first place to_dir = to_dir.ends_with("/") ? to_dir : (to_dir + "/"); Vector<String> fnames = drag_data["files"]; for (int i = 0; i < fnames.size(); ++i) { @@ -1968,7 +1930,6 @@ bool FileSystemDock::can_drop_data_fw(const Point2 &p_point, const Variant &p_da } void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { - if (!can_drop_data_fw(p_point, p_data, p_from)) return; Dictionary drag_data = p_data; @@ -1976,7 +1937,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, Vector<String> dirs = EditorSettings::get_singleton()->get_favorites(); if (drag_data.has("type") && String(drag_data["type"]) == "favorite") { - // Moving favorite around + // Moving favorite around. TreeItem *ti = tree->get_item_at_position(p_point); if (!ti) return; @@ -1988,20 +1949,20 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, TreeItem *resources_item = favorites_item->get_next(); if (ti == favorites_item) { - // Drop on the favorite folder + // Drop on the favorite folder. drop_position = 0; } else if (ti == resources_item) { - // Drop on the resource item + // Drop on the resource item. drop_position = dirs.size(); } else { - // Drop in the list + // Drop in the list. drop_position = dirs.find(ti->get_metadata(0)); if (drop_section == 1) { drop_position++; } } - // Remove dragged favorites + // Remove dragged favorites. Vector<int> to_remove; int offset = 0; for (int i = 0; i < files.size(); i++) { @@ -2017,7 +1978,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, dirs.remove(to_remove[i] - i); } - // Re-add them at the right position + // Re-add them at the right position. for (int i = 0; i < files.size(); i++) { dirs.insert(drop_position, files[i]); drop_position++; @@ -2032,7 +1993,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, } if (drag_data.has("type") && String(drag_data["type"]) == "resource") { - // Moving resource + // Moving resource. Ref<Resource> res = drag_data["resource"]; String to_dir; bool favorite; @@ -2044,7 +2005,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, } if (drag_data.has("type") && (String(drag_data["type"]) == "files" || String(drag_data["type"]) == "files_and_dirs")) { - // Move files or add to favorites + // Move files or add to favorites. String to_dir; bool favorite; _get_drag_target_folder(to_dir, favorite, p_point, p_from); @@ -2074,7 +2035,7 @@ void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favori target = String(); target_favorites = false; - // In the file list + // In the file list. if (p_from == files) { int pos = files->get_item_at_position(p_point, true); if (pos == -1) { @@ -2086,12 +2047,12 @@ void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favori return; } - // In the tree + // In the tree. if (p_from == tree) { TreeItem *ti = tree->get_item_at_position(p_point); int section = tree->get_drop_section_at_position(p_point); if (ti) { - // Check the favorites first + // Check the favorites first. if (ti == tree->get_root()->get_children() && section >= 0) { target_favorites = true; return; @@ -2102,13 +2063,13 @@ void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favori String fpath = ti->get_metadata(0); if (section == 0) { if (fpath.ends_with("/")) { - // We drop on a folder + // We drop on a folder. target = fpath; return; } } else { if (ti->get_parent() != tree->get_root()->get_children()) { - // Not in the favorite section + // Not in the favorite section. if (fpath != "res://") { // We drop between two files if (fpath.ends_with("/")) { @@ -2125,7 +2086,7 @@ void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favori } void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<String> p_paths, bool p_display_path_dependent_options) { - // Add options for files and folders + // Add options for files and folders. ERR_FAIL_COND(p_paths.empty()); Vector<String> filenames; @@ -2149,7 +2110,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str all_files_scenes &= (EditorFileSystem::get_singleton()->get_file_type(fpath) == "PackedScene"); } - // Check if in favorites + // Check if in favorites. bool found = false; for (int j = 0; j < favorites.size(); j++) { if (favorites[j] == fpath) { @@ -2165,7 +2126,6 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str } if (all_files) { - if (all_files_scenes) { if (filenames.size() == 1) { p_popup->add_item(TTR("Open Scene"), FILE_OPEN); @@ -2205,12 +2165,16 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str if (p_paths.size() == 1) { p_popup->add_item(TTR("Copy Path"), FILE_COPY_PATH); - p_popup->add_item(TTR("Rename..."), FILE_RENAME); - p_popup->add_item(TTR("Duplicate..."), FILE_DUPLICATE); + if (p_paths[0] != "res://") { + p_popup->add_item(TTR("Rename..."), FILE_RENAME); + p_popup->add_item(TTR("Duplicate..."), FILE_DUPLICATE); + } } - p_popup->add_item(TTR("Move To..."), FILE_MOVE); - p_popup->add_item(TTR("Delete"), FILE_REMOVE); + if (p_paths.size() > 1 || p_paths[0] != "res://") { + p_popup->add_item(TTR("Move To..."), FILE_MOVE); + p_popup->add_item(TTR("Delete"), FILE_REMOVE); + } if (p_paths.size() == 1) { p_popup->add_separator(); @@ -2219,6 +2183,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str p_popup->add_item(TTR("New Scene..."), FILE_NEW_SCENE); p_popup->add_item(TTR("New Script..."), FILE_NEW_SCRIPT); p_popup->add_item(TTR("New Resource..."), FILE_NEW_RESOURCE); + p_popup->add_separator(); } String fpath = p_paths[0]; @@ -2228,8 +2193,8 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str } void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos) { - // Right click is pressed in the tree - Vector<String> paths = _tree_get_selected(); + // Right click is pressed in the tree. + Vector<String> paths = _tree_get_selected(false); if (paths.size() == 1) { if (paths[0].ends_with("/")) { @@ -2239,7 +2204,7 @@ void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos) { } } - // Popup + // Popup. if (!paths.empty()) { tree_popup->clear(); tree_popup->set_size(Size2(1, 1)); @@ -2250,7 +2215,7 @@ void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos) { } void FileSystemDock::_tree_rmb_empty(const Vector2 &p_pos) { - // Right click is pressed in the empty space of the tree + // Right click is pressed in the empty space of the tree. path = "res://"; tree_popup->clear(); tree_popup->set_size(Size2(1, 1)); @@ -2267,7 +2232,7 @@ void FileSystemDock::_tree_empty_selected() { } void FileSystemDock::_file_list_rmb_select(int p_item, const Vector2 &p_pos) { - // Right click is pressed in the file list + // Right click is pressed in the file list. Vector<String> paths; for (int i = 0; i < files->get_item_count(); i++) { if (!files->is_selected(i)) @@ -2279,7 +2244,7 @@ void FileSystemDock::_file_list_rmb_select(int p_item, const Vector2 &p_pos) { paths.push_back(files->get_item_metadata(i)); } - // Popup + // Popup. if (!paths.empty()) { file_list_popup->clear(); file_list_popup->set_size(Size2(1, 1)); @@ -2290,7 +2255,7 @@ void FileSystemDock::_file_list_rmb_select(int p_item, const Vector2 &p_pos) { } void FileSystemDock::_file_list_rmb_pressed(const Vector2 &p_pos) { - // Right click on empty space for file list + // Right click on empty space for file list. if (searched_string.length() > 0) return; @@ -2301,19 +2266,18 @@ void FileSystemDock::_file_list_rmb_pressed(const Vector2 &p_pos) { file_list_popup->add_item(TTR("New Scene..."), FILE_NEW_SCENE); file_list_popup->add_item(TTR("New Script..."), FILE_NEW_SCRIPT); file_list_popup->add_item(TTR("New Resource..."), FILE_NEW_RESOURCE); - file_list_popup->add_item(TTR("Show in File Manager"), FILE_SHOW_IN_EXPLORER); + file_list_popup->add_separator(); + file_list_popup->add_item(TTR("Open in File Manager"), FILE_SHOW_IN_EXPLORER); file_list_popup->set_position(files->get_global_position() + p_pos); file_list_popup->popup(); } void FileSystemDock::select_file(const String &p_file) { - _navigate_to_path(p_file); } void FileSystemDock::_file_multi_selected(int p_index, bool p_selected) { - - // Set the path to the current focused item + // Set the path to the current focused item. int current = files->get_current(); if (current == p_index) { String fpath = files->get_item_metadata(current); @@ -2325,15 +2289,14 @@ void FileSystemDock::_file_multi_selected(int p_index, bool p_selected) { } } - // Update the import dock + // Update the import dock. import_dock_needs_update = true; call_deferred("_update_import_dock"); } void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { - if (get_viewport()->get_modal_stack_top()) - return; //ignore because of modal window + return; // Ignore because of modal window. Ref<InputEventKey> key = p_event; if (key.is_valid() && key->is_pressed() && !key->is_echo()) { @@ -2350,9 +2313,8 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { } void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { - if (get_viewport()->get_modal_stack_top()) - return; //ignore because of modal window + return; // Ignore because of modal window. Ref<InputEventKey> key = p_event; if (key.is_valid() && key->is_pressed() && !key->is_echo()) { @@ -2369,18 +2331,17 @@ void FileSystemDock::_file_list_gui_input(Ref<InputEvent> p_event) { } void FileSystemDock::_update_import_dock() { - if (!import_dock_needs_update) return; - // List selected + // List selected. Vector<String> selected; if (display_mode == DISPLAY_MODE_TREE_ONLY) { // Use the tree selected = _tree_get_selected(); } else { - // Use the file list + // Use the file list. for (int i = 0; i < files->get_item_count(); i++) { if (!files->is_selected(i)) continue; @@ -2389,7 +2350,7 @@ void FileSystemDock::_update_import_dock() { } } - // Check import + // Check import. Vector<String> imports; String import_type; for (int i = 0; i < selected.size(); i++) { @@ -2416,7 +2377,7 @@ void FileSystemDock::_update_import_dock() { if (import_type == "") { import_type = type; } else if (import_type != type) { - //all should be the same type + // All should be the same type. imports.clear(); break; } @@ -2435,12 +2396,10 @@ void FileSystemDock::_update_import_dock() { } void FileSystemDock::_feature_profile_changed() { - _update_display_mode(true); } void FileSystemDock::_bind_methods() { - ClassDB::bind_method(D_METHOD("_file_list_gui_input"), &FileSystemDock::_file_list_gui_input); ClassDB::bind_method(D_METHOD("_tree_gui_input"), &FileSystemDock::_tree_gui_input); @@ -2505,7 +2464,6 @@ void FileSystemDock::_bind_methods() { } FileSystemDock::FileSystemDock(EditorNode *p_editor) { - set_name("FileSystem"); editor = p_editor; path = "res://"; @@ -2586,7 +2544,6 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { tree->set_custom_minimum_size(Size2(0, 15 * EDSCALE)); split_box->add_child(tree); - tree->connect("item_edited", this, "_favorite_toggled"); tree->connect("item_activated", this, "_tree_activate_file"); tree->connect("multi_selected", this, "_tree_multi_selected"); tree->connect("item_rmb_selected", this, "_tree_rmb_select"); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index cc5ba94b48..49eb31e330 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -171,7 +171,7 @@ private: bool updating_tree; int tree_update_id; - Tree *tree; //directories + Tree *tree; ItemList *files; bool import_dock_needs_update; @@ -250,7 +250,6 @@ private: String name; String path; StringName type; - int import_status; //0 not imported, 1 - ok, 2- must reimport, 3- broken Vector<String> sources; bool import_broken; @@ -279,6 +278,7 @@ private: bool _is_file_type_disabled_by_feature_profile(const StringName &p_class); void _feature_profile_changed(); + Vector<String> _remove_self_included_paths(Vector<String> selected_strings); protected: void _notification(int p_what); diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index 84dd1aa866..9ea7c86e0c 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -29,8 +29,8 @@ /*************************************************************************/ #include "editor_scene_importer_gltf.h" +#include "core/crypto/crypto_core.h" #include "core/io/json.h" -#include "core/math/crypto_core.h" #include "core/math/math_defs.h" #include "core/os/file_access.h" #include "core/os/os.h" diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index cc707dbf44..785a1c107a 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -174,15 +174,6 @@ public: } }; -void CanvasItemEditor::_snap_if_closer_float(float p_value, float p_target_snap, float &r_current_snap, bool &r_snapped, float p_radius) { - float radius = p_radius / zoom; - float dist = Math::abs(p_value - p_target_snap); - if ((p_radius < 0 || dist < radius) && (!r_snapped || dist < Math::abs(r_current_snap - p_value))) { - r_current_snap = p_target_snap; - r_snapped = true; - } -} - bool CanvasItemEditor::_is_node_locked(const Node *p_node) { return p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); } @@ -200,108 +191,174 @@ bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning return true; } -void CanvasItemEditor::_snap_if_closer_point(Point2 p_value, Point2 p_target_snap, Point2 &r_current_snap, bool (&r_snapped)[2], real_t rotation, float p_radius) { +void CanvasItemEditor::_snap_if_closer_float( + float p_value, + float &r_current_snap, SnapTarget &r_current_snap_target, + float p_target_value, SnapTarget p_snap_target, + float p_radius) { + + float radius = p_radius / zoom; + float dist = Math::abs(p_value - p_target_value); + if ((p_radius < 0 || dist < radius) && (r_current_snap_target == SNAP_TARGET_NONE || dist < Math::abs(r_current_snap - p_value))) { + r_current_snap = p_target_value; + r_current_snap_target = p_snap_target; + } +} + +void CanvasItemEditor::_snap_if_closer_point( + Point2 p_value, + Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2], + Point2 p_target_value, SnapTarget p_snap_target, + real_t rotation, + float p_radius) { + Transform2D rot_trans = Transform2D(rotation, Point2()); p_value = rot_trans.inverse().xform(p_value); - p_target_snap = rot_trans.inverse().xform(p_target_snap); + p_target_value = rot_trans.inverse().xform(p_target_value); r_current_snap = rot_trans.inverse().xform(r_current_snap); - _snap_if_closer_float(p_value.x, p_target_snap.x, r_current_snap.x, r_snapped[0], p_radius); - _snap_if_closer_float(p_value.y, p_target_snap.y, r_current_snap.y, r_snapped[1], p_radius); + _snap_if_closer_float( + p_value.x, + r_current_snap.x, + r_current_snap_target[0], + p_target_value.x, + p_snap_target, + p_radius); + + _snap_if_closer_float( + p_value.y, + r_current_snap.y, + r_current_snap_target[1], + p_target_value.y, + p_snap_target, + p_radius); r_current_snap = rot_trans.xform(r_current_snap); } -void CanvasItemEditor::_snap_other_nodes(Point2 p_value, Point2 &r_current_snap, bool (&r_snapped)[2], const Node *p_current, const CanvasItem *p_to_snap) { +void CanvasItemEditor::_snap_other_nodes( + const Point2 p_value, + const Transform2D p_transform_to_snap, + Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2], + const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions, + const Node *p_current) { const CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_current); - if (canvas_item && (!p_to_snap || p_current != p_to_snap)) { + + // Check if the element is in the exception + bool exception = false; + for (List<const CanvasItem *>::Element *E = p_exceptions.front(); E; E = E->next()) { + if (E->get() == p_current) { + exception = true; + break; + } + }; + + if (canvas_item && !exception) { Transform2D ci_transform = canvas_item->get_global_transform_with_canvas(); - Transform2D to_snap_transform = p_to_snap ? p_to_snap->get_global_transform_with_canvas() : Transform2D(); - if (fmod(ci_transform.get_rotation() - to_snap_transform.get_rotation(), (real_t)360.0) == 0.0) { + if (fmod(ci_transform.get_rotation() - p_transform_to_snap.get_rotation(), (real_t)360.0) == 0.0) { if (canvas_item->_edit_use_rect()) { Point2 begin = ci_transform.xform(canvas_item->_edit_get_rect().get_position()); Point2 end = ci_transform.xform(canvas_item->_edit_get_rect().get_position() + canvas_item->_edit_get_rect().get_size()); - _snap_if_closer_point(p_value, begin, r_current_snap, r_snapped, ci_transform.get_rotation()); - _snap_if_closer_point(p_value, end, r_current_snap, r_snapped, ci_transform.get_rotation()); + + _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, begin, p_snap_target, ci_transform.get_rotation()); + _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, end, p_snap_target, ci_transform.get_rotation()); } else { Point2 position = ci_transform.xform(Point2()); - _snap_if_closer_point(p_value, position, r_current_snap, r_snapped, ci_transform.get_rotation()); + _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, position, p_snap_target, ci_transform.get_rotation()); } } } for (int i = 0; i < p_current->get_child_count(); i++) { - _snap_other_nodes(p_value, r_current_snap, r_snapped, p_current->get_child(i), p_to_snap); + _snap_other_nodes(p_value, p_transform_to_snap, r_current_snap, r_current_snap_target, p_snap_target, p_exceptions, p_current->get_child(i)); } } -Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, const CanvasItem *p_canvas_item, unsigned int p_forced_modes) { - bool snapped[2] = { false, false }; +Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsigned int p_forced_modes, const CanvasItem *p_self_canvas_item, List<CanvasItem *> p_other_nodes_exceptions) { + + snap_target[0] = SNAP_TARGET_NONE; + snap_target[1] = SNAP_TARGET_NONE; + bool is_snap_active = snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL); // Smart snap using the canvas position Vector2 output = p_target; real_t rotation = 0.0; - if (p_canvas_item) { - rotation = p_canvas_item->get_global_transform_with_canvas().get_rotation(); + if (p_self_canvas_item) { + rotation = p_self_canvas_item->get_global_transform_with_canvas().get_rotation(); // Parent sides and center if ((is_snap_active && snap_node_parent && (p_modes & SNAP_NODE_PARENT)) || (p_forced_modes & SNAP_NODE_PARENT)) { - if (const Control *c = Object::cast_to<Control>(p_canvas_item)) { - Point2 begin = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(0, 0))); - Point2 end = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(1, 1))); - _snap_if_closer_point(p_target, begin, output, snapped, rotation); - _snap_if_closer_point(p_target, (begin + end) / 2.0, output, snapped, rotation); - _snap_if_closer_point(p_target, end, output, snapped, rotation); - } else if (const CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_canvas_item->get_parent())) { + if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) { + Point2 begin = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(0, 0))); + Point2 end = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(1, 1))); + _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation); + _snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation); + _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation); + } else if (const CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_self_canvas_item->get_parent())) { if (parent_ci->_edit_use_rect()) { - Point2 begin = p_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position()); - Point2 end = p_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position() + parent_ci->_edit_get_rect().get_size()); - _snap_if_closer_point(p_target, begin, output, snapped, rotation); - _snap_if_closer_point(p_target, (begin + end) / 2.0, output, snapped, rotation); - _snap_if_closer_point(p_target, end, output, snapped, rotation); + Point2 begin = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position()); + Point2 end = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position() + parent_ci->_edit_get_rect().get_size()); + _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation); + _snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation); + _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation); } else { - Point2 position = p_canvas_item->get_transform().affine_inverse().xform(Point2()); - _snap_if_closer_point(p_target, position, output, snapped, rotation); + Point2 position = p_self_canvas_item->get_transform().affine_inverse().xform(Point2()); + _snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_PARENT, rotation); } } } // Self anchors if ((is_snap_active && snap_node_anchors && (p_modes & SNAP_NODE_ANCHORS)) || (p_forced_modes & SNAP_NODE_ANCHORS)) { - if (const Control *c = Object::cast_to<Control>(p_canvas_item)) { - Point2 begin = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(c->get_anchor(MARGIN_LEFT), c->get_anchor(MARGIN_TOP)))); - Point2 end = p_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(c->get_anchor(MARGIN_RIGHT), c->get_anchor(MARGIN_BOTTOM)))); - _snap_if_closer_point(p_target, begin, output, snapped, rotation); - _snap_if_closer_point(p_target, end, output, snapped, rotation); + if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) { + Point2 begin = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(c->get_anchor(MARGIN_LEFT), c->get_anchor(MARGIN_TOP)))); + Point2 end = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(c->get_anchor(MARGIN_RIGHT), c->get_anchor(MARGIN_BOTTOM)))); + _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF_ANCHORS, rotation); + _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF_ANCHORS, rotation); } } // Self sides if ((is_snap_active && snap_node_sides && (p_modes & SNAP_NODE_SIDES)) || (p_forced_modes & SNAP_NODE_SIDES)) { - if (p_canvas_item->_edit_use_rect()) { - Point2 begin = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position()); - Point2 end = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position() + p_canvas_item->_edit_get_rect().get_size()); - _snap_if_closer_point(p_target, begin, output, snapped, rotation); - _snap_if_closer_point(p_target, end, output, snapped, rotation); + if (p_self_canvas_item->_edit_use_rect()) { + Point2 begin = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_position()); + Point2 end = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_position() + p_self_canvas_item->_edit_get_rect().get_size()); + _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF, rotation); + _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF, rotation); } } // Self center if ((is_snap_active && snap_node_center && (p_modes & SNAP_NODE_CENTER)) || (p_forced_modes & SNAP_NODE_CENTER)) { - if (p_canvas_item->_edit_use_rect()) { - Point2 center = p_canvas_item->get_global_transform_with_canvas().xform(p_canvas_item->_edit_get_rect().get_position() + p_canvas_item->_edit_get_rect().get_size() / 2.0); - _snap_if_closer_point(p_target, center, output, snapped, rotation); + if (p_self_canvas_item->_edit_use_rect()) { + Point2 center = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_position() + p_self_canvas_item->_edit_get_rect().get_size() / 2.0); + _snap_if_closer_point(p_target, output, snap_target, center, SNAP_TARGET_SELF, rotation); } else { - Point2 position = p_canvas_item->get_global_transform_with_canvas().xform(Point2()); - _snap_if_closer_point(p_target, position, output, snapped, rotation); + Point2 position = p_self_canvas_item->get_global_transform_with_canvas().xform(Point2()); + _snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_SELF, rotation); } } } // Other nodes sides if ((is_snap_active && snap_other_nodes && (p_modes & SNAP_OTHER_NODES)) || (p_forced_modes & SNAP_OTHER_NODES)) { - _snap_other_nodes(p_target, output, snapped, get_tree()->get_edited_scene_root(), p_canvas_item); + Transform2D to_snap_transform = Transform2D(); + List<const CanvasItem *> exceptions = List<const CanvasItem *>(); + for (List<CanvasItem *>::Element *E = p_other_nodes_exceptions.front(); E; E = E->next()) { + exceptions.push_back(E->get()); + } + if (p_self_canvas_item) { + exceptions.push_back(p_self_canvas_item); + to_snap_transform = p_self_canvas_item->get_global_transform_with_canvas(); + } + + _snap_other_nodes( + p_target, to_snap_transform, + output, snap_target, + SNAP_TARGET_OTHER_NODE, + exceptions, + get_tree()->get_edited_scene_root()); } if (((is_snap_active && snap_guides && (p_modes & SNAP_GUIDES)) || (p_forced_modes & SNAP_GUIDES)) && fmod(rotation, (real_t)360.0) == 0.0) { @@ -309,14 +366,14 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, const if (EditorNode::get_singleton()->get_edited_scene() && EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_vertical_guides_")) { Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_"); for (int i = 0; i < vguides.size(); i++) { - _snap_if_closer_float(p_target.x, vguides[i], output.x, snapped[0]); + _snap_if_closer_float(p_target.x, output.x, snap_target[0], vguides[i], SNAP_TARGET_GUIDE); } } if (EditorNode::get_singleton()->get_edited_scene() && EditorNode::get_singleton()->get_edited_scene()->has_meta("_edit_horizontal_guides_")) { Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_"); for (int i = 0; i < hguides.size(); i++) { - _snap_if_closer_float(p_target.y, hguides[i], output.y, snapped[1]); + _snap_if_closer_float(p_target.y, output.y, snap_target[1], hguides[i], SNAP_TARGET_GUIDE); } } } @@ -335,7 +392,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, const Point2 grid_output; grid_output.x = Math::stepify(p_target.x - offset.x, grid_step.x * Math::pow(2.0, grid_step_multiplier)) + offset.x; grid_output.y = Math::stepify(p_target.y - offset.y, grid_step.y * Math::pow(2.0, grid_step_multiplier)) + offset.y; - _snap_if_closer_point(p_target, grid_output, output, snapped, 0.0, -1.0); + _snap_if_closer_point(p_target, output, snap_target, grid_output, SNAP_TARGET_GRID, 0.0, -1.0); } if (((snap_pixel && (p_modes & SNAP_PIXEL)) || (p_forced_modes & SNAP_PIXEL)) && rotation == 0.0) { @@ -343,6 +400,8 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, const output = output.snapped(Size2(1, 1)); } + snap_transform = Transform2D(rotation, output); + return output; } @@ -1205,10 +1264,11 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { if (drag_selection.size() > 0) { drag_from = transform.affine_inverse().xform((b.is_valid()) ? b->get_position() : viewport->get_local_mouse_position()); Vector2 new_pos; - if (drag_selection.size() == 1) - new_pos = snap_point(drag_from, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, drag_selection[0]); - else - new_pos = snap_point(drag_from, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL); + if (drag_selection.size() == 1) { + new_pos = snap_point(drag_from, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, drag_selection[0]); + } else { + new_pos = snap_point(drag_from, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, NULL, drag_selection); + } for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); canvas_item->_edit_set_pivot(canvas_item->get_global_transform_with_canvas().affine_inverse().xform(new_pos)); @@ -1228,7 +1288,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { _restore_canvas_item_state(drag_selection); Vector2 new_pos; if (drag_selection.size() == 1) - new_pos = snap_point(drag_to, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, drag_selection[0]); + new_pos = snap_point(drag_to, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, drag_selection[0]); else new_pos = snap_point(drag_to, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL); for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { @@ -1478,7 +1538,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { previous_anchor.y = (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_TOP_RIGHT) ? control->get_anchor(MARGIN_TOP) : control->get_anchor(MARGIN_BOTTOM); previous_anchor = xform.affine_inverse().xform(_anchor_to_position(control, previous_anchor)); - Vector2 new_anchor = xform.xform(snap_point(previous_anchor + (drag_to - drag_from), SNAP_GRID | SNAP_OTHER_NODES, control, SNAP_NODE_PARENT | SNAP_NODE_SIDES | SNAP_NODE_CENTER)); + Vector2 new_anchor = xform.xform(snap_point(previous_anchor + (drag_to - drag_from), SNAP_GRID | SNAP_OTHER_NODES, SNAP_NODE_PARENT | SNAP_NODE_SIDES | SNAP_NODE_CENTER, control)); new_anchor = _position_to_anchor(control, new_anchor).snapped(Vector2(0.001, 0.001)); bool use_single_axis = m->get_shift(); @@ -1624,8 +1684,8 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse(); - Point2 drag_to_snapped_begin = snap_point(xform.affine_inverse().xform(current_begin) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, canvas_item); - Point2 drag_to_snapped_end = snap_point(xform.affine_inverse().xform(current_end) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, canvas_item); + Point2 drag_to_snapped_begin = snap_point(xform.affine_inverse().xform(current_begin) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, canvas_item); + Point2 drag_to_snapped_end = snap_point(xform.affine_inverse().xform(current_end) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, canvas_item); Point2 drag_begin = xform.xform(drag_to_snapped_begin); Point2 drag_end = xform.xform(drag_to_snapped_end); @@ -1866,7 +1926,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } else { previous_pos = _get_encompassing_rect_from_list(drag_selection).position; } - Point2 new_pos = snap_point(previous_pos + (drag_to - drag_from), SNAP_GRID | SNAP_GUIDES | SNAP_PIXEL | SNAP_NODE_PARENT | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES); + Point2 new_pos = snap_point(previous_pos + (drag_to - drag_from), SNAP_GRID | SNAP_GUIDES | SNAP_PIXEL | SNAP_NODE_PARENT | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES, 0, NULL, drag_selection); bool single_axis = m->get_shift(); if (single_axis) { if (ABS(new_pos.x - previous_pos.x) > ABS(new_pos.y - previous_pos.y)) { @@ -2421,6 +2481,20 @@ void CanvasItemEditor::_draw_guides() { } } +void CanvasItemEditor::_draw_smart_snapping() { + Color line_color = EditorSettings::get_singleton()->get("editors/2d/smart_snapping_line_color"); + if (snap_target[0] != SNAP_TARGET_NONE && snap_target[0] != SNAP_TARGET_GRID) { + viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform); + viewport->draw_line(Point2(0, -1.0e+10F), Point2(0, 1.0e+10F), line_color); + viewport->draw_set_transform_matrix(viewport->get_transform()); + } + if (snap_target[1] != SNAP_TARGET_NONE && snap_target[1] != SNAP_TARGET_GRID) { + viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform); + viewport->draw_line(Point2(-1.0e+10F, 0), Point2(1.0e+10F, 0), line_color); + viewport->draw_set_transform_matrix(viewport->get_transform()); + } +} + void CanvasItemEditor::_draw_rulers() { Color bg_color = get_color("dark_color_2", "Editor"); Color graduation_color = get_color("font_color", "Editor").linear_interpolate(bg_color, 0.5); @@ -2505,6 +2579,8 @@ void CanvasItemEditor::_draw_rulers() { } } } + + // Draw the top left corner viewport->draw_rect(Rect2(Point2(), Size2(RULER_WIDTH, RULER_WIDTH)), graduation_color); } @@ -2623,7 +2699,7 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) { if (dragged_anchor >= 0) { // Draw the 4 lines when dragged - bool snapped; + bool anchor_snapped; Color color_snapped = Color(0.64, 0.93, 0.67, 0.5); Vector2 corners_pos[4]; @@ -2637,14 +2713,8 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) { float anchor_val = (i >= 2) ? ANCHOR_END - anchors_values[i] : anchors_values[i]; line_starts[i] = Vector2::linear_interpolate(corners_pos[i], corners_pos[(i + 1) % 4], anchor_val); line_ends[i] = Vector2::linear_interpolate(corners_pos[(i + 3) % 4], corners_pos[(i + 2) % 4], anchor_val); - snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0; - int line_width; - if (i == dragged_anchor || (i + 3) % 4 == dragged_anchor) { - line_width = 2; - } else { - line_width = 1; - } - viewport->draw_line(line_starts[i], line_ends[i], snapped ? color_snapped : color_base, Math::round(line_width * EDSCALE)); + anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0; + viewport->draw_line(line_starts[i], line_ends[i], anchor_snapped ? color_snapped : color_base, (i == dragged_anchor || (i + 3) % 4 == dragged_anchor) ? 2 : 1); } // Display the percentages next to the lines @@ -3312,6 +3382,7 @@ void CanvasItemEditor::_draw_viewport() { _draw_rulers(); if (show_guides) _draw_guides(); + _draw_smart_snapping(); _draw_focus(); _draw_hover(); } @@ -3321,6 +3392,10 @@ void CanvasItemEditor::update_viewport() { viewport->update(); } +void CanvasItemEditor::set_current_tool(Tool p_tool) { + _button_tool_select(p_tool); +} + void CanvasItemEditor::_notification(int p_what) { if (p_what == NOTIFICATION_PHYSICS_PROCESS) { diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index e6eab57810..a16d07599a 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -86,6 +86,17 @@ public: private: EditorNode *editor; + enum SnapTarget { + SNAP_TARGET_NONE = 0, + SNAP_TARGET_PARENT, + SNAP_TARGET_SELF_ANCHORS, + SNAP_TARGET_SELF, + SNAP_TARGET_OTHER_NODE, + SNAP_TARGET_GUIDE, + SNAP_TARGET_GRID, + SNAP_TARGET_PIXEL + }; + enum MenuOption { SNAP_USE, SNAP_USE_NODE_PARENT, @@ -440,6 +451,7 @@ private: void _draw_percentage_at_position(float p_value, Point2 p_position, Margin p_side); void _draw_straight_line(Point2 p_from, Point2 p_to, Color p_color); + void _draw_smart_snapping(); void _draw_rulers(); void _draw_guides(); void _draw_focus(); @@ -475,9 +487,25 @@ private: void _solve_IK(Node2D *leaf_node, Point2 target_position); - void _snap_if_closer_float(float p_value, float p_target_snap, float &r_current_snap, bool &r_snapped, float p_radius = 10.0); - void _snap_if_closer_point(Point2 p_value, Point2 p_target_snap, Point2 &r_current_snap, bool (&r_snapped)[2], real_t rotation = 0.0, float p_radius = 10.0); - void _snap_other_nodes(Point2 p_value, Point2 &r_current_snap, bool (&r_snapped)[2], const Node *p_current, const CanvasItem *p_to_snap = NULL); + SnapTarget snap_target[2]; + Transform2D snap_transform; + void _snap_if_closer_float( + float p_value, + float &r_current_snap, SnapTarget &r_current_snap_target, + float p_target_value, SnapTarget p_snap_target, + float p_radius = 10.0); + void _snap_if_closer_point( + Point2 p_value, + Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2], + Point2 p_target_value, SnapTarget p_snap_target, + real_t rotation = 0.0, + float p_radius = 10.0); + void _snap_other_nodes( + const Point2 p_value, + const Transform2D p_transform_to_snap, + Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2], + const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions, + const Node *p_current); void _set_anchors_preset(Control::LayoutPreset p_preset); void _set_margins_preset(Control::LayoutPreset p_preset); @@ -558,7 +586,7 @@ public: SNAP_DEFAULT = SNAP_GRID | SNAP_GUIDES | SNAP_PIXEL, }; - Point2 snap_point(Point2 p_target, unsigned int p_modes = SNAP_DEFAULT, const CanvasItem *p_canvas_item = NULL, unsigned int p_forced_modes = 0); + Point2 snap_point(Point2 p_target, unsigned int p_modes = SNAP_DEFAULT, unsigned int p_forced_modes = 0, const CanvasItem *p_self_canvas_item = NULL, List<CanvasItem *> p_other_nodes_exceptions = List<CanvasItem *>()); float snap_angle(float p_target, float p_start = 0) const; Transform2D get_canvas_transform() const { return transform; } @@ -581,6 +609,7 @@ public: void update_viewport(); Tool get_current_tool() { return tool; } + void set_current_tool(Tool p_tool); void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } void edit(CanvasItem *p_canvas_item); diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index b4cce9745e..26fae96025 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -972,7 +972,7 @@ static inline Vector<Point2i> line(int x0, int x1, int y0, int y1) { bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { - if (!node || !node->get_tileset().is_valid() || !node->is_visible_in_tree()) + if (!node || !node->get_tileset().is_valid() || !node->is_visible_in_tree() || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) return false; Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform(); @@ -1533,7 +1533,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { - if (!node) + if (!node || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) return; Transform2D cell_xf = node->get_cell_transform(); @@ -2136,6 +2136,7 @@ void TileMapEditorPlugin::make_visible(bool p_visible) { tile_map_editor->show(); tile_map_editor->get_toolbar()->show(); tile_map_editor->get_toolbar_right()->show(); + CanvasItemEditor::get_singleton()->set_current_tool(CanvasItemEditor::TOOL_SELECT); //Change to TOOL_SELECT when TileMap node is selected, to prevent accidental movement. } else { tile_map_editor->hide(); diff --git a/editor/project_export.cpp b/editor/project_export.cpp index c78a81dbe0..956da92c35 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -931,17 +931,8 @@ void ProjectExportDialog::_export_project() { export_project->add_filter("*." + extension_list[i] + " ; " + platform->get_name() + " Export"); } - String current_preset_export_path = current->get_export_path(); - - if (current_preset_export_path != "") { - - if (!DirAccess::exists(current_preset_export_path.get_base_dir())) { - - DirAccessRef da(DirAccess::create(DirAccess::ACCESS_FILESYSTEM)); - da->make_dir_recursive(current_preset_export_path.get_base_dir()); - } - - export_project->set_current_path(current_preset_export_path); + if (current->get_export_path() != "") { + export_project->set_current_path(current->get_export_path()); } else { if (extension_list.size() >= 1) { export_project->set_current_file(default_filename + "." + extension_list[0]); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index a418773131..5709bdc3fa 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -195,7 +195,7 @@ private: unzFile pkg = unzOpen2(valid_path.utf8().get_data(), &io); if (!pkg) { - set_message(TTR("Error opening package file, not in zip format."), MESSAGE_ERROR); + set_message(TTR("Error opening package file, not in ZIP format."), MESSAGE_ERROR); memdelete(d); get_ok()->set_disabled(true); unzClose(pkg); @@ -519,7 +519,7 @@ private: unzFile pkg = unzOpen2(zip_path.utf8().get_data(), &io); if (!pkg) { - dialog_error->set_text(TTR("Error opening package file, not in zip format.")); + dialog_error->set_text(TTR("Error opening package file, not in ZIP format.")); dialog_error->popup_centered_minsize(); return; } @@ -1255,19 +1255,22 @@ void ProjectList::create_project_item_control(int p_index) { TextureRect *tf = memnew(TextureRect); tf->set_texture(get_icon("DefaultProjectIcon", "EditorIcons")); + if (item.missing) { + tf->set_modulate(Color(1, 1, 1, 0.5)); + } hb->add_child(tf); hb->icon = tf; VBoxContainer *vb = memnew(VBoxContainer); if (item.grayed) - vb->set_modulate(Color(0.5, 0.5, 0.5)); + vb->set_modulate(Color(1, 1, 1, 0.5)); vb->set_h_size_flags(SIZE_EXPAND_FILL); hb->add_child(vb); Control *ec = memnew(Control); ec->set_custom_minimum_size(Size2(0, 1)); ec->set_mouse_filter(MOUSE_FILTER_PASS); vb->add_child(ec); - Label *title = memnew(Label(item.project_name)); + Label *title = memnew(Label(!item.missing ? item.project_name : TTR("Missing Project"))); title->add_font_override("font", get_font("title", "EditorFonts")); title->add_color_override("font_color", font_color); title->set_clip_text(true); @@ -1278,12 +1281,21 @@ void ProjectList::create_project_item_control(int p_index) { vb->add_child(path_hb); Button *show = memnew(Button); - show->set_icon(get_icon("Load", "EditorIcons")); // Folder icon + // Display a folder icon if the project directory can be opened, or a "broken file" icon if it can't + show->set_icon(get_icon(!item.missing ? "Load" : "FileBroken", "EditorIcons")); show->set_flat(true); - show->set_modulate(Color(1, 1, 1, 0.5)); + if (!item.grayed) { + // Don't make the icon less prominent if the parent is already grayed out + show->set_modulate(Color(1, 1, 1, 0.5)); + } path_hb->add_child(show); - show->connect("pressed", this, "_show_project", varray(item.path)); - show->set_tooltip(TTR("Show in File Manager")); + + if (!item.missing) { + show->connect("pressed", this, "_show_project", varray(item.path)); + show->set_tooltip(TTR("Show in File Manager")); + } else { + show->set_tooltip(TTR("Error: Project is missing on the filesystem.")); + } Label *fpath = memnew(Label(item.path)); path_hb->add_child(fpath); @@ -1737,10 +1749,18 @@ void ProjectManager::_update_project_buttons() { Vector<ProjectList::Item> selected_projects = _project_list->get_selected_projects(); bool empty_selection = selected_projects.empty(); + bool is_missing_project_selected = false; + for (int i = 0; i < selected_projects.size(); ++i) { + if (selected_projects[i].missing) { + is_missing_project_selected = true; + break; + } + } + erase_btn->set_disabled(empty_selection); - open_btn->set_disabled(empty_selection); - rename_btn->set_disabled(empty_selection); - run_btn->set_disabled(empty_selection); + open_btn->set_disabled(empty_selection || is_missing_project_selected); + rename_btn->set_disabled(empty_selection || is_missing_project_selected); + run_btn->set_disabled(empty_selection || is_missing_project_selected); erase_missing_btn->set_visible(_project_list->is_any_project_missing()); } @@ -1928,6 +1948,9 @@ void ProjectManager::_open_selected_projects_ask() { } ProjectList::Item project = _project_list->get_selected_projects()[0]; + if (project.missing) { + return; + } // Update the project settings or don't open String conf = project.path.plus_file("project.godot"); @@ -2110,7 +2133,7 @@ void ProjectManager::_erase_project() { void ProjectManager::_erase_missing_projects() { - erase_missing_ask->set_text(TTR("Remove all missing projects from the list? (Folders contents will not be modified)")); + erase_missing_ask->set_text(TTR("Remove all missing projects from the list?\nThe project folders' contents won't be modified.")); erase_missing_ask->popup_centered_minsize(); } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index f1eb7adbd2..f0114b393d 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2709,6 +2709,7 @@ void SceneTreeDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_feature_profile_changed"), &SceneTreeDock::_feature_profile_changed); ClassDB::bind_method(D_METHOD("instance"), &SceneTreeDock::instance); + ClassDB::bind_method(D_METHOD("get_tree_editor"), &SceneTreeDock::get_tree_editor); ClassDB::bind_method(D_METHOD("replace_node"), &SceneTreeDock::replace_node); ADD_SIGNAL(MethodInfo("remote_tree_selected")); diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index 7d0f40fe91..ffb3f5feab 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -34,6 +34,7 @@ #include "core/os/file_access.h" #include "core/project_settings.h" #include "core/script_language.h" +#include "core/string_builder.h" #include "editor/create_dialog.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" @@ -238,16 +239,22 @@ void ScriptCreateDialog::_parent_name_changed(const String &p_parent) { void ScriptCreateDialog::_template_changed(int p_template) { - String selected_template = p_template == 0 ? "" : template_menu->get_item_text(template_menu->get_selected()); + String selected_template = p_template == 0 ? "" : template_menu->get_item_text(p_template); EditorSettings::get_singleton()->set_project_metadata("script_setup", "last_selected_template", selected_template); if (p_template == 0) { //default script_template = ""; return; } - String ext = ScriptServer::get_language(language_menu->get_selected())->get_extension(); - String name = template_list[p_template - 1] + "." + ext; - script_template = EditorSettings::get_singleton()->get_script_templates_dir().plus_file(name); + int selected_id = template_menu->get_selected_id(); + + for (int i = 0; i < template_list.size(); i++) { + const ScriptTemplateInfo &sinfo = template_list[i]; + if (sinfo.id == selected_id) { + script_template = sinfo.dir.plus_file(sinfo.name + "." + sinfo.extension); + break; + } + } } void ScriptCreateDialog::ok_pressed() { @@ -368,23 +375,77 @@ void ScriptCreateDialog::_lang_changed(int l) { bool use_templates = language->is_using_templates(); template_menu->set_disabled(!use_templates); template_menu->clear(); - if (use_templates) { - template_list = EditorSettings::get_singleton()->get_script_templates(language->get_extension()); + if (use_templates) { + _update_script_templates(language->get_extension()); String last_lang = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", ""); String last_template = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_template", ""); template_menu->add_item(TTR("Default")); + + ScriptTemplateInfo *templates = template_list.ptrw(); + + Vector<String> origin_names; + origin_names.push_back(TTR("Project")); + origin_names.push_back(TTR("Editor")); + int cur_origin = -1; + + // Populate script template items previously sorted and now grouped by origin for (int i = 0; i < template_list.size(); i++) { - String s = template_list[i].capitalize(); - template_menu->add_item(s); - if (language_menu->get_item_text(language_menu->get_selected()) == last_lang && last_template == s) { - template_menu->select(i + 1); + + if (int(templates[i].origin) != cur_origin) { + template_menu->add_separator(); + + String origin_name = origin_names[templates[i].origin]; + + int last_index = template_menu->get_item_count() - 1; + template_menu->set_item_text(last_index, origin_name); + + cur_origin = templates[i].origin; } + String item_name = templates[i].name.capitalize(); + template_menu->add_item(item_name); + + int new_id = template_menu->get_item_count() - 1; + templates[i].id = new_id; } - } else { + // Disable overridden + for (Map<String, Vector<int> >::Element *E = template_overrides.front(); E; E = E->next()) { + const Vector<int> &overrides = E->get(); + + if (overrides.size() == 1) { + continue; // doesn't override anything + } + const ScriptTemplateInfo &extended = template_list[overrides[0]]; + + StringBuilder override_info; + override_info += TTR("Overrides"); + override_info += ": "; + + for (int i = 1; i < overrides.size(); i++) { + const ScriptTemplateInfo &overridden = template_list[overrides[i]]; + + int disable_index = template_menu->get_item_index(overridden.id); + template_menu->set_item_disabled(disable_index, true); + override_info += origin_names[overridden.origin]; + if (i < overrides.size() - 1) { + override_info += ", "; + } + } + template_menu->set_item_icon(extended.id, get_icon("Override", "EditorIcons")); + template_menu->get_popup()->set_item_tooltip(extended.id, override_info.as_string()); + } + // Reselect last selected template + for (int i = 0; i < template_menu->get_item_count(); i++) { + const String &ti = template_menu->get_item_text(i); + if (language_menu->get_item_text(language_menu->get_selected()) == last_lang && last_template == ti) { + template_menu->select(i); + break; + } + } + } else { template_menu->add_item(TTR("N/A")); script_template = ""; } @@ -396,6 +457,41 @@ void ScriptCreateDialog::_lang_changed(int l) { _update_dialog(); } +void ScriptCreateDialog::_update_script_templates(const String &p_extension) { + + template_list.clear(); + template_overrides.clear(); + + Vector<String> dirs; + + // Ordered from local to global for correct override mechanism + dirs.push_back(EditorSettings::get_singleton()->get_project_script_templates_dir()); + dirs.push_back(EditorSettings::get_singleton()->get_script_templates_dir()); + + for (int i = 0; i < dirs.size(); i++) { + + Vector<String> list = EditorSettings::get_singleton()->get_script_templates(p_extension, dirs[i]); + + for (int j = 0; j < list.size(); j++) { + ScriptTemplateInfo sinfo; + sinfo.origin = ScriptOrigin(i); + sinfo.dir = dirs[i]; + sinfo.name = list[j]; + sinfo.extension = p_extension; + template_list.push_back(sinfo); + + if (!template_overrides.has(sinfo.name)) { + Vector<int> overrides; + overrides.push_back(template_list.size() - 1); // first one + template_overrides.insert(sinfo.name, overrides); + } else { + Vector<int> &overrides = template_overrides[sinfo.name]; + overrides.push_back(template_list.size() - 1); + } + } + } +} + void ScriptCreateDialog::_built_in_pressed() { if (internal->is_pressed()) { diff --git a/editor/script_create_dialog.h b/editor/script_create_dialog.h index 202846fd3c..31cf2478cf 100644 --- a/editor/script_create_dialog.h +++ b/editor/script_create_dialog.h @@ -78,8 +78,25 @@ class ScriptCreateDialog : public ConfirmationDialog { int current_language; int default_language; bool re_check_path; + + enum ScriptOrigin { + SCRIPT_ORIGIN_PROJECT, + SCRIPT_ORIGIN_EDITOR, + }; + struct ScriptTemplateInfo { + int id; + ScriptOrigin origin; + String dir; + String name; + String extension; + }; + String script_template; - Vector<String> template_list; + Vector<ScriptTemplateInfo> template_list; + Map<String, Vector<int> > template_overrides; // name : indices + + void _update_script_templates(const String &p_extension); + String base_type; void _path_hbox_sorted(); diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index f7ff754a0b..fc5aecdbe9 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -1376,7 +1376,7 @@ void ScriptEditorDebugger::stop() { profiler->set_enabled(true); inspect_scene_tree->clear(); - inspector->edit(NULL); + EditorNode::get_singleton()->edit_current(); EditorNode::get_singleton()->get_pause_button()->set_pressed(false); EditorNode::get_singleton()->get_pause_button()->set_disabled(true); EditorNode::get_singleton()->get_scene_tree_dock()->hide_remote_tree(); diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index f7ba5e5e3c..4c5371769f 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -2037,8 +2037,11 @@ PortalSpatialGizmo::PortalSpatialGizmo(Portal *p_portal) { RayCastSpatialGizmoPlugin::RayCastSpatialGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); create_material("shape_material", gizmo_color); + const float gizmo_value = gizmo_color.get_v(); + const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); + create_material("shape_material_disabled", gizmo_color_disabled); } bool RayCastSpatialGizmoPlugin::has_gizmo(Spatial *p_spatial) { @@ -2064,7 +2067,8 @@ void RayCastSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { lines.push_back(Vector3()); lines.push_back(raycast->get_cast_to()); - Ref<SpatialMaterial> material = get_material("shape_material", p_gizmo); + const Ref<SpatialMaterial> material = + get_material(raycast->is_enabled() ? "shape_material" : "shape_material_disabled", p_gizmo); p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); @@ -3108,8 +3112,11 @@ void BakedIndirectLightGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { //// CollisionShapeSpatialGizmoPlugin::CollisionShapeSpatialGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); create_material("shape_material", gizmo_color); + const float gizmo_value = gizmo_color.get_v(); + const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); + create_material("shape_material_disabled", gizmo_color_disabled); create_handle_material("handles"); } @@ -3432,7 +3439,8 @@ void CollisionShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { if (s.is_null()) return; - Ref<Material> material = get_material("shape_material", p_gizmo); + const Ref<Material> material = + get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo); Ref<Material> handles_material = get_material("handles"); if (Object::cast_to<SphereShape>(*s)) { @@ -3733,8 +3741,11 @@ void CollisionShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { ///// CollisionPolygonSpatialGizmoPlugin::CollisionPolygonSpatialGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); create_material("shape_material", gizmo_color); + const float gizmo_value = gizmo_color.get_v(); + const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); + create_material("shape_material_disabled", gizmo_color_disabled); } bool CollisionPolygonSpatialGizmoPlugin::has_gizmo(Spatial *p_spatial) { @@ -3770,7 +3781,8 @@ void CollisionPolygonSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { lines.push_back(Vector3(points[i].x, points[i].y, -depth)); } - Ref<Material> material = get_material("shape_material", p_gizmo); + const Ref<Material> material = + get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo); p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); diff --git a/main/input_default.cpp b/main/input_default.cpp index 5ba98c20e2..60675f1115 100644 --- a/main/input_default.cpp +++ b/main/input_default.cpp @@ -472,6 +472,10 @@ void InputDefault::stop_joy_vibration(int p_device) { joy_vibration[p_device] = vibration; } +void InputDefault::vibrate_handheld(int p_duration_ms) { + OS::get_singleton()->vibrate_handheld(p_duration_ms); +} + void InputDefault::set_gravity(const Vector3 &p_gravity) { _THREAD_SAFE_METHOD_ diff --git a/main/input_default.h b/main/input_default.h index 80ee17656c..4fc4ad6506 100644 --- a/main/input_default.h +++ b/main/input_default.h @@ -226,6 +226,7 @@ public: virtual void start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration = 0); virtual void stop_joy_vibration(int p_device); + virtual void vibrate_handheld(int p_duration_ms = 500); void set_main_loop(MainLoop *p_main_loop); void set_mouse_position(const Point2 &p_posf); diff --git a/main/main.cpp b/main/main.cpp index 027273c4f4..582df4e866 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -30,6 +30,7 @@ #include "main.h" +#include "core/crypto/crypto.h" #include "core/input_map.h" #include "core/io/file_access_network.h" #include "core/io/file_access_pack.h" @@ -37,8 +38,6 @@ #include "core/io/image_loader.h" #include "core/io/ip.h" #include "core/io/resource_loader.h" -#include "core/io/stream_peer_ssl.h" -#include "core/io/stream_peer_tcp.h" #include "core/message_queue.h" #include "core/os/dir_access.h" #include "core/os/os.h" @@ -1741,7 +1740,7 @@ bool Main::start() { if (!project_manager && !editor) { // game // Load SSL Certificates from Project Settings (or builtin). - StreamPeerSSL::load_certs_from_memory(StreamPeerSSL::get_project_cert_array()); + Crypto::load_default_certificates(GLOBAL_DEF("network/ssl/certificates", "")); if (game_path != "") { Node *scene = NULL; @@ -1793,17 +1792,15 @@ bool Main::start() { } if (project_manager || editor) { - // Load SSL Certificates from Editor Settings (or builtin). - String certs = EditorSettings::get_singleton()->get_setting("network/ssl/editor_ssl_certificates").operator String(); - if (certs != "") - StreamPeerSSL::load_certs_from_file(certs); - else - StreamPeerSSL::load_certs_from_memory(StreamPeerSSL::get_project_cert_array()); - // Hide console window if requested (Windows-only). bool hide_console = EditorSettings::get_singleton()->get_setting("interface/editor/hide_console_window"); OS::get_singleton()->set_console_visible(!hide_console); } + + if (project_manager || editor) { + // Load SSL Certificates from Editor Settings (or builtin) + Crypto::load_default_certificates(EditorSettings::get_singleton()->get_setting("network/ssl/editor_ssl_certificates").operator String()); + } #endif } diff --git a/misc/ide/jetbrains/gradle/wrapper/gradle-wrapper.properties b/misc/ide/jetbrains/gradle/wrapper/gradle-wrapper.properties index b477add150..22e7a5c60b 100644 --- a/misc/ide/jetbrains/gradle/wrapper/gradle-wrapper.properties +++ b/misc/ide/jetbrains/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Mar 31 16:34:43 PDT 2019 +#Wed Aug 21 13:47:30 PDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp new file mode 100644 index 0000000000..1e02084ae2 --- /dev/null +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -0,0 +1,285 @@ +/*************************************************************************/ +/* crypto_mbedtls.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "crypto_mbedtls.h" + +#include "core/os/file_access.h" + +#include "core/engine.h" +#include "core/io/certs_compressed.gen.h" +#include "core/io/compression.h" +#include "core/project_settings.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif +#define PEM_BEGIN_CRT "-----BEGIN CERTIFICATE-----\n" +#define PEM_END_CRT "-----END CERTIFICATE-----\n" + +#include "mbedtls/pem.h" +#include <mbedtls/debug.h> + +CryptoKey *CryptoKeyMbedTLS::create() { + return memnew(CryptoKeyMbedTLS); +} + +Error CryptoKeyMbedTLS::load(String p_path) { + ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Key is in use"); + + PoolByteArray out; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + int flen = f->get_len(); + out.resize(flen + 1); + { + PoolByteArray::Write w = out.write(); + f->get_buffer(w.ptr(), flen); + w[flen] = 0; //end f string + } + memdelete(f); + + int ret = mbedtls_pk_parse_key(&pkey, out.read().ptr(), out.size(), NULL, 0); + // We MUST zeroize the memory for safety! + mbedtls_platform_zeroize(out.write().ptr(), out.size()); + ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing private key: " + itos(ret)); + + return OK; +} + +Error CryptoKeyMbedTLS::save(String p_path) { + FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + unsigned char w[16000]; + memset(w, 0, sizeof(w)); + + int ret = mbedtls_pk_write_key_pem(&pkey, w, sizeof(w)); + if (ret != 0) { + memdelete(f); + memset(w, 0, sizeof(w)); // Zeroize anything we might have written. + ERR_FAIL_V_MSG(FAILED, "Error writing key: " + itos(ret)); + } + + size_t len = strlen((char *)w); + f->store_buffer(w, len); + memdelete(f); + memset(w, 0, sizeof(w)); // Zeroize temporary buffer. + return OK; +} + +X509Certificate *X509CertificateMbedTLS::create() { + return memnew(X509CertificateMbedTLS); +} + +Error X509CertificateMbedTLS::load(String p_path) { + ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Certificate is in use"); + + PoolByteArray out; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + int flen = f->get_len(); + out.resize(flen + 1); + { + PoolByteArray::Write w = out.write(); + f->get_buffer(w.ptr(), flen); + w[flen] = 0; //end f string + } + memdelete(f); + + int ret = mbedtls_x509_crt_parse(&cert, out.read().ptr(), out.size()); + ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing some certificates: " + itos(ret)); + + return OK; +} + +Error X509CertificateMbedTLS::load_from_memory(const uint8_t *p_buffer, int p_len) { + ERR_FAIL_COND_V_MSG(locks, ERR_ALREADY_IN_USE, "Certificate is in use"); + + int ret = mbedtls_x509_crt_parse(&cert, p_buffer, p_len); + ERR_FAIL_COND_V_MSG(ret, FAILED, "Error parsing certificates: " + itos(ret)); + return OK; +} + +Error X509CertificateMbedTLS::save(String p_path) { + FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V(!f, ERR_INVALID_PARAMETER); + + mbedtls_x509_crt *crt = &cert; + while (crt) { + unsigned char w[4096]; + size_t wrote = 0; + int ret = mbedtls_pem_write_buffer(PEM_BEGIN_CRT, PEM_END_CRT, cert.raw.p, cert.raw.len, w, sizeof(w), &wrote); + if (ret != 0 || wrote == 0) { + memdelete(f); + ERR_FAIL_V_MSG(FAILED, "Error writing certificate: " + itos(ret)); + } + + f->store_buffer(w, wrote - 1); // don't write the string terminator + crt = crt->next; + } + memdelete(f); + return OK; +} + +Crypto *CryptoMbedTLS::create() { + return memnew(CryptoMbedTLS); +} + +void CryptoMbedTLS::initialize_crypto() { + +#ifdef DEBUG_ENABLED + mbedtls_debug_set_threshold(1); +#endif + + Crypto::_create = create; + Crypto::_load_default_certificates = load_default_certificates; + X509CertificateMbedTLS::make_default(); + CryptoKeyMbedTLS::make_default(); +} + +void CryptoMbedTLS::finalize_crypto() { + Crypto::_create = NULL; + Crypto::_load_default_certificates = NULL; + if (default_certs) { + memdelete(default_certs); + default_certs = NULL; + } + X509CertificateMbedTLS::finalize(); + CryptoKeyMbedTLS::finalize(); +} + +CryptoMbedTLS::CryptoMbedTLS() { + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + ERR_PRINTS(" failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret)); + } +} + +CryptoMbedTLS::~CryptoMbedTLS() { + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); +} + +X509CertificateMbedTLS *CryptoMbedTLS::default_certs = NULL; + +X509CertificateMbedTLS *CryptoMbedTLS::get_default_certificates() { + return default_certs; +} + +void CryptoMbedTLS::load_default_certificates(String p_path) { + ERR_FAIL_COND(default_certs != NULL); + + default_certs = memnew(X509CertificateMbedTLS); + ERR_FAIL_COND(default_certs == NULL); + + String certs_path = GLOBAL_DEF("network/ssl/certificates", ""); + + if (p_path != "") { + // Use certs defined in project settings. + default_certs->load(p_path); + } +#ifdef BUILTIN_CERTS_ENABLED + else { + // Use builtin certs only if user did not override it in project settings. + PoolByteArray out; + out.resize(_certs_uncompressed_size + 1); + PoolByteArray::Write w = out.write(); + Compression::decompress(w.ptr(), _certs_uncompressed_size, _certs_compressed, _certs_compressed_size, Compression::MODE_DEFLATE); + w[_certs_uncompressed_size] = 0; // Make sure it ends with string terminator +#ifdef DEBUG_ENABLED + print_verbose("Loaded builtin certs"); +#endif + default_certs->load_from_memory(out.read().ptr(), out.size()); + } +#endif +} + +Ref<CryptoKey> CryptoMbedTLS::generate_rsa(int p_bytes) { + Ref<CryptoKeyMbedTLS> out; + out.instance(); + int ret = mbedtls_pk_setup(&(out->pkey), mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)); + ERR_FAIL_COND_V(ret != 0, NULL); + ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(out->pkey), mbedtls_ctr_drbg_random, &ctr_drbg, p_bytes, 65537); + ERR_FAIL_COND_V(ret != 0, NULL); + return out; +} + +Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) { + Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS> >(p_key); + mbedtls_x509write_cert crt; + mbedtls_x509write_crt_init(&crt); + + mbedtls_x509write_crt_set_subject_key(&crt, &(key->pkey)); + mbedtls_x509write_crt_set_issuer_key(&crt, &(key->pkey)); + mbedtls_x509write_crt_set_subject_name(&crt, p_issuer_name.utf8().get_data()); + mbedtls_x509write_crt_set_issuer_name(&crt, p_issuer_name.utf8().get_data()); + mbedtls_x509write_crt_set_version(&crt, MBEDTLS_X509_CRT_VERSION_3); + mbedtls_x509write_crt_set_md_alg(&crt, MBEDTLS_MD_SHA256); + + mbedtls_mpi serial; + mbedtls_mpi_init(&serial); + uint8_t rand_serial[20]; + mbedtls_ctr_drbg_random(&ctr_drbg, rand_serial, 20); + ERR_FAIL_COND_V(mbedtls_mpi_read_binary(&serial, rand_serial, 20), NULL); + mbedtls_x509write_crt_set_serial(&crt, &serial); + + mbedtls_x509write_crt_set_validity(&crt, p_not_before.utf8().get_data(), p_not_after.utf8().get_data()); + mbedtls_x509write_crt_set_basic_constraints(&crt, 1, -1); + mbedtls_x509write_crt_set_basic_constraints(&crt, 1, 0); + + unsigned char buf[4096]; + memset(buf, 0, 4096); + Ref<X509CertificateMbedTLS> out; + out.instance(); + mbedtls_x509write_crt_pem(&crt, buf, 4096, mbedtls_ctr_drbg_random, &ctr_drbg); + + int err = mbedtls_x509_crt_parse(&(out->cert), buf, 4096); + if (err != 0) { + mbedtls_mpi_free(&serial); + mbedtls_x509write_crt_free(&crt); + ERR_PRINTS("Generated invalid certificate: " + itos(err)); + return NULL; + } + + mbedtls_mpi_free(&serial); + mbedtls_x509write_crt_free(&crt); + return out; +} + +PoolByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) { + PoolByteArray out; + out.resize(p_bytes); + mbedtls_ctr_drbg_random(&ctr_drbg, out.write().ptr(), p_bytes); + return out; +} diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h new file mode 100644 index 0000000000..06b3ecd234 --- /dev/null +++ b/modules/mbedtls/crypto_mbedtls.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* crypto_mbedtls.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CRYPTO_MBEDTLS_H +#define CRYPTO_MBEDTLS_H + +#include "core/crypto/crypto.h" +#include "core/resource.h" + +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ssl.h> + +class CryptoMbedTLS; +class SSLContextMbedTLS; +class CryptoKeyMbedTLS : public CryptoKey { + +private: + mbedtls_pk_context pkey; + int locks; + +public: + static CryptoKey *create(); + static void make_default() { CryptoKey::_create = create; } + static void finalize() { CryptoKey::_create = NULL; } + + virtual Error load(String p_path); + virtual Error save(String p_path); + + CryptoKeyMbedTLS() { + mbedtls_pk_init(&pkey); + locks = 0; + } + ~CryptoKeyMbedTLS() { + mbedtls_pk_free(&pkey); + } + + _FORCE_INLINE_ void lock() { locks++; } + _FORCE_INLINE_ void unlock() { locks--; } + + friend class CryptoMbedTLS; + friend class SSLContextMbedTLS; +}; + +class X509CertificateMbedTLS : public X509Certificate { + +private: + mbedtls_x509_crt cert; + int locks; + +public: + static X509Certificate *create(); + static void make_default() { X509Certificate::_create = create; } + static void finalize() { X509Certificate::_create = NULL; } + + virtual Error load(String p_path); + virtual Error load_from_memory(const uint8_t *p_buffer, int p_len); + virtual Error save(String p_path); + + X509CertificateMbedTLS() { + mbedtls_x509_crt_init(&cert); + locks = 0; + } + ~X509CertificateMbedTLS() { + mbedtls_x509_crt_free(&cert); + } + + _FORCE_INLINE_ void lock() { locks++; } + _FORCE_INLINE_ void unlock() { locks--; } + + friend class CryptoMbedTLS; + friend class SSLContextMbedTLS; +}; + +class CryptoMbedTLS : public Crypto { + +private: + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + static X509CertificateMbedTLS *default_certs; + +public: + static Crypto *create(); + static void initialize_crypto(); + static void finalize_crypto(); + static X509CertificateMbedTLS *get_default_certificates(); + static void load_default_certificates(String p_path); + + virtual PoolByteArray generate_random_bytes(int p_bytes); + virtual Ref<CryptoKey> generate_rsa(int p_bytes); + virtual Ref<X509Certificate> generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after); + + CryptoMbedTLS(); + ~CryptoMbedTLS(); +}; + +#endif // CRYPTO_MBEDTLS_H diff --git a/modules/mbedtls/register_types.cpp b/modules/mbedtls/register_types.cpp index 121ed5eb02..f7dc6c785f 100755 --- a/modules/mbedtls/register_types.cpp +++ b/modules/mbedtls/register_types.cpp @@ -30,15 +30,17 @@ #include "register_types.h" -#include "stream_peer_mbed_tls.h" +#include "crypto_mbedtls.h" +#include "stream_peer_mbedtls.h" void register_mbedtls_types() { - ClassDB::register_class<StreamPeerMbedTLS>(); + CryptoMbedTLS::initialize_crypto(); StreamPeerMbedTLS::initialize_ssl(); } void unregister_mbedtls_types() { StreamPeerMbedTLS::finalize_ssl(); + CryptoMbedTLS::finalize_crypto(); } diff --git a/modules/mbedtls/ssl_context_mbedtls.cpp b/modules/mbedtls/ssl_context_mbedtls.cpp new file mode 100644 index 0000000000..97b5e23f58 --- /dev/null +++ b/modules/mbedtls/ssl_context_mbedtls.cpp @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* ssl_context_mbed_tls.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "ssl_context_mbedtls.h" + +static void my_debug(void *ctx, int level, + const char *file, int line, + const char *str) { + + printf("%s:%04d: %s", file, line, str); + fflush(stdout); +} + +Error SSLContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode) { + ERR_FAIL_COND_V_MSG(inited, ERR_ALREADY_IN_USE, "This SSL context is already active"); + + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + inited = true; + + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); + if (ret != 0) { + clear(); // Never leave unusable resources around. + ERR_FAIL_V_MSG(FAILED, "mbedtls_ctr_drbg_seed returned an error" + itos(ret)); + } + + ret = mbedtls_ssl_config_defaults(&conf, p_endpoint, p_transport, MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + clear(); + ERR_FAIL_V_MSG(FAILED, "mbedtls_ssl_config_defaults returned an error" + itos(ret)); + } + mbedtls_ssl_conf_authmode(&conf, p_authmode); + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); + mbedtls_ssl_conf_dbg(&conf, my_debug, stdout); + return OK; +} + +Error SSLContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert) { + ERR_FAIL_COND_V(!p_pkey.is_valid(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!p_cert.is_valid(), ERR_INVALID_PARAMETER); + + Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, p_authmode); + ERR_FAIL_COND_V(err != OK, err); + + // Locking key and certificate(s) + pkey = p_pkey; + certs = p_cert; + if (pkey.is_valid()) + pkey->lock(); + if (certs.is_valid()) + certs->lock(); + + // Adding key and certificate + int ret = mbedtls_ssl_conf_own_cert(&conf, &(certs->cert), &(pkey->pkey)); + if (ret != 0) { + clear(); + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid cert/key combination " + itos(ret)); + } + // Adding CA chain if available. + if (certs->cert.next) { + mbedtls_ssl_conf_ca_chain(&conf, certs->cert.next, NULL); + } + mbedtls_ssl_setup(&ssl, &conf); + return OK; +} + +Error SSLContextMbedTLS::init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas) { + Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, p_authmode); + ERR_FAIL_COND_V(err != OK, err); + + X509CertificateMbedTLS *cas = NULL; + + if (p_valid_cas.is_valid()) { + // Locking CA certificates + certs = p_valid_cas; + certs->lock(); + cas = certs.ptr(); + } else { + // Fall back to default certificates (no need to lock those). + cas = CryptoMbedTLS::get_default_certificates(); + if (cas == NULL) { + clear(); + ERR_FAIL_V_MSG(ERR_UNCONFIGURED, "SSL module failed to initialize!"); + } + } + + // Set valid CAs + mbedtls_ssl_conf_ca_chain(&conf, &(cas->cert), NULL); + mbedtls_ssl_setup(&ssl, &conf); + return OK; +} + +void SSLContextMbedTLS::clear() { + if (!inited) + return; + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + // Unlock and key and certificates + if (certs.is_valid()) + certs->unlock(); + certs = Ref<X509Certificate>(); + if (pkey.is_valid()) + pkey->unlock(); + pkey = Ref<CryptoKeyMbedTLS>(); + inited = false; +} + +mbedtls_ssl_context *SSLContextMbedTLS::get_context() { + ERR_FAIL_COND_V(!inited, NULL); + return &ssl; +} + +SSLContextMbedTLS::SSLContextMbedTLS() { + inited = false; +} + +SSLContextMbedTLS::~SSLContextMbedTLS() { + clear(); +} diff --git a/modules/mbedtls/ssl_context_mbedtls.h b/modules/mbedtls/ssl_context_mbedtls.h new file mode 100644 index 0000000000..b78ee37b03 --- /dev/null +++ b/modules/mbedtls/ssl_context_mbedtls.h @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* ssl_context_mbed_tls.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SSL_CONTEXT_MBED_TLS_H +#define SSL_CONTEXT_MBED_TLS_H + +#include "crypto_mbedtls.h" + +#include "core/os/file_access.h" +#include "core/pool_vector.h" +#include "core/reference.h" + +#include <mbedtls/config.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/debug.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ssl.h> + +class SSLContextMbedTLS : public Reference { + +protected: + bool inited; + + static PoolByteArray _read_file(String p_path); + +public: + Ref<X509CertificateMbedTLS> certs; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + + Ref<CryptoKeyMbedTLS> pkey; + + Error _setup(int p_endpoint, int p_transport, int p_authmode); + Error init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert); + Error init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas); + void clear(); + + mbedtls_ssl_context *get_context(); + + SSLContextMbedTLS(); + ~SSLContextMbedTLS(); +}; + +#endif // SSL_CONTEXT_MBED_TLS_H diff --git a/modules/mbedtls/stream_peer_mbed_tls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index 4bb7557150..e2eb19fc74 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -28,19 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "stream_peer_mbed_tls.h" +#include "stream_peer_mbedtls.h" #include "core/io/stream_peer_tcp.h" #include "core/os/file_access.h" -static void my_debug(void *ctx, int level, - const char *file, int line, - const char *str) { - - printf("%s:%04d: %s", file, line, str); - fflush(stdout); -} - void _print_error(int ret) { printf("mbedtls error: returned -0x%x\n\n", -ret); fflush(stdout); @@ -86,18 +78,14 @@ int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { void StreamPeerMbedTLS::_cleanup() { - mbedtls_ssl_free(&ssl); - mbedtls_ssl_config_free(&conf); - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&entropy); - + ssl_ctx->clear(); base = Ref<StreamPeer>(); status = STATUS_DISCONNECTED; } Error StreamPeerMbedTLS::_do_handshake() { int ret = 0; - while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + while ((ret = mbedtls_ssl_handshake(ssl_ctx->get_context())) != 0) { if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { // An error occurred. ERR_PRINTS("TLS handshake error: " + itos(ret)); @@ -118,7 +106,7 @@ Error StreamPeerMbedTLS::_do_handshake() { return OK; } -Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname) { +Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) { ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); @@ -126,31 +114,11 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida int ret = 0; int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; - mbedtls_ssl_init(&ssl); - mbedtls_ssl_config_init(&conf); - mbedtls_ctr_drbg_init(&ctr_drbg); - mbedtls_entropy_init(&entropy); - - ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); - if (ret != 0) { - ERR_PRINTS(" failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret)); - _cleanup(); - return FAILED; - } + Error err = ssl_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, authmode, p_ca_certs); + ERR_FAIL_COND_V(err != OK, err); - mbedtls_ssl_config_defaults(&conf, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT); - - mbedtls_ssl_conf_authmode(&conf, authmode); - mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); - mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); - mbedtls_ssl_conf_dbg(&conf, my_debug, stdout); - mbedtls_ssl_setup(&ssl, &conf); - mbedtls_ssl_set_hostname(&ssl, p_for_hostname.utf8().get_data()); - - mbedtls_ssl_set_bio(&ssl, this, bio_send, bio_recv, NULL); + mbedtls_ssl_set_hostname(ssl_ctx->get_context(), p_for_hostname.utf8().get_data()); + mbedtls_ssl_set_bio(ssl_ctx->get_context(), this, bio_send, bio_recv, NULL); status = STATUS_HANDSHAKING; @@ -162,11 +130,26 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida return OK; } -Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base) { +Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) { + + ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER); + + Error err = ssl_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert); + ERR_FAIL_COND_V(err != OK, err); + + base = p_base; + + mbedtls_ssl_set_bio(ssl_ctx->get_context(), this, bio_send, bio_recv, NULL); + + status = STATUS_HANDSHAKING; + + if ((err = _do_handshake()) != OK) { + return FAILED; + } + status = STATUS_CONNECTED; return OK; } - Error StreamPeerMbedTLS::put_data(const uint8_t *p_data, int p_bytes) { ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_UNCONFIGURED); @@ -197,7 +180,7 @@ Error StreamPeerMbedTLS::put_partial_data(const uint8_t *p_data, int p_bytes, in if (p_bytes == 0) return OK; - int ret = mbedtls_ssl_write(&ssl, p_data, p_bytes); + int ret = mbedtls_ssl_write(ssl_ctx->get_context(), p_data, p_bytes); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { // Non blocking IO ret = 0; @@ -243,7 +226,7 @@ Error StreamPeerMbedTLS::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r r_received = 0; - int ret = mbedtls_ssl_read(&ssl, p_buffer, p_bytes); + int ret = mbedtls_ssl_read(ssl_ctx->get_context(), p_buffer, p_bytes); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { ret = 0; // non blocking io } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { @@ -273,7 +256,7 @@ void StreamPeerMbedTLS::poll() { // We could pass NULL as second parameter, but some behaviour sanitizers doesn't seem to like that. // Passing a 1 byte buffer to workaround it. uint8_t byte; - int ret = mbedtls_ssl_read(&ssl, &byte, 0); + int ret = mbedtls_ssl_read(ssl_ctx->get_context(), &byte, 0); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { // Nothing to read/write (non blocking IO) @@ -298,10 +281,11 @@ int StreamPeerMbedTLS::get_available_bytes() const { ERR_FAIL_COND_V(status != STATUS_CONNECTED, 0); - return mbedtls_ssl_get_bytes_avail(&ssl); + return mbedtls_ssl_get_bytes_avail(&(ssl_ctx->ssl)); } StreamPeerMbedTLS::StreamPeerMbedTLS() { + ssl_ctx.instance(); status = STATUS_DISCONNECTED; } @@ -317,7 +301,7 @@ void StreamPeerMbedTLS::disconnect_from_stream() { Ref<StreamPeerTCP> tcp = base; if (tcp.is_valid() && tcp->get_status() == StreamPeerTCP::STATUS_CONNECTED) { // We are still connected on the socket, try to send close notify. - mbedtls_ssl_close_notify(&ssl); + mbedtls_ssl_close_notify(ssl_ctx->get_context()); } _cleanup(); @@ -333,28 +317,9 @@ StreamPeerSSL *StreamPeerMbedTLS::_create_func() { return memnew(StreamPeerMbedTLS); } -mbedtls_x509_crt StreamPeerMbedTLS::cacert; - -void StreamPeerMbedTLS::_load_certs(const PoolByteArray &p_array) { - int arr_len = p_array.size(); - PoolByteArray::Read r = p_array.read(); - int err = mbedtls_x509_crt_parse(&cacert, &r[0], arr_len); - if (err != 0) { - WARN_PRINTS("Error parsing some certificates: " + itos(err)); - } -} - void StreamPeerMbedTLS::initialize_ssl() { _create = _create_func; - load_certs_func = _load_certs; - - mbedtls_x509_crt_init(&cacert); - -#ifdef DEBUG_ENABLED - mbedtls_debug_set_threshold(1); -#endif - available = true; } @@ -362,6 +327,4 @@ void StreamPeerMbedTLS::finalize_ssl() { available = false; _create = NULL; - load_certs_func = NULL; - mbedtls_x509_crt_free(&cacert); } diff --git a/modules/mbedtls/stream_peer_mbed_tls.h b/modules/mbedtls/stream_peer_mbedtls.h index ab87b779c1..060e76b4f3 100755 --- a/modules/mbedtls/stream_peer_mbed_tls.h +++ b/modules/mbedtls/stream_peer_mbedtls.h @@ -32,15 +32,7 @@ #define STREAM_PEER_OPEN_SSL_H #include "core/io/stream_peer_ssl.h" - -#include <mbedtls/config.h> -#include <mbedtls/ctr_drbg.h> -#include <mbedtls/debug.h> -#include <mbedtls/entropy.h> -#include <mbedtls/ssl.h> - -#include <stdio.h> -#include <stdlib.h> +#include "ssl_context_mbedtls.h" class StreamPeerMbedTLS : public StreamPeerSSL { private: @@ -50,19 +42,13 @@ private: Ref<StreamPeer> base; static StreamPeerSSL *_create_func(); - static void _load_certs(const PoolByteArray &p_array); static int bio_recv(void *ctx, unsigned char *buf, size_t len); static int bio_send(void *ctx, const unsigned char *buf, size_t len); void _cleanup(); protected: - static mbedtls_x509_crt cacert; - - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_ssl_context ssl; - mbedtls_ssl_config conf; + Ref<SSLContextMbedTLS> ssl_ctx; static void _bind_methods(); @@ -70,8 +56,8 @@ protected: public: virtual void poll(); - virtual Error accept_stream(Ref<StreamPeer> p_base); - virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String()); + virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()); + virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>()); virtual Status get_status() const; virtual void disconnect_from_stream(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs index d515254e65..309b917c71 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using GodotTools.IdeConnection; using GodotTools.Internals; using GodotTools.Utils; +using Directory = System.IO.Directory; using File = System.IO.File; using Thread = System.Threading.Thread; @@ -33,6 +34,9 @@ namespace GodotTools.Ides this.launchIdeAction = launchIdeAction; + // Make sure the directory exists + Directory.CreateDirectory(projectMetadataDir); + // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... const FileShare metaFileShare = FileShare.ReadWrite; diff --git a/modules/mono/glue/Managed/Files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs index b1c1dae3c2..0daa94057e 100644 --- a/modules/mono/glue/Managed/Files/Vector2.cs +++ b/modules/mono/glue/Managed/Files/Vector2.cs @@ -14,10 +14,19 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 2-element structure that can be used to represent positions in 2D space or any other pair of numeric values. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Vector2 : IEquatable<Vector2> { + public enum Axis + { + X = 0, + Y + } + public real_t x; public real_t y; @@ -202,6 +211,22 @@ namespace Godot return v; } + public Vector2 PosMod(real_t mod) + { + Vector2 v; + v.x = Mathf.PosMod(x, mod); + v.y = Mathf.PosMod(y, mod); + return v; + } + + public Vector2 PosMod(Vector2 modv) + { + Vector2 v; + v.x = Mathf.PosMod(x, modv.x); + v.y = Mathf.PosMod(y, modv.y); + return v; + } + public Vector2 Project(Vector2 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); @@ -236,6 +261,14 @@ namespace Godot y = v.y; } + public Vector2 Sign() + { + Vector2 v; + v.x = Mathf.Sign(x); + v.y = Mathf.Sign(y); + return v; + } + public Vector2 Slerp(Vector2 b, real_t t) { real_t theta = AngleTo(b); @@ -265,7 +298,7 @@ namespace Godot private static readonly Vector2 _up = new Vector2(0, -1); private static readonly Vector2 _down = new Vector2(0, 1); - private static readonly Vector2 _right = new Vector2(1, 0); + private static readonly Vector2 _right = new Vector2(1, 0); private static readonly Vector2 _left = new Vector2(-1, 0); public static Vector2 Zero { get { return _zero; } } @@ -346,6 +379,20 @@ namespace Godot return left; } + public static Vector2 operator %(Vector2 vec, real_t divisor) + { + vec.x %= divisor; + vec.y %= divisor; + return vec; + } + + public static Vector2 operator %(Vector2 vec, Vector2 divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + return vec; + } + public static bool operator ==(Vector2 left, Vector2 right) { return left.Equals(right); diff --git a/modules/mono/glue/Managed/Files/Vector3.cs b/modules/mono/glue/Managed/Files/Vector3.cs index c2da7b8bb1..9076dbd3b0 100644 --- a/modules/mono/glue/Managed/Files/Vector3.cs +++ b/modules/mono/glue/Managed/Files/Vector3.cs @@ -14,6 +14,9 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 3-element structure that can be used to represent positions in 3D space or any other pair of numeric values. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Vector3 : IEquatable<Vector3> @@ -225,6 +228,24 @@ namespace Godot ); } + public Vector3 PosMod(real_t mod) + { + Vector3 v; + v.x = Mathf.PosMod(x, mod); + v.y = Mathf.PosMod(y, mod); + v.z = Mathf.PosMod(z, mod); + return v; + } + + public Vector3 PosMod(Vector3 modv) + { + Vector3 v; + v.x = Mathf.PosMod(x, modv.x); + v.y = Mathf.PosMod(y, modv.y); + v.z = Mathf.PosMod(z, modv.z); + return v; + } + public Vector3 Project(Vector3 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); @@ -264,6 +285,15 @@ namespace Godot z = v.z; } + public Vector3 Sign() + { + Vector3 v; + v.x = Mathf.Sign(x); + v.y = Mathf.Sign(y); + v.z = Mathf.Sign(z); + return v; + } + public Vector3 Slerp(Vector3 b, real_t t) { real_t theta = AngleTo(b); @@ -397,6 +427,22 @@ namespace Godot return left; } + public static Vector3 operator %(Vector3 vec, real_t divisor) + { + vec.x %= divisor; + vec.y %= divisor; + vec.z %= divisor; + return vec; + } + + public static Vector3 operator %(Vector3 vec, Vector3 divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + vec.z %= divisorv.z; + return vec; + } + public static bool operator ==(Vector3 left, Vector3 right) { return left.Equals(right); diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 58b68d926b..6f97842064 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "register_types.h" +#include "core/project_settings.h" #include "webrtc_data_channel.h" #include "webrtc_peer_connection.h" @@ -43,6 +44,12 @@ #include "webrtc_multiplayer.h" void register_webrtc_types() { +#define _SET_HINT(NAME, _VAL_, _MAX_) \ + GLOBAL_DEF(NAME, _VAL_); \ + ProjectSettings::get_singleton()->set_custom_property_info(NAME, PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater")); + + _SET_HINT(WRTC_IN_BUF, 64, 4096); + #ifdef JAVASCRIPT_ENABLED WebRTCPeerConnectionJS::make_default(); #elif defined(WEBRTC_GDNATIVE_ENABLED) diff --git a/modules/webrtc/webrtc_data_channel.cpp b/modules/webrtc/webrtc_data_channel.cpp index 2bd30e68f5..7b3843410a 100644 --- a/modules/webrtc/webrtc_data_channel.cpp +++ b/modules/webrtc/webrtc_data_channel.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "webrtc_data_channel.h" +#include "core/project_settings.h" void WebRTCDataChannel::_bind_methods() { ClassDB::bind_method(D_METHOD("poll"), &WebRTCDataChannel::poll); @@ -58,6 +59,7 @@ void WebRTCDataChannel::_bind_methods() { } WebRTCDataChannel::WebRTCDataChannel() { + _in_buffer_shift = nearest_shift((int)GLOBAL_GET(WRTC_IN_BUF) - 1) + 10; } WebRTCDataChannel::~WebRTCDataChannel() { diff --git a/modules/webrtc/webrtc_data_channel.h b/modules/webrtc/webrtc_data_channel.h index 0b161da784..7e2c08d9d7 100644 --- a/modules/webrtc/webrtc_data_channel.h +++ b/modules/webrtc/webrtc_data_channel.h @@ -33,6 +33,8 @@ #include "core/io/packet_peer.h" +#define WRTC_IN_BUF "network/limits/webrtc/max_channel_in_buffer_kb" + class WebRTCDataChannel : public PacketPeer { GDCLASS(WebRTCDataChannel, PacketPeer); @@ -50,6 +52,8 @@ public: }; protected: + unsigned int _in_buffer_shift; + static void _bind_methods(); public: diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 996db35cba..2edd212a50 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -56,7 +56,7 @@ EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_message(void *obj, uint8_t *p_data, uint3 } void WebRTCDataChannelJS::_on_open() { - in_buffer.resize(16); + in_buffer.resize(_in_buffer_shift); } void WebRTCDataChannelJS::_on_close() { diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index b11bd2b70f..f94f3dfff7 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -35,7 +35,7 @@ #include "wsl_client.h" #include "wsl_server.h" -#include "core/math/crypto_core.h" +#include "core/crypto/crypto_core.h" #include "core/math/random_number_generator.h" #include "core/os/os.h" diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java index bfc65a3b78..f493b5f33f 100644 --- a/platform/android/java/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/src/org/godotengine/godot/Godot.java @@ -56,6 +56,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Messenger; +import android.os.Vibrator; import android.provider.Settings.Secure; import android.support.v4.content.ContextCompat; import android.view.Display; @@ -98,6 +99,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC static final int MAX_SINGLETONS = 64; static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; static final int REQUEST_CAMERA_PERMISSION = 2; + static final int REQUEST_VIBRATE_PERMISSION = 3; private IStub mDownloaderClientStub; private IDownloaderService mRemoteService; private TextView mStatusText; @@ -324,6 +326,15 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC }); } + public void vibrate(int p_duration_ms) { + if (requestPermission("VIBRATE")) { + Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(p_duration_ms); + } + } + } + public void restart() { // HACK: // @@ -989,6 +1000,13 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC return false; } } + + if (p_name.equals("VIBRATE")) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); + return false; + } + } return true; } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 339b14974c..c7dc1d124c 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -62,6 +62,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); + _vibrate = p_env->GetMethodID(cls, "vibrate", "(I)V"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -211,3 +212,10 @@ bool GodotJavaWrapper::is_activity_resumed() { return false; } } + +void GodotJavaWrapper::vibrate(int p_duration_ms) { + if (_vibrate) { + JNIEnv *env = ThreadAndroid::get_env(); + env->CallVoidMethod(godot_instance, _vibrate, p_duration_ms); + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 82c2a5d122..3e0e950180 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -57,6 +57,7 @@ private: jmethodID _init_input_devices = 0; jmethodID _get_surface = 0; jmethodID _is_activity_resumed = 0; + jmethodID _vibrate = 0; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); @@ -82,6 +83,7 @@ public: void init_input_devices(); jobject get_surface(); bool is_activity_resumed(); + void vibrate(int p_duration_ms); }; #endif /* !JAVA_GODOT_WRAPPER_H */ diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 701a351203..9b2df50f6c 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -700,6 +700,10 @@ String OS_Android::get_joy_guid(int p_device) const { return input->get_joy_guid_remapped(p_device); } +void OS_Android::vibrate_handheld(int p_duration_ms) { + godot_java->vibrate(p_duration_ms); +} + bool OS_Android::_check_internal_feature_support(const String &p_feature) { if (p_feature == "mobile") { //TODO support etc2 only if GLES3 driver is selected diff --git a/platform/android/os_android.h b/platform/android/os_android.h index e74d4cfd43..a17941f7c0 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -198,6 +198,7 @@ public: virtual bool is_joy_known(int p_device); virtual String get_joy_guid(int p_device) const; void joy_connection_changed(int p_device, bool p_connected, String p_name); + void vibrate_handheld(int p_duration_ms); virtual bool _check_internal_feature_support(const String &p_feature); OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion); diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index 64405bfa5b..3f1230faa8 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -37,6 +37,7 @@ #include "os_iphone.h" #import "GameController/GameController.h" +#import <AudioToolbox/AudioServices.h> #define kFilteringFactor 0.1 #define kRenderingFrequency 60 @@ -61,6 +62,10 @@ void _set_keep_screen_on(bool p_enabled) { [[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled]; }; +void _vibrate() { + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); +}; + @implementation AppDelegate @synthesize window; diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp index 825342f911..353078c51c 100644 --- a/platform/iphone/os_iphone.cpp +++ b/platform/iphone/os_iphone.cpp @@ -470,6 +470,7 @@ extern void _show_keyboard(String p_existing); extern void _hide_keyboard(); extern Error _shell_open(String p_uri); extern void _set_keep_screen_on(bool p_enabled); +extern void _vibrate(); void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect) { _show_keyboard(p_existing_text); @@ -585,6 +586,11 @@ void OSIPhone::native_video_stop() { _stop_video(); } +void OSIPhone::vibrate_handheld(int p_duration_ms) { + // iOS does not support duration for vibration + _vibrate(); +} + bool OSIPhone::_check_internal_feature_support(const String &p_feature) { return p_feature == "mobile"; diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index c16c29a858..804ba0b1af 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -195,6 +195,7 @@ public: virtual void native_video_unpause(); virtual void native_video_focus_out(); virtual void native_video_stop(); + virtual void vibrate_handheld(int p_duration_ms = 500); virtual bool _check_internal_feature_support(const String &p_feature); OSIPhone(int width, int height, String p_data_dir); diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index fc18661f62..68e3bc64fc 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -39,6 +39,10 @@ - (void)didReceiveMemoryWarning; +- (void)viewDidLoad; + +- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures; + - (BOOL)prefersStatusBarHidden; @end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 0358abf9e2..e52ad92bf2 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -83,6 +83,18 @@ int add_cmdline(int p_argc, char **p_args) { printf("*********** did receive memory warning!\n"); }; +- (void)viewDidLoad { + [super viewDidLoad]; + + if (@available(iOS 11.0, *)) { + [self setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; + } +} + +- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures { + return UIRectEdgeAll; +} + - (BOOL)shouldAutorotate { switch (OS::get_singleton()->get_screen_orientation()) { case OS::SCREEN_SENSOR: diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 662ee49935..ea110b11ca 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -30,9 +30,9 @@ #include "export.h" #include "core/bind/core_bind.h" +#include "core/crypto/crypto_core.h" #include "core/io/marshalls.h" #include "core/io/zip_io.h" -#include "core/math/crypto_core.h" #include "core/object.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 49da709a47..7cc937a64a 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -594,7 +594,7 @@ real_t RigidBody2D::get_mass() const { void RigidBody2D::set_inertia(real_t p_inertia) { - ERR_FAIL_COND(p_inertia <= 0); + ERR_FAIL_COND(p_inertia < 0); Physics2DServer::get_singleton()->body_set_param(get_rid(), Physics2DServer::BODY_PARAM_INERTIA, p_inertia); } @@ -1266,7 +1266,7 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const //all is a wall on_wall = true; } else { - if (collision.normal.dot(p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //floor + if (Math::acos(collision.normal.dot(p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; on_floor_body = collision.collider_rid; @@ -1281,7 +1281,7 @@ Vector2 KinematicBody2D::move_and_slide(const Vector2 &p_linear_velocity, const } } - } else if (collision.normal.dot(-p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //ceiling + } else if (Math::acos(collision.normal.dot(-p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; diff --git a/scene/3d/collision_polygon.cpp b/scene/3d/collision_polygon.cpp index db07059b32..37aa95fb43 100644 --- a/scene/3d/collision_polygon.cpp +++ b/scene/3d/collision_polygon.cpp @@ -151,6 +151,8 @@ float CollisionPolygon::get_depth() const { void CollisionPolygon::set_disabled(bool p_disabled) { disabled = p_disabled; + update_gizmo(); + if (parent) { parent->shape_owner_set_disabled(owner_id, p_disabled); } diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index 19d5f1dd3c..ebac968cb4 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -1192,7 +1192,7 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve //all is a wall on_wall = true; } else { - if (collision.normal.dot(p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //floor + if (Math::acos(collision.normal.dot(p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //floor on_floor = true; on_floor_body = collision.collider_rid; @@ -1209,7 +1209,7 @@ Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Ve is_on_slope = true; - } else if (collision.normal.dot(-p_floor_direction) >= Math::cos(p_floor_max_angle + FLOOR_ANGLE_THRESHOLD)) { //ceiling + } else if (Math::acos(collision.normal.dot(-p_floor_direction)) <= p_floor_max_angle + FLOOR_ANGLE_THRESHOLD) { //ceiling on_ceiling = true; } else { on_wall = true; diff --git a/scene/3d/ray_cast.cpp b/scene/3d/ray_cast.cpp index 10f92058e0..30eed8f1a7 100644 --- a/scene/3d/ray_cast.cpp +++ b/scene/3d/ray_cast.cpp @@ -102,6 +102,8 @@ Vector3 RayCast::get_collision_normal() const { void RayCast::set_enabled(bool p_enabled) { enabled = p_enabled; + update_gizmo(); + if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) set_physics_process_internal(p_enabled); if (!p_enabled) diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index b197971b61..1d529f4e72 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -247,15 +247,19 @@ void ColorPicker::_update_color(bool p_update_sliders) { } void ColorPicker::_update_presets() { + presets_per_row = 10; Size2 size = bt_add_preset->get_size(); - Size2 preset_size = Size2(size.width * presets.size(), size.height); + Size2 preset_size = Size2(MIN(size.width * presets.size(), presets_per_row * size.width), size.height * (Math::ceil((float)presets.size() / presets_per_row))); preset->set_custom_minimum_size(preset_size); - - preset->draw_texture_rect(get_icon("preset_bg", "ColorPicker"), Rect2(Point2(), preset_size), true); + preset_container->set_custom_minimum_size(preset_size); + preset->draw_rect(Rect2(Point2(), preset_size), Color(1, 1, 1, 0)); for (int i = 0; i < presets.size(); i++) { - preset->draw_rect(Rect2(Point2(size.width * i, 0), size), presets[i]); + int x = (i % presets_per_row) * size.width; + int y = (Math::floor((float)i / presets_per_row)) * size.height; + preset->draw_rect(Rect2(Point2(x, y), size), presets[i]); } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } void ColorPicker::_text_type_toggled() { @@ -288,8 +292,6 @@ void ColorPicker::add_preset(const Color &p_color) { presets.push_back(p_color); } preset->update(); - if (presets.size() == 10) - bt_add_preset->hide(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -533,14 +535,20 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - + int index = 0; if (bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { - int index = bev->get_position().x / (preset->get_size().x / presets.size()); + for (int i = 0; i < presets.size(); i++) { + int x = (i % presets_per_row) * bt_add_preset->get_size().x; + int y = (Math::floor((float)i / presets_per_row)) * bt_add_preset->get_size().y; + if (bev->get_position().x > x && bev->get_position().x < x + preset->get_size().x && bev->get_position().y > y && bev->get_position().y < y + preset->get_size().y) { + index = i; + } + } set_pick_color(presets[index]); _update_color(); emit_signal("color_changed", color); } else if (bev->is_pressed() && bev->get_button_index() == BUTTON_RIGHT && presets_enabled) { - int index = bev->get_position().x / (preset->get_size().x / presets.size()); + index = bev->get_position().x / (preset->get_size().x / presets.size()); Color clicked_preset = presets[index]; erase_preset(clicked_preset); emit_signal("preset_removed", clicked_preset); @@ -841,6 +849,7 @@ ColorPicker::ColorPicker() : add_child(preset_separator); preset_container = memnew(HBoxContainer); + preset_container->set_h_size_flags(SIZE_EXPAND_FILL); add_child(preset_container); preset = memnew(TextureRect); @@ -848,8 +857,11 @@ ColorPicker::ColorPicker() : preset->connect("gui_input", this, "_preset_input"); preset->connect("draw", this, "_update_presets"); + preset_container2 = memnew(HBoxContainer); + preset_container2->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(preset_container2); bt_add_preset = memnew(Button); - preset_container->add_child(bt_add_preset); + preset_container2->add_child(bt_add_preset); bt_add_preset->set_tooltip(TTR("Add current color as a preset.")); bt_add_preset->connect("pressed", this, "_add_preset_pressed"); } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 3af27a9856..167f7b33b3 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -54,6 +54,7 @@ private: TextureRect *sample; TextureRect *preset; HBoxContainer *preset_container; + HBoxContainer *preset_container2; HSeparator *preset_separator; Button *bt_add_preset; List<Color> presets; @@ -68,6 +69,7 @@ private: bool edit_alpha; Size2i ms; bool text_is_constructor; + int presets_per_row; Color color; bool raw_mode_enabled; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 9b9fc863dd..b655feecbe 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2925,10 +2925,10 @@ void Control::_bind_methods() { BIND_VMETHOD(MethodInfo(Variant::BOOL, "_clips_input")); ADD_GROUP("Anchor", "anchor_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.01,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.01,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.01,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.01,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_BOTTOM); ADD_GROUP("Margin", "margin_"); ADD_PROPERTYI(PropertyInfo(Variant::INT, "margin_left", PROPERTY_HINT_RANGE, "-4096,4096"), "set_margin", "get_margin", MARGIN_LEFT); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 7305d7459c..0c096f0d97 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -400,7 +400,7 @@ void FileDialog::update_file_list() { TreeItem *root = tree->create_item(); Ref<Texture> folder = get_icon("folder"); - const Color folder_color = get_color("folder"); + const Color folder_color = get_color("folder_icon_modulate"); List<String> files; List<String> dirs; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index a771794f25..f8c31121be 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -367,6 +367,10 @@ void TextEdit::_update_scrollbars() { total_width += cache.fold_gutter_width; } + if (draw_minimap) { + total_width += cache.minimap_width; + } + bool use_hscroll = true; bool use_vscroll = true; @@ -457,6 +461,7 @@ void TextEdit::_click_selection_held() { } void TextEdit::_update_selection_mode_pointer() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -472,6 +477,7 @@ void TextEdit::_update_selection_mode_pointer() { } void TextEdit::_update_selection_mode_word() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -528,6 +534,7 @@ void TextEdit::_update_selection_mode_word() { } void TextEdit::_update_selection_mode_line() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -552,6 +559,30 @@ void TextEdit::_update_selection_mode_line() { click_select_held->start(); } +void TextEdit::_update_minimap_scroll() { + Point2 mp = get_local_mouse_position(); + + int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); + if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) { + minimap_clicked = false; + return; + } + minimap_clicked = true; + dragging_minimap = true; + + int row; + _get_minimap_mouse_row(Point2i(mp.x, mp.y), row); + + int wi; + int first_line = row - num_lines_from_rows(row, 0, -get_visible_rows() / 2, wi) + 1; + double delta = get_scroll_pos_for_line(first_line, wi) - get_v_scroll(); + if (delta < 0) { + _scroll_up(-delta); + } else { + _scroll_down(delta); + } +} + void TextEdit::_notification(int p_what) { switch (p_what) { @@ -573,6 +604,7 @@ void TextEdit::_notification(int p_what) { _update_caches(); _update_wrap_at(); + syntax_highlighting_cache.clear(); } break; case MainLoop::NOTIFICATION_WM_FOCUS_IN: { window_has_focus = true; @@ -588,22 +620,24 @@ void TextEdit::_notification(int p_what) { if (scrolling && get_v_scroll() != target_v_scroll) { double target_y = target_v_scroll - get_v_scroll(); double dist = sqrt(target_y * target_y); - double vel = ((target_y / dist) * v_scroll_speed) * get_physics_process_delta_time(); + // To ensure minimap is responsive overide the speed setting. + double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { set_v_scroll(target_v_scroll); scrolling = false; + minimap_clicked = false; set_physics_process_internal(false); } else { set_v_scroll(get_v_scroll() + vel); } } else { scrolling = false; + minimap_clicked = false; set_physics_process_internal(false); } } break; case NOTIFICATION_DRAW: { - if (first_draw) { // Size may not be the final one, so attempts to ensure cursor was visible may have failed. adjust_viewport_to_cursor(); @@ -635,6 +669,11 @@ void TextEdit::_notification(int p_what) { cache.fold_gutter_width = 0; } + cache.minimap_width = 0; + if (draw_minimap) { + cache.minimap_width = minimap_width; + } + int line_number_char_count = 0; { @@ -658,7 +697,8 @@ void TextEdit::_notification(int p_what) { RID ci = get_canvas_item(); VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; - int xmargin_end = size.width - cache.style_normal->get_margin(MARGIN_RIGHT); + + int xmargin_end = size.width - cache.style_normal->get_margin(MARGIN_RIGHT) - cache.minimap_width; // Let's do it easy for now. cache.style_normal->draw(ci, Rect2(Point2(), size)); if (readonly) { @@ -847,9 +887,162 @@ void TextEdit::_notification(int p_what) { FontDrawer drawer(cache.font, Color(1, 1, 1)); - int line = get_first_visible_line() - 1; + int first_visible_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += times_line_wraps(line + 1); + draw_amount += times_line_wraps(first_visible_line + 1); + + // minimap + if (draw_minimap) { + int minimap_visible_lines = _get_minimap_visible_rows(); + int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); + int minimap_tab_size = minimap_char_size.x * indent_size; + + // calculate viewport size and y offset + int viewport_height = (draw_amount - 1) * minimap_line_height; + int control_height = size.height; + control_height -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) { + control_height -= h_scroll->get_size().height; + } + control_height -= viewport_height; + int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + + // calculate the first line. + int num_lines_before = round((viewport_offset_y) / minimap_line_height); + int wi; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + if (minimap_line >= 0) { + minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); + minimap_line -= (smooth_scroll_enabled ? 1 : 0); + } + int minimap_draw_amount = minimap_visible_lines + times_line_wraps(minimap_line + 1); + + // draw the minimap + Color viewport_color = cache.current_line_color; + viewport_color.a /= 2; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, cache.minimap_width, viewport_height), viewport_color); + for (int i = 0; i < minimap_draw_amount; i++) { + + minimap_line++; + + if (minimap_line < 0 || minimap_line >= (int)text.size()) { + break; + } + + while (is_line_hidden(minimap_line)) { + minimap_line++; + if (minimap_line < 0 || minimap_line >= (int)text.size()) { + break; + } + } + + Map<int, HighlighterInfo> color_map; + if (syntax_coloring) { + color_map = _get_line_syntax_highlighting(minimap_line); + } + + Color current_color = cache.font_color; + if (readonly) { + current_color = cache.font_color_readonly; + } + + Vector<String> wrap_rows = get_wrap_rows_text(minimap_line); + int line_wrap_amount = times_line_wraps(minimap_line); + int last_wrap_column = 0; + + for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) { + if (line_wrap_index != 0) { + i++; + if (i >= minimap_draw_amount) + break; + } + + const String &str = wrap_rows[line_wrap_index]; + int indent_px = line_wrap_index != 0 ? get_indent_level(minimap_line) : 0; + if (indent_px >= wrap_at) { + indent_px = 0; + } + indent_px = minimap_char_size.x * indent_px; + + if (line_wrap_index > 0) { + last_wrap_column += wrap_rows[line_wrap_index - 1].length(); + } + + if (minimap_line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color); + } + + Color previous_color; + int characters = 0; + int tabs = 0; + for (int j = 0; j < str.length(); j++) { + if (syntax_coloring) { + if (color_map.has(last_wrap_column + j)) { + current_color = color_map[last_wrap_column + j].color; + if (readonly) { + current_color.a = cache.font_color_readonly.a; + } + } + color = current_color; + } + + if (j == 0) { + previous_color = color; + } + + int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs; + bool out_of_bounds = (xpos >= xmargin_end + cache.minimap_width); + + bool is_whitespace = _is_whitespace(str[j]); + if (!is_whitespace) { + characters++; + + if (j < str.length() - 1 && color == previous_color && !out_of_bounds) { + continue; + } + + // If we've changed colour we are at the start of a new section, therefore we need to go back to the end + // of the previous section to draw it, we'll also add the character back on. + if (color != previous_color) { + characters--; + j--; + + if (str[j] == '\t') { + tabs -= minimap_tab_size; + } + } + } + + if (characters > 0) { + previous_color.a *= 0.6; + // take one for zero indexing, and if we hit whitespace / the end of a word. + int chars = MAX(0, (j - (characters - 1)) - (is_whitespace ? 1 : 0)) + 1; + int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color); + } + + if (out_of_bounds) { + break; + } + + // re-adjust if we went backwards. + if (color != previous_color && !is_whitespace) { + characters++; + } + + if (str[j] == '\t') { + tabs += minimap_tab_size; + } + + previous_color = color; + characters = 0; + } + } + } + } + + // draw main text + int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -1521,7 +1714,6 @@ void TextEdit::_notification(int p_what) { OS::get_singleton()->set_ime_active(true); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height())); } - } break; case NOTIFICATION_FOCUS_ENTER: { @@ -1862,6 +2054,66 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co r_col = col; } +void TextEdit::_get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const { + + float rows = p_mouse.y; + rows -= cache.style_normal->get_margin(MARGIN_TOP); + rows /= (minimap_char_size.y + minimap_line_spacing); + rows += get_v_scroll_offset(); + + // calculate visible lines + int minimap_visible_lines = _get_minimap_visible_rows(); + int visible_rows = get_visible_rows() + 1; + int first_visible_line = get_first_visible_line() - 1; + int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + draw_amount += times_line_wraps(first_visible_line + 1); + int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); + + // calculate viewport size and y offset + int viewport_height = (draw_amount - 1) * minimap_line_height; + int control_height = get_size().height; + control_height -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) { + control_height -= h_scroll->get_size().height; + } + control_height -= viewport_height; + int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + + // calculate the first line. + int num_lines_before = round((viewport_offset_y) / minimap_line_height); + int wi; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + if (first_visible_line > 0 && minimap_line >= 0) { + minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); + minimap_line -= (smooth_scroll_enabled ? 1 : 0); + } else { + minimap_line = 0; + } + + int row = minimap_line + Math::floor(rows); + int wrap_index = 0; + + if (is_wrap_enabled() || is_hiding_enabled()) { + + int f_ofs = num_lines_from_rows(minimap_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; + if (rows < 0) { + row = minimap_line - f_ofs; + } else { + row = minimap_line + f_ofs; + } + } + + if (row < 0) { + row = 0; + } + + if (row >= text.size()) { + row = text.size() - 1; + } + + r_row = row; +} + void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { double prev_v_scroll = v_scroll->get_value(); @@ -1986,6 +2238,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } + // minimap + if (draw_minimap) { + _update_minimap_scroll(); + if (dragging_minimap) { + return; + } + } + int prev_col = cursor.column; int prev_line = cursor.line; @@ -2100,8 +2360,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } else { - if (mb->get_button_index() == BUTTON_LEFT) + if (mb->get_button_index() == BUTTON_LEFT) { + dragging_minimap = false; + dragging_selection = false; click_select_held->stop(); + } // Notify to show soft keyboard. notification(NOTIFICATION_FOCUS_ENTER); @@ -2147,18 +2410,24 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mm->get_button_mask() & BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); - switch (selection.selecting_mode) { - case Selection::MODE_POINTER: { - _update_selection_mode_pointer(); - } break; - case Selection::MODE_WORD: { - _update_selection_mode_word(); - } break; - case Selection::MODE_LINE: { - _update_selection_mode_line(); - } break; - default: { - break; + if (draw_minimap && !dragging_selection) { + _update_minimap_scroll(); + } + + if (!dragging_minimap) { + switch (selection.selecting_mode) { + case Selection::MODE_POINTER: { + _update_selection_mode_pointer(); + } break; + case Selection::MODE_WORD: { + _update_selection_mode_word(); + } break; + case Selection::MODE_LINE: { + _update_selection_mode_line(); + } break; + default: { + break; + } } } } @@ -3343,8 +3612,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { void TextEdit::_scroll_up(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) { scrolling = false; + minimap_clicked = false; + } if (scrolling) { target_v_scroll = (target_v_scroll - p_delta); @@ -3369,8 +3640,10 @@ void TextEdit::_scroll_up(real_t p_delta) { void TextEdit::_scroll_down(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) { scrolling = false; + minimap_clicked = false; + } if (scrolling) { target_v_scroll = (target_v_scroll + p_delta); @@ -3419,6 +3692,7 @@ void TextEdit::_post_shift_selection() { void TextEdit::_scroll_lines_up() { scrolling = false; + minimap_clicked = false; // Adjust the vertical scroll. set_v_scroll(get_v_scroll() - 1); @@ -3438,6 +3712,7 @@ void TextEdit::_scroll_lines_up() { void TextEdit::_scroll_lines_down() { scrolling = false; + minimap_clicked = false; // Adjust the vertical scroll. set_v_scroll(get_v_scroll() + 1); @@ -3714,6 +3989,15 @@ void TextEdit::_line_edited_from(int p_line) { for (int i = p_line; i < cache_size; i++) { color_region_cache.erase(i); } + + if (syntax_highlighting_cache.size() > 0) { + cache_size = syntax_highlighting_cache.back()->key(); + for (int i = p_line - 1; i < cache_size; i++) { + if (syntax_highlighting_cache.has(i)) { + syntax_highlighting_cache.erase(i); + } + } + } } int TextEdit::get_char_count() { @@ -3745,6 +4029,15 @@ int TextEdit::get_visible_rows() const { return total; } +int TextEdit::_get_minimap_visible_rows() const { + int total = get_size().height; + total -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) + total -= h_scroll->get_size().height; + total /= (minimap_char_size.y + minimap_line_spacing); + return total; +} + int TextEdit::get_total_visible_rows() const { // Returns the total amount of rows we need in the editor. @@ -3764,7 +4057,7 @@ int TextEdit::get_total_visible_rows() const { void TextEdit::_update_wrap_at() { - wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - wrap_right_offset; + wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width - wrap_right_offset; update_cursor_wrap_offset(); text.clear_wrap_cache(); @@ -3781,6 +4074,7 @@ void TextEdit::adjust_viewport_to_cursor() { // Make sure cursor is visible on the screen. scrolling = false; + minimap_clicked = false; int cur_line = cursor.line; int cur_wrap = get_cursor_wrap_index(); @@ -3798,7 +4092,7 @@ void TextEdit::adjust_viewport_to_cursor() { set_line_as_last_visible(cur_line, cur_wrap); } - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width; if (v_scroll->is_visible_in_tree()) visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // Give it a little more space. @@ -3824,12 +4118,13 @@ void TextEdit::center_viewport_to_cursor() { // Move viewport so the cursor is in the center of the screen. scrolling = false; + minimap_clicked = false; if (is_line_hidden(cursor.line)) unfold_line(cursor.line); set_line_as_center_visible(cursor.line, get_cursor_wrap_index()); - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width; if (v_scroll->is_visible_in_tree()) visible_width -= v_scroll->get_combined_minimum_size().width; visible_width -= 20; // Give it a little more space. @@ -4112,6 +4407,7 @@ bool TextEdit::is_right_click_moving_caret() const { void TextEdit::_v_scroll_input() { scrolling = false; + minimap_clicked = false; } void TextEdit::_scroll_moved(double p_to_val) { @@ -4302,6 +4598,11 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { return CURSOR_ARROW; } else { + int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); + if (p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) { + return CURSOR_ARROW; + } + int row, col; _get_mouse_pos(p_pos, row, col); // EOL fold icon. @@ -4569,6 +4870,7 @@ void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter) syntax_highlighter->set_text_editor(this); syntax_highlighter->_update_cache(); } + syntax_highlighting_cache.clear(); update(); } @@ -4635,6 +4937,7 @@ void TextEdit::clear_colors() { keywords.clear(); color_regions.clear(); color_region_cache.clear(); + syntax_highlighting_cache.clear(); text.clear_width_cache(); } @@ -6112,10 +6415,30 @@ void TextEdit::query_code_comple() { c--; } - if (ofs > 0 && (inquote || _is_completable(l[ofs - 1]) || completion_prefixes.has(String::chr(l[ofs - 1])))) - emit_signal("request_completion"); - else if (ofs > 1 && l[ofs - 1] == ' ' && completion_prefixes.has(String::chr(l[ofs - 2]))) // Make it work with a space too, it's good enough. - emit_signal("request_completion"); + bool ignored = completion_active && !completion_options.empty(); + if (ignored) { + ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT; + const ScriptCodeCompletionOption *previous_option = NULL; + for (int i = 0; i < completion_options.size(); i++) { + const ScriptCodeCompletionOption ¤t_option = completion_options[i]; + if (!previous_option) { + previous_option = ¤t_option; + kind = current_option.kind; + } + if (previous_option->kind != current_option.kind) { + ignored = false; + break; + } + } + ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL); + } + + if (!ignored) { + if (ofs > 0 && (inquote || _is_completable(l[ofs - 1]) || completion_prefixes.has(String::chr(l[ofs - 1])))) + emit_signal("request_completion"); + else if (ofs > 1 && l[ofs - 1] == ' ' && completion_prefixes.has(String::chr(l[ofs - 2]))) // Make it work with a space too, it's good enough. + emit_signal("request_completion"); + } } void TextEdit::set_code_hint(const String &p_hint) { @@ -6310,6 +6633,24 @@ int TextEdit::get_info_gutter_width() const { return info_gutter_width; } +void TextEdit::set_draw_minimap(bool p_draw) { + draw_minimap = p_draw; + update(); +} + +bool TextEdit::is_drawing_minimap() const { + return draw_minimap; +} + +void TextEdit::set_minimap_width(int p_minimap_width) { + minimap_width = p_minimap_width; + update(); +} + +int TextEdit::get_minimap_width() const { + return minimap_width; +} + void TextEdit::set_hiding_enabled(bool p_enabled) { if (!p_enabled) unhide_all_lines(); @@ -6514,6 +6855,11 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_breakpoints"), &TextEdit::get_breakpoints_array); ClassDB::bind_method(D_METHOD("remove_breakpoints"), &TextEdit::remove_breakpoints); + ClassDB::bind_method(D_METHOD("draw_minimap", "draw"), &TextEdit::set_draw_minimap); + ClassDB::bind_method(D_METHOD("is_drawing_minimap"), &TextEdit::is_drawing_minimap); + ClassDB::bind_method(D_METHOD("set_minimap_width", "width"), &TextEdit::set_minimap_width); + ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); @@ -6531,6 +6877,10 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled"); + ADD_GROUP("Minimap", "minimap_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "draw_minimap", "is_drawing_minimap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width"), "set_minimap_width", "get_minimap_width"); + ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled"); @@ -6667,8 +7017,15 @@ TextEdit::TextEdit() { select_identifiers_enabled = false; smooth_scroll_enabled = false; scrolling = false; + minimap_clicked = false; + dragging_minimap = false; + dragging_selection = false; target_v_scroll = 0; v_scroll_speed = 80; + draw_minimap = false; + minimap_width = 80; + minimap_char_size = Point2(1, 2); + minimap_line_spacing = 1; context_menu_enabled = true; menu = memnew(PopupMenu); @@ -6687,8 +7044,14 @@ TextEdit::~TextEdit() { /////////////////////////////////////////////////////////////////////////////// Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighting_cache.has(p_line)) { + return syntax_highlighting_cache[p_line]; + } + if (syntax_highlighter != NULL) { - return syntax_highlighter->_get_line_syntax_highlighting(p_line); + Map<int, HighlighterInfo> color_map = syntax_highlighter->_get_line_syntax_highlighting(p_line); + syntax_highlighting_cache[p_line] = color_map; + return color_map; } Map<int, HighlighterInfo> color_map; @@ -6870,6 +7233,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int } } + syntax_highlighting_cache[p_line] = color_map; return color_map; } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index b47dac0902..889be3eaa5 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -211,9 +211,11 @@ private: int breakpoint_gutter_width; int fold_gutter_width; int info_gutter_width; + int minimap_width; } cache; Map<int, int> color_region_cache; + Map<int, Map<int, HighlighterInfo> > syntax_highlighting_cache; struct TextOperation { @@ -313,6 +315,10 @@ private: bool hiding_enabled; bool draw_info_gutter; int info_gutter_width; + bool draw_minimap; + int minimap_width; + Point2 minimap_char_size; + int minimap_line_spacing; bool highlight_all_occurrences; bool scroll_past_end_of_file_enabled; @@ -326,6 +332,9 @@ private: bool smooth_scroll_enabled; bool scrolling; + bool dragging_selection; + bool dragging_minimap; + bool minimap_clicked; float target_v_scroll; float v_scroll_speed; @@ -360,6 +369,8 @@ private: int get_visible_rows() const; int get_total_visible_rows() const; + int _get_minimap_visible_rows() const; + void update_cursor_wrap_offset(); void _update_wrap_at(); bool line_wraps(int line) const; @@ -395,6 +406,7 @@ private: void _update_selection_mode_word(); void _update_selection_mode_line(); + void _update_minimap_scroll(); void _scroll_up(real_t p_delta); void _scroll_down(real_t p_delta); @@ -484,6 +496,7 @@ public: virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const; void _get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const; + void _get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const; //void delete_char(); //void delete_line(); @@ -697,6 +710,12 @@ public: void set_info_gutter_width(int p_gutter_width); int get_info_gutter_width() const; + void set_draw_minimap(bool p_draw); + bool is_drawing_minimap() const; + + void set_minimap_width(int p_minimap_width); + int get_minimap_width() const; + void set_hiding_enabled(bool p_enabled); bool is_hiding_enabled() const; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 73f2b0b018..1a5f57ce48 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -760,7 +760,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const // FileDialog theme->set_icon("folder", "FileDialog", make_icon(icon_folder_png)); - theme->set_color("folder", "FileDialog", Color(1, 1, 1)); + theme->set_color("folder_icon_modulate", "FileDialog", Color(1, 1, 1)); theme->set_color("files_disabled", "FileDialog", Color(0, 0, 0, 0.7)); // colorPicker diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 08ce47692c..699410719c 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -2419,6 +2419,8 @@ void VisualShaderNodeGroupBase::_bind_methods() { ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &VisualShaderNodeGroupBase::set_editable); ClassDB::bind_method(D_METHOD("is_editable"), &VisualShaderNodeGroupBase::is_editable); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); } String VisualShaderNodeGroupBase::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { diff --git a/servers/physics/body_sw.cpp b/servers/physics/body_sw.cpp index 172a2a3429..a3bb581cb5 100644 --- a/servers/physics/body_sw.cpp +++ b/servers/physics/body_sw.cpp @@ -266,6 +266,7 @@ void BodySW::set_mode(PhysicsServer::BodyMode p_mode) { _inv_mass = mass > 0 ? (1.0 / mass) : 0; _set_static(false); + angular_velocity = Vector3(); } break; } diff --git a/servers/physics_2d/body_2d_sw.cpp b/servers/physics_2d/body_2d_sw.cpp index b9ebd30021..60fb3a3a95 100644 --- a/servers/physics_2d/body_2d_sw.cpp +++ b/servers/physics_2d/body_2d_sw.cpp @@ -47,8 +47,10 @@ void Body2DSW::update_inertias() { case Physics2DServer::BODY_MODE_RIGID: { - if (user_inertia) break; - + if (user_inertia) { + _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; + break; + } //update tensor for allshapes, not the best way but should be somehow OK. (inspired from bullet) real_t total_area = 0; @@ -57,7 +59,7 @@ void Body2DSW::update_inertias() { total_area += get_shape_aabb(i).get_area(); } - real_t _inertia = 0; + inertia = 0; for (int i = 0; i < get_shape_count(); i++) { @@ -73,15 +75,10 @@ void Body2DSW::update_inertias() { Transform2D mtx = get_shape_transform(i); Vector2 scale = mtx.get_scale(); - _inertia += shape->get_moment_of_inertia(mass, scale) + mass * mtx.get_origin().length_squared(); - //Rect2 ab = get_shape_aabb(i); - //_inertia+=mass*ab.size.dot(ab.size)/12.0f; + inertia += shape->get_moment_of_inertia(mass, scale) + mass * mtx.get_origin().length_squared(); } - if (_inertia != 0) - _inv_inertia = 1.0 / _inertia; - else - _inv_inertia = 0.0; //wathever + _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; if (mass) _inv_mass = 1.0 / mass; @@ -160,6 +157,7 @@ void Body2DSW::set_param(Physics2DServer::BodyParameter p_param, real_t p_value) _update_inertia(); } else { user_inertia = true; + inertia = p_value; _inv_inertia = 1.0 / p_value; } } break; @@ -194,7 +192,7 @@ real_t Body2DSW::get_param(Physics2DServer::BodyParameter p_param) const { return mass; } case Physics2DServer::BODY_PARAM_INERTIA: { - return _inv_inertia == 0 ? 0 : 1.0 / _inv_inertia; + return inertia; } case Physics2DServer::BODY_PARAM_GRAVITY_SCALE: { return gravity_scale; @@ -226,6 +224,7 @@ void Body2DSW::set_mode(Physics2DServer::BodyMode p_mode) { _set_inv_transform(get_transform().affine_inverse()); _inv_mass = 0; + _inv_inertia = 0; _set_static(p_mode == Physics2DServer::BODY_MODE_STATIC); set_active(p_mode == Physics2DServer::BODY_MODE_KINEMATIC && contacts.size()); linear_velocity = Vector2(); @@ -237,17 +236,21 @@ void Body2DSW::set_mode(Physics2DServer::BodyMode p_mode) { case Physics2DServer::BODY_MODE_RIGID: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; + _inv_inertia = inertia > 0 ? (1.0 / inertia) : 0; _set_static(false); } break; case Physics2DServer::BODY_MODE_CHARACTER: { _inv_mass = mass > 0 ? (1.0 / mass) : 0; + _inv_inertia = 0; _set_static(false); + angular_velocity = 0; } break; } - - _update_inertia(); + if (p_mode == Physics2DServer::BODY_MODE_RIGID && _inv_inertia == 0) { + _update_inertia(); + } /* if (get_space()) _update_queries(); diff --git a/servers/physics_2d/body_2d_sw.h b/servers/physics_2d/body_2d_sw.h index 8c64dc9230..5df184c894 100644 --- a/servers/physics_2d/body_2d_sw.h +++ b/servers/physics_2d/body_2d_sw.h @@ -52,6 +52,7 @@ class Body2DSW : public CollisionObject2DSW { real_t gravity_scale; real_t mass; + real_t inertia; real_t bounce; real_t friction; |