Browse Source

Merge pull request #60557 from akien-mga/3.x-cherrypicks

Rémi Verschelde 3 years ago
parent
commit
b974c9816c

+ 5 - 0
core/bind/core_bind.cpp

@@ -1102,6 +1102,10 @@ void _OS::dump_resources_to_file(const String &p_file) {
 	OS::get_singleton()->dump_resources_to_file(p_file.utf8().get_data());
 	OS::get_singleton()->dump_resources_to_file(p_file.utf8().get_data());
 }
 }
 
 
+Error _OS::move_to_trash(const String &p_path) const {
+	return OS::get_singleton()->move_to_trash(p_path);
+}
+
 String _OS::get_user_data_dir() const {
 String _OS::get_user_data_dir() const {
 	return OS::get_singleton()->get_user_data_dir();
 	return OS::get_singleton()->get_user_data_dir();
 };
 };
@@ -1399,6 +1403,7 @@ void _OS::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &_OS::get_static_memory_peak_usage);
 	ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &_OS::get_static_memory_peak_usage);
 	ClassDB::bind_method(D_METHOD("get_dynamic_memory_usage"), &_OS::get_dynamic_memory_usage);
 	ClassDB::bind_method(D_METHOD("get_dynamic_memory_usage"), &_OS::get_dynamic_memory_usage);
 
 
+	ClassDB::bind_method(D_METHOD("move_to_trash", "path"), &_OS::move_to_trash);
 	ClassDB::bind_method(D_METHOD("get_user_data_dir"), &_OS::get_user_data_dir);
 	ClassDB::bind_method(D_METHOD("get_user_data_dir"), &_OS::get_user_data_dir);
 	ClassDB::bind_method(D_METHOD("get_system_dir", "dir", "shared_storage"), &_OS::get_system_dir, DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("get_system_dir", "dir", "shared_storage"), &_OS::get_system_dir, DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("get_config_dir"), &_OS::get_config_dir);
 	ClassDB::bind_method(D_METHOD("get_config_dir"), &_OS::get_config_dir);

+ 1 - 0
core/bind/core_bind.h

@@ -354,6 +354,7 @@ public:
 
 
 	String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
 	String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
 
 
+	Error move_to_trash(const String &p_path) const;
 	String get_user_data_dir() const;
 	String get_user_data_dir() const;
 	String get_config_dir() const;
 	String get_config_dir() const;
 	String get_data_dir() const;
 	String get_data_dir() const;

+ 57 - 60
core/image.cpp

@@ -2224,6 +2224,39 @@ Ref<Image> Image::get_rect(const Rect2 &p_area) const {
 	return img;
 	return img;
 }
 }
 
 
+void Image::_get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const {
+	r_clipped_dest_rect.position = p_dest;
+	r_clipped_src_rect = p_src_rect;
+
+	if (r_clipped_src_rect.position.x < 0) {
+		r_clipped_dest_rect.position.x -= r_clipped_src_rect.position.x;
+		r_clipped_src_rect.size.x += r_clipped_src_rect.position.x;
+		r_clipped_src_rect.position.x = 0;
+	}
+	if (r_clipped_src_rect.position.y < 0) {
+		r_clipped_dest_rect.position.y -= r_clipped_src_rect.position.y;
+		r_clipped_src_rect.size.y += r_clipped_src_rect.position.y;
+		r_clipped_src_rect.position.y = 0;
+	}
+
+	if (r_clipped_dest_rect.position.x < 0) {
+		r_clipped_src_rect.position.x -= r_clipped_dest_rect.position.x;
+		r_clipped_src_rect.size.x += r_clipped_dest_rect.position.x;
+		r_clipped_dest_rect.position.x = 0;
+	}
+	if (r_clipped_dest_rect.position.y < 0) {
+		r_clipped_src_rect.position.y -= r_clipped_dest_rect.position.y;
+		r_clipped_src_rect.size.y += r_clipped_dest_rect.position.y;
+		r_clipped_dest_rect.position.y = 0;
+	}
+
+	r_clipped_src_rect.size.x = MAX(0, MIN(r_clipped_src_rect.size.x, MIN(p_src->width - r_clipped_src_rect.position.x, width - r_clipped_dest_rect.position.x)));
+	r_clipped_src_rect.size.y = MAX(0, MIN(r_clipped_src_rect.size.y, MIN(p_src->height - r_clipped_src_rect.position.y, height - r_clipped_dest_rect.position.y)));
+
+	r_clipped_dest_rect.size.x = r_clipped_src_rect.size.x;
+	r_clipped_dest_rect.size.y = r_clipped_src_rect.size.y;
+}
+
 void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest) {
 void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest) {
 	ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object.");
 	ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object.");
 	int dsize = data.size();
 	int dsize = data.size();
@@ -2233,22 +2266,13 @@ void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Po
 	ERR_FAIL_COND(format != p_src->format);
 	ERR_FAIL_COND(format != p_src->format);
 	ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot blit_rect in compressed or custom image formats.");
 	ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot blit_rect in compressed or custom image formats.");
 
 
-	Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect);
-
-	if (p_dest.x < 0) {
-		clipped_src_rect.position.x = ABS(p_dest.x);
-	}
-	if (p_dest.y < 0) {
-		clipped_src_rect.position.y = ABS(p_dest.y);
-	}
-
-	if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) {
+	Rect2i src_rect;
+	Rect2i dest_rect;
+	_get_clipped_src_and_dest_rects(p_src, p_src_rect, p_dest, src_rect, dest_rect);
+	if (src_rect.has_no_area() || dest_rect.has_no_area()) {
 		return;
 		return;
 	}
 	}
 
 
-	Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y));
-	Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size));
-
 	PoolVector<uint8_t>::Write wp = data.write();
 	PoolVector<uint8_t>::Write wp = data.write();
 	uint8_t *dst_data_ptr = wp.ptr();
 	uint8_t *dst_data_ptr = wp.ptr();
 
 
@@ -2259,8 +2283,8 @@ void Image::blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Po
 
 
 	for (int i = 0; i < dest_rect.size.y; i++) {
 	for (int i = 0; i < dest_rect.size.y; i++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
-			int src_x = clipped_src_rect.position.x + j;
-			int src_y = clipped_src_rect.position.y + i;
+			int src_x = src_rect.position.x + j;
+			int src_y = src_rect.position.y + i;
 
 
 			int dst_x = dest_rect.position.x + j;
 			int dst_x = dest_rect.position.x + j;
 			int dst_y = dest_rect.position.y + i;
 			int dst_y = dest_rect.position.y + i;
@@ -2288,22 +2312,13 @@ void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, co
 	ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height.");
 	ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height.");
 	ERR_FAIL_COND(format != p_src->format);
 	ERR_FAIL_COND(format != p_src->format);
 
 
-	Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect);
-
-	if (p_dest.x < 0) {
-		clipped_src_rect.position.x = ABS(p_dest.x);
-	}
-	if (p_dest.y < 0) {
-		clipped_src_rect.position.y = ABS(p_dest.y);
-	}
-
-	if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) {
+	Rect2i src_rect;
+	Rect2i dest_rect;
+	_get_clipped_src_and_dest_rects(p_src, p_src_rect, p_dest, src_rect, dest_rect);
+	if (src_rect.has_no_area() || dest_rect.has_no_area()) {
 		return;
 		return;
 	}
 	}
 
 
