Add @export_tool_button annotation for easily creating inspector buttons
Co-authored-by: jordi <creptthrust@gmail.com> Co-authored-by: K. S. Ernest (iFire) Lee <ernest.lee@chibifire.com> Co-authored-by: Mack <86566939+Macksaur@users.noreply.github.com>
This commit is contained in:
parent
76a135926a
commit
85dfd89653
14 changed files with 257 additions and 10 deletions
|
|
@ -669,6 +669,41 @@
|
|||
[b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups.
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@export_tool_button">
|
||||
<return type="void" />
|
||||
<param index="0" name="text" type="String" />
|
||||
<param index="1" name="icon" type="String" default="""" />
|
||||
<description>
|
||||
Export a [Callable] property as a clickable button with the label [param text]. When the button is pressed, the callable is called.
|
||||
If [param icon] is specified, it is used to fetch an icon for the button via [method Control.get_theme_icon], from the [code]"EditorIcons"[/code] theme type. If [param icon] is omitted, the default [code]"Callable"[/code] icon is used instead.
|
||||
Consider using the [EditorUndoRedoManager] to allow the action to be reverted safely.
|
||||
See also [constant PROPERTY_HINT_TOOL_BUTTON].
|
||||
[codeblock]
|
||||
@tool
|
||||
extends Sprite2D
|
||||
|
||||
@export_tool_button("Hello") var hello_action = hello
|
||||
@export_tool_button("Randomize the color!", "ColorRect")
|
||||
var randomize_color_action = randomize_color
|
||||
|
||||
func hello():
|
||||
print("Hello world!")
|
||||
|
||||
func randomize_color():
|
||||
var undo_redo = EditorInterface.get_editor_undo_redo()
|
||||
undo_redo.create_action("Randomized Sprite2D Color")
|
||||
undo_redo.add_do_property(self, &"self_modulate", Color(randf(), randf(), randf()))
|
||||
undo_redo.add_undo_property(self, &"self_modulate", self_modulate)
|
||||
undo_redo.commit_action()
|
||||
[/codeblock]
|
||||
[b]Note:[/b] The property is exported without the [constant PROPERTY_USAGE_STORAGE] flag because a [Callable] cannot be properly serialized and stored in a file.
|
||||
[b]Note:[/b] In an exported project neither [EditorInterface] nor [EditorUndoRedoManager] exist, which may cause some scripts to break. To prevent this, you can use [method Engine.get_singleton] and omit the static type from the variable declaration:
|
||||
[codeblock]
|
||||
var undo_redo = Engine.get_singleton(&"EditorInterface").get_editor_undo_redo()
|
||||
[/codeblock]
|
||||
[b]Note:[/b] Avoid storing lambda callables in member variables of [RefCounted]-based classes (e.g. resources), as this can lead to memory leaks. Use only method callables and optionally [method Callable.bind] or [method Callable.unbind].
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@icon">
|
||||
<return type="void" />
|
||||
<param index="0" name="icon_path" type="String" />
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ GDScriptParser::GDScriptParser() {
|
|||
register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
|
||||
register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation);
|
||||
register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT));
|
||||
register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray(""));
|
||||
// Export grouping annotations.
|
||||
register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);
|
||||
register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
|
||||
|
|
@ -4618,10 +4619,10 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
|
|||
// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,
|
||||
// or handle array exports in a special way, so they are implemented as separate methods.
|
||||
|
||||
bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
|
||||
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
|
||||
bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
|
||||
|
||||
VariableNode *variable = static_cast<VariableNode *>(p_node);
|
||||
VariableNode *variable = static_cast<VariableNode *>(p_target);
|
||||
if (variable->is_static) {
|
||||
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
|
||||
return false;
|
||||
|
|
@ -4640,11 +4641,11 @@ bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Nod
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
|
||||
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
|
||||
bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
|
||||
ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)");
|
||||
|
||||
VariableNode *variable = static_cast<VariableNode *>(p_node);
|
||||
VariableNode *variable = static_cast<VariableNode *>(p_target);
|
||||
if (variable->is_static) {
|
||||
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
|
||||
return false;
|
||||
|
|
@ -4668,12 +4669,56 @@ bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node
|
|||
return true;
|
||||
}
|
||||
|
||||
template <PropertyUsageFlags t_usage>
|
||||
bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||
if (p_annotation->resolved_arguments.is_empty()) {
|
||||
bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
|
||||
ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
|
||||
|
||||
if (!is_tool()) {
|
||||
push_error(R"(Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).)", p_annotation);
|
||||
return false;
|
||||
}
|
||||
|
||||
VariableNode *variable = static_cast<VariableNode *>(p_target);
|
||||
|
||||
if (variable->is_static) {
|
||||
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
|
||||
return false;
|
||||
}
|
||||
if (variable->exported) {
|
||||
push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
|
||||
return false;
|
||||
}
|
||||
|
||||
const DataType variable_type = variable->get_datatype();
|
||||
if (!variable_type.is_variant() && variable_type.is_hard_type()) {
|
||||
if (variable_type.kind != DataType::BUILTIN || variable_type.builtin_type != Variant::CALLABLE) {
|
||||
push_error(vformat(R"("@export_tool_button" annotation requires a variable of type "Callable", but type "%s" was given instead.)", variable_type.to_string()), p_annotation);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
variable->exported = true;
|
||||
|
||||
// Build the hint string (format: `<text>[,<icon>]`).
|
||||
String hint_string = p_annotation->resolved_arguments[0].operator String(); // Button text.
|
||||
if (p_annotation->resolved_arguments.size() > 1) {
|
||||
hint_string += "," + p_annotation->resolved_arguments[1].operator String(); // Button icon.
|
||||
}
|
||||
|
||||
variable->export_info.type = Variant::CALLABLE;
|
||||
variable->export_info.hint = PROPERTY_HINT_TOOL_BUTTON;
|
||||
variable->export_info.hint_string = hint_string;
|
||||
variable->export_info.usage = PROPERTY_USAGE_EDITOR;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
return true; // Only available in editor.
|
||||
}
|
||||
|
||||
template <PropertyUsageFlags t_usage>
|
||||
bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||
ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
|
||||
|
||||
p_annotation->export_info.name = p_annotation->resolved_arguments[0];
|
||||
|
||||
switch (t_usage) {
|
||||
|
|
|
|||
|
|
@ -1507,6 +1507,7 @@ private:
|
|||
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||
bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||
bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||
bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||
template <PropertyUsageFlags t_usage>
|
||||
bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||
bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
@export_tool_button("Click me!") var action
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
@tool
|
||||
class_name ExportVariableTest
|
||||
extends Node
|
||||
|
||||
|
|
@ -47,6 +48,10 @@ const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")
|
|||
@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_weak_int = 5
|
||||
@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_hard_int: int = 6
|
||||
|
||||
# `@export_tool_button`.
|
||||
@export_tool_button("Click me!") var test_tool_button_1: Callable
|
||||
@export_tool_button("Click me!", "ColorRect") var test_tool_button_2: Callable
|
||||
|
||||
func test():
|
||||
for property in get_property_list():
|
||||
if str(property.name).begins_with("test_"):
|
||||
|
|
|
|||
|
|
@ -55,3 +55,7 @@ var test_export_custom_weak_int: int = 5
|
|||
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
|
||||
var test_export_custom_hard_int: int = 6
|
||||
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
|
||||
var test_tool_button_1: Callable = Callable()
|
||||
hint=TOOL_BUTTON hint_string="Click me!" usage=EDITOR|SCRIPT_VARIABLE class_name=&""
|
||||
var test_tool_button_2: Callable = Callable()
|
||||
hint=TOOL_BUTTON hint_string="Click me!,ColorRect" usage=EDITOR|SCRIPT_VARIABLE class_name=&""
|
||||
|
|
|
|||
|
|
@ -205,6 +205,9 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
|
|||
return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
|
||||
PROPERTY_HINT_PASSWORD:
|
||||
return "PROPERTY_HINT_PASSWORD"
|
||||
PROPERTY_HINT_TOOL_BUTTON:
|
||||
return "PROPERTY_HINT_TOOL_BUTTON"
|
||||
|
||||
printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
|
||||
return "<invalid hint>"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue