Repository: godotengine/godot (language: cpp)

Issue:
Layout Mode becomes `2` when parent type is changed
### Tested versions

4.5 dev1
Probably earlier too

### System information

W10

### Issue description

When a Control node is under a Container and the Container is changed to Control, the child's Layout Mode changes to `2` (which is Container, but without name?)

https://github.com/user-attachments/assets/f0bb6975-4a6a-4516-92b1-93e795917629

### Steps to reproduce

1. Add a Container node
2. Add a Control as its child
3. Change Type of the Container to Control
4. Check the Layout Mode of its child

### Minimal reproduction project (MRP)

N/A


Discussion / hints:
Could confirm, saw it before in 4.4 stable.
It showing `2` is directly caused by #102743 as it made the actual value be shown in case it doesn't match any value from the `hint_string`.

Control changes `hint_string` for the `layout_mode` in `validate_property` depending on the setup:
https://github.com/godotengine/godot/blob/6b5b84c0c50135e88691e035d3e00c68a2ae5aa4/scene/gui/control.cpp#L489-L490
https://github.com/godotengine/godot/blob/6b5b84c0c50135e88691e035d3e00c68a2ae5aa4/scene/gui/control.cpp#L505-L507
https://github.com/godotengine/godot/blob/6b5b84c0c50135e88691e035d3e00c68a2ae5aa4/scene/gui/control.cpp#L555-L557

So the actual problem seems to be that it doesn't automatically reset to a valid `layout_mode` according to the current setup? 🤔

Fix patch:
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index a64de8fb3c5d..57bc748bfe90 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -481,9 +481,9 @@ void Control::_validate_property(PropertyInfo &p_property) const {
 	}
 
 	// Validate which positioning properties should be displayed depending on the parent and the layout mode.
-	Node *parent_node = get_parent_control();
-	if (!parent_node) {
-		// If there is no parent, display both anchor and container options.
+	Control *parent_control = get_parent_control();
+	if (!parent_control) {
+		// If there is no parent control, display both anchor and container options.
 
 		// Set the layout mode to be disabled with the proper value.
 		if (p_property.name == "layout_mode") {
@@ -496,7 +496,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
 		if (!use_custom_anchors && (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_"))) {
 			p_property.usage ^= PROPERTY_USAGE_EDITOR;
 		}
-	} else if (Object::cast_to<Container>(parent_node)) {
+	} else if (Object::cast_to<Container>(parent_control)) {
 		// If the parent is a container, display only container-related properties.
 		if (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_") || p_property.name == "anchors_preset") {
 			p_property.usage ^= PROPERTY_USAGE_DEFAULT;
@@ -508,7 +508,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
 			p_property.usage |= PROPERTY_USAGE_READ_ONLY;
 		} else if (p_property.name == "size_flags_horizontal" || p_property.name == "size_flags_vertical") {
 			// Filter allowed size flags based on the parent container configuration.
-			Container *parent_container = Object::cast_to<Container>(parent_node);
+			Container *parent_container = Object::cast_to<Container>(parent_control);
 			Vector<int> size_flags;
 			if (p_property.name == "size_flags_horizontal") {
 				size_flags = parent_container->get_allowed_size_flags_horizontal();
@@ -548,7 +548,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
 			}
 		}
 	} else {
-		// If the parent is NOT a container or not a control at all, display only anchoring-related properties.
+		// If the parent is a non-container control, display only anchoring-related properties.
 		if (p_property.name.begins_with("size_flags_")) {
 			p_property.usage ^= PROPERTY_USAGE_EDITOR;
 
@@ -570,7 +570,7 @@ void Control::_validate_property(PropertyInfo &p_property) const {
 	}
 
 	// Disable the property if it's managed by the parent container.
-	if (!Object::cast_to<Container>(parent_node)) {
+	if (!Object::cast_to<Container>(parent_control)) {
 		return;
 	}
 	bool property_is_managed_by_container = false;
@@ -905,11 +905,11 @@ void Control::_update_layout_mode() {
 }
 
 Control::LayoutMode Control::_get_layout_mode() const {
-	Node *parent_node = get_parent_control();
+	Control *parent_control = get_parent_control();
 	// In these modes the property is read-only.
-	if (!parent_node) {
+	if (!parent_control) {
 		return LayoutMode::LAYOUT_MODE_UNCONTROLLED;
-	} else if (Object::cast_to<Container>(parent_node)) {
+	} else if (Object::cast_to<Container>(parent_control)) {
 		return LayoutMode::LAYOUT_MODE_CONTAINER;
 	}
 
@@ -918,20 +918,25 @@ Control::LayoutMode Control::_get_layout_mode() const {
 		return LayoutMode::LAYOUT_MODE_ANCHORS;
 	}
 
-	// Otherwise fallback on what's stored.
-	return data.stored_layout_mode;
+	// Only position/anchors modes are valid for non-container control parent.
+	if (data.stored_layout_mode == LayoutMode::LAYOUT_MODE_POSITION || data.stored_layout_mode == LayoutMode::LAYOUT_MODE_ANCHORS) {
+		return data.stored_layout_mode;
+	}
+
+	// Otherwise fallback to position mode.
+	return LayoutMode::LAYOUT_MODE_POSITION;
 }
 
 Control::LayoutMode Control::_get_default_layout_mode() const {
-	Node *parent_node = get_parent_control();
+	Control *parent_control = get_parent_control();
 	// In these modes the property is read-only.
-	if (!parent_node) {
+	if (!parent_control) {
 		return LayoutMode::LAYOUT_MODE_UNCONTROLLED;
-	} else if (Object::cast_to<Container>(parent_node)) {
+	} else if (Object::cast_to<Container>(parent_control)) {
 		return LayoutMode::LAYOUT_MODE_CONTAINER;
 	}
 
-	// Otherwise fallback on the position mode.
+	// Otherwise fallback to the position mode.
 	return LayoutMode::LAYOUT_MODE_POSITION;
 }
 


Test patch: