From 346b1ea1196880bc92a2142c8e0dc20eecea69e6 Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Fri, 3 Jul 2026 20:14:47 -0500 Subject: [PATCH 01/26] Revert "Fix layout rectangle computations" This reverts commit 4d03c18823a1f26e7296950b483114cbc28707b5. --- doc/classes/Control.xml | 10 +++--- scene/gui/control.cpp | 67 ++++++++++++++++++----------------------- scene/gui/control.h | 3 +- 3 files changed, 36 insertions(+), 44 deletions(-) diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index baa85b3701..bce4f8454e 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1115,10 +1115,10 @@ The node's global position, relative to the world (usually to the [CanvasLayer]). - Controls the direction on the horizontal axis in which the control should grow or shrink if its horizontal size is changed. + Controls the direction on the horizontal axis in which the control should grow if its horizontal minimum size is changed to be greater than its current size, as the control always has to be at least the minimum size. - Controls the direction on the vertical axis in which the control should grow or shrink if its vertical size is changed. + Controls the direction on the vertical axis in which the control should grow if its vertical minimum size is changed to be greater than its current size, as the control always has to be at least the minimum size. Controls layout direction and text writing direction. Right-to-left layouts are necessary for certain languages (e.g. Arabic and Hebrew). See also [method is_layout_rtl]. @@ -1568,13 +1568,13 @@ [b]Note:[/b] If the control has received [signal mouse_entered] but not [signal mouse_exited], changing the [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] will cause [signal mouse_exited] to be emitted. - The control will grow/shrink to the left or top if its size is changed to be larger/smaller than its current size on the respective axis. + The control will grow to the left or top to make up if its minimum size is changed to be greater than its current size on the respective axis. - The control will grow/shrink to the right or bottom if its size is changed to be larger/smaller than its current size on the respective axis. + The control will grow to the right or bottom to make up if its minimum size is changed to be greater than its current size on the respective axis. - The control will grow/shrink in both directions equally if its size is changed to be larger/smaller than its current size. + The control will grow in both directions equally to make up if its minimum size is changed to be greater than its current size. Snaps one of the 4 anchor's sides to the origin of the node's [code]Rect[/code], in the top left. Use it with one of the [code]anchor_*[/code] member variables, like [member anchor_left]. To change all 4 anchors at once, use [method set_anchors_preset]. diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 248f0ad840..51048bf29f 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -918,49 +918,32 @@ Control::GrowDirection Control::get_v_grow_direction() const { return data.v_grow; } -void Control::_compute_layout_rect(Rect2 p_rect, bool p_keep_offsets) { +void Control::_compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]) { Size2 parent_rect_size = get_parent_anchorable_rect().size; - - if (p_keep_offsets) { - // If computing anchors, we need to ensure the parent rect size is valid to avoid division by zero. - ERR_FAIL_COND(parent_rect_size.x == 0.0); - ERR_FAIL_COND(parent_rect_size.y == 0.0); - } + ERR_FAIL_COND(parent_rect_size.x == 0.0); + ERR_FAIL_COND(parent_rect_size.y == 0.0); real_t x = p_rect.position.x; - real_t y = p_rect.position.y; - - if (_get_layout_mode() != LayoutMode::LAYOUT_MODE_CONTAINER) { - float left_grow_factor = - (data.h_grow == GROW_DIRECTION_BEGIN) ? 1.0f - : (data.h_grow == GROW_DIRECTION_END) ? 0.0f - : 0.5f; - float top_grow_factor = - (data.v_grow == GROW_DIRECTION_BEGIN) ? 1.0f - : (data.v_grow == GROW_DIRECTION_END) ? 0.0f - : 0.5f; - - Size2 size_diff = p_rect.size - data.size_cache; - - x -= size_diff.x * (is_layout_rtl() ? (1.0f - left_grow_factor) : left_grow_factor); - y -= size_diff.y * top_grow_factor; - } - if (is_layout_rtl()) { x = parent_rect_size.x - x - p_rect.size.x; } + r_anchors[0] = (x - p_offsets[0]) / parent_rect_size.x; + r_anchors[1] = (p_rect.position.y - p_offsets[1]) / parent_rect_size.y; + r_anchors[2] = (x + p_rect.size.x - p_offsets[2]) / parent_rect_size.x; + r_anchors[3] = (p_rect.position.y + p_rect.size.y - p_offsets[3]) / parent_rect_size.y; +} - if (p_keep_offsets) { - data.anchor[0] = (x - data.offset[0]) / parent_rect_size.x; - data.anchor[1] = (y - data.offset[1]) / parent_rect_size.y; - data.anchor[2] = (x + p_rect.size.x - data.offset[2]) / parent_rect_size.x; - data.anchor[3] = (y + p_rect.size.y - data.offset[3]) / parent_rect_size.y; - } else { - data.offset[0] = x - (data.anchor[0] * parent_rect_size.x); - data.offset[1] = y - (data.anchor[1] * parent_rect_size.y); - data.offset[2] = x + p_rect.size.x - (data.anchor[2] * parent_rect_size.x); - data.offset[3] = y + p_rect.size.y - (data.anchor[3] * parent_rect_size.y); +void Control::_compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]) { + Size2 parent_rect_size = get_parent_anchorable_rect().size; + + real_t x = p_rect.position.x; + if (is_layout_rtl()) { + x = parent_rect_size.x - x - p_rect.size.x; } + r_offsets[0] = x - (p_anchors[0] * parent_rect_size.x); + r_offsets[1] = p_rect.position.y - (p_anchors[1] * parent_rect_size.y); + r_offsets[2] = x + p_rect.size.x - (p_anchors[2] * parent_rect_size.x); + r_offsets[3] = p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y); } /// Presets and layout modes. @@ -1493,7 +1476,11 @@ void Control::set_position(const Point2 &p_point, bool p_keep_offsets) { } #endif // TOOLS_ENABLED - _compute_layout_rect(Rect2(p_point, data.size_cache), p_keep_offsets); + if (p_keep_offsets) { + _compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor); + } else { + _compute_offsets(Rect2(p_point, data.size_cache), data.anchor, data.offset); + } _size_changed(); } @@ -1565,7 +1552,11 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) { data.expanded_by_desired_size = false; - _compute_layout_rect(Rect2(data.pos_cache, new_size), p_keep_offsets); + if (p_keep_offsets) { + _compute_anchors(Rect2(data.pos_cache, new_size), data.offset, data.anchor); + } else { + _compute_offsets(Rect2(data.pos_cache, new_size), data.anchor, data.offset); + } _size_changed(); } @@ -1585,7 +1576,7 @@ void Control::set_rect(const Rect2 &p_rect) { data.anchor[i] = ANCHOR_BEGIN; } - _compute_layout_rect(p_rect); + _compute_offsets(p_rect, data.anchor, data.offset); if (is_inside_tree()) { _size_changed(); } diff --git a/scene/gui/control.h b/scene/gui/control.h index 44709c6508..9f3d248e29 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -365,7 +365,8 @@ private: void _set_global_position(const Point2 &p_point); void _set_size(const Size2 &p_size); - void _compute_layout_rect(Rect2 p_rect, bool p_keep_offsets = false); + void _compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]); + void _compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]); void _set_layout_mode(LayoutMode p_mode); void _update_layout_mode(); From 042e55efb63dbdbfa25e76cbbfe080a874c21827 Mon Sep 17 00:00:00 2001 From: Anish Kumar Date: Tue, 30 Jun 2026 15:25:50 +0530 Subject: [PATCH 02/26] Don't automatically open virtual keyboard when popup menu shows (cherry picked from commit 763875309f009215635d49f5b25ae895d782970a) --- scene/gui/popup_menu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index de7915a451..06c1fff88d 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -3795,6 +3795,7 @@ PopupMenu::PopupMenu() { search_bar->set_clear_button_enabled(true); search_bar->set_placeholder(ETR("Search")); search_bar->set_keep_editing_on_text_submit(true); + search_bar->set_virtual_keyboard_show_on_focus(false); search_bar->connect(SceneStringName(text_changed), callable_mp(this, &PopupMenu::_search_bar_text_changed)); search_bar->connect(SceneStringName(focus_entered), callable_mp(this, &PopupMenu::_search_bar_focus_entered)); vbox_container->add_child(search_bar, false, INTERNAL_MODE_FRONT); From 657bed37fc48f4d6e2f3fe02458c7c2ee1e385e3 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Thu, 2 Jul 2026 13:56:42 -0700 Subject: [PATCH 03/26] Fix extracting materials without a per-material override (cherry picked from commit fc31c5a4e9dcc2d095901a4b70f408620ed3f87f) --- editor/import/3d/resource_importer_scene.cpp | 52 +++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 814f52fa0b..c407bfb81f 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -1613,12 +1613,35 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap< continue; } const String mat_id = mat->get_meta("import_id", mat->get_name()); - if (mat_id.is_empty() || !p_material_data.has(mat_id)) { + if (mat_id.is_empty()) { continue; } - Dictionary matdata = p_material_data[mat_id]; + // Start with per-material import settings, if any. This is populated by the + // Advanced Import Settings dialog, but may be empty when using only the Import dock. + Dictionary matdata; + if (p_material_data.has(mat_id)) { + matdata = p_material_data[mat_id]; + } + String file_path; + // Read any existing "use_external" settings to get the enabled status and file path. + if (matdata.has("use_external/enabled")) { + const bool enabled = bool(matdata["use_external/enabled"]); + // If the user has explicitly overwrote this material to not be external, skip it. + if (!enabled) { + continue; + } + // Read any existing file path from this material's import settings, if present. + if (matdata.has("use_external/fallback_path")) { + file_path = matdata["use_external/fallback_path"]; + } else if (matdata.has("use_external/path")) { + file_path = matdata["use_external/path"]; + if (file_path.begins_with("uid://")) { + file_path = ResourceUID::get_singleton()->uid_to_path(file_path); + } + } + } + // For any material settings that are missing, fill them with default values. { - //fill node settings for this node with default values List iopts; get_internal_import_options(INTERNAL_IMPORT_CATEGORY_MATERIAL, &iopts); for (const ImportOption &E : iopts) { @@ -1631,18 +1654,21 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap< post_importer_plugins.write[j]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL, p_root, p_node, mat, matdata); } if (extract_mat != 0) { - const String ext = material_extension[p_options.has("materials/extract_format") ? (int)p_options["materials/extract_format"] : 0]; - const String path = spath.path_join(mat_id.validate_filename() + ext); - const String uid_path = ResourceUID::path_to_uid(path); - - matdata["use_external/enabled"] = true; - matdata["use_external/path"] = uid_path; - matdata["use_external/fallback_path"] = path; - if (!FileAccess::exists(path) || extract_mat == 2 /*overwrite*/) { - ResourceSaver::save(mat, path); + // If no file path was specified, generate one based on the material name and the selected format. + if (file_path.is_empty()) { + const String ext = material_extension[p_options.has("materials/extract_format") ? (int)p_options["materials/extract_format"] : 0]; + file_path = spath.path_join(mat_id.validate_filename() + ext); + const String uid_path = ResourceUID::path_to_uid(file_path); + matdata["use_external/enabled"] = true; + matdata["use_external/path"] = uid_path; + matdata["use_external/fallback_path"] = file_path; + } + // Write the file if it doesn't exist, or if the user has selected to overwrite existing files. + if (!FileAccess::exists(file_path) || extract_mat == 2 /*overwrite*/) { + ResourceSaver::save(mat, file_path); } - Ref external_mat = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE); + Ref external_mat = ResourceLoader::load(file_path, "", ResourceFormatLoader::CACHE_MODE_REPLACE); if (external_mat.is_valid()) { m->set_surface_material(i, external_mat); } From 5ccc031eca401b621d183db88a68109e803b26f6 Mon Sep 17 00:00:00 2001 From: Natalie Martin Date: Thu, 2 Jul 2026 16:52:59 -0500 Subject: [PATCH 04/26] Editor: Fix crash in project settings when autoload has been freed The `EditorAutoloadSettings` class does not expect an autoload node to be freed in between its initial instantiation and any calls to `update_autoload()`. But this actually *can* happen if the autoload is an editor script and happens to run something like `queue_free()` shortly after being initialized. When this happens, opening the project settings panel results in a crash, since the editor is trying to locate data such as the node's script after it has already been deleted. To fix this, just verify that the node still exists prior to accessing any of the node's data. (cherry picked from commit 90d0be8fa34f69bd7cf3a40432a2504234fa5797) --- editor/settings/editor_autoload_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/settings/editor_autoload_settings.cpp b/editor/settings/editor_autoload_settings.cpp index 2f6d6ab187..20c5bfd9cd 100644 --- a/editor/settings/editor_autoload_settings.cpp +++ b/editor/settings/editor_autoload_settings.cpp @@ -502,7 +502,7 @@ void EditorAutoloadSettings::update_autoload() { if (old_info.path == info.path) { // Still the same resource, check status info.node = old_info.node; - if (info.node) { + if (info.node && info.node->is_instance()) { Ref