├── LICENSE ├── addons └── godot-plugin-refresher │ ├── LICENSE │ ├── plugin.cfg │ ├── plugin_refresher.gd │ ├── plugin_refresher.tscn │ └── plugin_refresher_plugin.gd └── example_dropdown.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2021 Will Nations 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/godot-plugin-refresher/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2021 Will Nations 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/godot-plugin-refresher/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Godot Plugin Refresher" 4 | description="A toolbar addition to facilitate toggling off/on a selected plugin. Updated for Godot 4.3" 5 | author="willnationsdev" 6 | version="1.2" 7 | script="plugin_refresher_plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/godot-plugin-refresher/plugin_refresher.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends HBoxContainer 3 | 4 | signal request_refresh_plugin(p_name: String) 5 | signal confirm_refresh_plugin(p_name: String) 6 | 7 | @onready var options: OptionButton = $OptionButton 8 | 9 | 10 | func _ready() -> void: 11 | if get_tree().edited_scene_root == self: 12 | return # This is the scene opened in the editor! 13 | $RefreshButton.icon = EditorInterface.get_editor_theme().get_icon("Reload", "EditorIcons") 14 | 15 | 16 | func update_items(p_plugins_info: Array) -> void: 17 | if not options: 18 | return 19 | options.clear() 20 | 21 | var plugins := p_plugins_info[0] as Dictionary 22 | var display_names_map := p_plugins_info[1] as Dictionary 23 | 24 | var plugin_dirs: Array[String] = [] 25 | plugin_dirs.assign(plugins.keys()) 26 | for idx in plugin_dirs.size(): 27 | var plugin_dirname := plugin_dirs[idx] 28 | var plugin_data = plugins[plugin_dirname] # Array[String] used as a Tuple. 29 | var plugin_name := plugin_data[0] as String 30 | var plugin_path := plugin_data[1] as String 31 | var display_name := display_names_map[plugin_path] as String 32 | 33 | options.add_item(display_name, idx) 34 | options.set_item_metadata(idx, plugin_path) 35 | 36 | 37 | # Note: For whatever reason, statically typing `p_name` inexplicably causes 38 | # an error about converting from Nil to String, even if the value is converted. 39 | func select_plugin(p_name) -> void: 40 | if not options or not p_name: 41 | return 42 | 43 | for idx in options.get_item_count(): 44 | var plugin := str(options.get_item_metadata(idx)) 45 | if plugin == str(p_name): 46 | options.selected = options.get_item_id(idx) 47 | break 48 | 49 | 50 | func _on_RefreshButton_pressed() -> void: 51 | if options.selected == -1: 52 | return # nothing selected 53 | 54 | var plugin := str(options.get_item_metadata(options.selected)) 55 | if not plugin: 56 | return 57 | emit_signal("request_refresh_plugin", plugin) 58 | 59 | 60 | func show_warning(p_name: String) -> void: 61 | $ConfirmationDialog.dialog_text = ( 62 | """ 63 | Plugin `%s` is currently disabled.\n 64 | Do you want to enable it now? 65 | """ 66 | % [p_name] 67 | ) 68 | $ConfirmationDialog.popup_centered() 69 | 70 | 71 | func _on_ConfirmationDialog_confirmed() -> void: 72 | var plugin := options.get_item_metadata(options.selected) as String 73 | emit_signal("confirm_refresh_plugin", plugin) 74 | -------------------------------------------------------------------------------- /addons/godot-plugin-refresher/plugin_refresher.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dnladpgp5dwts"] 2 | 3 | [ext_resource type="Script" path="res://addons/godot-plugin-refresher/plugin_refresher.gd" id="1"] 4 | 5 | [node name="HBoxContainer" type="HBoxContainer"] 6 | script = ExtResource("1") 7 | 8 | [node name="VSeparator" type="VSeparator" parent="."] 9 | layout_mode = 2 10 | 11 | [node name="OptionButton" type="OptionButton" parent="."] 12 | layout_mode = 2 13 | 14 | [node name="RefreshButton" type="Button" parent="."] 15 | layout_mode = 2 16 | 17 | [node name="ConfirmationDialog" type="ConfirmationDialog" parent="."] 18 | dialog_autowrap = true 19 | 20 | [connection signal="pressed" from="RefreshButton" to="." method="_on_RefreshButton_pressed"] 21 | [connection signal="confirmed" from="ConfirmationDialog" to="." method="_on_ConfirmationDialog_confirmed"] 22 | -------------------------------------------------------------------------------- /addons/godot-plugin-refresher/plugin_refresher_plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const ADDONS_PATH := "res://addons/" 5 | const PLUGIN_CONFIG_DIR := "plugins/plugin_refresher" 6 | const PLUGIN_CONFIG := "settings.cfg" 7 | const PLUGIN_NAME := "Godot Plugin Refresher" 8 | const SETTINGS := "settings" 9 | const SETTING_RECENT := "recently_used" 10 | const Refresher := preload("plugin_refresher.gd") 11 | 12 | var plugin_config := ConfigFile.new() 13 | var refresher: Refresher = null 14 | 15 | 16 | func _enter_tree() -> void: 17 | refresher = preload("plugin_refresher.tscn").instantiate() as Refresher 18 | add_control_to_container(CONTAINER_TOOLBAR, refresher) 19 | 20 | # Watch whether any plugin is changed, added or removed on the filesystem 21 | var efs := EditorInterface.get_resource_filesystem() 22 | efs.filesystem_changed.connect(_on_filesystem_changed) 23 | 24 | refresher.request_refresh_plugin.connect(_on_request_refresh_plugin) 25 | refresher.confirm_refresh_plugin.connect(_on_confirm_refresh_plugin) 26 | 27 | _reload_plugins_list() 28 | _load_settings() 29 | 30 | 31 | func _exit_tree() -> void: 32 | remove_control_from_container(CONTAINER_TOOLBAR, refresher) 33 | refresher.free() 34 | 35 | 36 | func _reload_plugins_list() -> void: 37 | var cfg_paths: Array[String] = [] 38 | var plugins := {} 39 | var display_names_map := {} # full path to display name 40 | 41 | find_cfgs(ADDONS_PATH, cfg_paths) 42 | 43 | for cfg_path in cfg_paths: 44 | var plugin_cfg := ConfigFile.new() 45 | var err := plugin_cfg.load(cfg_path) 46 | if err: 47 | push_error("ERROR LOADING PLUGIN FILE: %s" % err) 48 | else: 49 | var plugin_name := plugin_cfg.get_value("plugin", "name") 50 | if plugin_name != PLUGIN_NAME: 51 | var addon_dir_name = cfg_path.split("addons/")[-1].split("/plugin.cfg")[0] 52 | plugins[addon_dir_name] = [plugin_name, cfg_path] 53 | 54 | # This will be an array of the addon/* directory names. 55 | var plugin_dirs: Array[String] = [] 56 | plugin_dirs.assign(plugins.keys()) # typed array "casting" 57 | 58 | var plugin_names: Array[String] = [] 59 | plugin_names.assign(plugin_dirs.map(func(k): return plugins[k][0])) 60 | 61 | for plugin_dirname in plugin_dirs: 62 | var plugin_name = plugins[plugin_dirname][0] 63 | var display_name = plugin_name if plugin_names.count(plugin_name) == 1 else "%s (%s)" % [plugin_name, plugin_dirname] 64 | display_names_map[plugins[plugin_dirname][1]] = display_name 65 | 66 | refresher.update_items([plugins, display_names_map]) 67 | 68 | 69 | func find_cfgs(dir_path: String, cfgs: Array): 70 | var dir := DirAccess.open(dir_path) 71 | var cfg_path := dir_path.path_join("plugin.cfg") 72 | 73 | if dir.file_exists(cfg_path): 74 | cfgs.append(cfg_path) 75 | return 76 | 77 | if dir: 78 | dir.list_dir_begin() 79 | var file_name := dir.get_next() 80 | while file_name != "": 81 | if dir.current_is_dir(): 82 | find_cfgs(dir_path.path_join(file_name), cfgs) 83 | file_name = dir.get_next() 84 | 85 | 86 | func _load_settings() -> void: 87 | var path := get_settings_path() 88 | 89 | if not FileAccess.file_exists(path): 90 | # Create new if running for the first time 91 | var config := ConfigFile.new() 92 | DirAccess.make_dir_recursive_absolute(path.get_base_dir()) 93 | config.save(path) 94 | else: 95 | plugin_config.load(path) 96 | 97 | 98 | func _save_settings() -> void: 99 | plugin_config.save(get_settings_path()) 100 | 101 | 102 | func get_settings_path() -> String: 103 | var editor_paths := EditorInterface.get_editor_paths() 104 | var dir := editor_paths.get_project_settings_dir() 105 | 106 | var home := dir.path_join(PLUGIN_CONFIG_DIR) 107 | var path := home.path_join(PLUGIN_CONFIG) 108 | 109 | return path 110 | 111 | 112 | func _on_filesystem_changed() -> void: 113 | if refresher: 114 | _reload_plugins_list() 115 | var recent = get_recent_plugin() 116 | if recent: 117 | refresher.select_plugin(recent) 118 | 119 | 120 | func get_recent_plugin() -> String: 121 | if not plugin_config.has_section_key(SETTINGS, SETTING_RECENT): 122 | return "" # not saved yet 123 | 124 | var recent = str(plugin_config.get_value(SETTINGS, SETTING_RECENT)) 125 | return recent 126 | 127 | 128 | func _on_request_refresh_plugin(p_path: String) -> void: 129 | assert(not p_path.is_empty()) 130 | 131 | var disabled := not EditorInterface.is_plugin_enabled(p_path) 132 | if disabled: 133 | refresher.show_warning(p_path) 134 | else: 135 | refresh_plugin(p_path) 136 | 137 | 138 | func _on_confirm_refresh_plugin(p_path: String) -> void: 139 | refresh_plugin(p_path) 140 | 141 | 142 | func get_plugin_path() -> String: 143 | return get_script().resource_path.get_base_dir() 144 | 145 | 146 | func refresh_plugin(p_path: String) -> void: 147 | print("Refreshing plugin: ", p_path) 148 | 149 | var enabled := EditorInterface.is_plugin_enabled(p_path) 150 | if enabled: # can only disable an active plugin 151 | EditorInterface.set_plugin_enabled(p_path, false) 152 | 153 | EditorInterface.set_plugin_enabled(p_path, true) 154 | 155 | plugin_config.set_value(SETTINGS, SETTING_RECENT, p_path) 156 | _save_settings() 157 | -------------------------------------------------------------------------------- /example_dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godot-extended-libraries/godot-plugin-refresher/e5007ae892dea5f963bc8f0ce75becfc5c721573/example_dropdown.png --------------------------------------------------------------------------------