-	Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y));
-	Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size));
-
 	PoolVector<uint8_t>::Write wp = data.write();
 	PoolVector<uint8_t>::Write wp = data.write();
 	uint8_t *dst_data_ptr = wp.ptr();
 	uint8_t *dst_data_ptr = wp.ptr();
 
 
@@ -2317,8 +2332,8 @@ void Image::blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, co
 
 
 	for (int i = 0; i < dest_rect.size.y; i++) {
 	for (int i = 0; i < dest_rect.size.y; i++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
-			int src_x = clipped_src_rect.position.x + j;
-			int src_y = clipped_src_rect.position.y + i;
+			int src_x = src_rect.position.x + j;
+			int src_y = src_rect.position.y + i;
 
 
 			if (msk->get_pixel(src_x, src_y).a != 0) {
 			if (msk->get_pixel(src_x, src_y).a != 0) {
 				int dst_x = dest_rect.position.x + j;
 				int dst_x = dest_rect.position.x + j;
@@ -2345,30 +2360,21 @@ void Image::blend_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const P
 	ERR_FAIL_COND(srcdsize == 0);
 	ERR_FAIL_COND(srcdsize == 0);
 	ERR_FAIL_COND(format != p_src->format);
 	ERR_FAIL_COND(format != p_src->format);
 
 
-	Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect);
-
-	if (p_dest.x < 0) {
-		clipped_src_rect.position.x = ABS(p_dest.x);
-	}
-	if (p_dest.y < 0) {
-		clipped_src_rect.position.y = ABS(p_dest.y);
-	}
-
-	if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) {
+	Rect2i src_rect;
+	Rect2i dest_rect;
+	_get_clipped_src_and_dest_rects(p_src, p_src_rect, p_dest, src_rect, dest_rect);
+	if (src_rect.has_no_area() || dest_rect.has_no_area()) {
 		return;
 		return;
 	}
 	}
 
 
-	Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y));
-	Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size));
-
 	lock();
 	lock();
 	Ref<Image> img = p_src;
 	Ref<Image> img = p_src;
 	img->lock();
 	img->lock();
 
 
 	for (int i = 0; i < dest_rect.size.y; i++) {
 	for (int i = 0; i < dest_rect.size.y; i++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
-			int src_x = clipped_src_rect.position.x + j;
-			int src_y = clipped_src_rect.position.y + i;
+			int src_x = src_rect.position.x + j;
+			int src_y = src_rect.position.y + i;
 
 
 			int dst_x = dest_rect.position.x + j;
 			int dst_x = dest_rect.position.x + j;
 			int dst_y = dest_rect.position.y + i;
 			int dst_y = dest_rect.position.y + i;
@@ -2399,22 +2405,13 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c
 	ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height.");
 	ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height.");
 	ERR_FAIL_COND(format != p_src->format);
 	ERR_FAIL_COND(format != p_src->format);
 
 
-	Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect);
-
-	if (p_dest.x < 0) {
-		clipped_src_rect.position.x = ABS(p_dest.x);
-	}
-	if (p_dest.y < 0) {
-		clipped_src_rect.position.y = ABS(p_dest.y);
-	}
-
-	if (clipped_src_rect.size.x <= 0 || clipped_src_rect.size.y <= 0) {
+	Rect2i src_rect;
+	Rect2i dest_rect;
+	_get_clipped_src_and_dest_rects(p_src, p_src_rect, p_dest, src_rect, dest_rect);
+	if (src_rect.has_no_area() || dest_rect.has_no_area()) {
 		return;
 		return;
 	}
 	}
 
 
-	Point2 src_underscan = Point2(MIN(0, p_src_rect.position.x), MIN(0, p_src_rect.position.y));
-	Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size));
-
 	lock();
 	lock();
 	Ref<Image> img = p_src;
 	Ref<Image> img = p_src;
 	Ref<Image> msk = p_mask;
 	Ref<Image> msk = p_mask;
@@ -2423,8 +2420,8 @@ void Image::blend_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, c
 
 
 	for (int i = 0; i < dest_rect.size.y; i++) {
 	for (int i = 0; i < dest_rect.size.y; i++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
 		for (int j = 0; j < dest_rect.size.x; j++) {
-			int src_x = clipped_src_rect.position.x + j;
-			int src_y = clipped_src_rect.position.y + i;
+			int src_x = src_rect.position.x + j;
+			int src_y = src_rect.position.y + i;
 
 
 			// If the mask's pixel is transparent then we skip it
 			// If the mask's pixel is transparent then we skip it
 			//Color c = msk->get_pixel(src_x, src_y);
 			//Color c = msk->get_pixel(src_x, src_y);

+ 2 - 0
core/image.h

@@ -186,6 +186,8 @@ private:
 	static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1);
 	static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1);
 	bool _can_modify(Format p_format) const;
 	bool _can_modify(Format p_format) const;
 
 
+	_FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const;
+
 	_FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel);
 	_FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel);
 	_FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel);
 	_FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel);
 
 

+ 61 - 52
core/os/time.cpp

@@ -97,12 +97,17 @@ VARIANT_ENUM_CAST(Time::Weekday);
 
 
 #define VALIDATE_YMDHMS(ret)                                                                                                                                              \
 #define VALIDATE_YMDHMS(ret)                                                                                                                                              \
 	ERR_FAIL_COND_V_MSG(month == 0, ret, "Invalid month value of: " + itos(month) + ", months are 1-indexed and cannot be 0. See the Time.Month enum for valid values."); \
 	ERR_FAIL_COND_V_MSG(month == 0, ret, "Invalid month value of: " + itos(month) + ", months are 1-indexed and cannot be 0. See the Time.Month enum for valid values."); \
+	ERR_FAIL_COND_V_MSG(month < 0, ret, "Invalid month value of: " + itos(month) + ".");                                                                                  \
 	ERR_FAIL_COND_V_MSG(month > 12, ret, "Invalid month value of: " + itos(month) + ". See the Time.Month enum for valid values.");                                       \
 	ERR_FAIL_COND_V_MSG(month > 12, ret, "Invalid month value of: " + itos(month) + ". See the Time.Month enum for valid values.");                                       \
 	ERR_FAIL_COND_V_MSG(hour > 23, ret, "Invalid hour value of: " + itos(hour) + ".");                                                                                    \
 	ERR_FAIL_COND_V_MSG(hour > 23, ret, "Invalid hour value of: " + itos(hour) + ".");                                                                                    \
+	ERR_FAIL_COND_V_MSG(hour < 0, ret, "Invalid hour value of: " + itos(hour) + ".");                                                                                     \
 	ERR_FAIL_COND_V_MSG(minute > 59, ret, "Invalid minute value of: " + itos(minute) + ".");                                                                              \
 	ERR_FAIL_COND_V_MSG(minute > 59, ret, "Invalid minute value of: " + itos(minute) + ".");                                                                              \
+	ERR_FAIL_COND_V_MSG(minute < 0, ret, "Invalid minute value of: " + itos(minute) + ".");                                                                               \
 	ERR_FAIL_COND_V_MSG(second > 59, ret, "Invalid second value of: " + itos(second) + " (leap seconds are not supported).");                                             \
 	ERR_FAIL_COND_V_MSG(second > 59, ret, "Invalid second value of: " + itos(second) + " (leap seconds are not supported).");                                             \
+	ERR_FAIL_COND_V_MSG(second < 0, ret, "Invalid second value of: " + itos(second) + ".");                                                                               \
+	ERR_FAIL_COND_V_MSG(day == 0, ret, "Invalid day value of: " + itos(day) + ", days are 1-indexed and cannot be 0.");                                                   \
+	ERR_FAIL_COND_V_MSG(day < 0, ret, "Invalid day value of: " + itos(day) + ".");                                                                                        \
 	/* Do this check after month is tested as valid. */                                                                                                                   \
 	/* Do this check after month is tested as valid. */                                                                                                                   \
-	ERR_FAIL_COND_V_MSG(day == 0, ret, "Invalid day value of: " + itos(month) + ", days are 1-indexed and cannot be 0.");                                                 \
 	uint8_t days_in_this_month = MONTH_DAYS_TABLE[IS_LEAP_YEAR(year)][month - 1];                                                                                         \
 	uint8_t days_in_this_month = MONTH_DAYS_TABLE[IS_LEAP_YEAR(year)][month - 1];                                                                                         \
 	ERR_FAIL_COND_V_MSG(day > days_in_this_month, ret, "Invalid day value of: " + itos(day) + " which is larger than the maximum for this month, " + itos(days_in_this_month) + ".");
 	ERR_FAIL_COND_V_MSG(day > days_in_this_month, ret, "Invalid day value of: " + itos(day) + " which is larger than the maximum for this month, " + itos(days_in_this_month) + ".");
 
 
@@ -124,61 +129,65 @@ VARIANT_ENUM_CAST(Time::Weekday);
 		}                                                                           \
 		}                                                                           \
 	}
 	}
 
 
-#define PARSE_ISO8601_STRING                                     \
-	int64_t year = UNIX_EPOCH_YEAR_AD;                           \
-	Month month = MONTH_JANUARY;                                 \
-	uint8_t day = 1;                                             \
-	uint8_t hour = 0;                                            \
-	uint8_t minute = 0;                                          \
-	uint8_t second = 0;                                          \
-	{                                                            \
-		bool has_date = false, has_time = false;                 \
-		String date, time;                                       \
-		if (p_datetime.find_char('T') > 0) {                     \
-			has_date = has_time = true;                          \
-			Vector<String> array = p_datetime.split("T");        \
-			date = array[0];                                     \
-			time = array[1];                                     \
-		} else if (p_datetime.find_char(' ') > 0) {              \
-			has_date = has_time = true;                          \
-			Vector<String> array = p_datetime.split(" ");        \
-			date = array[0];                                     \
-			time = array[1];                                     \
-		} else if (p_datetime.find_char('-', 1) > 0) {           \
-			has_date = true;                                     \
-			date = p_datetime;                                   \
-		} else if (p_datetime.find_char(':') > 0) {              \
-			has_time = true;                                     \
-			time = p_datetime;                                   \
-		}                                                        \
-		/* Set the variables from the contents of the string. */ \
-		if (has_date) {                                          \
-			Vector<int> array = date.split_ints("-", false);     \
-			year = array[0];                                     \
-			month = (Month)array[1];                             \
-			day = array[2];                                      \
-			/* Handle negative years. */                         \
-			if (p_datetime.find_char('-') == 0) {                \
-				year *= -1;                                      \
-			}                                                    \
-		}                                                        \
-		if (has_time) {                                          \
-			Vector<int> array = time.split_ints(":", false);     \
-			hour = array[0];                                     \
-			minute = array[1];                                   \
-			second = array[2];                                   \
-		}                                                        \
+#define PARSE_ISO8601_STRING(ret)                                                             \
+	int64_t year = UNIX_EPOCH_YEAR_AD;                                                        \
+	Month month = MONTH_JANUARY;                                                              \
+	int day = 1;                                                                              \
+	int hour = 0;                                                                             \
+	int minute = 0;                                                                           \
+	int second = 0;                                                                           \
+	{                                                                                         \
+		bool has_date = false, has_time = false;                                              \
+		String date, time;                                                                    \
+		if (p_datetime.find_char('T') > 0) {                                                  \
+			has_date = has_time = true;                                                       \
+			Vector<String> array = p_datetime.split("T");                                     \
+			ERR_FAIL_COND_V_MSG(array.size() < 2, ret, "Invalid ISO 8601 date/time string."); \
+			date = array[0];                                                                  \
+			time = array[1];                                                                  \
+		} else if (p_datetime.find_char(' ') > 0) {                                           \
+			has_date = has_time = true;                                                       \
+			Vector<String> array = p_datetime.split(" ");                                     \
+			ERR_FAIL_COND_V_MSG(array.size() < 2, ret, "Invalid ISO 8601 date/time string."); \
+			date = array[0];                                                                  \
+			time = array[1];                                                                  \
+		} else if (p_datetime.find_char('-', 1) > 0) {                                        \
+			has_date = true;                                                                  \
+			date = p_datetime;                                                                \
+		} else if (p_datetime.find_char(':') > 0) {                                           \
+			has_time = true;                                                                  \
+			time = p_datetime;                                                                \
+		}                                                                                     \
+		/* Set the variables from the contents of the string. */                              \
+		if (has_date) {                                                                       \
+			Vector<int> array = date.split_ints("-", false);                                  \
+			ERR_FAIL_COND_V_MSG(array.size() < 3, ret, "Invalid ISO 8601 date string.");      \
+			year = array[0];                                                                  \
+			month = (Month)array[1];                                                          \
+			day = array[2];                                                                   \
+			/* Handle negative years. */                                                      \
+			if (p_datetime.find_char('-') == 0) {                                             \
+				year *= -1;                                                                   \
+			}                                                                                 \
+		}                                                                                     \
+		if (has_time) {                                                                       \
+			Vector<int> array = time.split_ints(":", false);                                  \
+			ERR_FAIL_COND_V_MSG(array.size() < 3, ret, "Invalid ISO 8601 time string.");      \
+			hour = array[0];                                                                  \
+			minute = array[1];                                                                \
+			second = array[2];                                                                \
+		}                                                                                     \
 	}
 	}
 
 
 #define EXTRACT_FROM_DICTIONARY                                                                   \
 #define EXTRACT_FROM_DICTIONARY                                                                   \
 	/* Get all time values from the dictionary. If it doesn't exist, set the */                   \
 	/* Get all time values from the dictionary. If it doesn't exist, set the */                   \
 	/* values to the default values for Unix epoch (1970-01-01 00:00:00). */                      \
 	/* values to the default values for Unix epoch (1970-01-01 00:00:00). */                      \
 	int64_t year = p_datetime.has(YEAR_KEY) ? int64_t(p_datetime[YEAR_KEY]) : UNIX_EPOCH_YEAR_AD; \
 	int64_t year = p_datetime.has(YEAR_KEY) ? int64_t(p_datetime[YEAR_KEY]) : UNIX_EPOCH_YEAR_AD; \
