├── readme ├── .gdignore ├── logo.png └── preview.png ├── addons └── tool_button │ ├── plugin.cfg │ ├── TB_Plugin.gd │ ├── TB_Button.gd │ └── TB_InspectorPlugin.gd ├── .gitignore ├── LICENSE └── README.md /readme/.gdignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /readme/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teebarjunk/godot-tool_button/HEAD/readme/logo.png -------------------------------------------------------------------------------- /readme/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teebarjunk/godot-tool_button/HEAD/readme/preview.png -------------------------------------------------------------------------------- /addons/tool_button/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="ToolButtonPlugin" 4 | description="Add tool buttons wherever you need them." 5 | author="teebar" 6 | version="1.0" 7 | script="TB_Plugin.gd" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot-specific ignores 2 | .import/ 3 | export.cfg 4 | export_presets.cfg 5 | 6 | # Imported translations (automatically generated from CSV files) 7 | *.translation 8 | 9 | # Mono-specific ignores 10 | .mono/ 11 | data_*/ 12 | 13 | .gitignore 14 | -------------------------------------------------------------------------------- /addons/tool_button/TB_Plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | var plugin 5 | 6 | func _enter_tree(): 7 | plugin = preload("res://addons/tool_button/TB_InspectorPlugin.gd").new(self) 8 | add_inspector_plugin(plugin) 9 | 10 | func _exit_tree(): 11 | remove_inspector_plugin(plugin) 12 | 13 | func rescan_filesystem(): 14 | var fs = get_editor_interface().get_resource_filesystem() 15 | fs.update_script_classes() 16 | fs.scan_sources() 17 | fs.scan() 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 teebarjunk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/tool_button/TB_Button.gd: -------------------------------------------------------------------------------- 1 | extends HBoxContainer 2 | 3 | var button = Button.new() 4 | var object:Object 5 | var info 6 | var pluginref 7 | 8 | func _init(obj:Object, d, p): 9 | object = obj 10 | pluginref = p 11 | 12 | if d is String: 13 | info = {"call": d} 14 | else: 15 | info = d 16 | 17 | alignment = BoxContainer.ALIGN_CENTER 18 | size_flags_horizontal = SIZE_EXPAND_FILL 19 | 20 | add_child(button) 21 | button.size_flags_horizontal = SIZE_EXPAND_FILL 22 | button.text = get_label() 23 | button.modulate = info.get("tint", Color.white) 24 | button.disabled = info.get("disabled", false) 25 | button.connect("pressed", self, "_on_button_pressed") 26 | 27 | button.hint_tooltip = "%s(%s)" % [info.call, get_args_string()] 28 | 29 | if "hint" in info: 30 | button.hint_tooltip += "\n%s" % [info.hint] 31 | 32 | button.flat = info.get("flat", false) 33 | button.align = info.get("align", Button.ALIGN_CENTER) 34 | 35 | if "icon" in info: 36 | button.expand_icon = false 37 | button.set_button_icon(load(info.icon)) 38 | 39 | func get_args_string(): 40 | if not "args" in info: 41 | return "" 42 | var args = PoolStringArray() 43 | for a in info.args: 44 | if a is String: 45 | args.append('"%s"' % [a]) 46 | else: 47 | args.append(str(a)) 48 | return args.join(", ") 49 | 50 | func get_label(): 51 | if "text" in info: 52 | return info.text 53 | 54 | if "args" in info: 55 | return "%s (%s)" % [info.call.capitalize(), get_args_string()] 56 | 57 | return info.call.capitalize() 58 | 59 | func _on_button_pressed(): 60 | var returned 61 | 62 | if "args" in info: 63 | returned = object.callv(info.call, info.args) 64 | else: 65 | returned = object.call(info.call) 66 | 67 | if info.get("print", false): 68 | var a = get_args_string() 69 | if a: 70 | print(">> %s(%s): %s" % [info.call, a, returned]) 71 | else: 72 | print(">> %s: %s" % [info.call, returned]) 73 | 74 | if info.get("update_filesystem", false): 75 | pluginref.rescan_filesystem() 76 | -------------------------------------------------------------------------------- /addons/tool_button/TB_InspectorPlugin.gd: -------------------------------------------------------------------------------- 1 | extends EditorInspectorPlugin 2 | 3 | var InspectorToolButton = preload("res://addons/tool_button/TB_Button.gd") 4 | var pluginref 5 | 6 | var cache_methods = {} 7 | var cache_selected = {} 8 | 9 | func _init(p): 10 | pluginref = p 11 | 12 | func can_handle(object): 13 | if not object in cache_methods: 14 | cache_methods[object] = _collect_methods(object) 15 | return cache_methods[object] or object.has_method("_get_tool_buttons") 16 | 17 | func parse_category(object, category): 18 | match category: 19 | # automatic buttons 20 | "Node", "Resource": 21 | if cache_methods[object]: 22 | for method in cache_methods[object]: 23 | add_custom_control(InspectorToolButton.new(object, { 24 | tint=Color.greenyellow, 25 | call=method, 26 | print=true, 27 | update_filesystem=true 28 | }, pluginref)) 29 | 30 | func parse_begin(object): 31 | # explicitly selected buttons 32 | if object.has_method("_get_tool_buttons"): 33 | var methods 34 | if object is Resource: 35 | methods = object.get_script()._get_tool_buttons() 36 | else: 37 | methods = object._get_tool_buttons() 38 | 39 | if methods: 40 | for method in methods: 41 | add_custom_control(InspectorToolButton.new(object, method, pluginref)) 42 | 43 | func _collect_methods(object:Object): 44 | var script = object.get_script() 45 | if not script or not script.is_tool(): 46 | return [] 47 | 48 | var default_methods = [] 49 | 50 | # ignore methods of parent 51 | if object is Resource: 52 | for m in ClassDB.class_get_method_list(object.get_script().get_class()): 53 | default_methods.append(m.name) 54 | else: 55 | for m in ClassDB.class_get_method_list(object.get_class()): 56 | default_methods.append(m.name) 57 | 58 | var methods = [] 59 | for item in object.get_method_list(): 60 | if not item.name in default_methods: 61 | # has all default arguments 62 | if len(item.args) == len(item.default_args): 63 | methods.append(item.name) 64 | 65 | return methods 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## ToolButtonPlugin for Godot - v1.3 3 | 4 | Get buttons in the inspector with one line: `tool`. 5 | 6 | ![ReadMe](readme/preview.png "Preview") 7 | 8 | ## Start 9 | 10 | - Enable plugin. 11 | - Add `tool` to top of your script. 12 | - Add func `_get_tool_buttons` to return names of functions. 13 | - :new: Buttons automatically added. `_get_tool_buttons` not needed, except for fine control. 14 | 15 | ## Simpler Example (v1.3) 16 | ```gd 17 | tool 18 | extends Node 19 | 20 | func my_function(): 21 | print("I was called!") 22 | ``` 23 | 24 | ## Simple Example 25 | 26 | ```gd 27 | tool 28 | extends Node 29 | 30 | func _get_tool_buttons(): return ["my_function"] 31 | 32 | func my_function(): 33 | print("I was called!") 34 | ``` 35 | 36 | ## Advanced Example 37 | 38 | Using *Dictionary*s instead of *String*s. 39 | 40 | *call* is mandatory. Other's are optional. 41 | 42 | |key |desc |default | 43 | |:------|:------------------------------|:--------------------| 44 | |call | Method to call. | - | 45 | |args | Array of arguments to pass. | - | 46 | |text | Button label. | - | 47 | |tint | Button color. | Color.white | 48 | |icon | Button icon. | - 49 | |flat | Button is flat style. | false | 50 | |hint | Hint text for mouse over. | - | 51 | |print | Print output of method call? | false | 52 | |align | Button alignment. | Button.ALIGN_CENTER | 53 | |disable| Disable button? | false | 54 | |update_filesystem| Tells Godot editor to rescan file system. | false | 55 | 56 | ```gd 57 | var _direction:String = "" 58 | var _score:int = 0 59 | 60 | func _get_tool_buttons(): 61 | return [{ 62 | call="go_towards", 63 | args=["West"] 64 | },{ 65 | call="go_towards", 66 | args=["East", true], 67 | text="Bad Move", 68 | tint=Color.red, 69 | }] 70 | 71 | func go_towards(direction:String, bad_action:bool=false): 72 | _direction = direction 73 | 74 | if bad_action: 75 | _score -= 10 76 | 77 | return _score 78 | ``` 79 | 80 | ## More Advanced Example 81 | 82 | Showing optional buttons by using Godot's **property_list_changed_notify** to force update. 83 | 84 | ```gd 85 | tool 86 | extends Node 87 | 88 | export(bool) var show_score_button:bool = false setget set_show_score_button 89 | export(int) var score:int = 0 90 | 91 | func set_show_score_button(value): 92 | show_score_button = value 93 | property_list_changed_notify() # force property list to "redraw" 94 | 95 | func _get_tool_buttons(): 96 | var out = [] 97 | if show_score_button: 98 | # hidden unless show_score_button is enabled 99 | out.append({call="add_score", args=[10], disable=score>100, tint=Color.aqua}) 100 | return out 101 | 102 | func add_score(amount:int): 103 | self.score += amount 104 | property_list_changed_notify() # force property list to "redraw" 105 | 106 | ``` 107 | 108 | ## Resource Example 109 | 110 | To get things working on a resource, the _get_tool_buttons needs to be static. 111 | 112 | ```gd 113 | tool 114 | extends Resource 115 | class_name MyResource 116 | 117 | # STATIC 118 | static func _get_tool_buttons(): 119 | return ["my_button"] 120 | 121 | # LOCAL 122 | export(String) var my_name:String = "" 123 | 124 | func my_button(): 125 | print(my_name) 126 | ``` 127 | 128 | ## Changes 129 | 130 | *1.3* 131 | 132 | - Automatic. No need for `_get_tool_buttons`. Just add `tool` to top of script. Buttons will be at the bottom of the editor inspector. 133 | 134 | *1.2* 135 | 136 | - Works on Resources now. 137 | 138 | *1.1* 139 | 140 | - added tag `update_filesystem` 141 | --------------------------------------------------------------------------------