└── addons └── simple_tool_button ├── plugin.gd ├── plugin.cfg └── inspector.gd /addons/simple_tool_button/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | var inspector_plugin = preload("res://addons/simple_tool_button/inspector.gd").new() 5 | 6 | func _enter_tree(): 7 | add_inspector_plugin(inspector_plugin) 8 | 9 | func _exit_tree(): 10 | remove_inspector_plugin(inspector_plugin) 11 | -------------------------------------------------------------------------------- /addons/simple_tool_button/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Simple Tool Button" 4 | description="Use buttons in your tool script. Simply export a bool variable with the name prefix btn_ and call a function in the setter." 5 | author="domske" 6 | version="1.4.1" 7 | script="plugin.gd" 8 | repo="https://github.com/domske/godot-addon-simple-tool-button" 9 | -------------------------------------------------------------------------------- /addons/simple_tool_button/inspector.gd: -------------------------------------------------------------------------------- 1 | extends EditorInspectorPlugin 2 | 3 | # NOTE Changes may require a restart (reload addon). 4 | 5 | # NOTE Advanced buttons variable names must be updated when value changed. Or use get without set. 6 | 7 | # See available EditorIcons: 8 | # - print(EditorInterface.get_editor_theme().get_icon_list("EditorIcons")) 9 | # - https://github.com/godotengine/godot/tree/master/editor/icons 10 | # - Autocomplete (CTRL + SPACE) should also show the icon in editor. 11 | 12 | var colors := { 13 | "danger": Color.html("#da1d0b"), 14 | "warning": Color.html("#e5a11a"), 15 | "success": Color.html("#22c358"), 16 | "info": Color.html("#1a5ee5"), 17 | } 18 | 19 | # Advanced buttons only. 20 | var known_buttons: Dictionary = {} 21 | var current_node: Object 22 | 23 | func _can_handle(object: Object) -> bool: 24 | current_node = object 25 | known_buttons.clear() 26 | return object is Object 27 | 28 | func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: 29 | if name.begins_with("btn_"): 30 | match(type): 31 | # Use bool setter as button. This buttons also works without this addon. 32 | TYPE_BOOL: 33 | var button = create_button() 34 | var parts = name.split("_") 35 | var button_text = "_".join(parts.slice(1, parts.size())) 36 | 37 | if parts.size() > 2: 38 | var color_name = parts[1] 39 | if colors.has(color_name): 40 | var color: Color = colors.get(color_name) 41 | button_text = "_".join(parts.slice(2, parts.size())) 42 | set_color(button, color) 43 | 44 | button.text = button_text.capitalize() 45 | button.pressed.connect(func (): object[name] = true) 46 | 47 | add_custom_control(button) 48 | return true 49 | 50 | # Use dictonary to pass data to this addon. This button only works with this addon. 51 | TYPE_ARRAY, TYPE_DICTIONARY: 52 | var button_list: Array 53 | if type == TYPE_DICTIONARY: 54 | var dict: Dictionary = object.get(name) 55 | button_list = [dict] 56 | else: 57 | button_list = object.get(name) 58 | 59 | var buttons_size := button_list.size() 60 | 61 | var container := HBoxContainer.new() 62 | container.alignment = BoxContainer.ALIGNMENT_CENTER; 63 | known_buttons[name] = container 64 | 65 | for button_index in range(buttons_size): 66 | var button_item: Dictionary = button_list[button_index] 67 | var button = create_button() 68 | button.text = name.right(-4).capitalize() 69 | if buttons_size > 1: 70 | button.text += " " + str(button_index + 1) 71 | update_button(button, button_item, container) 72 | container.add_child(button) 73 | 74 | add_custom_control(container) 75 | return true 76 | 77 | return false 78 | 79 | func create_button() -> Button: 80 | var button := Button.new() 81 | button.set("theme_override_styles/normal", new_style(Color(0.5, 0.5, 0.5, 0.1))) 82 | return button 83 | 84 | func new_style(color: Color, alpha = color.a) -> StyleBox: 85 | var style_box := StyleBoxFlat.new() 86 | style_box.set_corner_radius_all(5) 87 | if alpha: 88 | color.a = alpha 89 | style_box.bg_color = color 90 | return style_box 91 | 92 | func set_color(button: Button, color: Color) -> void: 93 | button.set("theme_override_styles/normal", new_style(color, 0.2)) 94 | button.set("theme_override_styles/hover", new_style(color, 0.3)) 95 | button.set("theme_override_styles/hover_pressed", new_style(color, 0.4)) 96 | button.set("theme_override_styles/pressed", new_style(color, 0.4)) 97 | 98 | button.set("theme_override_colors/font_color", color) 99 | button.set("theme_override_colors/font_hover_color", color) 100 | button.set("theme_override_colors/font_hover_color", color) 101 | button.set("theme_override_colors/font_pressed_color", color.lightened(0.3)) 102 | button.set("theme_override_colors/font_focus_color", color) 103 | 104 | func update_button(button: Button, props: Dictionary, container: HBoxContainer) -> void: 105 | if props.has("text"): 106 | button.text = props.get("text") 107 | 108 | if props.has("click"): 109 | var callable := Callable(props.get("click")) 110 | button.pressed.connect(func(): 111 | var trigger_refresh = callable.call() 112 | if trigger_refresh: 113 | refresh() 114 | ) 115 | 116 | if props.has("icon"): 117 | var icon_value = props.get("icon") 118 | var icon: Texture2D 119 | if icon_value.begins_with("res://"): 120 | icon = load(props.get("icon")) 121 | else: 122 | icon = EditorInterface.get_editor_theme().get_icon(icon_value, "EditorIcons") 123 | if icon: 124 | button.icon = icon 125 | 126 | if props.has("color"): 127 | var color_value: Variant = props.get("color") 128 | var color: Color 129 | if typeof(color_value) == TYPE_COLOR: 130 | color = color_value 131 | elif typeof(color_value) == TYPE_STRING: 132 | if color_value.begins_with('#'): 133 | color = Color.html(color_value) 134 | elif colors.has(color_value): 135 | color = colors.get(color_value) 136 | if color: 137 | set_color(button, color) 138 | 139 | if props.has("fill"): 140 | if props.get("fill"): 141 | button.size_flags_horizontal = Control.SIZE_EXPAND | Control.SIZE_FILL 142 | else: 143 | button.size_flags_horizontal = 0 144 | 145 | if props.has("align"): 146 | var align = props.get("align") 147 | match(align): 148 | "begin": container.alignment = BoxContainer.ALIGNMENT_BEGIN; 149 | "center": container.alignment = BoxContainer.ALIGNMENT_CENTER; 150 | "end": container.alignment = BoxContainer.ALIGNMENT_END; 151 | 152 | func refresh(): 153 | # Is there no official way to refresh inspector? 154 | for property_name in known_buttons: 155 | var container: HBoxContainer = known_buttons[property_name] 156 | var property_value = current_node.get(property_name) 157 | if typeof(property_value) == TYPE_DICTIONARY: 158 | property_value = [property_value] 159 | 160 | for i in property_value.size(): 161 | var props: Dictionary = property_value[i] 162 | props.erase("click") 163 | update_button(container.get_child(i), props, container) 164 | --------------------------------------------------------------------------------