diff options
38 files changed, 1339 insertions, 1098 deletions
diff --git a/SConstruct b/SConstruct index f8e3a68edd..e5646a7d0a 100644 --- a/SConstruct +++ b/SConstruct @@ -551,11 +551,10 @@ if selected_platform in platform_list: if env["target"] == "release": if env["tools"]: - print("Tools can only be built with targets 'debug' and 'release_debug'.") + print("Error: The editor can only be built with `target=debug` or `target=release_debug`.") Exit(255) suffix += ".opt" env.Append(CPPDEFINES=["NDEBUG"]) - elif env["target"] == "release_debug": if env["tools"]: suffix += ".opt.tools" @@ -563,8 +562,14 @@ if selected_platform in platform_list: suffix += ".opt.debug" else: if env["tools"]: + print( + "Note: Building a debug binary (which will run slowly). Use `target=release_debug` to build an optimized release binary." + ) suffix += ".tools" else: + print( + "Note: Building a debug binary (which will run slowly). Use `target=release` to build an optimized release binary." + ) suffix += ".debug" if env["arch"] != "": diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 78f04e57d3..8000dd4290 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -30,9 +30,6 @@ #include "http_client.h" -#include "core/io/stream_peer_ssl.h" -#include "core/version.h" - const char *HTTPClient::_methods[METHOD_MAX] = { "GET", "HEAD", @@ -45,698 +42,23 @@ const char *HTTPClient::_methods[METHOD_MAX] = { "PATCH" }; -#ifndef JAVASCRIPT_ENABLED -Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { - close(); - - conn_port = p_port; - conn_host = p_host; - - ssl = p_ssl; - ssl_verify_host = p_verify_host; - - String host_lower = conn_host.to_lower(); - if (host_lower.begins_with("http://")) { - conn_host = conn_host.substr(7, conn_host.length() - 7); - } else if (host_lower.begins_with("https://")) { - ssl = true; - conn_host = conn_host.substr(8, conn_host.length() - 8); - } - - ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER); - - if (conn_port < 0) { - if (ssl) { - conn_port = PORT_HTTPS; - } else { - conn_port = PORT_HTTP; - } - } - - connection = tcp_connection; - - if (conn_host.is_valid_ip_address()) { - // Host contains valid IP - Error err = tcp_connection->connect_to_host(IPAddress(conn_host), p_port); - if (err) { - status = STATUS_CANT_CONNECT; - return err; - } - - status = STATUS_CONNECTING; - } else { - // Host contains hostname and needs to be resolved to IP - resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host); - status = STATUS_RESOLVING; +HTTPClient *HTTPClient::create() { + if (_create) { + return _create(); } - - return OK; + return nullptr; } -void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) { - ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object."); - - if (ssl) { - ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerSSL>(p_connection.ptr()), - "Connection is not a reference to a valid StreamPeerSSL object."); - } - - if (connection == p_connection) { - return; - } - - close(); - connection = p_connection; - status = STATUS_CONNECTED; +Error HTTPClient::_request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { + int size = p_body.size(); + return request(p_method, p_url, p_headers, size > 0 ? p_body.ptr() : nullptr, size); } -Ref<StreamPeer> HTTPClient::get_connection() const { - return connection; +Error HTTPClient::_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) { + int size = p_body.length(); + return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)p_body.utf8().get_data() : nullptr, size); } -static bool _check_request_url(HTTPClient::Method p_method, const String &p_url) { - switch (p_method) { - case HTTPClient::METHOD_CONNECT: { - // Authority in host:port format, as in RFC7231 - int pos = p_url.find_char(':'); - return 0 < pos && pos < p_url.length() - 1; - } - case HTTPClient::METHOD_OPTIONS: { - if (p_url == "*") { - return true; - } - [[fallthrough]]; - } - default: - // Absolute path or absolute URL - return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://"); - } -} - -Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { - ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA); - - String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n"; - bool add_host = true; - bool add_clen = p_body.size() > 0; - bool add_uagent = true; - bool add_accept = true; - for (int i = 0; i < p_headers.size(); i++) { - request += p_headers[i] + "\r\n"; - if (add_host && p_headers[i].findn("Host:") == 0) { - add_host = false; - } - if (add_clen && p_headers[i].findn("Content-Length:") == 0) { - add_clen = false; - } - if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { - add_uagent = false; - } - if (add_accept && p_headers[i].findn("Accept:") == 0) { - add_accept = false; - } - } - if (add_host) { - if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) { - // Don't append the standard ports - request += "Host: " + conn_host + "\r\n"; - } else { - request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; - } - } - if (add_clen) { - request += "Content-Length: " + itos(p_body.size()) + "\r\n"; - // Should it add utf8 encoding? - } - if (add_uagent) { - request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; - } - if (add_accept) { - request += "Accept: */*\r\n"; - } - request += "\r\n"; - CharString cs = request.utf8(); - - Vector<uint8_t> data; - data.resize(cs.length()); - { - uint8_t *data_write = data.ptrw(); - for (int i = 0; i < cs.length(); i++) { - data_write[i] = cs[i]; - } - } - - data.append_array(p_body); - - const uint8_t *r = data.ptr(); - Error err = connection->put_data(&r[0], data.size()); - - if (err) { - close(); - status = STATUS_CONNECTION_ERROR; - return err; - } - - status = STATUS_REQUESTING; - head_request = p_method == METHOD_HEAD; - - return OK; -} - -Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) { - ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA); - - String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n"; - bool add_host = true; - bool add_uagent = true; - bool add_accept = true; - bool add_clen = p_body.length() > 0; - for (int i = 0; i < p_headers.size(); i++) { - request += p_headers[i] + "\r\n"; - if (add_host && p_headers[i].findn("Host:") == 0) { - add_host = false; - } - if (add_clen && p_headers[i].findn("Content-Length:") == 0) { - add_clen = false; - } - if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { - add_uagent = false; - } - if (add_accept && p_headers[i].findn("Accept:") == 0) { - add_accept = false; - } - } - if (add_host) { - if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) { - // Don't append the standard ports - request += "Host: " + conn_host + "\r\n"; - } else { - request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; - } - } - if (add_clen) { - request += "Content-Length: " + itos(p_body.utf8().length()) + "\r\n"; - // Should it add utf8 encoding? - } - if (add_uagent) { - request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; - } - if (add_accept) { - request += "Accept: */*\r\n"; - } - request += "\r\n"; - request += p_body; - - CharString cs = request.utf8(); - Error err = connection->put_data((const uint8_t *)cs.ptr(), cs.length()); - if (err) { - close(); - status = STATUS_CONNECTION_ERROR; - return err; - } - - status = STATUS_REQUESTING; - head_request = p_method == METHOD_HEAD; - - return OK; -} - -bool HTTPClient::has_response() const { - return response_headers.size() != 0; -} - -bool HTTPClient::is_response_chunked() const { - return chunked; -} - -int HTTPClient::get_response_code() const { - return response_num; -} - -Error HTTPClient::get_response_headers(List<String> *r_response) { - if (!response_headers.size()) { - return ERR_INVALID_PARAMETER; - } - - for (int i = 0; i < response_headers.size(); i++) { - r_response->push_back(response_headers[i]); - } - - response_headers.clear(); - - return OK; -} - -void HTTPClient::close() { - if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) { - tcp_connection->disconnect_from_host(); - } - - connection.unref(); - status = STATUS_DISCONNECTED; - head_request = false; - if (resolving != IP::RESOLVER_INVALID_ID) { - IP::get_singleton()->erase_resolve_item(resolving); - resolving = IP::RESOLVER_INVALID_ID; - } - - response_headers.clear(); - response_str.clear(); - body_size = -1; - body_left = 0; - chunk_left = 0; - chunk_trailer_part = false; - read_until_eof = false; - response_num = 0; - handshaking = false; -} - -Error HTTPClient::poll() { - switch (status) { - case STATUS_RESOLVING: { - ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG); - - IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); - switch (rstatus) { - case IP::RESOLVER_STATUS_WAITING: - return OK; // Still resolving - - case IP::RESOLVER_STATUS_DONE: { - IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving); - Error err = tcp_connection->connect_to_host(host, conn_port); - IP::get_singleton()->erase_resolve_item(resolving); - resolving = IP::RESOLVER_INVALID_ID; - if (err) { - status = STATUS_CANT_CONNECT; - return err; - } - - status = STATUS_CONNECTING; - } break; - case IP::RESOLVER_STATUS_NONE: - case IP::RESOLVER_STATUS_ERROR: { - IP::get_singleton()->erase_resolve_item(resolving); - resolving = IP::RESOLVER_INVALID_ID; - close(); - status = STATUS_CANT_RESOLVE; - return ERR_CANT_RESOLVE; - } break; - } - } break; - case STATUS_CONNECTING: { - StreamPeerTCP::Status s = tcp_connection->get_status(); - switch (s) { - case StreamPeerTCP::STATUS_CONNECTING: { - return OK; - } break; - case StreamPeerTCP::STATUS_CONNECTED: { - if (ssl) { - Ref<StreamPeerSSL> ssl; - if (!handshaking) { - // Connect the StreamPeerSSL and start handshaking - ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); - ssl->set_blocking_handshake_enabled(false); - Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); - if (err != OK) { - close(); - status = STATUS_SSL_HANDSHAKE_ERROR; - return ERR_CANT_CONNECT; - } - connection = ssl; - handshaking = true; - } else { - // We are already handshaking, which means we can use your already active SSL connection - ssl = static_cast<Ref<StreamPeerSSL>>(connection); - if (ssl.is_null()) { - close(); - status = STATUS_SSL_HANDSHAKE_ERROR; - return ERR_CANT_CONNECT; - } - - ssl->poll(); // Try to finish the handshake - } - - if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { - // Handshake has been successful - handshaking = false; - status = STATUS_CONNECTED; - return OK; - } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { - // Handshake has failed - close(); - status = STATUS_SSL_HANDSHAKE_ERROR; - return ERR_CANT_CONNECT; - } - // ... we will need to poll more for handshake to finish - } else { - status = STATUS_CONNECTED; - } - return OK; - } break; - case StreamPeerTCP::STATUS_ERROR: - case StreamPeerTCP::STATUS_NONE: { - close(); - status = STATUS_CANT_CONNECT; - return ERR_CANT_CONNECT; - } break; - } - } break; - case STATUS_BODY: - case STATUS_CONNECTED: { - // Check if we are still connected - if (ssl) { - Ref<StreamPeerSSL> tmp = connection; - tmp->poll(); - if (tmp->get_status() != StreamPeerSSL::STATUS_CONNECTED) { - status = STATUS_CONNECTION_ERROR; - return ERR_CONNECTION_ERROR; - } - } else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) { - status = STATUS_CONNECTION_ERROR; - return ERR_CONNECTION_ERROR; - } - // Connection established, requests can now be made - return OK; - } break; - case STATUS_REQUESTING: { - while (true) { - uint8_t byte; - int rec = 0; - Error err = _get_http_data(&byte, 1, rec); - if (err != OK) { - close(); - status = STATUS_CONNECTION_ERROR; - return ERR_CONNECTION_ERROR; - } - - if (rec == 0) { - return OK; // Still requesting, keep trying! - } - - response_str.push_back(byte); - int rs = response_str.size(); - if ( - (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') || - (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) { - // End of response, parse. - response_str.push_back(0); - String response; - response.parse_utf8((const char *)response_str.ptr()); - Vector<String> responses = response.split("\n"); - body_size = -1; - chunked = false; - body_left = 0; - chunk_left = 0; - chunk_trailer_part = false; - read_until_eof = false; - response_str.clear(); - response_headers.clear(); - response_num = RESPONSE_OK; - - // Per the HTTP 1.1 spec, keep-alive is the default. - // Not following that specification breaks standard implementations. - // Broken web servers should be fixed. - bool keep_alive = true; - - for (int i = 0; i < responses.size(); i++) { - String header = responses[i].strip_edges(); - String s = header.to_lower(); - if (s.length() == 0) { - continue; - } - if (s.begins_with("content-length:")) { - body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int(); - body_left = body_size; - - } else if (s.begins_with("transfer-encoding:")) { - String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges(); - if (encoding == "chunked") { - chunked = true; - } - } else if (s.begins_with("connection: close")) { - keep_alive = false; - } - - if (i == 0 && responses[i].begins_with("HTTP")) { - String num = responses[i].get_slicec(' ', 1); - response_num = num.to_int(); - } else { - response_headers.push_back(header); - } - } - - // This is a HEAD request, we won't receive anything. - if (head_request) { - body_size = 0; - body_left = 0; - } - - if (body_size != -1 || chunked) { - status = STATUS_BODY; - } else if (!keep_alive) { - read_until_eof = true; - status = STATUS_BODY; - } else { - status = STATUS_CONNECTED; - } - return OK; - } - } - } break; - case STATUS_DISCONNECTED: { - return ERR_UNCONFIGURED; - } break; - case STATUS_CONNECTION_ERROR: - case STATUS_SSL_HANDSHAKE_ERROR: { - return ERR_CONNECTION_ERROR; - } break; - case STATUS_CANT_CONNECT: { - return ERR_CANT_CONNECT; - } break; - case STATUS_CANT_RESOLVE: { - return ERR_CANT_RESOLVE; - } break; - } - - return OK; -} - -int HTTPClient::get_response_body_length() const { - return body_size; -} - -PackedByteArray HTTPClient::read_response_body_chunk() { - ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); - - PackedByteArray ret; - Error err = OK; - - if (chunked) { - while (true) { - if (chunk_trailer_part) { - // We need to consume the trailer part too or keep-alive will break - uint8_t b; - int rec = 0; - err = _get_http_data(&b, 1, rec); - - if (rec == 0) { - break; - } - - chunk.push_back(b); - int cs = chunk.size(); - if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) { - if (cs == 2) { - // Finally over - chunk_trailer_part = false; - status = STATUS_CONNECTED; - chunk.clear(); - break; - } else { - // We do not process nor return the trailer data - chunk.clear(); - } - } - } else if (chunk_left == 0) { - // Reading length - uint8_t b; - int rec = 0; - err = _get_http_data(&b, 1, rec); - - if (rec == 0) { - break; - } - - chunk.push_back(b); - - if (chunk.size() > 32) { - ERR_PRINT("HTTP Invalid chunk hex len"); - status = STATUS_CONNECTION_ERROR; - break; - } - - if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') { - int len = 0; - for (int i = 0; i < chunk.size() - 2; i++) { - char c = chunk[i]; - int v = 0; - if (c >= '0' && c <= '9') { - v = c - '0'; - } else if (c >= 'a' && c <= 'f') { - v = c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - v = c - 'A' + 10; - } else { - ERR_PRINT("HTTP Chunk len not in hex!!"); - status = STATUS_CONNECTION_ERROR; - break; - } - len <<= 4; - len |= v; - if (len > (1 << 24)) { - ERR_PRINT("HTTP Chunk too big!! >16mb"); - status = STATUS_CONNECTION_ERROR; - break; - } - } - - if (len == 0) { - // End reached! - chunk_trailer_part = true; - chunk.clear(); - break; - } - - chunk_left = len + 2; - chunk.resize(chunk_left); - } - } else { - int rec = 0; - err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec); - if (rec == 0) { - break; - } - chunk_left -= rec; - - if (chunk_left == 0) { - if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') { - ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)"); - status = STATUS_CONNECTION_ERROR; - break; - } - - ret.resize(chunk.size() - 2); - uint8_t *w = ret.ptrw(); - memcpy(w, chunk.ptr(), chunk.size() - 2); - chunk.clear(); - } - - break; - } - } - - } else { - int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; - ret.resize(to_read); - int _offset = 0; - while (to_read > 0) { - int rec = 0; - { - uint8_t *w = ret.ptrw(); - err = _get_http_data(w + _offset, to_read, rec); - } - if (rec <= 0) { // Ended up reading less - ret.resize(_offset); - break; - } else { - _offset += rec; - to_read -= rec; - if (!read_until_eof) { - body_left -= rec; - } - } - if (err != OK) { - break; - } - } - } - - if (err != OK) { - close(); - - if (err == ERR_FILE_EOF) { - status = STATUS_DISCONNECTED; // Server disconnected - } else { - status = STATUS_CONNECTION_ERROR; - } - } else if (body_left == 0 && !chunked && !read_until_eof) { - status = STATUS_CONNECTED; - } - - return ret; -} - -HTTPClient::Status HTTPClient::get_status() const { - return status; -} - -void HTTPClient::set_blocking_mode(bool p_enable) { - blocking = p_enable; -} - -bool HTTPClient::is_blocking_mode_enabled() const { - return blocking; -} - -Error HTTPClient::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) { - if (blocking) { - // We can't use StreamPeer.get_data, since when reaching EOF we will get an - // error without knowing how many bytes we received. - Error err = ERR_FILE_EOF; - int read = 0; - int left = p_bytes; - r_received = 0; - while (left > 0) { - err = connection->get_partial_data(p_buffer + r_received, left, read); - if (err == OK) { - r_received += read; - } else if (err == ERR_FILE_EOF) { - r_received += read; - return err; - } else { - return err; - } - left -= read; - } - return err; - } else { - return connection->get_partial_data(p_buffer, p_bytes, r_received); - } -} - -void HTTPClient::set_read_chunk_size(int p_size) { - ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24)); - read_chunk_size = p_size; -} - -int HTTPClient::get_read_chunk_size() const { - return read_chunk_size; -} - -HTTPClient::HTTPClient() { - tcp_connection.instantiate(); -} - -HTTPClient::~HTTPClient() {} - -#endif // #ifndef JAVASCRIPT_ENABLED - String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { String query = ""; Array keys = p_dict.keys(); @@ -802,8 +124,8 @@ void HTTPClient::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "use_ssl", "verify_host"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("set_connection", "connection"), &HTTPClient::set_connection); ClassDB::bind_method(D_METHOD("get_connection"), &HTTPClient::get_connection); - ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::request_raw); - ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::request, DEFVAL(String())); + ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::_request_raw); + ClassDB::bind_method(D_METHOD("request", "method", "url", "headers", "body"), &HTTPClient::_request, DEFVAL(String())); ClassDB::bind_method(D_METHOD("close"), &HTTPClient::close); ClassDB::bind_method(D_METHOD("has_response"), &HTTPClient::has_response); diff --git a/core/io/http_client.h b/core/io/http_client.h index f70999836f..718c3a905e 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -142,7 +142,7 @@ public: }; -private: +protected: static const char *_methods[METHOD_MAX]; static const int HOST_MIN_LEN = 4; @@ -152,79 +152,48 @@ private: }; -#ifndef JAVASCRIPT_ENABLED - Status status = STATUS_DISCONNECTED; - IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; - int conn_port = -1; - String conn_host; - bool ssl = false; - bool ssl_verify_host = false; - bool blocking = false; - bool handshaking = false; - bool head_request = false; - - Vector<uint8_t> response_str; - - bool chunked = false; - Vector<uint8_t> chunk; - int chunk_left = 0; - bool chunk_trailer_part = false; - int body_size = -1; - int body_left = 0; - bool read_until_eof = false; - - Ref<StreamPeerTCP> tcp_connection; - Ref<StreamPeer> connection; - - int response_num = 0; - Vector<String> response_headers; - // 64 KiB by default (favors fast download speeds at the cost of memory usage). - int read_chunk_size = 65536; - - Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received); - -#else -#include "platform/javascript/http_client.h.inc" -#endif - PackedStringArray _get_response_headers(); Dictionary _get_response_headers_as_dictionary(); + Error _request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body); + Error _request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String()); + + static HTTPClient *(*_create)(); static void _bind_methods(); public: - Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true); + static HTTPClient *create(); - void set_connection(const Ref<StreamPeer> &p_connection); - Ref<StreamPeer> get_connection() const; + String query_string_from_dict(const Dictionary &p_dict); - Error request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body); - Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body = String()); + virtual Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) = 0; + virtual Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) = 0; - void close(); + virtual void set_connection(const Ref<StreamPeer> &p_connection) = 0; + virtual Ref<StreamPeer> get_connection() const = 0; - Status get_status() const; + virtual void close() = 0; - bool has_response() const; - bool is_response_chunked() const; - int get_response_code() const; - Error get_response_headers(List<String> *r_response); - int get_response_body_length() const; + virtual Status get_status() const = 0; - PackedByteArray read_response_body_chunk(); // Can't get body as partial text because of most encodings UTF8, gzip, etc. + virtual bool has_response() const = 0; + virtual bool is_response_chunked() const = 0; + virtual int get_response_code() const = 0; + virtual Error get_response_headers(List<String> *r_response) = 0; + virtual int get_response_body_length() const = 0; - void set_blocking_mode(bool p_enable); // Useful mostly if running in a thread - bool is_blocking_mode_enabled() const; + virtual PackedByteArray read_response_body_chunk() = 0; // Can't get body as partial text because of most encodings UTF8, gzip, etc. - void set_read_chunk_size(int p_size); - int get_read_chunk_size() const; + virtual void set_blocking_mode(bool p_enable) = 0; // Useful mostly if running in a thread + virtual bool is_blocking_mode_enabled() const = 0; - Error poll(); + virtual void set_read_chunk_size(int p_size) = 0; + virtual int get_read_chunk_size() const = 0; - String query_string_from_dict(const Dictionary &p_dict); + virtual Error poll() = 0; - HTTPClient(); - ~HTTPClient(); + HTTPClient() {} + virtual ~HTTPClient() {} }; VARIANT_ENUM_CAST(HTTPClient::ResponseCode) diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp new file mode 100644 index 0000000000..f9b3165a07 --- /dev/null +++ b/core/io/http_client_tcp.cpp @@ -0,0 +1,666 @@ +/*************************************************************************/ +/* http_client_tcp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 JAVASCRIPT_ENABLED + +#include "http_client_tcp.h" + +#include "core/io/stream_peer_ssl.h" +#include "core/version.h" + +HTTPClient *HTTPClientTCP::_create_func() { + return memnew(HTTPClientTCP); +} + +Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { + close(); + + conn_port = p_port; + conn_host = p_host; + + ssl = p_ssl; + ssl_verify_host = p_verify_host; + + String host_lower = conn_host.to_lower(); + if (host_lower.begins_with("http://")) { + conn_host = conn_host.substr(7, conn_host.length() - 7); + } else if (host_lower.begins_with("https://")) { + ssl = true; + conn_host = conn_host.substr(8, conn_host.length() - 8); + } + + ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER); + + if (conn_port < 0) { + if (ssl) { + conn_port = PORT_HTTPS; + } else { + conn_port = PORT_HTTP; + } + } + + connection = tcp_connection; + + if (conn_host.is_valid_ip_address()) { + // Host contains valid IP + Error err = tcp_connection->connect_to_host(IPAddress(conn_host), p_port); + if (err) { + status = STATUS_CANT_CONNECT; + return err; + } + + status = STATUS_CONNECTING; + } else { + // Host contains hostname and needs to be resolved to IP + resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host); + status = STATUS_RESOLVING; + } + + return OK; +} + +void HTTPClientTCP::set_connection(const Ref<StreamPeer> &p_connection) { + ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object."); + + if (ssl) { + ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerSSL>(p_connection.ptr()), + "Connection is not a reference to a valid StreamPeerSSL object."); + } + + if (connection == p_connection) { + return; + } + + close(); + connection = p_connection; + status = STATUS_CONNECTED; +} + +Ref<StreamPeer> HTTPClientTCP::get_connection() const { + return connection; +} + +static bool _check_request_url(HTTPClientTCP::Method p_method, const String &p_url) { + switch (p_method) { + case HTTPClientTCP::METHOD_CONNECT: { + // Authority in host:port format, as in RFC7231 + int pos = p_url.find_char(':'); + return 0 < pos && pos < p_url.length() - 1; + } + case HTTPClientTCP::METHOD_OPTIONS: { + if (p_url == "*") { + return true; + } + [[fallthrough]]; + } + default: + // Absolute path or absolute URL + return p_url.begins_with("/") || p_url.begins_with("http://") || p_url.begins_with("https://"); + } +} + +Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) { + ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!_check_request_url(p_method, p_url), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA); + + String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n"; + bool add_host = true; + bool add_clen = p_body_size > 0; + bool add_uagent = true; + bool add_accept = true; + for (int i = 0; i < p_headers.size(); i++) { + request += p_headers[i] + "\r\n"; + if (add_host && p_headers[i].findn("Host:") == 0) { + add_host = false; + } + if (add_clen && p_headers[i].findn("Content-Length:") == 0) { + add_clen = false; + } + if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { + add_uagent = false; + } + if (add_accept && p_headers[i].findn("Accept:") == 0) { + add_accept = false; + } + } + if (add_host) { + if ((ssl && conn_port == PORT_HTTPS) || (!ssl && conn_port == PORT_HTTP)) { + // Don't append the standard ports + request += "Host: " + conn_host + "\r\n"; + } else { + request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; + } + } + if (add_clen) { + request += "Content-Length: " + itos(p_body_size) + "\r\n"; + // Should it add utf8 encoding? + } + if (add_uagent) { + request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; + } + if (add_accept) { + request += "Accept: */*\r\n"; + } + request += "\r\n"; + CharString cs = request.utf8(); + + Vector<uint8_t> data; + data.resize(cs.length() + p_body_size); + memcpy(data.ptrw(), cs.get_data(), cs.length()); + if (p_body_size > 0) { + memcpy(data.ptrw() + cs.length(), p_body, p_body_size); + } + + // TODO Implement non-blocking requests. + Error err = connection->put_data(data.ptr(), data.size()); + + if (err) { + close(); + status = STATUS_CONNECTION_ERROR; + return err; + } + + status = STATUS_REQUESTING; + head_request = p_method == METHOD_HEAD; + + return OK; +} + +bool HTTPClientTCP::has_response() const { + return response_headers.size() != 0; +} + +bool HTTPClientTCP::is_response_chunked() const { + return chunked; +} + +int HTTPClientTCP::get_response_code() const { + return response_num; +} + +Error HTTPClientTCP::get_response_headers(List<String> *r_response) { + if (!response_headers.size()) { + return ERR_INVALID_PARAMETER; + } + + for (int i = 0; i < response_headers.size(); i++) { + r_response->push_back(response_headers[i]); + } + + response_headers.clear(); + + return OK; +} + +void HTTPClientTCP::close() { + if (tcp_connection->get_status() != StreamPeerTCP::STATUS_NONE) { + tcp_connection->disconnect_from_host(); + } + + connection.unref(); + status = STATUS_DISCONNECTED; + head_request = false; + if (resolving != IP::RESOLVER_INVALID_ID) { + IP::get_singleton()->erase_resolve_item(resolving); + resolving = IP::RESOLVER_INVALID_ID; + } + + response_headers.clear(); + response_str.clear(); + body_size = -1; + body_left = 0; + chunk_left = 0; + chunk_trailer_part = false; + read_until_eof = false; + response_num = 0; + handshaking = false; +} + +Error HTTPClientTCP::poll() { + switch (status) { + case STATUS_RESOLVING: { + ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG); + + IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); + switch (rstatus) { + case IP::RESOLVER_STATUS_WAITING: + return OK; // Still resolving + + case IP::RESOLVER_STATUS_DONE: { + IPAddress host = IP::get_singleton()->get_resolve_item_address(resolving); + Error err = tcp_connection->connect_to_host(host, conn_port); + IP::get_singleton()->erase_resolve_item(resolving); + resolving = IP::RESOLVER_INVALID_ID; + if (err) { + status = STATUS_CANT_CONNECT; + return err; + } + + status = STATUS_CONNECTING; + } break; + case IP::RESOLVER_STATUS_NONE: + case IP::RESOLVER_STATUS_ERROR: { + IP::get_singleton()->erase_resolve_item(resolving); + resolving = IP::RESOLVER_INVALID_ID; + close(); + status = STATUS_CANT_RESOLVE; + return ERR_CANT_RESOLVE; + } break; + } + } break; + case STATUS_CONNECTING: { + StreamPeerTCP::Status s = tcp_connection->get_status(); + switch (s) { + case StreamPeerTCP::STATUS_CONNECTING: { + return OK; + } break; + case StreamPeerTCP::STATUS_CONNECTED: { + if (ssl) { + Ref<StreamPeerSSL> ssl; + if (!handshaking) { + // Connect the StreamPeerSSL and start handshaking + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ssl->set_blocking_handshake_enabled(false); + Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); + if (err != OK) { + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + connection = ssl; + handshaking = true; + } else { + // We are already handshaking, which means we can use your already active SSL connection + ssl = static_cast<Ref<StreamPeerSSL>>(connection); + if (ssl.is_null()) { + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + + ssl->poll(); // Try to finish the handshake + } + + if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { + // Handshake has been successful + handshaking = false; + status = STATUS_CONNECTED; + return OK; + } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { + // Handshake has failed + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + // ... we will need to poll more for handshake to finish + } else { + status = STATUS_CONNECTED; + } + return OK; + } break; + case StreamPeerTCP::STATUS_ERROR: + case StreamPeerTCP::STATUS_NONE: { + close(); + status = STATUS_CANT_CONNECT; + return ERR_CANT_CONNECT; + } break; + } + } break; + case STATUS_BODY: + case STATUS_CONNECTED: { + // Check if we are still connected + if (ssl) { + Ref<StreamPeerSSL> tmp = connection; + tmp->poll(); + if (tmp->get_status() != StreamPeerSSL::STATUS_CONNECTED) { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + } else if (tcp_connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + // Connection established, requests can now be made + return OK; + } break; + case STATUS_REQUESTING: { + while (true) { + uint8_t byte; + int rec = 0; + Error err = _get_http_data(&byte, 1, rec); + if (err != OK) { + close(); + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + + if (rec == 0) { + return OK; // Still requesting, keep trying! + } + + response_str.push_back(byte); + int rs = response_str.size(); + if ( + (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') || + (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) { + // End of response, parse. + response_str.push_back(0); + String response; + response.parse_utf8((const char *)response_str.ptr()); + Vector<String> responses = response.split("\n"); + body_size = -1; + chunked = false; + body_left = 0; + chunk_left = 0; + chunk_trailer_part = false; + read_until_eof = false; + response_str.clear(); + response_headers.clear(); + response_num = RESPONSE_OK; + + // Per the HTTP 1.1 spec, keep-alive is the default. + // Not following that specification breaks standard implementations. + // Broken web servers should be fixed. + bool keep_alive = true; + + for (int i = 0; i < responses.size(); i++) { + String header = responses[i].strip_edges(); + String s = header.to_lower(); + if (s.length() == 0) { + continue; + } + if (s.begins_with("content-length:")) { + body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int(); + body_left = body_size; + + } else if (s.begins_with("transfer-encoding:")) { + String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges(); + if (encoding == "chunked") { + chunked = true; + } + } else if (s.begins_with("connection: close")) { + keep_alive = false; + } + + if (i == 0 && responses[i].begins_with("HTTP")) { + String num = responses[i].get_slicec(' ', 1); + response_num = num.to_int(); + } else { + response_headers.push_back(header); + } + } + + // This is a HEAD request, we won't receive anything. + if (head_request) { + body_size = 0; + body_left = 0; + } + + if (body_size != -1 || chunked) { + status = STATUS_BODY; + } else if (!keep_alive) { + read_until_eof = true; + status = STATUS_BODY; + } else { + status = STATUS_CONNECTED; + } + return OK; + } + } + } break; + case STATUS_DISCONNECTED: { + return ERR_UNCONFIGURED; + } break; + case STATUS_CONNECTION_ERROR: + case STATUS_SSL_HANDSHAKE_ERROR: { + return ERR_CONNECTION_ERROR; + } break; + case STATUS_CANT_CONNECT: { + return ERR_CANT_CONNECT; + } break; + case STATUS_CANT_RESOLVE: { + return ERR_CANT_RESOLVE; + } break; + } + + return OK; +} + +int HTTPClientTCP::get_response_body_length() const { + return body_size; +} + +PackedByteArray HTTPClientTCP::read_response_body_chunk() { + ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); + + PackedByteArray ret; + Error err = OK; + + if (chunked) { + while (true) { + if (chunk_trailer_part) { + // We need to consume the trailer part too or keep-alive will break + uint8_t b; + int rec = 0; + err = _get_http_data(&b, 1, rec); + + if (rec == 0) { + break; + } + + chunk.push_back(b); + int cs = chunk.size(); + if ((cs >= 2 && chunk[cs - 2] == '\r' && chunk[cs - 1] == '\n')) { + if (cs == 2) { + // Finally over + chunk_trailer_part = false; + status = STATUS_CONNECTED; + chunk.clear(); + break; + } else { + // We do not process nor return the trailer data + chunk.clear(); + } + } + } else if (chunk_left == 0) { + // Reading length + uint8_t b; + int rec = 0; + err = _get_http_data(&b, 1, rec); + + if (rec == 0) { + break; + } + + chunk.push_back(b); + + if (chunk.size() > 32) { + ERR_PRINT("HTTP Invalid chunk hex len"); + status = STATUS_CONNECTION_ERROR; + break; + } + + if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') { + int len = 0; + for (int i = 0; i < chunk.size() - 2; i++) { + char c = chunk[i]; + int v = 0; + if (c >= '0' && c <= '9') { + v = c - '0'; + } else if (c >= 'a' && c <= 'f') { + v = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + v = c - 'A' + 10; + } else { + ERR_PRINT("HTTP Chunk len not in hex!!"); + status = STATUS_CONNECTION_ERROR; + break; + } + len <<= 4; + len |= v; + if (len > (1 << 24)) { + ERR_PRINT("HTTP Chunk too big!! >16mb"); + status = STATUS_CONNECTION_ERROR; + break; + } + } + + if (len == 0) { + // End reached! + chunk_trailer_part = true; + chunk.clear(); + break; + } + + chunk_left = len + 2; + chunk.resize(chunk_left); + } + } else { + int rec = 0; + err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec); + if (rec == 0) { + break; + } + chunk_left -= rec; + + if (chunk_left == 0) { + if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') { + ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)"); + status = STATUS_CONNECTION_ERROR; + break; + } + + ret.resize(chunk.size() - 2); + uint8_t *w = ret.ptrw(); + memcpy(w, chunk.ptr(), chunk.size() - 2); + chunk.clear(); + } + + break; + } + } + + } else { + int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; + ret.resize(to_read); + int _offset = 0; + while (to_read > 0) { + int rec = 0; + { + uint8_t *w = ret.ptrw(); + err = _get_http_data(w + _offset, to_read, rec); + } + if (rec <= 0) { // Ended up reading less + ret.resize(_offset); + break; + } else { + _offset += rec; + to_read -= rec; + if (!read_until_eof) { + body_left -= rec; + } + } + if (err != OK) { + break; + } + } + } + + if (err != OK) { + close(); + + if (err == ERR_FILE_EOF) { + status = STATUS_DISCONNECTED; // Server disconnected + } else { + status = STATUS_CONNECTION_ERROR; + } + } else if (body_left == 0 && !chunked && !read_until_eof) { + status = STATUS_CONNECTED; + } + + return ret; +} + +HTTPClientTCP::Status HTTPClientTCP::get_status() const { + return status; +} + +void HTTPClientTCP::set_blocking_mode(bool p_enable) { + blocking = p_enable; +} + +bool HTTPClientTCP::is_blocking_mode_enabled() const { + return blocking; +} + +Error HTTPClientTCP::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received) { + if (blocking) { + // We can't use StreamPeer.get_data, since when reaching EOF we will get an + // error without knowing how many bytes we received. + Error err = ERR_FILE_EOF; + int read = 0; + int left = p_bytes; + r_received = 0; + while (left > 0) { + err = connection->get_partial_data(p_buffer + r_received, left, read); + if (err == OK) { + r_received += read; + } else if (err == ERR_FILE_EOF) { + r_received += read; + return err; + } else { + return err; + } + left -= read; + } + return err; + } else { + return connection->get_partial_data(p_buffer, p_bytes, r_received); + } +} + +void HTTPClientTCP::set_read_chunk_size(int p_size) { + ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24)); + read_chunk_size = p_size; +} + +int HTTPClientTCP::get_read_chunk_size() const { + return read_chunk_size; +} + +HTTPClientTCP::HTTPClientTCP() { + tcp_connection.instantiate(); +} + +HTTPClient *(*HTTPClient::_create)() = HTTPClientTCP::_create_func; + +#endif // #ifndef JAVASCRIPT_ENABLED diff --git a/platform/javascript/http_client.h.inc b/core/io/http_client_tcp.h index 6544d41c98..e178399fbe 100644 --- a/platform/javascript/http_client.h.inc +++ b/core/io/http_client_tcp.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* http_client.h.inc */ +/* http_client_tcp.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,24 +28,65 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -// HTTPClient's additional private members in the javascript platform +#ifndef HTTP_CLIENT_TCP_H +#define HTTP_CLIENT_TCP_H -Error make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len); -static void _parse_headers(int p_len, const char **p_headers, void *p_ref); +#include "http_client.h" -int js_id = 0; -// 64 KiB by default (favors fast download speeds at the cost of memory usage). -int read_limit = 65536; -Status status = STATUS_DISCONNECTED; +class HTTPClientTCP : public HTTPClient { +private: + Status status = STATUS_DISCONNECTED; + IP::ResolverID resolving = IP::RESOLVER_INVALID_ID; + int conn_port = -1; + String conn_host; + bool ssl = false; + bool ssl_verify_host = false; + bool blocking = false; + bool handshaking = false; + bool head_request = false; -String host; -int port = -1; -bool use_tls = false; + Vector<uint8_t> response_str; -int polled_response_code = 0; -Vector<String> response_headers; -Vector<uint8_t> response_buffer; + bool chunked = false; + Vector<uint8_t> chunk; + int chunk_left = 0; + bool chunk_trailer_part = false; + int body_size = -1; + int body_left = 0; + bool read_until_eof = false; -#ifdef DEBUG_ENABLED -uint64_t last_polling_frame = 0; -#endif + Ref<StreamPeerTCP> tcp_connection; + Ref<StreamPeer> connection; + + int response_num = 0; + Vector<String> response_headers; + // 64 KiB by default (favors fast download speeds at the cost of memory usage). + int read_chunk_size = 65536; + + Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received); + +public: + static HTTPClient *_create_func(); + + Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override; + + Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) override; + void set_connection(const Ref<StreamPeer> &p_connection) override; + Ref<StreamPeer> get_connection() const override; + void close() override; + Status get_status() const override; + bool has_response() const override; + bool is_response_chunked() const override; + int get_response_code() const override; + Error get_response_headers(List<String> *r_response) override; + int get_response_body_length() const override; + PackedByteArray read_response_body_chunk() override; + void set_blocking_mode(bool p_enable) override; + bool is_blocking_mode_enabled() const override; + void set_read_chunk_size(int p_size) override; + int get_read_chunk_size() const override; + Error poll() override; + HTTPClientTCP(); +}; + +#endif // HTTP_CLIENT_TCP_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index a81f8ce8d1..6ce230b77b 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -153,14 +153,20 @@ void register_core_types() { ClassDB::register_class<InputEventPanGesture>(); ClassDB::register_class<InputEventMIDI>(); + // Network + ClassDB::register_virtual_class<IP>(); + ClassDB::register_virtual_class<StreamPeer>(); ClassDB::register_class<StreamPeerBuffer>(); ClassDB::register_class<StreamPeerTCP>(); ClassDB::register_class<TCPServer>(); + + ClassDB::register_virtual_class<PacketPeer>(); + ClassDB::register_class<PacketPeerStream>(); ClassDB::register_class<PacketPeerUDP>(); ClassDB::register_class<UDPServer>(); - ClassDB::register_custom_instance_class<PacketPeerDTLS>(); - ClassDB::register_custom_instance_class<DTLSServer>(); + + ClassDB::register_custom_instance_class<HTTPClient>(); // Crypto ClassDB::register_class<HashingContext>(); @@ -170,22 +176,20 @@ void register_core_types() { ClassDB::register_custom_instance_class<HMACContext>(); ClassDB::register_custom_instance_class<Crypto>(); ClassDB::register_custom_instance_class<StreamPeerSSL>(); + ClassDB::register_custom_instance_class<PacketPeerDTLS>(); + ClassDB::register_custom_instance_class<DTLSServer>(); resource_format_saver_crypto.instantiate(); ResourceSaver::add_resource_format_saver(resource_format_saver_crypto); resource_format_loader_crypto.instantiate(); ResourceLoader::add_resource_format_loader(resource_format_loader_crypto); - ClassDB::register_virtual_class<IP>(); - ClassDB::register_virtual_class<PacketPeer>(); - ClassDB::register_class<PacketPeerStream>(); ClassDB::register_virtual_class<NetworkedMultiplayerPeer>(); ClassDB::register_class<MultiplayerAPI>(); ClassDB::register_class<MainLoop>(); ClassDB::register_class<Translation>(); ClassDB::register_class<OptimizedTranslation>(); ClassDB::register_class<UndoRedo>(); - ClassDB::register_class<HTTPClient>(); ClassDB::register_class<TriangleMesh>(); ClassDB::register_class<ResourceFormatLoader>(); diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h index c4aa93c394..31278b71bd 100644 --- a/core/templates/rid_owner.h +++ b/core/templates/rid_owner.h @@ -351,6 +351,9 @@ public: for (size_t i = 0; i < max_alloc; i++) { uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk]; + if (validator & 0x80000000) { + continue; //uninitialized + } if (validator != 0xFFFFFFFF) { chunks[i / elements_in_chunk][i % elements_in_chunk].~T(); } diff --git a/doc/classes/CylinderMesh.xml b/doc/classes/CylinderMesh.xml index 3a81e98c3a..827fb5c10c 100644 --- a/doc/classes/CylinderMesh.xml +++ b/doc/classes/CylinderMesh.xml @@ -4,7 +4,7 @@ Class representing a cylindrical [PrimitiveMesh]. </brief_description> <description> - Class representing a cylindrical [PrimitiveMesh]. This class can be used to create cones by setting either the [member top_radius] or [member bottom_radius] properties to 0.0. + Class representing a cylindrical [PrimitiveMesh]. This class can be used to create cones by setting either the [member top_radius] or [member bottom_radius] properties to [code]0.0[/code]. </description> <tutorials> </tutorials> @@ -12,19 +12,19 @@ </methods> <members> <member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" default="1.0"> - Bottom radius of the cylinder. + Bottom radius of the cylinder. If set to [code]0.0[/code], the bottom faces will not be generated, resulting in a conic shape. </member> <member name="height" type="float" setter="set_height" getter="get_height" default="2.0"> Full height of the cylinder. </member> <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="64"> - Number of radial segments on the cylinder. + Number of radial segments on the cylinder. Higher values result in a more detailed cylinder/cone at the cost of performance. </member> <member name="rings" type="int" setter="set_rings" getter="get_rings" default="4"> - Number of edge rings along the height of the cylinder. + Number of edge rings along the height of the cylinder. Changing [member rings] does not have any visual impact unless a shader or procedural mesh tool is used to alter the vertex data. Higher values result in more subdivisions, which can be used to create smoother-looking effects with shaders or procedural mesh tools (at the cost of performance). When not altering the vertex data using a shader or procedural mesh tool, [member rings] should be kept to its default value. </member> <member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" default="1.0"> - Top radius of the cylinder. + Top radius of the cylinder. If set to [code]0.0[/code], the top faces will not be generated, resulting in a conic shape. </member> </members> <constants> diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index ed193b9f7e..253822cf32 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -72,10 +72,9 @@ <argument index="0" name="delta" type="float"> </argument> <description> - Processes the [Tween] by given [code]delta[/code] value, in seconds. Mostly useful when the [Tween] is paused, for controlling it manually. + Processes the [Tween] by given [code]delta[/code] value, in seconds. Mostly useful when the [Tween] is paused, for controlling it manually. Can also be used to end the [Tween] animation immediately, by using [code]delta[/code] longer than the whole duration. Returns [code]true[/code] if the [Tween] still has [Tweener]s that haven't finished. [b]Note:[/b] The [Tween] will become invalid after finished, but you can call [method stop] after the step, to keep it and reset. - [b]Note:[/b] [method custom_step] will process only one step of the [Tween]. If the [code]delta[/code] is greater than the remaining time, the excessive time will not have any effect. </description> </method> <method name="interpolate_value"> diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index bf328e50e7..16f55312bb 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -105,6 +105,11 @@ void FindReplaceBar::_notification(int p_what) { hide_button->set_custom_minimum_size(hide_button->get_normal_texture()->get_size()); } else if (p_what == NOTIFICATION_THEME_CHANGED) { matches_label->add_theme_color_override("font_color", results_count > 0 ? get_theme_color("font_color", "Label") : get_theme_color("error_color", "Editor")); + } else if (p_what == NOTIFICATION_PREDELETE) { + if (base_text_editor) { + base_text_editor->remove_find_replace_bar(); + base_text_editor = nullptr; + } } } @@ -595,6 +600,10 @@ void FindReplaceBar::set_text_edit(CodeTextEditor *p_text_editor) { text_editor = nullptr; } + if (!p_text_editor) { + return; + } + results_count = -1; base_text_editor = p_text_editor; text_editor = base_text_editor->get_text_editor(); @@ -1667,6 +1676,11 @@ void CodeTextEditor::_notification(int p_what) { } set_process_input(is_visible_in_tree()); } break; + case NOTIFICATION_PREDELETE: { + if (find_replace_bar) { + find_replace_bar->set_text_edit(nullptr); + } + } break; default: break; } diff --git a/editor/node_3d_editor_gizmos.cpp b/editor/node_3d_editor_gizmos.cpp index 4d61e982c1..2a399f4b03 100644 --- a/editor/node_3d_editor_gizmos.cpp +++ b/editor/node_3d_editor_gizmos.cpp @@ -600,8 +600,6 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, r_normal = -p_camera->project_ray_normal(p_point); return true; } - - return false; } if (collision_segments.size()) { @@ -652,8 +650,6 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, r_normal = -p_camera->project_ray_normal(p_point); return true; } - - return false; } if (collision_mesh.is_valid()) { diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index 0821f140b3..b0cafd83be 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -56,7 +56,7 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { } break; case OccluderInstance3D::BAKE_ERROR_NO_MESHES: { - EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.")); + EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.\nMake sure there is at least one MeshInstance3D node in the scene whose visual layers are part of the OccluderInstance3D's Bake Mask property.")); break; } default: { diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 89b486c4df..498d5b0711 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -760,6 +760,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { _update_members_overview_visibility(); _update_help_overview_visibility(); _save_layout(); + _update_find_replace_bar(); } void ScriptEditor::_close_current_tab(bool p_save) { @@ -829,6 +830,7 @@ void ScriptEditor::_close_all_tabs() { _close_current_tab(false); } + _update_find_replace_bar(); } void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) { @@ -1640,15 +1642,13 @@ void ScriptEditor::ensure_select_current() { ScriptEditorBase *se = _get_current_editor(); if (se) { se->enable_editor(); - se->set_find_replace_bar(find_replace_bar); if (!grab_focus_block && is_visible_in_tree()) { se->ensure_focus(); } - } else { - find_replace_bar->hide(); } } + _update_find_replace_bar(); _update_selected_editor_menu(); } @@ -2520,6 +2520,16 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { } } +void ScriptEditor::_update_find_replace_bar() { + ScriptEditorBase *se = _get_current_editor(); + if (se) { + se->set_find_replace_bar(find_replace_bar); + } else { + find_replace_bar->set_text_edit(nullptr); + find_replace_bar->hide(); + } +} + void ScriptEditor::_autosave_scripts() { save_all_scripts(); } diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index a03a4b393b..72a649ffbf 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -328,6 +328,7 @@ class ScriptEditor : public PanelContainer { void _show_error_dialog(String p_path); void _close_tab(int p_idx, bool p_save = true, bool p_history_back = true); + void _update_find_replace_bar(); void _close_current_tab(bool p_save = true); void _close_discard_current_tab(const String &p_str); diff --git a/editor/project_export.cpp b/editor/project_export.cpp index ec65694772..75736a0723 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -1110,9 +1110,9 @@ ProjectExportDialog::ProjectExportDialog() { exclude_filters->connect("text_changed", callable_mp(this, &ProjectExportDialog::_filter_changed)); script_mode = memnew(OptionButton); - resources_vb->add_margin_child(TTR("Script Export Mode:"), script_mode); + resources_vb->add_margin_child(TTR("GDScript Export Mode:"), script_mode); script_mode->add_item(TTR("Text"), (int)EditorExportPreset::MODE_SCRIPT_TEXT); - script_mode->add_item(TTR("Compiled"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED); + script_mode->add_item(TTR("Compiled Bytecode (Faster Loading)"), (int)EditorExportPreset::MODE_SCRIPT_COMPILED); script_mode->connect("item_selected", callable_mp(this, &ProjectExportDialog::_script_export_mode_changed)); // Feature tags. @@ -1137,12 +1137,12 @@ ProjectExportDialog::ProjectExportDialog() { enc_pck = memnew(CheckButton); enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed)); - enc_pck->set_text(TTR("Encrypt exported PCK")); + enc_pck->set_text(TTR("Encrypt Exported PCK")); sec_vb->add_child(enc_pck); enc_directory = memnew(CheckButton); enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed)); - enc_directory->set_text("Encrypt index (file names and info)."); + enc_directory->set_text("Encrypt Index (File Names and Info)"); sec_vb->add_child(enc_directory); enc_in_filters = memnew(LineEdit); @@ -1160,9 +1160,9 @@ ProjectExportDialog::ProjectExportDialog() { script_key = memnew(LineEdit); script_key->connect("text_changed", callable_mp(this, &ProjectExportDialog::_script_encryption_key_changed)); script_key_error = memnew(Label); - script_key_error->set_text("- " + TTR("Invalid Encryption Key (must be 64 characters long)")); + script_key_error->set_text("- " + TTR("Invalid Encryption Key (must be 64 hexadecimal characters long)")); script_key_error->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color("error_color", "Editor")); - sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hex):"), script_key); + sec_vb->add_margin_child(TTR("Encryption Key (256-bits as hexadecimal):"), script_key); sec_vb->add_child(script_key_error); sections->add_child(sec_vb); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 21fe2706b2..451ce39f5c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -114,12 +114,12 @@ namespace GodotTools.Ides if (Utils.OS.IsMacOS && editorId == ExternalEditorId.VisualStudioForMac) { vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ?? - new MonoDevelop.Instantiate(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); return vsForMacInstance; } monoDevelInstance = (monoDevelInstance?.IsDisposed ?? true ? null : monoDevelInstance) ?? - new MonoDevelop.Instantiate(solutionPath, MonoDevelop.EditorId.MonoDevelop); + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop); return monoDevelInstance; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs index 763f470504..214bbf5179 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs @@ -8,9 +8,9 @@ namespace Godot /// `Node.NotificationInstanced` notification on the root node. /// </summary> /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> - public T Instance<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class + public T Instantiate<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class { - return (T)(object)Instance(editState); + return (T)(object)Instantiate(editState); } /// <summary> @@ -19,9 +19,9 @@ namespace Godot /// `Node.NotificationInstanced` notification on the root node. /// </summary> /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam> - public T InstanceOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class + public T InstantiateOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class { - return Instance(editState) as T; + return Instantiate(editState) as T; } } } diff --git a/modules/opensimplex/noise_texture.cpp b/modules/opensimplex/noise_texture.cpp index 9e0155da94..66c52ffbf9 100644 --- a/modules/opensimplex/noise_texture.cpp +++ b/modules/opensimplex/noise_texture.cpp @@ -187,6 +187,7 @@ Ref<OpenSimplexNoise> NoiseTexture::get_noise() { } void NoiseTexture::set_width(int p_width) { + ERR_FAIL_COND(p_width <= 0); if (p_width == size.x) { return; } @@ -195,6 +196,7 @@ void NoiseTexture::set_width(int p_width) { } void NoiseTexture::set_height(int p_height) { + ERR_FAIL_COND(p_height <= 0); if (p_height == size.y) { return; } diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 51de8afc53..69920a81b6 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -1300,7 +1300,7 @@ void VisualScriptEditor::_create_function_dialog() { void VisualScriptEditor::_create_function() { String name = _validate_name((func_name_box->get_text() == "") ? "new_func" : func_name_box->get_text()); selected = name; - Vector2 ofs = _get_available_pos(); + Vector2 pos = _get_available_pos(); Ref<VisualScriptFunction> func_node; func_node.instantiate(); @@ -1322,7 +1322,7 @@ void VisualScriptEditor::_create_function() { undo_redo->create_action(TTR("Add Function")); undo_redo->add_do_method(script.ptr(), "add_function", name, func_node_id); undo_redo->add_undo_method(script.ptr(), "remove_function", name); - undo_redo->add_do_method(script.ptr(), "add_node", func_node_id, func_node, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", func_node_id, func_node, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", func_node_id); undo_redo->add_do_method(this, "_update_members"); undo_redo->add_undo_method(this, "_update_members"); @@ -1417,7 +1417,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt } else if (p_button == 0) { String name = _validate_name("new_function"); selected = name; - Vector2 ofs = _get_available_pos(); + Vector2 pos = _get_available_pos(); Ref<VisualScriptFunction> func_node; func_node.instantiate(); @@ -1426,7 +1426,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt undo_redo->create_action(TTR("Add Function")); undo_redo->add_do_method(script.ptr(), "add_function", name, fn_id); - undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos); undo_redo->add_undo_method(script.ptr(), "remove_function", name); undo_redo->add_do_method(script.ptr(), "remove_node", fn_id); undo_redo->add_do_method(this, "_update_members"); @@ -1621,17 +1621,19 @@ void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id updating_graph = false; } -Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const { - if (centered) { - ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5; - } - +Vector2 VisualScriptEditor::_get_pos_in_graph(Vector2 p_point) const { + Vector2 pos = (graph->get_scroll_ofs() + p_point) / (graph->get_zoom() * EDSCALE); if (graph->is_using_snap()) { int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); + pos = pos.snapped(Vector2(snap, snap)); } + return pos; +} - ofs /= EDSCALE; +Vector2 VisualScriptEditor::_get_available_pos(bool p_centered, Vector2 p_pos) const { + if (p_centered) { + p_pos = _get_pos_in_graph(graph->get_size() * 0.5); + } while (true) { bool exists = false; @@ -1639,8 +1641,8 @@ Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const script->get_node_list(&existing); for (List<int>::Element *E = existing.front(); E; E = E->next()) { Point2 pos = script->get_node_position(E->get()); - if (pos.distance_to(ofs) < 50) { - ofs += Vector2(graph->get_snap(), graph->get_snap()); + if (pos.distance_to(p_pos) < 50) { + p_pos += Vector2(graph->get_snap(), graph->get_snap()); exists = true; break; } @@ -1651,7 +1653,7 @@ Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const break; } - return ofs; + return p_pos; } String VisualScriptEditor::_validate_name(const String &p_name) const { @@ -2049,16 +2051,9 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da return; } - Vector2 ofs = graph->get_scroll_ofs() + p_point; - - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - - ofs /= EDSCALE; + Vector2 pos = _get_pos_in_graph(p_point); - int new_id = _create_new_node_from_name(d["node_type"], ofs); + int new_id = _create_new_node_from_name(d["node_type"], pos); Node *node = graph->get_node(itos(new_id)); if (node) { @@ -2073,13 +2068,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da #else bool use_set = Input::get_singleton()->is_key_pressed(KEY_CTRL); #endif - Vector2 ofs = graph->get_scroll_ofs() + p_point; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - - ofs /= EDSCALE; + Vector2 pos = _get_pos_in_graph(p_point); Ref<VisualScriptNode> vnode; if (use_set) { @@ -2097,7 +2086,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -2111,13 +2100,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } if (String(d["type"]) == "visual_script_function_drag") { - Vector2 ofs = graph->get_scroll_ofs() + p_point; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - - ofs /= EDSCALE; + Vector2 pos = _get_pos_in_graph(p_point); Ref<VisualScriptFunctionCall> vnode; vnode.instantiate(); @@ -2126,7 +2109,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); undo_redo->add_do_method(vnode.ptr(), "set_base_type", script->get_instance_base_type()); undo_redo->add_do_method(vnode.ptr(), "set_function", d["function"]); @@ -2143,13 +2126,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } if (String(d["type"]) == "visual_script_signal_drag") { - Vector2 ofs = graph->get_scroll_ofs() + p_point; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - - ofs /= EDSCALE; + Vector2 pos = _get_pos_in_graph(p_point); Ref<VisualScriptEmitSignal> vnode; vnode.instantiate(); @@ -2158,7 +2135,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -2172,13 +2149,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } if (String(d["type"]) == "resource") { - Vector2 ofs = graph->get_scroll_ofs() + p_point; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - - ofs /= EDSCALE; + Vector2 pos = _get_pos_in_graph(p_point); Ref<VisualScriptPreload> prnode; prnode.instantiate(); @@ -2187,7 +2158,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Preload Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -2201,13 +2172,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } if (String(d["type"]) == "files") { - Vector2 ofs = graph->get_scroll_ofs() + p_point; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - - ofs /= EDSCALE; + Vector2 pos = _get_pos_in_graph(p_point); Array files = d["files"]; @@ -2227,11 +2192,11 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da prnode.instantiate(); prnode->set_preload(res); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); new_ids.push_back(new_id); new_id++; - ofs += Vector2(20, 20) * EDSCALE; + pos += Vector2(20, 20); } undo_redo->add_do_method(this, "_update_graph"); @@ -2264,13 +2229,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da Array nodes = d["nodes"]; - Vector2 ofs = graph->get_scroll_ofs() + p_point; - - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - ofs /= EDSCALE; + Vector2 pos = _get_pos_in_graph(p_point); undo_redo->create_action(TTR("Add Node(s) From Tree")); int base_id = script->get_available_id(); @@ -2305,11 +2264,11 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da selecting_method_id = base_id; } - undo_redo->add_do_method(script.ptr(), "add_node", base_id, n, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", base_id, n, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", base_id); base_id++; - ofs += Vector2(25, 25); + pos += Vector2(25, 25); } undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -2331,14 +2290,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } Node *node = Object::cast_to<Node>(obj); - Vector2 ofs = graph->get_scroll_ofs() + p_point; - - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } + Vector2 pos = _get_pos_in_graph(p_point); - ofs /= EDSCALE; #ifdef OSX_ENABLED bool use_get = Input::get_singleton()->is_key_pressed(KEY_META); #else @@ -2375,7 +2328,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da vnode = pget; } - undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, pos); undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]); if (!use_get) { undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]); @@ -2420,7 +2373,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } vnode = pget; } - undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, pos); undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]); if (!use_get) { undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]); @@ -3009,7 +2962,7 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro if (!vsn.is_valid()) { return; } - if (vsn->get_output_value_port_count()) { + if (vsn->get_output_value_port_count() || vsn->get_output_sequence_port_count()) { port_action_pos = p_release_pos; } @@ -3075,13 +3028,6 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac } void VisualScriptEditor::_port_action_menu(int p_option) { - Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - ofs /= EDSCALE; - Set<int> vn; switch (p_option) { @@ -3172,13 +3118,7 @@ void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<Visua } void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) { - Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - ofs /= EDSCALE; - ofs /= graph->get_zoom(); + Vector2 pos = _get_pos_in_graph(port_action_pos); Set<int> vn; @@ -3216,7 +3156,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode_new, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode_new, pos); if (vnode_old.is_valid() && p_connecting) { connect_seq(vnode_old, vnode_new, new_id); connect_data(vnode_old, vnode_new, new_id); @@ -3279,7 +3219,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); undo_redo->add_do_method(this, "_update_graph", new_id); undo_redo->add_undo_method(this, "_update_graph", new_id); @@ -3478,9 +3418,9 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons func_node->add_argument(minfo.arguments[i].type, minfo.arguments[i].name, -1, minfo.arguments[i].hint, minfo.arguments[i].hint_string); } - Vector2 ofs = _get_available_pos(); + Vector2 pos = _get_available_pos(); - undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos); undo_redo->add_undo_method(script.ptr(), "remove_node", fn_id); if (minfo.return_val.type != Variant::NIL || minfo.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { Ref<VisualScriptReturn> ret_node; @@ -3489,7 +3429,7 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons ret_node->set_enable_return_value(true); ret_node->set_name(name); int nid = script->get_available_id() + 1; - undo_redo->add_do_method(script.ptr(), "add_node", nid, ret_node, _get_available_pos(false, ofs + Vector2(500, 0))); + undo_redo->add_do_method(script.ptr(), "add_node", nid, ret_node, _get_available_pos(false, pos + Vector2(500, 0))); undo_redo->add_undo_method(script.ptr(), "remove_node", nid); } @@ -3993,7 +3933,7 @@ void VisualScriptEditor::_menu_option(int p_what) { { String new_fn = _validate_name("new_function"); - Vector2 ofs = _get_available_pos(false, script->get_node_position(start_node) - Vector2(80, 150)); + Vector2 pos = _get_available_pos(false, script->get_node_position(start_node) - Vector2(80, 150)); Ref<VisualScriptFunction> func_node; func_node.instantiate(); @@ -4002,7 +3942,7 @@ void VisualScriptEditor::_menu_option(int p_what) { undo_redo->create_action(TTR("Create Function")); undo_redo->add_do_method(script.ptr(), "add_function", new_fn, fn_id); - undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos); undo_redo->add_undo_method(script.ptr(), "remove_function", new_fn); undo_redo->add_undo_method(script.ptr(), "remove_node", fn_id); undo_redo->add_do_method(this, "_update_members"); @@ -4045,8 +3985,8 @@ void VisualScriptEditor::_menu_option(int p_what) { int ret_id = fn_id + (m++); selections.insert(ret_id); - Vector2 ofsi = _get_available_pos(false, script->get_node_position(G->get()) + Vector2(80, -100)); - undo_redo->add_do_method(script.ptr(), "add_node", ret_id, ret_node, ofsi); + Vector2 posi = _get_available_pos(false, script->get_node_position(G->get()) + Vector2(80, -100)); + undo_redo->add_do_method(script.ptr(), "add_node", ret_id, ret_node, posi); undo_redo->add_undo_method(script.ptr(), "remove_node", ret_id); undo_redo->add_do_method(script.ptr(), "sequence_connect", G->get(), 0, ret_id); diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index ca06b807cc..1f0f087be7 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -226,7 +226,8 @@ class VisualScriptEditor : public ScriptEditorBase { void _update_node_size(int p_id); void _port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input); - Vector2 _get_available_pos(bool centered = true, Vector2 ofs = Vector2()) const; + Vector2 _get_pos_in_graph(Vector2 p_point) const; + Vector2 _get_available_pos(bool p_centered = true, Vector2 p_pos = Vector2()) const; bool node_has_sequence_connections(int p_id); diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 60a132c6a5..60392d8f42 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -946,39 +946,69 @@ static const char *op_names[] = { }; String VisualScriptOperator::get_caption() const { - static const char32_t *op_names[] = { - //comparison - U"A = B", //OP_EQUAL, - U"A \u2260 B", //OP_NOT_EQUAL, - U"A < B", //OP_LESS, - U"A \u2264 B", //OP_LESS_EQUAL, - U"A > B", //OP_GREATER, - U"A \u2265 B", //OP_GREATER_EQUAL, - //mathematic - U"A + B", //OP_ADD, - U"A - B", //OP_SUBTRACT, - U"A \u00D7 B", //OP_MULTIPLY, - U"A \u00F7 B", //OP_DIVIDE, - U"\u00AC A", //OP_NEGATE, - U"+ A", //OP_POSITIVE, - U"A mod B", //OP_MODULE, - U"A .. B", //OP_STRING_CONCAT, - //bitwise - U"A << B", //OP_SHIFT_LEFT, - U"A >> B", //OP_SHIFT_RIGHT, - U"A & B", //OP_BIT_AND, - U"A | B", //OP_BIT_OR, - U"A ^ B", //OP_BIT_XOR, - U"~A", //OP_BIT_NEGATE, - //logic - U"A and B", //OP_AND, - U"A or B", //OP_OR, - U"A xor B", //OP_XOR, - U"not A", //OP_NOT, - U"A in B", //OP_IN, - - }; - return op_names[op]; + switch (op) { + // comparison + case Variant::OP_EQUAL: + return U"A = B"; + case Variant::OP_NOT_EQUAL: + return U"A \u2260 B"; + case Variant::OP_LESS: + return U"A < B"; + case Variant::OP_LESS_EQUAL: + return U"A \u2264 B"; + case Variant::OP_GREATER: + return U"A > B"; + case Variant::OP_GREATER_EQUAL: + return U"A \u2265 B"; + + // mathematic + case Variant::OP_ADD: + return U"A + B"; + case Variant::OP_SUBTRACT: + return U"A - B"; + case Variant::OP_MULTIPLY: + return U"A \u00D7 B"; + case Variant::OP_DIVIDE: + return U"A \u00F7 B"; + case Variant::OP_NEGATE: + return U"\u00AC A"; + case Variant::OP_POSITIVE: + return U"+ A"; + case Variant::OP_MODULE: + return U"A mod B"; + + // bitwise + case Variant::OP_SHIFT_LEFT: + return U"A << B"; + case Variant::OP_SHIFT_RIGHT: + return U"A >> B"; + case Variant::OP_BIT_AND: + return U"A & B"; + case Variant::OP_BIT_OR: + return U"A | B"; + case Variant::OP_BIT_XOR: + return U"A ^ B"; + case Variant::OP_BIT_NEGATE: + return U"~A"; + + // logic + case Variant::OP_AND: + return U"A and B"; + case Variant::OP_OR: + return U"A or B"; + case Variant::OP_XOR: + return U"A xor B"; + case Variant::OP_NOT: + return U"not A"; + case Variant::OP_IN: + return U"A in B"; + + default: { + ERR_FAIL_V_MSG( + U"Unknown node", + U"Unknown node type encountered, caption not available."); + } + } } void VisualScriptOperator::set_operator(Variant::Operator p_op) { diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 923435eec5..d01232665b 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -2568,19 +2568,35 @@ public: // Sensitive additions must be done below the logging statement. print_verbose("Build Android project using gradle command: " + String("\n") + build_command + " " + join_list(cmdline, String(" "))); - if (should_sign && !p_debug) { - // Pass the release keystore info as well - String release_keystore = p_preset->get("keystore/release"); - String release_username = p_preset->get("keystore/release_user"); - String release_password = p_preset->get("keystore/release_password"); - if (!FileAccess::exists(release_keystore)) { - EditorNode::add_io_error("Could not find keystore, unable to export."); - return ERR_FILE_CANT_OPEN; - } + if (should_sign) { + if (p_debug) { + String debug_keystore = p_preset->get("keystore/debug"); + String debug_password = p_preset->get("keystore/debug_password"); + String debug_user = p_preset->get("keystore/debug_user"); + + if (debug_keystore.is_empty()) { + debug_keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); + debug_password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); + debug_user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); + } + + cmdline.push_back("-Pdebug_keystore_file=" + debug_keystore); // argument to specify the debug keystore file. + cmdline.push_back("-Pdebug_keystore_alias=" + debug_user); // argument to specify the debug keystore alias. + cmdline.push_back("-Pdebug_keystore_password=" + debug_password); // argument to specify the debug keystore password. + } else { + // Pass the release keystore info as well + String release_keystore = p_preset->get("keystore/release"); + String release_username = p_preset->get("keystore/release_user"); + String release_password = p_preset->get("keystore/release_password"); + if (!FileAccess::exists(release_keystore)) { + EditorNode::add_io_error("Could not find keystore, unable to export."); + return ERR_FILE_CANT_OPEN; + } - cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file. - cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias. - cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specity the release keystore password. + cmdline.push_back("-Prelease_keystore_file=" + release_keystore); // argument to specify the release keystore file. + cmdline.push_back("-Prelease_keystore_alias=" + release_username); // argument to specify the release keystore alias. + cmdline.push_back("-Prelease_keystore_password=" + release_password); // argument to specify the release keystore password. + } } int result = EditorNode::get_singleton()->execute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline); diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 1b1fb47bd8..18e07c3762 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -6,7 +6,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { classpath libraries.androidGradlePlugin @@ -18,9 +18,8 @@ apply plugin: 'com.android.application' allprojects { repositories { - mavenCentral() google() - jcenter() + mavenCentral() // Godot user plugins custom maven repos String[] mavenRepos = getGodotPluginsMavenRepos() @@ -113,6 +112,15 @@ android { } signingConfigs { + debug { + if (hasCustomDebugKeystore()) { + storeFile new File(getDebugKeystoreFile()) + storePassword getDebugKeystorePassword() + keyAlias getDebugKeyAlias() + keyPassword getDebugKeystorePassword() + } + } + release { File keystoreFile = new File(getReleaseKeystoreFile()) if (keystoreFile.isFile()) { diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index b278d15bdf..81fc87b7ef 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,11 +1,11 @@ ext.versions = [ - androidGradlePlugin: '4.0.1', + androidGradlePlugin: '4.2.1', compileSdk : 29, minSdk : 18, targetSdk : 29, buildTools : '30.0.3', supportCoreUtils : '1.0.0', - kotlinVersion : '1.4.10', + kotlinVersion : '1.5.10', v4Support : '1.0.0', javaVersion : 1.8, ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. @@ -191,6 +191,35 @@ ext.getGodotPluginsLocalBinaries = { -> return binDeps } +ext.getDebugKeystoreFile = { -> + String keystoreFile = project.hasProperty("debug_keystore_file") ? project.property("debug_keystore_file") : "" + if (keystoreFile == null || keystoreFile.isEmpty()) { + keystoreFile = "." + } + return keystoreFile +} + +ext.hasCustomDebugKeystore = { -> + File keystoreFile = new File(getDebugKeystoreFile()) + return keystoreFile.isFile() +} + +ext.getDebugKeystorePassword = { -> + String keystorePassword = project.hasProperty("debug_keystore_password") ? project.property("debug_keystore_password") : "" + if (keystorePassword == null || keystorePassword.isEmpty()) { + keystorePassword = "android" + } + return keystorePassword +} + +ext.getDebugKeyAlias = { -> + String keyAlias = project.hasProperty("debug_keystore_alias") ? project.property("debug_keystore_alias") : "" + if (keyAlias == null || keyAlias.isEmpty()) { + keyAlias = "androiddebugkey" + } + return keyAlias +} + ext.getReleaseKeystoreFile = { -> String keystoreFile = project.hasProperty("release_keystore_file") ? project.property("release_keystore_file") : "" if (keystoreFile == null || keystoreFile.isEmpty()) { diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index a28888d80d..ee24a46d9f 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -5,7 +5,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { classpath libraries.androidGradlePlugin @@ -16,7 +16,6 @@ buildscript { allprojects { repositories { google() - jcenter() mavenCentral() } } diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index a7d8a0f310..74c5636f8a 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Sep 02 02:44:30 PDT 2019 +#Wed Jun 23 23:42:22 PDT 2021 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 8e977e1783..1d1961ac2f 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -367,6 +367,26 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) for (int i = 0; i < found_plugins.size(); i++) { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "plugins/" + found_plugins[i].name), false)); } + + for (int i = 0; i < found_plugins.size(); i++) { + // Editable plugin plist values + PluginConfigIOS plugin = found_plugins[i]; + const String *K = nullptr; + + while ((K = plugin.plist.next(K))) { + String key = *K; + PluginConfigIOS::PlistItem item = plugin.plist[key]; + switch (item.type) { + case PluginConfigIOS::PlistItemType::STRING_INPUT: { + String preset_name = "plugins_plist/" + key; + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, preset_name), item.value)); + } break; + default: + continue; + } + } + } + plugins_changed.clear(); plugins = found_plugins; @@ -1467,13 +1487,28 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> while ((K = plugin.plist.next(K))) { String key = *K; - String value = plugin.plist[key]; + PluginConfigIOS::PlistItem item = plugin.plist[key]; + + String value; + + switch (item.type) { + case PluginConfigIOS::PlistItemType::STRING_INPUT: { + String preset_name = "plugins_plist/" + key; + String input_value = p_preset->get(preset_name); + value = "<string>" + input_value + "</string>"; + } break; + default: + value = item.value; + break; + } if (key.is_empty() || value.is_empty()) { continue; } - plist_values[key] = value; + String plist_key = "<key>" + key + "</key>"; + + plist_values[plist_key] = value; } // CPP Code @@ -1500,7 +1535,7 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> continue; } - p_config_data.plist_content += "<key>" + key + "</key><string>" + value + "</string>\n"; + p_config_data.plist_content += key + value + "\n"; } } diff --git a/platform/iphone/plugin/godot_plugin_config.h b/platform/iphone/plugin/godot_plugin_config.h index 4d0c67bfff..06770260aa 100644 --- a/platform/iphone/plugin/godot_plugin_config.h +++ b/platform/iphone/plugin/godot_plugin_config.h @@ -70,6 +70,20 @@ struct PluginConfigIOS { inline static const char *PLIST_SECTION = "plist"; + enum PlistItemType { + UNKNOWN, + STRING, + INTEGER, + BOOLEAN, + RAW, + STRING_INPUT, + }; + + struct PlistItem { + PlistItemType type; + String value; + }; + // Set to true when the config file is properly loaded. bool valid_config = false; bool supports_targets = false; @@ -93,8 +107,10 @@ struct PluginConfigIOS { Vector<String> linker_flags; // Optional plist section - // Supports only string types for now - HashMap<String, String> plist; + // String value is default value. + // Currently supports `string`, `boolean`, `integer`, `raw`, `string_input` types + // <name>:<type> = <value> + HashMap<String, PlistItem> plist; }; static inline String resolve_local_dependency_path(String plugin_config_dir, String dependency_path) { @@ -273,13 +289,68 @@ static inline PluginConfigIOS load_plugin_config(Ref<ConfigFile> config_file, co config_file->get_section_keys(PluginConfigIOS::PLIST_SECTION, &keys); for (int i = 0; i < keys.size(); i++) { - String value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); + Vector<String> key_components = keys[i].split(":"); + + String key_value = ""; + PluginConfigIOS::PlistItemType key_type = PluginConfigIOS::PlistItemType::UNKNOWN; + + if (key_components.size() == 1) { + key_value = key_components[0]; + key_type = PluginConfigIOS::PlistItemType::STRING; + } else if (key_components.size() == 2) { + key_value = key_components[0]; + + if (key_components[1].to_lower() == "string") { + key_type = PluginConfigIOS::PlistItemType::STRING; + } else if (key_components[1].to_lower() == "integer") { + key_type = PluginConfigIOS::PlistItemType::INTEGER; + } else if (key_components[1].to_lower() == "boolean") { + key_type = PluginConfigIOS::PlistItemType::BOOLEAN; + } else if (key_components[1].to_lower() == "raw") { + key_type = PluginConfigIOS::PlistItemType::RAW; + } else if (key_components[1].to_lower() == "string_input") { + key_type = PluginConfigIOS::PlistItemType::STRING_INPUT; + } + } - if (value.is_empty()) { + if (key_value.is_empty() || key_type == PluginConfigIOS::PlistItemType::UNKNOWN) { continue; } - plugin_config.plist[keys[i]] = value; + String value; + + switch (key_type) { + case PluginConfigIOS::PlistItemType::STRING: { + String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); + value = "<string>" + raw_value + "</string>"; + } break; + case PluginConfigIOS::PlistItemType::INTEGER: { + int raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], 0); + Dictionary value_dictionary; + String value_format = "<integer>$value</integer>"; + value_dictionary["value"] = raw_value; + value = value_format.format(value_dictionary, "$_"); + } break; + case PluginConfigIOS::PlistItemType::BOOLEAN: + if (config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], false)) { + value = "<true/>"; + } else { + value = "<false/>"; + } + break; + case PluginConfigIOS::PlistItemType::RAW: { + String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); + value = raw_value; + } break; + case PluginConfigIOS::PlistItemType::STRING_INPUT: { + String raw_value = config_file->get_value(PluginConfigIOS::PLIST_SECTION, keys[i], String()); + value = raw_value; + } break; + default: + continue; + } + + plugin_config.plist[key_value] = PluginConfigIOS::PlistItem{ key_type, value }; } } diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index a6cf4b0eb8..f7d78abcea 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -28,45 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/io/http_client.h" +#include "http_client_javascript.h" -#ifdef __cplusplus -extern "C" { -#endif - -#include "stddef.h" - -typedef enum { - GODOT_JS_FETCH_STATE_REQUESTING = 0, - GODOT_JS_FETCH_STATE_BODY = 1, - GODOT_JS_FETCH_STATE_DONE = 2, - GODOT_JS_FETCH_STATE_ERROR = -1, -} godot_js_fetch_state_t; - -extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len); -extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref); -extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size); -extern void godot_js_fetch_free(int p_id); -extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id); -extern int godot_js_fetch_body_length_get(int p_id); -extern int godot_js_fetch_http_status_get(int p_id); -extern int godot_js_fetch_is_chunked(int p_id); - -#ifdef __cplusplus -} -#endif - -void HTTPClient::_parse_headers(int p_len, const char **p_headers, void *p_ref) { - HTTPClient *client = static_cast<HTTPClient *>(p_ref); +void HTTPClientJavaScript::_parse_headers(int p_len, const char **p_headers, void *p_ref) { + HTTPClientJavaScript *client = static_cast<HTTPClientJavaScript *>(p_ref); for (int i = 0; i < p_len; i++) { client->response_headers.push_back(String::utf8(p_headers[i])); } } -Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { +Error HTTPClientJavaScript::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { close(); if (p_ssl && !p_verify_host) { - WARN_PRINT("Disabling HTTPClient's host verification is not supported for the HTML5 platform, host will be verified"); + WARN_PRINT("Disabling HTTPClientJavaScript's host verification is not supported for the HTML5 platform, host will be verified"); } port = p_port; @@ -97,15 +71,15 @@ Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, return OK; } -void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) { - ERR_FAIL_MSG("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform."); +void HTTPClientJavaScript::set_connection(const Ref<StreamPeer> &p_connection) { + ERR_FAIL_MSG("Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform."); } -Ref<StreamPeer> HTTPClient::get_connection() const { - ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform."); +Ref<StreamPeer> HTTPClientJavaScript::get_connection() const { + ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform."); } -Error HTTPClient::make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) { +Error HTTPClientJavaScript::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) { ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform."); ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); @@ -128,22 +102,7 @@ Error HTTPClient::make_request(Method p_method, const String &p_url, const Vecto return OK; } -Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { - if (p_body.is_empty()) { - return make_request(p_method, p_url, p_headers, nullptr, 0); - } - return make_request(p_method, p_url, p_headers, p_body.ptr(), p_body.size()); -} - -Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) { - if (p_body.is_empty()) { - return make_request(p_method, p_url, p_headers, nullptr, 0); - } - const CharString cs = p_body.utf8(); - return make_request(p_method, p_url, p_headers, (const uint8_t *)cs.get_data(), cs.size() - 1); -} - -void HTTPClient::close() { +void HTTPClientJavaScript::close() { host = ""; port = -1; use_tls = false; @@ -157,23 +116,23 @@ void HTTPClient::close() { } } -HTTPClient::Status HTTPClient::get_status() const { +HTTPClientJavaScript::Status HTTPClientJavaScript::get_status() const { return status; } -bool HTTPClient::has_response() const { +bool HTTPClientJavaScript::has_response() const { return response_headers.size() > 0; } -bool HTTPClient::is_response_chunked() const { +bool HTTPClientJavaScript::is_response_chunked() const { return godot_js_fetch_is_chunked(js_id); } -int HTTPClient::get_response_code() const { +int HTTPClientJavaScript::get_response_code() const { return polled_response_code; } -Error HTTPClient::get_response_headers(List<String> *r_response) { +Error HTTPClientJavaScript::get_response_headers(List<String> *r_response) { if (!response_headers.size()) { return ERR_INVALID_PARAMETER; } @@ -184,11 +143,11 @@ Error HTTPClient::get_response_headers(List<String> *r_response) { return OK; } -int HTTPClient::get_response_body_length() const { +int HTTPClientJavaScript::get_response_body_length() const { return godot_js_fetch_body_length_get(js_id); } -PackedByteArray HTTPClient::read_response_body_chunk() { +PackedByteArray HTTPClientJavaScript::read_response_body_chunk() { ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); if (response_buffer.size() != read_limit) { @@ -213,23 +172,23 @@ PackedByteArray HTTPClient::read_response_body_chunk() { return chunk; } -void HTTPClient::set_blocking_mode(bool p_enable) { - ERR_FAIL_COND_MSG(p_enable, "HTTPClient blocking mode is not supported for the HTML5 platform."); +void HTTPClientJavaScript::set_blocking_mode(bool p_enable) { + ERR_FAIL_COND_MSG(p_enable, "HTTPClientJavaScript blocking mode is not supported for the HTML5 platform."); } -bool HTTPClient::is_blocking_mode_enabled() const { +bool HTTPClientJavaScript::is_blocking_mode_enabled() const { return false; } -void HTTPClient::set_read_chunk_size(int p_size) { +void HTTPClientJavaScript::set_read_chunk_size(int p_size) { read_limit = p_size; } -int HTTPClient::get_read_chunk_size() const { +int HTTPClientJavaScript::get_read_chunk_size() const { return read_limit; } -Error HTTPClient::poll() { +Error HTTPClientJavaScript::poll() { switch (status) { case STATUS_DISCONNECTED: return ERR_UNCONFIGURED; @@ -263,7 +222,7 @@ Error HTTPClient::poll() { #ifdef DEBUG_ENABLED // forcing synchronous requests is not possible on the web if (last_polling_frame == Engine::get_singleton()->get_process_frames()) { - WARN_PRINT("HTTPClient polled multiple times in one frame, " + WARN_PRINT("HTTPClientJavaScript polled multiple times in one frame, " "but request cannot progress more than once per " "frame on the HTML5 platform."); } @@ -294,9 +253,15 @@ Error HTTPClient::poll() { return OK; } -HTTPClient::HTTPClient() { +HTTPClient *HTTPClientJavaScript::_create_func() { + return memnew(HTTPClientJavaScript); +} + +HTTPClient *(*HTTPClient::_create)() = HTTPClientJavaScript::_create_func; + +HTTPClientJavaScript::HTTPClientJavaScript() { } -HTTPClient::~HTTPClient() { +HTTPClientJavaScript::~HTTPClientJavaScript() { close(); } diff --git a/platform/javascript/http_client_javascript.h b/platform/javascript/http_client_javascript.h new file mode 100644 index 0000000000..33f91f67b6 --- /dev/null +++ b/platform/javascript/http_client_javascript.h @@ -0,0 +1,108 @@ +/*************************************************************************/ +/* http_client_javascript.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 HTTP_CLIENT_JAVASCRIPT_H +#define HTTP_CLIENT_JAVASCRIPT_H + +#include "core/io/http_client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stddef.h" + +typedef enum { + GODOT_JS_FETCH_STATE_REQUESTING = 0, + GODOT_JS_FETCH_STATE_BODY = 1, + GODOT_JS_FETCH_STATE_DONE = 2, + GODOT_JS_FETCH_STATE_ERROR = -1, +} godot_js_fetch_state_t; + +extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len); +extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref); +extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size); +extern void godot_js_fetch_free(int p_id); +extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id); +extern int godot_js_fetch_body_length_get(int p_id); +extern int godot_js_fetch_http_status_get(int p_id); +extern int godot_js_fetch_is_chunked(int p_id); + +#ifdef __cplusplus +} +#endif + +class HTTPClientJavaScript : public HTTPClient { +private: + int js_id = 0; + Status status = STATUS_DISCONNECTED; + + // 64 KiB by default (favors fast download speeds at the cost of memory usage). + int read_limit = 65536; + + String host; + int port = -1; + bool use_tls = false; + + int polled_response_code = 0; + Vector<String> response_headers; + Vector<uint8_t> response_buffer; + +#ifdef DEBUG_ENABLED + uint64_t last_polling_frame = 0; +#endif + + static void _parse_headers(int p_len, const char **p_headers, void *p_ref); + +public: + static HTTPClient *_create_func(); + + Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override; + + Error connect_to_host(const String &p_host, int p_port = -1, bool p_ssl = false, bool p_verify_host = true) override; + void set_connection(const Ref<StreamPeer> &p_connection) override; + Ref<StreamPeer> get_connection() const override; + void close() override; + Status get_status() const override; + bool has_response() const override; + bool is_response_chunked() const override; + int get_response_code() const override; + Error get_response_headers(List<String> *r_response) override; + int get_response_body_length() const override; + PackedByteArray read_response_body_chunk() override; + void set_blocking_mode(bool p_enable) override; + bool is_blocking_mode_enabled() const override; + void set_read_chunk_size(int p_size) override; + int get_read_chunk_size() const override; + Error poll() override; + HTTPClientJavaScript(); + ~HTTPClientJavaScript(); +}; +#endif // HTTP_CLIENT_JAVASCRIPT_H diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index ec51ec78d3..4a672a4b17 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -2120,6 +2120,12 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { ignore_warp = true; warp_events.clear(); mouse_mode = p_mode; + + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CursorShape p_shape = cursor_shape; + cursor_shape = DisplayServer::CURSOR_MAX; + cursor_set_shape(p_shape); + } } DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const { diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index 1eec2a3833..860e95b51e 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -365,7 +365,7 @@ void Polygon2D::_notification(int p_what) { arr[RS::ARRAY_INDEX] = index_array; RS::get_singleton()->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); - RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(), texture.is_valid() ? texture->get_rid() : RID()); + RS::get_singleton()->canvas_item_add_mesh(get_canvas_item(), mesh, Transform2D(), Color(1, 1, 1), texture.is_valid() ? texture->get_rid() : RID()); } } break; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 0ce0130ad5..6580d794d1 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -453,6 +453,7 @@ void Label::set_text(const String &p_string) { visible_chars = get_total_character_count() * percent_visible; } update(); + minimum_size_changed(); } void Label::set_text_direction(Control::TextDirection p_text_direction) { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index b02b4edd8e..f677b3592a 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1846,78 +1846,76 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int prev_hl_ofs = base_ofs; while (c) { - if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { - int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); - int parent_ofs = p_pos.x + cache.item_margin; - Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; + if (htotal >= 0) { + int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c); - if (c->get_first_child() != nullptr) { - root_pos -= Point2i(cache.arrow->get_width(), 0); - } + // Draw relationship lines. + if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { + int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); + int parent_ofs = p_pos.x + cache.item_margin; + Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; - float line_width = cache.relationship_line_width; - float parent_line_width = cache.parent_hl_line_width; - float children_line_width = cache.children_hl_line_width; + if (c->get_first_child() != nullptr) { + root_pos -= Point2i(cache.arrow->get_width(), 0); + } + + float line_width = cache.relationship_line_width; + float parent_line_width = cache.parent_hl_line_width; + float children_line_width = cache.children_hl_line_width; #ifdef TOOLS_ENABLED - line_width *= Math::round(EDSCALE); - parent_line_width *= Math::round(EDSCALE); - children_line_width *= Math::round(EDSCALE); + line_width *= Math::round(EDSCALE); + parent_line_width *= Math::round(EDSCALE); + children_line_width *= Math::round(EDSCALE); #endif - Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs; + Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs; - int more_prev_ofs = 0; + int more_prev_ofs = 0; - if (root_pos.y + line_width >= 0) { - if (rtl) { - root_pos.x = get_size().width - root_pos.x; - parent_pos.x = get_size().width - parent_pos.x; - } + if (root_pos.y + line_width >= 0) { + if (rtl) { + root_pos.x = get_size().width - root_pos.x; + parent_pos.x = get_size().width - parent_pos.x; + } - // Order of parts on this bend: the horizontal line first, then the vertical line. - if (_is_branch_selected(c)) { - // If this item or one of its children is selected, we draw the line using parent highlight style. - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); - - more_prev_ofs = cache.parent_hl_line_margin; - prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); - } else if (p_item->is_selected(0)) { - // If parent item is selected (but this item is not), we draw the line using children highlight style. - // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. - if (_is_sibling_branch_selected(c)) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + // Order of parts on this bend: the horizontal line first, then the vertical line. + if (_is_branch_selected(c)) { + // If this item or one of its children is selected, we draw the line using parent highlight style. + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width); RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + more_prev_ofs = cache.parent_hl_line_margin; prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else if (p_item->is_selected(0)) { + // If parent item is selected (but this item is not), we draw the line using children highlight style. + // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. + if (_is_sibling_branch_selected(c)) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + + prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width); + } } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width); - } - } else { - // If nothing of the above is true, we draw the line using normal style. - // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. - if (_is_sibling_branch_selected(c)) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + // If nothing of the above is true, we draw the line using normal style. + // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. + if (_is_sibling_branch_selected(c)) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); - prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); - } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width); + prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width); + } } } - } - if (htotal < 0) { - return -1; + prev_ofs = root_pos.y + more_prev_ofs; } - prev_ofs = root_pos.y + more_prev_ofs; - } - - if (htotal >= 0) { - int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c); if (child_h < 0) { if (cache.draw_relationship_lines == 0) { diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp index f3c7d128ba..775dfa4c46 100644 --- a/scene/main/http_request.cpp +++ b/scene/main/http_request.cpp @@ -322,7 +322,8 @@ bool HTTPRequest::_update_connection() { } else { // Did not request yet, do request - Error err = client->request_raw(method, request_string, headers, request_data); + int size = request_data.size(); + Error err = client->request(method, request_string, headers, size > 0 ? request_data.ptr() : nullptr, size); if (err != OK) { call_deferred("_request_done", RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray()); return true; @@ -627,7 +628,7 @@ void HTTPRequest::_bind_methods() { } HTTPRequest::HTTPRequest() { - client.instantiate(); + client = Ref<HTTPClient>(HTTPClient::create()); timer = memnew(Timer); timer->set_one_shot(true); timer->connect("timeout", callable_mp(this, &HTTPRequest::_timeout)); diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index a745df522b..2b2ebb5c16 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -866,9 +866,9 @@ void CylinderMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_top_radius", "get_top_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_top_radius", "get_top_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); } diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index b8dd8b1c14..acc85cf7df 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -1595,6 +1595,7 @@ void GradientTexture::_update() { } void GradientTexture::set_width(int p_width) { + ERR_FAIL_COND(p_width <= 0); width = p_width; _queue_update(); } diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl index 2186bd174b..a443bcdcb8 100644 --- a/servers/rendering/renderer_rd/shaders/canvas.glsl +++ b/servers/rendering/renderer_rd/shaders/canvas.glsl @@ -65,7 +65,7 @@ void main() { #elif defined(USE_ATTRIBUTES) vec2 vertex = vertex_attrib; - vec4 color = color_attrib; + vec4 color = color_attrib * draw_data.modulation; vec2 uv = uv_attrib; uvec4 bones = bone_attrib; |