summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/string/ustring.cpp10
-rw-r--r--core/string/ustring.h2
-rw-r--r--doc/classes/UndoRedo.xml10
-rw-r--r--editor/editor_properties_array_dict.cpp102
-rw-r--r--editor/editor_properties_array_dict.h2
-rw-r--r--editor/filesystem_dock.cpp54
-rw-r--r--main/main.cpp2
-rw-r--r--methods.py25
-rw-r--r--modules/text_server_adv/text_server_adv.cpp22
-rw-r--r--modules/text_server_fb/text_server_fb.cpp37
-rw-r--r--modules/upnp/doc_classes/UPNPDevice.xml4
-rw-r--r--modules/upnp/upnp.cpp29
-rw-r--r--platform/windows/display_server_windows.cpp87
-rw-r--r--platform/windows/display_server_windows.h1
-rw-r--r--scene/animation/animation_mixer.cpp14
-rw-r--r--scene/animation/animation_mixer.h7
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp2
-rw-r--r--tests/core/math/test_vector2.h4
18 files changed, 285 insertions, 129 deletions
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index bdef1b9bbe..2b62b72a51 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -2110,12 +2110,12 @@ CharString String::utf8() const {
String String::utf16(const char16_t *p_utf16, int p_len) {
String ret;
- ret.parse_utf16(p_utf16, p_len);
+ ret.parse_utf16(p_utf16, p_len, true);
return ret;
}
-Error String::parse_utf16(const char16_t *p_utf16, int p_len) {
+Error String::parse_utf16(const char16_t *p_utf16, int p_len, bool p_default_little_endian) {
if (!p_utf16) {
return ERR_INVALID_DATA;
}
@@ -2125,8 +2125,12 @@ Error String::parse_utf16(const char16_t *p_utf16, int p_len) {
int cstr_size = 0;
int str_size = 0;
+#ifdef BIG_ENDIAN_ENABLED
+ bool byteswap = p_default_little_endian;
+#else
+ bool byteswap = !p_default_little_endian;
+#endif
/* HANDLE BOM (Byte Order Mark) */
- bool byteswap = false; // assume correct endianness if no BOM found
if (p_len < 0 || p_len >= 1) {
bool has_bom = false;
if (uint16_t(p_utf16[0]) == 0xfeff) { // correct BOM, read as is
diff --git a/core/string/ustring.h b/core/string/ustring.h
index fa904c8200..693df6dcba 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -393,7 +393,7 @@ public:
static String utf8(const char *p_utf8, int p_len = -1);
Char16String utf16() const;
- Error parse_utf16(const char16_t *p_utf16, int p_len = -1);
+ Error parse_utf16(const char16_t *p_utf16, int p_len = -1, bool p_default_little_endian = true);
static String utf16(const char16_t *p_utf16, int p_len = -1);
static uint32_t hash(const char32_t *p_cstr, int p_len); /* hash the string */
diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml
index 3d36eafb08..e197f7748c 100644
--- a/doc/classes/UndoRedo.xml
+++ b/doc/classes/UndoRedo.xml
@@ -112,7 +112,8 @@
<return type="void" />
<param index="0" name="object" type="Object" />
<description>
- Register a reference for "do" that will be erased if the "do" history is lost. This is useful mostly for new nodes created for the "do" call. Do not use for resources.
+ Register a reference to an object that will be erased if the "do" history is deleted. This is useful for objects added by the "do" action and removed by the "undo" action.
+ When the "do" history is deleted, if the object is a [RefCounted], it will be unreferenced. Otherwise, it will be freed. Do not use for resources.
[codeblock]
var node = Node2D.new()
undo_redo.create_action("Add node")
@@ -143,7 +144,8 @@
<return type="void" />
<param index="0" name="object" type="Object" />
<description>
- Register a reference for "undo" that will be erased if the "undo" history is lost. This is useful mostly for nodes removed with the "do" call (not the "undo" call!).
+ Register a reference to an object that will be erased if the "undo" history is deleted. This is useful for objects added by the "undo" action and removed by the "do" action.
+ When the "undo" history is deleted, if the object is a [RefCounted], it will be unreferenced. Otherwise, it will be freed. Do not use for resources.
[codeblock]
var node = $Node2D
undo_redo.create_action("Remove node")
@@ -272,10 +274,10 @@
Makes "do"/"undo" operations stay in separate actions.
</constant>
<constant name="MERGE_ENDS" value="1" enum="MergeMode">
- Makes so that the action's "undo" operations are from the first action created and the "do" operations are from the last subsequent action with the same name.
+ Merges this action with the previous one if they have the same name. Keeps only the first action's "undo" operations and the last action's "do" operations. Useful for sequential changes to a single value.
</constant>
<constant name="MERGE_ALL" value="2" enum="MergeMode">
- Makes subsequent actions with the same name be merged into one.
+ Merges this action with the previous one if they have the same name.
</constant>
</constants>
</class>
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 632deecbb9..c59c221e02 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -484,9 +484,10 @@ bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const {
}
Dictionary drag_data = p_drag_data;
+ const String drop_type = drag_data.get("type", "");
- if (drag_data.has("type") && String(drag_data["type"]) == "files") {
- Vector<String> files = drag_data["files"];
+ if (drop_type == "files") {
+ PackedStringArray files = drag_data["files"];
for (int i = 0; i < files.size(); i++) {
const String &file = files[i];
@@ -505,6 +506,56 @@ bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const {
return true;
}
+ if (drop_type == "nodes") {
+ Array node_paths = drag_data["nodes"];
+
+ PackedStringArray allowed_subtype_array;
+ if (allowed_type == "NodePath") {
+ if (subtype_hint_string == "NodePath") {
+ return true;
+ } else {
+ for (int j = 0; j < subtype_hint_string.get_slice_count(","); j++) {
+ String ast = subtype_hint_string.get_slice(",", j).strip_edges();
+ allowed_subtype_array.append(ast);
+ }
+ }
+ }
+
+ bool is_drop_allowed = true;
+
+ for (int i = 0; i < node_paths.size(); i++) {
+ const Node *dropped_node = get_node_or_null(node_paths[i]);
+ ERR_FAIL_NULL_V_MSG(dropped_node, false, "Could not get the dropped node by its path.");
+
+ if (allowed_type != "NodePath") {
+ if (!ClassDB::is_parent_class(dropped_node->get_class_name(), allowed_type)) {
+ // Fail if one of the nodes is not of allowed type.
+ return false;
+ }
+ }
+
+ // The array of NodePaths is restricted to specific types using @export_node_path().
+ if (allowed_type == "NodePath" && subtype_hint_string != "NodePath") {
+ if (!allowed_subtype_array.has(dropped_node->get_class_name())) {
+ // The dropped node type was not found in the allowed subtype array, we must check if it inherits one of them.
+ for (const String &ast : allowed_subtype_array) {
+ if (ClassDB::is_parent_class(dropped_node->get_class_name(), ast)) {
+ is_drop_allowed = true;
+ break;
+ } else {
+ is_drop_allowed = false;
+ }
+ }
+ if (!is_drop_allowed) {
+ break;
+ }
+ }
+ }
+ }
+
+ return is_drop_allowed;
+ }
+
return false;
}
@@ -516,18 +567,18 @@ void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_d
ERR_FAIL_COND(!_is_drop_valid(p_data));
Dictionary drag_data = p_data;
+ const String drop_type = drag_data.get("type", "");
+ Variant array = object->get_array();
- if (drag_data.has("type") && String(drag_data["type"]) == "files") {
- Vector<String> files = drag_data["files"];
-
- Variant array = object->get_array();
+ // Handle the case where array is not initialized yet.
+ if (!array.is_array()) {
+ initialize_array(array);
+ } else {
+ array = array.duplicate();
+ }
- // Handle the case where array is not initialized yet.
- if (!array.is_array()) {
- initialize_array(array);
- } else {
- array = array.duplicate();
- }
+ if (drop_type == "files") {
+ PackedStringArray files = drag_data["files"];
// Loop the file array and add to existing array.
for (int i = 0; i < files.size(); i++) {
@@ -541,6 +592,33 @@ void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_d
emit_changed(get_edited_property(), array);
}
+
+ if (drop_type == "nodes") {
+ Array node_paths = drag_data["nodes"];
+ Node *base_node = get_base_node();
+
+ for (int i = 0; i < node_paths.size(); i++) {
+ const NodePath &path = node_paths[i];
+
+ if (subtype == Variant::OBJECT) {
+ array.call("push_back", get_node(path));
+ } else if (subtype == Variant::NODE_PATH) {
+ array.call("push_back", base_node->get_path().rel_path_to(path));
+ }
+ }
+
+ emit_changed(get_edited_property(), array);
+ }
+}
+
+Node *EditorPropertyArray::get_base_node() {
+ Node *base_node = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());
+
+ if (!base_node) {
+ base_node = get_tree()->get_edited_scene_root();
+ }
+
+ return base_node;
}
void EditorPropertyArray::_notification(int p_what) {
diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h
index b1bf45f1b7..dae0fc52a6 100644
--- a/editor/editor_properties_array_dict.h
+++ b/editor/editor_properties_array_dict.h
@@ -135,6 +135,8 @@ class EditorPropertyArray : public EditorProperty {
void _reorder_button_up();
void _create_new_property_slot();
+ Node *get_base_node();
+
protected:
Ref<EditorPropertyArrayObject> object;
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 9937b3fc51..233f20a8b3 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1561,10 +1561,42 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin
}
void FileSystemDock::_update_resource_paths_after_move(const HashMap<String, String> &p_renames, const HashMap<String, ResourceUID::ID> &p_uids) const {
- // Update the paths in ResourceUID, so that UIDs remain valid.
- for (const KeyValue<String, ResourceUID::ID> &pair : p_uids) {
- if (p_renames.has(pair.key)) {
- ResourceUID::get_singleton()->set_id(pair.value, p_renames[pair.key]);
+ for (const KeyValue<String, String> &pair : p_renames) {
+ // Update UID path.
+ const String &old_path = pair.key;
+ const String &new_path = pair.value;
+
+ const HashMap<String, ResourceUID::ID>::ConstIterator I = p_uids.find(old_path);
+ if (I) {
+ ResourceUID::get_singleton()->set_id(I->value, new_path);
+ }
+
+ ScriptServer::remove_global_class_by_path(old_path);
+
+ int index = -1;
+ EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->find_file(old_path, &index);
+
+ if (!efd || index < 0) {
+ // The file was removed.
+ continue;
+ }
+
+ // Update paths for global classes.
+ if (!efd->get_file_script_class_name(index).is_empty()) {
+ String lang;
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ if (ScriptServer::get_language(i)->handles_global_class_type(efd->get_file_type(index))) {
+ lang = ScriptServer::get_language(i)->get_name();
+ break;
+ }
+ }
+ if (lang.is_empty()) {
+ continue; // No language found that can handle this global class.
+ }
+
+ ScriptServer::add_global_class(efd->get_file_script_class_name(index), efd->get_file_script_class_extends(index), lang, new_path);
+ EditorNode::get_editor_data().script_class_set_icon_path(efd->get_file_script_class_name(index), efd->get_file_script_class_icon_path(index));
+ EditorNode::get_editor_data().script_class_set_name(new_path, efd->get_file_script_class_name(index));
}
}
@@ -1583,10 +1615,13 @@ void FileSystemDock::_update_resource_paths_after_move(const HashMap<String, Str
if (p_renames.has(base_path)) {
base_path = p_renames[base_path];
+ r->set_path(base_path + extra_path);
}
-
- r->set_path(base_path + extra_path);
}
+
+ ScriptServer::save_global_classes();
+ EditorNode::get_editor_data().script_class_save_icon_paths();
+ EditorFileSystem::get_singleton()->emit_signal(SNAME("script_classes_updated"));
}
void FileSystemDock::_update_dependencies_after_move(const HashMap<String, String> &p_renames, const HashSet<String> &p_file_owners) const {
@@ -1710,6 +1745,13 @@ void FileSystemDock::_make_scene_confirm() {
}
void FileSystemDock::_resource_removed(const Ref<Resource> &p_resource) {
+ const Ref<Script> &scr = p_resource;
+ if (scr.is_valid()) {
+ ScriptServer::remove_global_class_by_path(scr->get_path());
+ ScriptServer::save_global_classes();
+ EditorNode::get_editor_data().script_class_save_icon_paths();
+ EditorFileSystem::get_singleton()->emit_signal(SNAME("script_classes_updated"));
+ }
emit_signal(SNAME("resource_removed"), p_resource);
}
diff --git a/main/main.cpp b/main/main.cpp
index cf40f764cf..6a132a37ae 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -347,7 +347,6 @@ void initialize_navigation_server() {
// Fall back to dummy if no default server has been registered.
if (!navigation_server_3d) {
- WARN_PRINT_ONCE("No NavigationServer3D implementation has been registered! Falling back to a dummy implementation: navigation features will be unavailable.");
navigation_server_3d = memnew(NavigationServer3DDummy);
}
@@ -358,7 +357,6 @@ void initialize_navigation_server() {
// Init 2D Navigation Server
navigation_server_2d = NavigationServer2DManager::new_default_server();
if (!navigation_server_2d) {
- WARN_PRINT_ONCE("No NavigationServer2D implementation has been registered! Falling back to a dummy implementation: navigation features will be unavailable.");
navigation_server_2d = memnew(NavigationServer2DDummy);
}
diff --git a/methods.py b/methods.py
index 7af621befc..e65d2757bd 100644
--- a/methods.py
+++ b/methods.py
@@ -228,7 +228,32 @@ def get_version_info(module_version_string="", silent=False):
return version_info
+_cleanup_env = None
+_cleanup_bool = False
+
+
def write_file_if_needed(path, string):
+ """Generates a file only if it doesn't already exist or the content has changed.
+
+ Utilizes a dedicated SCons environment to ensure the files are properly removed
+ during cleanup; will not attempt to create files during cleanup.
+
+ - `path` - Path to the file in question; used to create cleanup logic.
+ - `string` - Content to compare against an existing file.
+ """
+ global _cleanup_env
+ global _cleanup_bool
+
+ if _cleanup_env is None:
+ from SCons.Environment import Environment
+
+ _cleanup_env = Environment()
+ _cleanup_bool = _cleanup_env.GetOption("clean")
+
+ _cleanup_env.Clean("#", path)
+ if _cleanup_bool:
+ return
+
try:
with open(path, "r", encoding="utf-8", newline="\n") as f:
if f.read() == string:
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index cc1ceec41e..c092fd7511 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -1432,8 +1432,25 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
#endif
if (!p_font_data->face_init) {
- // Get style flags and name.
- if (fd->face->family_name != nullptr) {
+ // When a font does not provide a `family_name`, FreeType tries to synthesize one based on other names.
+ // FreeType automatically converts non-ASCII characters to "?" in the synthesized name.
+ // To avoid that behavior, use the format-specific name directly if available.
+ hb_face_t *hb_face = hb_font_get_face(fd->hb_handle);
+ unsigned int num_entries = 0;
+ const hb_ot_name_entry_t *names = hb_ot_name_list_names(hb_face, &num_entries);
+ const hb_language_t english = hb_language_from_string("en", -1);
+ for (unsigned int i = 0; i < num_entries; i++) {
+ if (names[i].name_id != HB_OT_NAME_ID_FONT_FAMILY) {
+ continue;
+ }
+ if (!p_font_data->font_name.is_empty() && names[i].language != english) {
+ continue;
+ }
+ unsigned int text_size = hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, nullptr, nullptr) + 1;
+ p_font_data->font_name.resize(text_size);
+ hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)p_font_data->font_name.ptrw());
+ }
+ if (p_font_data->font_name.is_empty() && fd->face->family_name != nullptr) {
p_font_data->font_name = String::utf8((const char *)fd->face->family_name);
}
if (fd->face->style_name != nullptr) {
@@ -1452,7 +1469,6 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
p_font_data->style_flags.set_flag(FONT_FIXED_WIDTH);
}
- hb_face_t *hb_face = hb_font_get_face(fd->hb_handle);
// Get supported scripts from OpenType font data.
p_font_data->supported_scripts.clear();
unsigned int count = hb_ot_layout_table_get_script_tags(hb_face, HB_OT_TAG_GSUB, 0, nullptr, nullptr);
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 1a0a4ec356..c3f62d7324 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -71,8 +71,10 @@ using namespace godot;
#endif
#endif
-#ifdef MODULE_SVG_ENABLED
#ifdef MODULE_FREETYPE_ENABLED
+#include FT_SFNT_NAMES_H
+#include FT_TRUETYPE_IDS_H
+#ifdef MODULE_SVG_ENABLED
#include "thorvg_svg_in_ot.h"
#endif
#endif
@@ -857,8 +859,37 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
fd->underline_thickness = (FT_MulFix(fd->face->underline_thickness, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale;
if (!p_font_data->face_init) {
- // Get style flags and name.
- if (fd->face->family_name != nullptr) {
+ // When a font does not provide a `family_name`, FreeType tries to synthesize one based on other names.
+ // FreeType automatically converts non-ASCII characters to "?" in the synthesized name.
+ // To avoid that behavior, use the format-specific name directly if available.
+ if (FT_IS_SFNT(fd->face)) {
+ int name_count = FT_Get_Sfnt_Name_Count(fd->face);
+ for (int i = 0; i < name_count; i++) {
+ FT_SfntName sfnt_name;
+ if (FT_Get_Sfnt_Name(fd->face, i, &sfnt_name) != 0) {
+ continue;
+ }
+ if (sfnt_name.name_id != TT_NAME_ID_FONT_FAMILY && sfnt_name.name_id != TT_NAME_ID_TYPOGRAPHIC_FAMILY) {
+ continue;
+ }
+ if (!p_font_data->font_name.is_empty() && sfnt_name.language_id != TT_MS_LANGID_ENGLISH_UNITED_STATES) {
+ continue;
+ }
+
+ switch (sfnt_name.platform_id) {
+ case TT_PLATFORM_APPLE_UNICODE: {
+ p_font_data->font_name.parse_utf16((const char16_t *)sfnt_name.string, sfnt_name.string_len / 2, false);
+ } break;
+
+ case TT_PLATFORM_MICROSOFT: {
+ if (sfnt_name.encoding_id == TT_MS_ID_UNICODE_CS || sfnt_name.encoding_id == TT_MS_ID_UCS_4) {
+ p_font_data->font_name.parse_utf16((const char16_t *)sfnt_name.string, sfnt_name.string_len / 2, false);
+ }
+ } break;
+ }
+ }
+ }
+ if (p_font_data->font_name.is_empty() && fd->face->family_name != nullptr) {
p_font_data->font_name = String::utf8((const char *)fd->face->family_name);
}
if (fd->face->style_name != nullptr) {
diff --git a/modules/upnp/doc_classes/UPNPDevice.xml b/modules/upnp/doc_classes/UPNPDevice.xml
index a70ae1b9cc..82ac179611 100644
--- a/modules/upnp/doc_classes/UPNPDevice.xml
+++ b/modules/upnp/doc_classes/UPNPDevice.xml
@@ -71,7 +71,7 @@
<constant name="IGD_STATUS_HTTP_EMPTY" value="2" enum="IGDStatus">
Empty HTTP response.
</constant>
- <constant name="IGD_STATUS_NO_URLS" value="3" enum="IGDStatus">
+ <constant name="IGD_STATUS_NO_URLS" value="3" enum="IGDStatus" deprecated="This value is no longer used.">
Returned response contained no URLs.
</constant>
<constant name="IGD_STATUS_NO_IGD" value="4" enum="IGDStatus">
@@ -86,7 +86,7 @@
<constant name="IGD_STATUS_INVALID_CONTROL" value="7" enum="IGDStatus">
Invalid control.
</constant>
- <constant name="IGD_STATUS_MALLOC_ERROR" value="8" enum="IGDStatus">
+ <constant name="IGD_STATUS_MALLOC_ERROR" value="8" enum="IGDStatus" deprecated="This value is no longer used.">
Memory allocation error.
</constant>
<constant name="IGD_STATUS_UNKNOWN_ERROR" value="9" enum="IGDStatus">
diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp
index 2812f37eb2..95453c1ecd 100644
--- a/modules/upnp/upnp.cpp
+++ b/modules/upnp/upnp.cpp
@@ -121,33 +121,20 @@ void UPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) {
return;
}
- struct UPNPUrls *urls = (UPNPUrls *)malloc(sizeof(struct UPNPUrls));
-
- if (!urls) {
- dev->set_igd_status(UPNPDevice::IGD_STATUS_MALLOC_ERROR);
- return;
- }
-
+ struct UPNPUrls urls = {};
struct IGDdatas data;
- memset(urls, 0, sizeof(struct UPNPUrls));
-
parserootdesc(xml, size, &data);
free(xml);
xml = nullptr;
- GetUPNPUrls(urls, &data, dev->get_description_url().utf8().get_data(), 0);
-
- if (!urls) {
- dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_URLS);
- return;
- }
+ GetUPNPUrls(&urls, &data, dev->get_description_url().utf8().get_data(), 0);
char addr[16];
- int i = UPNP_GetValidIGD(devlist, urls, &data, (char *)&addr, 16);
+ int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16);
if (i != 1) {
- FreeUPNPUrls(urls);
+ FreeUPNPUrls(&urls);
switch (i) {
case 0:
@@ -165,18 +152,18 @@ void UPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) {
}
}
- if (urls->controlURL[0] == '\0') {
- FreeUPNPUrls(urls);
+ if (urls.controlURL[0] == '\0') {
+ FreeUPNPUrls(&urls);
dev->set_igd_status(UPNPDevice::IGD_STATUS_INVALID_CONTROL);
return;
}
- dev->set_igd_control_url(urls->controlURL);
+ dev->set_igd_control_url(urls.controlURL);
dev->set_igd_service_type(data.first.servicetype);
dev->set_igd_our_addr(addr);
dev->set_igd_status(UPNPDevice::IGD_STATUS_OK);
- FreeUPNPUrls(urls);
+ FreeUPNPUrls(&urls);
}
int UPNP::upnp_result(int in) {
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index e540b7617f..2e007b5efc 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -3709,63 +3709,18 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
return MA_NOACTIVATE; // Do not activate, but process mouse messages.
}
} break;
- case WM_SETFOCUS: {
- windows[window_id].window_has_focus = true;
- last_focused_window = window_id;
-
- // Restore mouse mode.
- _set_mouse_mode_impl(mouse_mode);
-
- if (!app_focused) {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
- }
- app_focused = true;
- }
- } break;
- case WM_KILLFOCUS: {
- windows[window_id].window_has_focus = false;
- last_focused_window = window_id;
-
- // Release capture unconditionally because it can be set due to dragging, in addition to captured mode.
- ReleaseCapture();
-
- // Release every touch to avoid sticky points.
- for (const KeyValue<int, Vector2> &E : touch_state) {
- _touch_event(window_id, false, E.value.x, E.value.y, E.key);
- }
- touch_state.clear();
-
- bool self_steal = false;
- HWND new_hwnd = (HWND)wParam;
- if (IsWindow(new_hwnd)) {
- self_steal = true;
+ case WM_ACTIVATEAPP: {
+ bool new_app_focused = (bool)wParam;
+ if (new_app_focused == app_focused) {
+ break;
}
-
- if (!self_steal) {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
- }
- app_focused = false;
+ app_focused = new_app_focused;
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(app_focused ? MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN : MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
} break;
- case WM_ACTIVATE: { // Watch for window activate message.
- if (!windows[window_id].window_focused) {
- _process_activate_event(window_id, wParam, lParam);
- } else {
- windows[window_id].saved_wparam = wParam;
- windows[window_id].saved_lparam = lParam;
-
- // Run a timer to prevent event catching warning if the focused window is closing.
- windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
- }
- if (wParam != WA_INACTIVE) {
- track_mouse_leave_event(hWnd);
-
- if (!IsIconic(hWnd)) {
- SetFocus(hWnd);
- }
- }
+ case WM_ACTIVATE: {
+ _process_activate_event(window_id, wParam, lParam);
return 0; // Return to the message loop.
} break;
case WM_GETMINMAXINFO: {
@@ -3864,9 +3819,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_CLOSE: // Did we receive a close message?
{
- if (windows[window_id].focus_timer_id != 0U) {
- KillTimer(windows[window_id].hWnd, windows[window_id].focus_timer_id);
- }
_send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST);
return 0; // Jump back.
@@ -4625,10 +4577,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
if (!Main::is_iterating()) {
Main::iteration();
}
- } else if (wParam == windows[window_id].focus_timer_id) {
- _process_activate_event(window_id, windows[window_id].saved_wparam, windows[window_id].saved_lparam);
- KillTimer(windows[window_id].hWnd, wParam);
- windows[window_id].focus_timer_id = 0U;
}
} break;
case WM_SYSKEYUP:
@@ -4834,20 +4782,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam) {
if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) {
- _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN);
- windows[p_window_id].window_focused = true;
+ last_focused_window = p_window_id;
alt_mem = false;
control_mem = false;
shift_mem = false;
gr_mem = false;
-
- // Restore mouse mode.
_set_mouse_mode_impl(mouse_mode);
+ if (!IsIconic(windows[p_window_id].hWnd)) {
+ SetFocus(windows[p_window_id].hWnd);
+ }
+ windows[p_window_id].window_focused = true;
+ _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN);
} else { // WM_INACTIVE.
Input::get_singleton()->release_pressed_events();
- _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT);
- windows[p_window_id].window_focused = false;
+ track_mouse_leave_event(windows[p_window_id].hWnd);
+ // Release capture unconditionally because it can be set due to dragging, in addition to captured mode.
+ ReleaseCapture();
alt_mem = false;
+ windows[p_window_id].window_focused = false;
+ _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT);
}
if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window_id].wtctx) {
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 1191f22968..cb209948b1 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -402,7 +402,6 @@ class DisplayServerWindows : public DisplayServer {
// Timers.
uint32_t move_timer_id = 0U;
- uint32_t focus_timer_id = 0U;
HANDLE wtctx;
LOGCONTEXTW wtlc;
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 757ac68e46..01e1de8f23 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -689,14 +689,19 @@ bool AnimationMixer::_update_caches() {
track_value->init_value = anim->track_get_key_value(i, 0);
track_value->init_value.zero();
+ track_value->init_use_continuous = callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS;
+
// Can't interpolate them, need to convert.
track_value->is_variant_interpolatable = Animation::is_variant_interpolatable(track_value->init_value);
// If there is a Reset Animation, it takes precedence by overwriting.
if (has_reset_anim) {
int rt = reset_anim->find_track(path, track_src_type);
- if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) {
- track_value->init_value = track_src_type == Animation::TYPE_VALUE ? reset_anim->track_get_key_value(rt, 0) : (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
+ if (rt >= 0) {
+ track_value->init_use_continuous = track_value->init_use_continuous || (reset_anim->value_track_get_update_mode(rt) != Animation::UPDATE_DISCRETE); // Take precedence Force Continuous.
+ if (reset_anim->track_get_key_count(rt) > 0) {
+ track_value->init_value = track_src_type == Animation::TYPE_VALUE ? reset_anim->track_get_key_value(rt, 0) : (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
+ }
}
}
@@ -996,6 +1001,7 @@ void AnimationMixer::_blend_init() {
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
t->value = Animation::cast_to_blendwise(t->init_value);
t->element_size = t->init_value.is_string() ? (real_t)(t->init_value.operator String()).length() : 0;
+ t->use_continuous = t->init_use_continuous;
t->use_discrete = false;
} break;
case Animation::TYPE_AUDIO: {
@@ -1415,6 +1421,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
bool is_discrete = is_value && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE;
bool force_continuous = callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS;
if (t->is_variant_interpolatable && (!is_discrete || force_continuous)) {
+ t->use_continuous = true;
Variant value = is_value ? a->value_track_interpolate(i, time, is_discrete && force_continuous ? backward : false) : Variant(a->bezier_track_interpolate(i, time));
value = post_process_key_value(a, i, value, t->object_id);
if (value == Variant()) {
@@ -1727,7 +1734,7 @@ void AnimationMixer::_blend_apply() {
case Animation::TYPE_VALUE: {
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
- if (!t->is_variant_interpolatable || (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT && t->use_discrete)) {
+ if (!t->is_variant_interpolatable || !t->use_continuous || (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT && t->use_discrete)) {
break; // Don't overwrite the value set by UPDATE_DISCRETE.
}
@@ -1969,6 +1976,7 @@ void AnimationMixer::_build_backup_track_cache() {
if (t_obj) {
t->value = Animation::cast_to_blendwise(t_obj->get_indexed(t->subpath));
}
+ t->use_continuous = true;
t->use_discrete = false;
if (t->init_value.is_array()) {
t->element_size = MAX(t->element_size.operator int(), (t->value.operator Array()).size());
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index ed291bfe63..b7898cffc9 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -222,9 +222,14 @@ protected:
Variant init_value;
Variant value;
Vector<StringName> subpath;
+
+ // TODO: There are many boolean, can be packed into one integer.
+ bool init_use_continuous = false;
+ bool use_continuous = false;
bool use_discrete = false;
bool is_using_angle = false;
bool is_variant_interpolatable = true;
+
Variant element_size;
TrackCacheValue(const TrackCacheValue &p_other) :
@@ -232,6 +237,8 @@ protected:
init_value(p_other.init_value),
value(p_other.value),
subpath(p_other.subpath),
+ init_use_continuous(p_other.init_use_continuous),
+ use_continuous(p_other.use_continuous),
use_discrete(p_other.use_discrete),
is_using_angle(p_other.is_using_angle),
is_variant_interpolatable(p_other.is_variant_interpolatable),
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index b54f511d05..48c9cda253 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -777,7 +777,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
if (rb->get_scaling_3d_mode() != RS::VIEWPORT_SCALING_3D_MODE_OFF) {
// can't do blit subpass because we're scaling
using_subpass_post_process = false;
- } else if (p_render_data->environment.is_valid() && (environment_get_glow_enabled(p_render_data->environment) || RSG::camera_attributes->camera_attributes_uses_auto_exposure(p_render_data->camera_attributes) || RSG::camera_attributes->camera_attributes_uses_dof(p_render_data->camera_attributes))) {
+ } else if (p_render_data->environment.is_valid() && (environment_get_glow_enabled(p_render_data->environment) || RSG::camera_attributes->camera_attributes_uses_auto_exposure(p_render_data->camera_attributes) || RSG::camera_attributes->camera_attributes_uses_dof(p_render_data->camera_attributes) || environment_get_background(p_render_data->environment) == RS::ENV_BG_CANVAS)) {
// can't do blit subpass because we're using post processes
using_subpass_post_process = false;
}
diff --git a/tests/core/math/test_vector2.h b/tests/core/math/test_vector2.h
index f23fffe5eb..fc3fd6a87d 100644
--- a/tests/core/math/test_vector2.h
+++ b/tests/core/math/test_vector2.h
@@ -354,6 +354,7 @@ TEST_CASE("[Vector2] Plane methods") {
const Vector2 vector_y = Vector2(0, 1);
const Vector2 vector_normal = Vector2(0.95879811270838721622267, 0.2840883296913739899919);
const Vector2 vector_non_normal = Vector2(5.4, 1.6);
+ const real_t p_d = 99.1;
CHECK_MESSAGE(
vector.bounce(vector_y) == Vector2(1.2, -3.4),
"Vector2 bounce on a plane with normal of the Y axis should.");
@@ -373,6 +374,9 @@ TEST_CASE("[Vector2] Plane methods") {
vector.project(vector_normal).is_equal_approx(Vector2(2.0292559899117276166, 0.60126103404791929382)),
"Vector2 projected on a normal should return expected value.");
CHECK_MESSAGE(
+ vector_normal.plane_project(p_d, vector).is_equal_approx(Vector2(94.187635516479631, 30.951892004882851)),
+ "Vector2 plane_project should return expected value.");
+ CHECK_MESSAGE(
vector.slide(vector_y) == Vector2(1.2, 0),
"Vector2 slide on a plane with normal of the Y axis should set the Y to zero.");
CHECK_MESSAGE(