buttonsingodot.png

I made a demo project for Godot 4+ here: Demo on Gitlab

So, you're making a Node and you need a "go" button in the Inspector to run your function and actually do stuff. Enter the Button. Yeah; no. Nope! Godot says Nogo!

At this point we all give up and use some form of boolean toggle:

@export var press_me:bool = false:
set(b):
    press_me = false
    my_fancy_func()

It's not great looking, nor does it make sense. A Button is the way to go. Took me ages, but with some luck I have a solution. There are a few sections to it:

One: The Basic Plugin files

"res://addons/YourAddonNameHere/plugin.gd"

@tool
extends EditorPlugin

var inspector_plugin

func _enter_tree() -> void:
    if Engine.is_editor_hint():
        # Add our custom Go button!
         inspector_plugin = preload("inspector_button_plugin.gd").new()
         add_inspector_plugin(inspector_plugin)

func _exit_tree() -> void:
    remove_inspector_plugin(inspector_plugin)

func _get_plugin_name() -> String:
  return "MyWhateveraddon"

"res://addons/YourAddonNameHere/plugin.cfg"

[plugin]
name="MyWhateveraddon"
description="Faster than light travel!"
author="<you>"
version="0.0.1"
script="plugin.gd"

Two: The Script that makes the Button(s)

"res://addons/YourAddonNameHere/inspector_button_plugin.gd"

This script hooks-into the parser and so can seek certain @ tags. It can then replace strings at the last moment—so we use this to intercept an @export var and return a button instead.

extends EditorInspectorPlugin

var InspectorToolButton = preload("inspector_button.gd")

var button_text : String

func _can_handle(object) -> bool:
    return true

func _parse_property(
    object: Object, type: Variant.Type, 
    name: String, hint_type: PropertyHint, 
    hint_string: String, usage_flags, wide: bool):
    if name.begins_with("go_"):
        var s = str(name.split("go_")[1])
        s = s.replace("_", " ")
        s = "Press to %s" % s
        add_custom_control( InspectorToolButton.new(object, s) )
        return true #Returning true removes the built-in editor for this property
    return false # else leave it

Three: The Script that is a Button

"res://addons/YourAddonNameHere/inspector_button.gd"

extends MarginContainer

var object: Object

func _init(obj: Object, text:String):
    object = obj
    size_flags_horizontal = Control.SIZE_SHRINK_CENTER
    var button := Button.new()
    add_child(button)
    button.size_flags_horizontal = SIZE_EXPAND_FILL
    button.text = text
    button.button_down.connect(object._on_button_pressed.bind(text))

Four and Final: Your own Script

At the place where you want the button to appear, in your code, do this:

# This is your own script code

@export var go_run_my_fancy_func:String

#To capture the button press, add the connected signal func:
func _on_button_pressed(text:String):
    if text.to_upper() == "PRESS TO RUN MY FANCY FUNC":
        my_fancy_func()

Last thoughts

Don't foget to activate the plugin.

If there are any bugs, please let me know on Mastodon or Cohost, or on Gitlab!

That's it!

HTH
/d