-	Month month = Month((p_datetime.has(MONTH_KEY)) ? uint8_t(p_datetime[MONTH_KEY]) : 1);        \
-	uint8_t day = p_datetime.has(DAY_KEY) ? uint8_t(p_datetime[DAY_KEY]) : 1;                     \
-	uint8_t hour = p_datetime.has(HOUR_KEY) ? uint8_t(p_datetime[HOUR_KEY]) : 0;                  \
-	uint8_t minute = p_datetime.has(MINUTE_KEY) ? uint8_t(p_datetime[MINUTE_KEY]) : 0;            \
-	uint8_t second = p_datetime.has(SECOND_KEY) ? uint8_t(p_datetime[SECOND_KEY]) : 0;
+	Month month = Month((p_datetime.has(MONTH_KEY)) ? int(p_datetime[MONTH_KEY]) : 1);            \
+	int day = p_datetime.has(DAY_KEY) ? int(p_datetime[DAY_KEY]) : 1;                             \
+	int hour = p_datetime.has(HOUR_KEY) ? int(p_datetime[HOUR_KEY]) : 0;                          \
+	int minute = p_datetime.has(MINUTE_KEY) ? int(p_datetime[MINUTE_KEY]) : 0;                    \
+	int second = p_datetime.has(SECOND_KEY) ? int(p_datetime[SECOND_KEY]) : 0;
 
 
 Time *Time::singleton = nullptr;
 Time *Time::singleton = nullptr;
 
 
