Inspector property that has boolean and integer

Godot Version

4.2.1

Question

I’m currently writing a class that has use for a property that falls in the category of “integer but only if a boolean is set”, much like the UI overrides. I’ve done enough with editor plugins to know roughly how I might go about crafting an inspector bit like this that way, but I’m curious, is there a way to do it via _get_property_list()?

I’m specifically looking to create something like the highlighted section below:
image

To be clear, I know how to create the property mechanics of two dependent variables. I’m specifically looking for the path of least resistance for the UI bit, which I have a few uses of already.

You’ll need to add the PROPERTY_USAGE_CHECKABLE in the usage field.

For example:

@tool
extends Node


var test = null


func _get_property_list() -> Array[Dictionary]:
	var properties:Array[Dictionary] = []

	properties.push_back({
		"name": "test",
		"type": TYPE_INT,
		"usage": PROPERTY_USAGE_CHECKABLE | PROPERTY_USAGE_DEFAULT
	})

	return properties

1 Like

Thank you, that flag did not jump out as what I was looking for when I reviewed that documentation.

It does seem finnicky but I’m not sure why. If I use the example above and use no type hint, I can’t toggle the checkbox on manually, but if I set the integer, it turns on. If I add an : int = 0 annotation and default to the test variable, I can check the box manually but not uncheck it.

I’m assuming this is because of how it’s implemented under the hood? Basically int can’t be null, but PROPERTY_USAGE_CHECKABLE uses the null as the unset state?

Is the behavior I’m seeing of not being able to check the box manually expected? That’s not how the inspector element I screenshotted above works. :thinking:

That’s because the editor is doing more than that godot/scene/gui/control.cpp at 8f0c20ee8d5564d4a8131deb4d5341b60edbcff8 · godotengine/godot · GitHub

@tool
extends Node


var _test = null


func _get(property: StringName) -> Variant:
	match property:
		&"test":
			return _test

	return null


func _set(property: StringName, value: Variant) -> bool:
	match property:
		&"test":
			_test = value
			return true

	return false


func _get_property_list() -> Array[Dictionary]:
	var properties:Array[Dictionary] = []

	var usage = PROPERTY_USAGE_CHECKABLE | PROPERTY_USAGE_EDITOR

	if _test != null:
		usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED

	properties.push_back({
		"name": "test",
		"type": TYPE_INT,
		"usage": usage
	})

	return properties
1 Like

Wow, you really know this thing in and out. Thank you so, so much. The extra code in _get_property_list() makes sense to me. The need to add _get() and _set(), not so much. That said, it does seem necessary to get things to work the way I expect them to.

I see the documentation for _get() alludes to this, but what’s missing is why either a normal variable or a property with get and set don’t seem to work.

In short: you’ve gotten me where I need to go, but if you don’t mind, I’d love to understand a little bit better why this is necessary. Thank you again so much!

Edit: Is it because the properties simply don’t exist at the time _get_property_list is called? That makes it clear to me why we might need the _test variable above, but not why a normal setget wouldn’t work.

Script variables are also added when getting the property list with get_property_list().

With _get_property_list() you are adding new properties, not overriding old ones. The inspector parses the properties with get_property_list() it finds the variable one and skips it, then it finds the new property and creates the inspector property.

When doing the check logic here godot/editor/editor_inspector.cpp at master · godotengine/godot · GitHub it breaks at the first occurrence which is the script variable which:

  • if it’s marked as an int (var test:int = 0) it can’t be unchecked because an int can’t be null. int has no constructor that accepts a null.
  • if it’s marked as a Variant (var test = 0) it can’t be checked because there’s no default Variant constructor which makes it be null which automatically unchecks the check.

I’m not sure if this can be considered a bug or not. If you think it’s one feel free to open an issue here Issues · godotengine/godot · GitHub

You can use Object._validate_property() insead if you don’t want to use _get()/_set()/_get_property_list(). You can modify the script variable usage flags in it.

@tool
extends Node


var test = null


func _ready() -> void:
	print(test)


func _validate_property(property: Dictionary) -> void:
	if property.name == "test":
		property.type = TYPE_INT
		property.usage |= PROPERTY_USAGE_CHECKABLE | PROPERTY_USAGE_EDITOR
		if test != null:
				property.usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED

You can’t type your variable as an int though, it will have the same issue (no constructor with null).

1 Like

Thank you. I have a lot more to learn, and I’ll think about whether or not I want to submit that as a bug. At the very least I think there’s an enhancement request about this behavior, I just have to figure out what it should be.

This solution with _validate_property() seems more elegant and more concise, as I don’t have to define the entire property dictionary (I can just modify what’s there), and I don’t have to create additional _set() and _get() functions.

I appreciate how there’s multiple ways to go about this, but at the same time wish it was a little more straightforward. I suppose a request asking for an @export annotation for the CHECKED behavior wouldn’t be unreasonable!

Thank you again so much!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.