@@ -253,7 +262,7 @@ String Time::get_time_string_from_unix_time(int64_t p_unix_time_val) const {
 }
 }
 
 
 Dictionary Time::get_datetime_dict_from_string(String p_datetime, bool p_weekday) const {
 Dictionary Time::get_datetime_dict_from_string(String p_datetime, bool p_weekday) const {
-	PARSE_ISO8601_STRING
+	PARSE_ISO8601_STRING(Dictionary())
 	Dictionary dict;
 	Dictionary dict;
 	dict[YEAR_KEY] = year;
 	dict[YEAR_KEY] = year;
 	dict[MONTH_KEY] = (uint8_t)month;
 	dict[MONTH_KEY] = (uint8_t)month;
@@ -293,7 +302,7 @@ int64_t Time::get_unix_time_from_datetime_dict(const Dictionary p_datetime) cons
 }
 }
 
 
 int64_t Time::get_unix_time_from_datetime_string(String p_datetime) const {
 int64_t Time::get_unix_time_from_datetime_string(String p_datetime) const {
-	PARSE_ISO8601_STRING
+	PARSE_ISO8601_STRING(-1)
 	VALIDATE_YMDHMS(0)
 	VALIDATE_YMDHMS(0)
 	YMD_TO_DAY_NUMBER
 	YMD_TO_DAY_NUMBER
 	return day_number * SECONDS_PER_DAY + hour * 3600 + minute * 60 + second;
 	return day_number * SECONDS_PER_DAY + hour * 3600 + minute * 60 + second;

+ 1 - 1
core/os/time.h

@@ -51,7 +51,7 @@ class Time : public Object {
 public:
 public:
 	static Time *get_singleton();
 	static Time *get_singleton();
 
 
-	enum Month : uint8_t {
+	enum Month {
 		/// Start at 1 to follow Windows SYSTEMTIME structure
 		/// Start at 1 to follow Windows SYSTEMTIME structure
 		/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx
 		/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx
 		MONTH_JANUARY = 1,
 		MONTH_JANUARY = 1,

+ 2 - 1
doc/classes/Control.xml

@@ -508,7 +508,8 @@
 		<method name="is_drag_successful" qualifiers="const">
 		<method name="is_drag_successful" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>
-				Returns [code]true[/code] if drag operation is successful.
+				Returns [code]true[/code] if a drag operation is successful. Alternative to [method Viewport.gui_is_drag_successful].
+				Best used with [constant Node.NOTIFICATION_DRAG_END].
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="minimum_size_changed">
 		<method name="minimum_size_changed">

+ 2 - 1
doc/classes/Directory.xml

@@ -153,7 +153,8 @@
 			<return type="int" enum="Error" />
 			<return type="int" enum="Error" />
 			<argument index="0" name="path" type="String" />
 			<argument index="0" name="path" type="String" />
 			<description>
 			<description>
-				Deletes the target file or an empty directory. The argument can be relative to the current directory, or an absolute path. If the target directory is not empty, the operation will fail.
+				Permanently deletes the target file or an empty directory. The argument can be relative to the current directory, or an absolute path. If the target directory is not empty, the operation will fail.
+				If you don't want to delete the file/directory permanently, use [method OS.move_to_trash] instead.
 				Returns one of the [enum Error] code constants ([code]OK[/code] on success).
 				Returns one of the [enum Error] code constants ([code]OK[/code] on success).
 			</description>
 			</description>
 		</method>
 		</method>

+ 4 - 4
doc/classes/Image.xml

@@ -18,7 +18,7 @@
 			<argument index="1" name="src_rect" type="Rect2" />
 			<argument index="1" name="src_rect" type="Rect2" />
 			<argument index="2" name="dst" type="Vector2" />
 			<argument index="2" name="dst" type="Vector2" />
 			<description>
 			<description>
-				Alpha-blends [code]src_rect[/code] from [code]src[/code] image to this image at coordinates [code]dest[/code].
+				Alpha-blends [code]src_rect[/code] from [code]src[/code] image to this image at coordinates [code]dest[/code], clipped accordingly to both image bounds. This image and [code]src[/code] image [b]must[/b] have the same format. [code]src_rect[/code] with not positive size is treated as empty.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="blend_rect_mask">
 		<method name="blend_rect_mask">
@@ -28,7 +28,7 @@
 			<argument index="2" name="src_rect" type="Rect2" />
 			<argument index="2" name="src_rect" type="Rect2" />
 			<argument index="3" name="dst" type="Vector2" />
 			<argument index="3" name="dst" type="Vector2" />
 			<description>
 			<description>
-				Alpha-blends [code]src_rect[/code] from [code]src[/code] image to this image using [code]mask[/code] image at coordinates [code]dst[/code]. Alpha channels are required for both [code]src[/code] and [code]mask[/code]. [code]dst[/code] pixels and [code]src[/code] pixels will blend if the corresponding mask pixel's alpha value is not 0. [code]src[/code] image and [code]mask[/code] image [b]must[/b] have the same size (width and height) but they can have different formats.
+				Alpha-blends [code]src_rect[/code] from [code]src[/code] image to this image using [code]mask[/code] image at coordinates [code]dst[/code], clipped accordingly to both image bounds. Alpha channels are required for both [code]src[/code] and [code]mask[/code]. [code]dst[/code] pixels and [code]src[/code] pixels will blend if the corresponding mask pixel's alpha value is not 0. This image and [code]src[/code] image [b]must[/b] have the same format. [code]src[/code] image and [code]mask[/code] image [b]must[/b] have the same size (width and height) but they can have different formats. [code]src_rect[/code] with not positive size is treated as empty.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="blit_rect">
 		<method name="blit_rect">
@@ -37,7 +37,7 @@
 			<argument index="1" name="src_rect" type="Rect2" />
 			<argument index="1" name="src_rect" type="Rect2" />
 			<argument index="2" name="dst" type="Vector2" />
 			<argument index="2" name="dst" type="Vector2" />
 			<description>
 			<description>
-				Copies [code]src_rect[/code] from [code]src[/code] image to this image at coordinates [code]dst[/code].
+				Copies [code]src_rect[/code] from [code]src[/code] image to this image at coordinates [code]dst[/code], clipped accordingly to both image bounds. This image and [code]src[/code] image [b]must[/b] have the same format. [code]src_rect[/code] with not positive size is treated as empty.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="blit_rect_mask">
 		<method name="blit_rect_mask">
@@ -47,7 +47,7 @@
 			<argument index="2" name="src_rect" type="Rect2" />
 			<argument index="2" name="src_rect" type="Rect2" />
 			<argument index="3" name="dst" type="Vector2" />
 			<argument index="3" name="dst" type="Vector2" />
 			<description>
 			<description>
-				Blits [code]src_rect[/code] area from [code]src[/code] image to this image at the coordinates given by [code]dst[/code]. [code]src[/code] pixel is copied onto [code]dst[/code] if the corresponding [code]mask[/code] pixel's alpha value is not 0. [code]src[/code] image and [code]mask[/code] image [b]must[/b] have the same size (width and height) but they can have different formats.
+				Blits [code]src_rect[/code] area from [code]src[/code] image to this image at the coordinates given by [code]dst[/code], clipped accordingly to both image bounds. [code]src[/code] pixel is copied onto [code]dst[/code] if the corresponding [code]mask[/code] pixel's alpha value is not 0. This image and [code]src[/code] image [b]must[/b] have the same format. [code]src[/code] image and [code]mask[/code] image [b]must[/b] have the same size (width and height) but they can have different formats. [code]src_rect[/code] with not positive size is treated as empty.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="bumpmap_to_normalmap">
 		<method name="bumpmap_to_normalmap">

+ 5 - 2
doc/classes/Node.xml

@@ -825,10 +825,13 @@
 			Notification received when the node is instanced.
 			Notification received when the node is instanced.
 		</constant>
 		</constant>
 		<constant name="NOTIFICATION_DRAG_BEGIN" value="21">
 		<constant name="NOTIFICATION_DRAG_BEGIN" value="21">
-			Notification received when a drag begins.
+			Notification received when a drag operation begins. All nodes receive this notification, not only the dragged one.
+			Can be triggered either by dragging a [Control] that provides drag data (see [method Control.get_drag_data]) or using [method Control.force_drag].
+			Use [method Viewport.gui_get_drag_data] to get the dragged data.
 		</constant>
 		</constant>
 		<constant name="NOTIFICATION_DRAG_END" value="22">
 		<constant name="NOTIFICATION_DRAG_END" value="22">
-			Notification received when a drag ends.
+			Notification received when a drag operation ends.
+			Use [method Viewport.gui_is_drag_successful] to check if the drag succeeded.
 		</constant>
 		</constant>
 		<constant name="NOTIFICATION_PATH_CHANGED" value="23">
 		<constant name="NOTIFICATION_PATH_CHANGED" value="23">
 			Notification received when the node's [NodePath] changed.
 			Notification received when the node's [NodePath] changed.

+ 8 - 0
doc/classes/OS.xml

@@ -766,6 +766,14 @@
 				[b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows.
 				[b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="move_to_trash" qualifiers="const">
+			<return type="int" enum="Error" />
+			<argument index="0" name="path" type="String" />
+			<description>
+				Moves the file or directory to the system's recycle bin. See also [method Directory.remove].
+				[b]Note:[/b] If the user has disabled the recycle bin on their system, the file will be permanently deleted instead.
+			</description>
+		</method>
 		<method name="move_window_to_foreground">
 		<method name="move_window_to_foreground">
 			<return type="void" />
 			<return type="void" />
 			<description>
 			<description>

+ 3 - 0
doc/classes/Time.xml

@@ -50,6 +50,7 @@
 			<description>
 			<description>
 				Converts the given ISO 8601 date and time string (YYYY-MM-DDTHH:MM:SS) to a dictionary of keys: [code]year[/code], [code]month[/code], [code]day[/code], [code]weekday[/code], [code]hour[/code], [code]minute[/code], and [code]second[/code].
 				Converts the given ISO 8601 date and time string (YYYY-MM-DDTHH:MM:SS) to a dictionary of keys: [code]year[/code], [code]month[/code], [code]day[/code], [code]weekday[/code], [code]hour[/code], [code]minute[/code], and [code]second[/code].
 				If [code]weekday[/code] is false, then the [code]weekday[/code] entry is excluded (the calculation is relatively expensive).
 				If [code]weekday[/code] is false, then the [code]weekday[/code] entry is excluded (the calculation is relatively expensive).
+				[b]Note:[/b] Any decimal fraction in the time string will be ignored silently.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="get_datetime_dict_from_system" qualifiers="const">
 		<method name="get_datetime_dict_from_system" qualifiers="const">
@@ -171,12 +172,14 @@
 			<description>
 			<description>
 				Converts the given ISO 8601 date and/or time string to a Unix timestamp. The string can contain a date only, a time only, or both.
 				Converts the given ISO 8601 date and/or time string to a Unix timestamp. The string can contain a date only, a time only, or both.
 				[b]Note:[/b] Unix timestamps are often in UTC. This method does not do any timezone conversion, so the timestamp will be in the same timezone as the given datetime string.
 				[b]Note:[/b] Unix timestamps are often in UTC. This method does not do any timezone conversion, so the timestamp will be in the same timezone as the given datetime string.
+				[b]Note:[/b] Any decimal fraction in the time string will be ignored silently.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="get_unix_time_from_system" qualifiers="const">
 		<method name="get_unix_time_from_system" qualifiers="const">
 			<return type="float" />
 			<return type="float" />
 			<description>
 			<description>
 				Returns the current Unix timestamp in seconds based on the system time in UTC. This method is implemented by the operating system and always returns the time in UTC.
 				Returns the current Unix timestamp in seconds based on the system time in UTC. This method is implemented by the operating system and always returns the time in UTC.
+				[b]Note:[/b] Unlike other methods that use integer timestamps, this method returns the timestamp as a [float] for sub-second precision.
 			</description>
 			</description>
 		</method>
 		</method>
 	</methods>
 	</methods>

+ 1 - 1
doc/classes/Vector3.xml

@@ -4,7 +4,7 @@
 		Vector used for 3D math.
 		Vector used for 3D math.
 	</brief_description>
 	</brief_description>
 	<description>
 	<description>
-		3-element structure that can be used to represent positions in 3D space or any other pair of numeric values.
+		3-element structure that can be used to represent positions in 3D space or any other triplet of numeric values.
 		[b]Note:[/b] In a boolean context, a Vector3 will evaluate to [code]false[/code] if it's equal to [code]Vector3(0, 0, 0)[/code]. Otherwise, a Vector3 will always evaluate to [code]true[/code].
 		[b]Note:[/b] In a boolean context, a Vector3 will evaluate to [code]false[/code] if it's equal to [code]Vector3(0, 0, 0)[/code]. Otherwise, a Vector3 will always evaluate to [code]true[/code].
 	</description>
 	</description>
 	<tutorials>
 	<tutorials>

+ 1 - 0
doc/classes/Viewport.xml

@@ -124,6 +124,7 @@
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>
 				Returns [code]true[/code] if the viewport is currently performing a drag operation.
 				Returns [code]true[/code] if the viewport is currently performing a drag operation.
+				Alternative to [constant Node.NOTIFICATION_DRAG_BEGIN] and [constant Node.NOTIFICATION_DRAG_END] when you prefer polling the value.
 			</description>
 			</description>
 		</method>
 		</method>
 		<method name="input">
 		<method name="input">

+ 12 - 13
editor/editor_node.cpp

@@ -619,15 +619,14 @@ void EditorNode::_notification(int p_what) {
 
 
 			PopupMenu *p = help_menu->get_popup();
 			PopupMenu *p = help_menu->get_popup();
 			p->set_item_icon(p->get_item_index(HELP_SEARCH), gui_base->get_icon("HelpSearch", "EditorIcons"));
 			p->set_item_icon(p->get_item_index(HELP_SEARCH), gui_base->get_icon("HelpSearch", "EditorIcons"));
-			p->set_item_icon(p->get_item_index(HELP_DOCS), gui_base->get_icon("Instance", "EditorIcons"));
-			p->set_item_icon(p->get_item_index(HELP_QA), gui_base->get_icon("Instance", "EditorIcons"));
-			p->set_item_icon(p->get_item_index(HELP_REPORT_A_BUG), gui_base->get_icon("Instance", "EditorIcons"));
-			p->set_item_icon(p->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_icon("Instance", "EditorIcons"));
-			p->set_item_icon(p->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_icon("Instance", "EditorIcons"));
-			p->set_item_icon(p->get_item_index(HELP_COMMUNITY), gui_base->get_icon("Instance", "EditorIcons"));
+			p->set_item_icon(p->get_item_index(HELP_DOCS), gui_base->get_icon("ExternalLink", "EditorIcons"));
+			p->set_item_icon(p->get_item_index(HELP_QA), gui_base->get_icon("ExternalLink", "EditorIcons"));
+			p->set_item_icon(p->get_item_index(HELP_REPORT_A_BUG), gui_base->get_icon("ExternalLink", "EditorIcons"));
+			p->set_item_icon(p->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_icon("ExternalLink", "EditorIcons"));
+			p->set_item_icon(p->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_icon("ExternalLink", "EditorIcons"));
+			p->set_item_icon(p->get_item_index(HELP_COMMUNITY), gui_base->get_icon("ExternalLink", "EditorIcons"));
 			p->set_item_icon(p->get_item_index(HELP_ABOUT), gui_base->get_icon("Godot", "EditorIcons"));
 			p->set_item_icon(p->get_item_index(HELP_ABOUT), gui_base->get_icon("Godot", "EditorIcons"));
 			p->set_item_icon(p->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_icon("Heart", "EditorIcons"));
 			p->set_item_icon(p->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_icon("Heart", "EditorIcons"));
-
 			_update_update_spinner();
 			_update_update_spinner();
 		} break;
 		} break;
 
 
@@ -6560,12 +6559,12 @@ EditorNode::EditorNode() {
 	p->add_icon_shortcut(gui_base->get_icon("HelpSearch", "EditorIcons"), ED_SHORTCUT("editor/editor_help", TTR("Search Help"), KEY_F1), HELP_SEARCH);
 	p->add_icon_shortcut(gui_base->get_icon("HelpSearch", "EditorIcons"), ED_SHORTCUT("editor/editor_help", TTR("Search Help"), KEY_F1), HELP_SEARCH);
 #endif
 #endif
 	p->add_separator();
 	p->add_separator();
-	p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
-	p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/q&a", TTR("Questions & Answers")), HELP_QA);
-	p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
-	p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
-	p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
-	p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/community", TTR("Community")), HELP_COMMUNITY);
+	p->add_icon_shortcut(gui_base->get_icon("ExternalLink", "EditorIcons"), ED_SHORTCUT("editor/online_docs", TTR("Online Documentation")), HELP_DOCS);
+	p->add_icon_shortcut(gui_base->get_icon("ExternalLink", "EditorIcons"), ED_SHORTCUT("editor/q&a", TTR("Questions & Answers")), HELP_QA);
+	p->add_icon_shortcut(gui_base->get_icon("ExternalLink", "EditorIcons"), ED_SHORTCUT("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG);
+	p->add_icon_shortcut(gui_base->get_icon("ExternalLink", "EditorIcons"), ED_SHORTCUT("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE);
+	p->add_icon_shortcut(gui_base->get_icon("ExternalLink", "EditorIcons"), ED_SHORTCUT("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK);
+	p->add_icon_shortcut(gui_base->get_icon("ExternalLink", "EditorIcons"), ED_SHORTCUT("editor/community", TTR("Community")), HELP_COMMUNITY);
 	p->add_separator();
 	p->add_separator();
 	p->add_icon_shortcut(gui_base->get_icon("Godot", "EditorIcons"), ED_SHORTCUT("editor/about", TTR("About Godot")), HELP_ABOUT);
 	p->add_icon_shortcut(gui_base->get_icon("Godot", "EditorIcons"), ED_SHORTCUT("editor/about", TTR("About Godot")), HELP_ABOUT);
 	p->add_icon_shortcut(gui_base->get_icon("Heart", "EditorIcons"), ED_SHORTCUT("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT);
 	p->add_icon_shortcut(gui_base->get_icon("Heart", "EditorIcons"), ED_SHORTCUT("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT);

+ 3 - 0
editor/editor_property_name_processor.cpp

@@ -110,6 +110,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["arm64-v8a"] = "arm64-v8a";
 	capitalize_string_remaps["arm64-v8a"] = "arm64-v8a";
 	capitalize_string_remaps["armeabi-v7a"] = "armeabi-v7a";
 	capitalize_string_remaps["armeabi-v7a"] = "armeabi-v7a";
 	capitalize_string_remaps["arvr"] = "ARVR";
 	capitalize_string_remaps["arvr"] = "ARVR";
+	capitalize_string_remaps["bidi"] = "BiDi";
 	capitalize_string_remaps["bg"] = "BG";
 	capitalize_string_remaps["bg"] = "BG";
 	capitalize_string_remaps["bp"] = "BP";
 	capitalize_string_remaps["bp"] = "BP";
 	capitalize_string_remaps["bpc"] = "BPC";
 	capitalize_string_remaps["bpc"] = "BPC";
@@ -129,6 +130,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["erp"] = "ERP";
 	capitalize_string_remaps["erp"] = "ERP";
 	capitalize_string_remaps["etc"] = "ETC";
 	capitalize_string_remaps["etc"] = "ETC";
 	capitalize_string_remaps["etc2"] = "ETC2";
 	capitalize_string_remaps["etc2"] = "ETC2";
+	capitalize_string_remaps["filesystem"] = "FileSystem";
 	capitalize_string_remaps["fbx"] = "FBX";
 	capitalize_string_remaps["fbx"] = "FBX";
 	capitalize_string_remaps["fft"] = "FFT";
 	capitalize_string_remaps["fft"] = "FFT";
 	capitalize_string_remaps["fg"] = "FG";
 	capitalize_string_remaps["fg"] = "FG";
@@ -181,6 +183,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	//capitalize_string_remaps["msec"] = "(msec)"; // Unit.
 	//capitalize_string_remaps["msec"] = "(msec)"; // Unit.
 	capitalize_string_remaps["msaa"] = "MSAA";
 	capitalize_string_remaps["msaa"] = "MSAA";
 	capitalize_string_remaps["nfc"] = "NFC";
 	capitalize_string_remaps["nfc"] = "NFC";
+	capitalize_string_remaps["navmesh"] = "NavMesh";
 	capitalize_string_remaps["normalmap"] = "Normal Map";
 	capitalize_string_remaps["normalmap"] = "Normal Map";
 	capitalize_string_remaps["ofs"] = "Offset";
 	capitalize_string_remaps["ofs"] = "Offset";
 	capitalize_string_remaps["ok"] = "OK";
 	capitalize_string_remaps["ok"] = "OK";

+ 4 - 3
editor/filesystem_dock.cpp

@@ -223,10 +223,11 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo
 	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
 	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
 	bool fav_changed = false;
 	bool fav_changed = false;
 	for (int i = favorite_paths.size() - 1; i >= 0; i--) {
 	for (int i = favorite_paths.size() - 1; i >= 0; i--) {
-		if (!da->dir_exists(favorite_paths[i])) {
-			favorite_paths.remove(i);
-			fav_changed = true;
+		if (da->dir_exists(favorite_paths[i]) || da->file_exists(favorite_paths[i])) {
+			continue;
 		}
 		}
+		favorite_paths.remove(i);
+		fav_changed = true;
 	}
 	}
 	if (fav_changed) {
 	if (fav_changed) {
 		EditorSettings::get_singleton()->set_favorites(favorite_paths);
 		EditorSettings::get_singleton()->set_favorites(favorite_paths);

+ 1 - 0
editor/icons/icon_external_link.svg

@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h16v16h-16z"/></clipPath><g clip-path="url(#a)" fill="#e0e0e0"><path d="m-1940-64.061 5.5-5.5-2.44-2.439h7v7l-2.439-2.439-5.5 5.5z" transform="translate(1944.939 73)"/><path d="m12 15h-8a3.079 3.079 0 0 1 -3-3v-8a3.04 3.04 0 0 1 3-3h2a1 1 0 0 1 0 2h-2a1.04 1.04 0 0 0 -1 1v8a1.083 1.083 0 0 0 1 1h8a1.068 1.068 0 0 0 1-1v-2a1 1 0 0 1 2 0v2a3.063 3.063 0 0 1 -3 3z"/></g></svg>

+ 1 - 1
editor/plugins/gradient_editor_plugin.cpp

@@ -51,7 +51,7 @@ void GradientEditor::_gradient_changed() {
 void GradientEditor::_ramp_changed() {
 void GradientEditor::_ramp_changed() {
 	editing = true;
 	editing = true;
 	UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
 	UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
-	undo_redo->create_action(TTR("Gradient Edited"));
+	undo_redo->create_action(TTR("Gradient Edited"), UndoRedo::MERGE_ENDS);
 	undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets());
 	undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets());
 	undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors());
 	undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors());
 	undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets());
 	undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets());

+ 1 - 1
editor/plugins/script_editor_plugin.cpp

@@ -1429,7 +1429,7 @@ void ScriptEditor::_notification(int p_what) {
 		}
 		}
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			help_search->set_icon(get_icon("HelpSearch", "EditorIcons"));
 			help_search->set_icon(get_icon("HelpSearch", "EditorIcons"));
-			site_search->set_icon(get_icon("Instance", "EditorIcons"));
+			site_search->set_icon(get_icon("ExternalLink", "EditorIcons"));
 
 
 			script_forward->set_icon(get_icon("Forward", "EditorIcons"));
 			script_forward->set_icon(get_icon("Forward", "EditorIcons"));
 			script_back->set_icon(get_icon("Back", "EditorIcons"));
 			script_back->set_icon(get_icon("Back", "EditorIcons"));

+ 1 - 1
editor/plugins/shader_editor_plugin.cpp

@@ -353,7 +353,7 @@ void ShaderEditor::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			PopupMenu *popup = help_menu->get_popup();
 			PopupMenu *popup = help_menu->get_popup();
-			popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_icon("Instance", "EditorIcons"));
+			popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_icon("ExternalLink", "EditorIcons"));
 		} break;
 		} break;
 
 
 		case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
 		case MainLoop::NOTIFICATION_WM_FOCUS_IN: {

+ 1 - 1
editor/script_editor_debugger.cpp

@@ -2210,7 +2210,7 @@ void ScriptEditorDebugger::_error_tree_item_rmb_selected(const Vector2 &p_pos) {
 
 
 	if (error_tree->is_anything_selected()) {
 	if (error_tree->is_anything_selected()) {
 		item_menu->add_icon_item(get_icon("ActionCopy", "EditorIcons"), TTR("Copy Error"), ITEM_MENU_COPY_ERROR);
 		item_menu->add_icon_item(get_icon("ActionCopy", "EditorIcons"), TTR("Copy Error"), ITEM_MENU_COPY_ERROR);
-		item_menu->add_icon_item(get_icon("Instance", "EditorIcons"), TTR("Open C++ Source on GitHub"), ITEM_MENU_OPEN_SOURCE);
+		item_menu->add_icon_item(get_icon("ExternalLink", "EditorIcons"), TTR("Open C++ Source on GitHub"), ITEM_MENU_OPEN_SOURCE);
 	}
 	}
 
 
 	if (item_menu->get_item_count() > 0) {
 	if (item_menu->get_item_count() > 0) {

+ 6 - 8
modules/csg/csg_gizmos.cpp

@@ -377,14 +377,12 @@ void CSGShapeSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
 	p_gizmo->add_lines(lines, material);
 	p_gizmo->add_lines(lines, material);
 	p_gizmo->add_collision_segments(lines);
 	p_gizmo->add_collision_segments(lines);
 
 
-	Array csg_meshes = cs->get_meshes();
-	if (csg_meshes.size() != 2) {
-		return;
-	}
-
-	Ref<Mesh> csg_mesh = csg_meshes[1];
-	if (csg_mesh.is_valid()) {
-		p_gizmo->add_collision_triangles(csg_mesh->generate_triangle_mesh());
+	if (cs->is_root_shape()) {
+		Array csg_meshes = cs->get_meshes();
+		Ref<Mesh> csg_mesh = csg_meshes[1];
+		if (csg_mesh.is_valid()) {
+			p_gizmo->add_collision_triangles(csg_mesh->generate_triangle_mesh());
+		}
 	}
 	}
 
 
 	if (p_gizmo->is_selected()) {
 	if (p_gizmo->is_selected()) {

+ 0 - 4
platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java

@@ -65,10 +65,6 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
 		} else {
 		} else {
 			Log.v(TAG, "Creating new Godot fragment instance.");
 			Log.v(TAG, "Creating new Godot fragment instance.");
 			godotFragment = initGodotInstance();
 			godotFragment = initGodotInstance();
-			if (godotFragment == null) {
-				throw new IllegalStateException("Godot instance must be non-null.");
-			}
-
 			getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
 			getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
 		}
 		}
 	}
 	}

+ 18 - 24
platform/android/java/lib/src/org/godotengine/godot/Godot.java

@@ -629,17 +629,14 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 				translucent = true;
 				translucent = true;
 			} else if (command_line[i].equals("--use_immersive")) {
 			} else if (command_line[i].equals("--use_immersive")) {
 				use_immersive = true;
 				use_immersive = true;
-				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
-					window.getDecorView().setSystemUiVisibility(
-							View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
-							View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
-							View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
-							View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
-							View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
-							View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
-					UiChangeListener();
-				}
+				window.getDecorView().setSystemUiVisibility(
+						View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+						View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+						View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+						View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
+						View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
+						View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+				UiChangeListener();
 			} else if (command_line[i].equals("--use_apk_expansion")) {
 			} else if (command_line[i].equals("--use_apk_expansion")) {
 				use_apk_expansion = true;
 				use_apk_expansion = true;
 			} else if (has_extra && command_line[i].equals("--apk_expansion_md5")) {
 			} else if (has_extra && command_line[i].equals("--apk_expansion_md5")) {
@@ -825,7 +822,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 		mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
 		mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
 		mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME);
 		mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME);
 
 
-		if (use_immersive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
+		if (use_immersive) {
 			Window window = getActivity().getWindow();
 			Window window = getActivity().getWindow();
 			window.getDecorView().setSystemUiVisibility(
 			window.getDecorView().setSystemUiVisibility(
 					View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
 					View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
@@ -848,15 +845,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 		final View decorView = getActivity().getWindow().getDecorView();
 		final View decorView = getActivity().getWindow().getDecorView();
 		decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
 		decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
 			if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
 			if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
-				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-					decorView.setSystemUiVisibility(
-							View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
-							View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
-							View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
-							View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
-							View.SYSTEM_UI_FLAG_FULLSCREEN |
-							View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-				}
+				decorView.setSystemUiVisibility(
+						View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+						View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+						View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+						View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+						View.SYSTEM_UI_FLAG_FULLSCREEN |
+						View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
 			}
 			}
 		});
 		});
 	}
 	}
@@ -1024,9 +1019,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 
 
 			// Create Hex String
 			// Create Hex String
 			StringBuilder hexString = new StringBuilder();
 			StringBuilder hexString = new StringBuilder();
-			for (int i = 0; i < messageDigest.length; i++) {
-				String s = Integer.toHexString(0xFF & messageDigest[i]);
-
+			for (byte b : messageDigest) {
+				String s = Integer.toHexString(0xFF & b);
 				if (s.length() == 1) {
 				if (s.length() == 1) {
 					s = "0" + s;
 					s = "0" + s;
 				}
 				}

+ 2 - 2
platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java

@@ -43,8 +43,8 @@ public class Crypt {
 
 
 			// Create Hex String
 			// Create Hex String
 			StringBuilder hexString = new StringBuilder();
 			StringBuilder hexString = new StringBuilder();
-			for (int i = 0; i < messageDigest.length; i++)
-				hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
+			for (byte b : messageDigest)
+				hexString.append(Integer.toHexString(0xFF & b));
 			return hexString.toString();
 			return hexString.toString();
 
 
 		} catch (Exception e) {
 		} catch (Exception e) {

+ 8 - 3
platform/x11/detect.py

@@ -114,7 +114,7 @@ def configure(env):
 
 
     ## Architecture
     ## Architecture
 
 
-    is64 = sys.maxsize > 2 ** 32
+    is64 = sys.maxsize > 2**32
     if env["bits"] == "default":
     if env["bits"] == "default":
         env["bits"] = "64" if is64 else "32"
         env["bits"] = "64" if is64 else "32"
 
 
@@ -336,13 +336,14 @@ def configure(env):
     if os.system("pkg-config --exists alsa") == 0:  # 0 means found
     if os.system("pkg-config --exists alsa") == 0:  # 0 means found
         env["alsa"] = True
         env["alsa"] = True
         env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"])
         env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"])
+        env.ParseConfig("pkg-config alsa --cflags")  # Only cflags, we dlopen the library.
     else:
     else:
         print("Warning: ALSA libraries not found. Disabling the ALSA audio driver.")
         print("Warning: ALSA libraries not found. Disabling the ALSA audio driver.")
 
 
     if env["pulseaudio"]:
     if env["pulseaudio"]:
         if os.system("pkg-config --exists libpulse") == 0:  # 0 means found
         if os.system("pkg-config --exists libpulse") == 0:  # 0 means found
             env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"])
             env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"])
-            env.ParseConfig("pkg-config --cflags libpulse")
+            env.ParseConfig("pkg-config libpulse --cflags")  # Only cflags, we dlopen the library.
         else:
         else:
             print("Warning: PulseAudio development libraries not found. Disabling the PulseAudio audio driver.")
             print("Warning: PulseAudio development libraries not found. Disabling the PulseAudio audio driver.")
 
 
@@ -351,6 +352,7 @@ def configure(env):
         if env["udev"]:
         if env["udev"]:
             if os.system("pkg-config --exists libudev") == 0:  # 0 means found
             if os.system("pkg-config --exists libudev") == 0:  # 0 means found
                 env.Append(CPPDEFINES=["UDEV_ENABLED"])
                 env.Append(CPPDEFINES=["UDEV_ENABLED"])
+                env.ParseConfig("pkg-config libudev --cflags")  # Only cflags, we dlopen the library.
             else:
             else:
                 print("Warning: libudev development libraries not found. Disabling controller hotplugging support.")
                 print("Warning: libudev development libraries not found. Disabling controller hotplugging support.")
     else:
     else:
@@ -362,7 +364,10 @@ def configure(env):
 
 
     env.Prepend(CPPPATH=["#platform/x11"])
     env.Prepend(CPPPATH=["#platform/x11"])
     env.Append(CPPDEFINES=["X11_ENABLED", "UNIX_ENABLED", "OPENGL_ENABLED", "GLES_ENABLED", ("_FILE_OFFSET_BITS", 64)])
     env.Append(CPPDEFINES=["X11_ENABLED", "UNIX_ENABLED", "OPENGL_ENABLED", "GLES_ENABLED", ("_FILE_OFFSET_BITS", 64)])
-    env.Append(LIBS=["GL", "pthread"])
+
+    env.ParseConfig("pkg-config gl --cflags --libs")
+
+    env.Append(LIBS=["pthread"])
 
 
     if platform.system() == "Linux":
     if platform.system() == "Linux":
         env.Append(LIBS=["dl"])
         env.Append(LIBS=["dl"])

+ 4 - 5
scene/animation/root_motion_view.cpp

@@ -110,9 +110,8 @@ void RootMotionView::_notification(int p_what) {
 		first = false;
 		first = false;
 
 
 		transform.orthonormalize(); //don't want scale, too imprecise
 		transform.orthonormalize(); //don't want scale, too imprecise
-		transform.affine_invert();
 
 
-		accumulated = transform * accumulated;
+		accumulated = accumulated * transform;
 		accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size);
 		accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size);
 		if (zero_y) {
 		if (zero_y) {
 			accumulated.origin.y = 0;
 			accumulated.origin.y = 0;
@@ -129,9 +128,9 @@ void RootMotionView::_notification(int p_what) {
 				Vector3 from(i * cell_size, 0, j * cell_size);
 				Vector3 from(i * cell_size, 0, j * cell_size);
 				Vector3 from_i((i + 1) * cell_size, 0, j * cell_size);
 				Vector3 from_i((i + 1) * cell_size, 0, j * cell_size);
 				Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size);
 				Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size);
-				from = accumulated.xform(from);
-				from_i = accumulated.xform(from_i);
-				from_j = accumulated.xform(from_j);
+				from = accumulated.xform_inv(from);
+				from_i = accumulated.xform_inv(from_i);
+				from_j = accumulated.xform_inv(from_j);
 
 
 				Color c = color, c_i = color, c_j = color;
 				Color c = color, c_i = color, c_j = color;
 				c.a *= MAX(0, 1.0 - from.length() / radius);
 				c.a *= MAX(0, 1.0 - from.length() / radius);