mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00:00
Added Class System to ExtensionsAPI (#808)
* Added Class System to Api * formatting * removed extends * update extension version * Added Some New Fail-Safes * fix typo * Formatting * Update ExtensionsAPI.gd * Typo * formatting * formatting
This commit is contained in:
parent
512c85ac64
commit
e1facda618
|
@ -272,7 +272,7 @@ config/macos_native_icon="res://assets/graphics/icons/icon.icns"
|
|||
config/windows_native_icon="res://assets/graphics/icons/icon.ico"
|
||||
config/custom_user_dir_name.X11="pixelorama"
|
||||
config/Version="v0.11-dev"
|
||||
config/ExtensionsAPI_Version=2
|
||||
config/ExtensionsAPI_Version=3
|
||||
|
||||
[audio]
|
||||
|
||||
|
|
|
@ -1,247 +1,313 @@
|
|||
# gdlint: ignore=max-public-methods
|
||||
extends Node
|
||||
|
||||
enum { FILE, EDIT, SELECT, IMAGE, VIEW, WINDOW, HELP }
|
||||
# use these variables in your extension to access the api
|
||||
var general = GeneralAPI.new()
|
||||
var menu = MenuAPI.new()
|
||||
var dialog = DialogAPI.new()
|
||||
var panel = PanelAPI.new()
|
||||
var theme = ThemeAPI.new()
|
||||
var tools = ToolAPI.new()
|
||||
var project = ProjectAPI.new()
|
||||
|
||||
# This fail-safe below is designed to work ONLY if Pixelorama is launched in Godot Editor
|
||||
var _action_history: Dictionary = {}
|
||||
|
||||
|
||||
func show_error(text: String) -> void:
|
||||
# useful for displaying messages like "Incompatible API" etc...
|
||||
Global.error_dialog.set_text(text)
|
||||
Global.error_dialog.popup_centered()
|
||||
Global.dialog_open(true)
|
||||
func check_sanity(extension_name: String):
|
||||
if extension_name in _action_history.keys():
|
||||
var extension_history = _action_history[extension_name]
|
||||
if extension_history != []:
|
||||
var error_msg = str(
|
||||
"Extension: ",
|
||||
extension_name,
|
||||
" contains actons: ",
|
||||
extension_history,
|
||||
" which are not removed properly"
|
||||
)
|
||||
print(error_msg)
|
||||
|
||||
|
||||
func clear_history(extension_name: String):
|
||||
if extension_name in _action_history.keys():
|
||||
_action_history.erase(extension_name)
|
||||
|
||||
|
||||
func add_action(action: String):
|
||||
var extension_name = _get_caller_extension_name()
|
||||
if extension_name != "Unknown":
|
||||
if extension_name in _action_history.keys():
|
||||
var extension_history: Array = _action_history[extension_name]
|
||||
extension_history.append(action)
|
||||
else: # If the extension history does'nt exist yet then creat it
|
||||
_action_history[extension_name] = [action]
|
||||
|
||||
|
||||
func remove_action(action: String):
|
||||
var extension_name = _get_caller_extension_name()
|
||||
if extension_name != "Unknown":
|
||||
if extension_name in _action_history.keys():
|
||||
_action_history[extension_name].erase(action)
|
||||
|
||||
|
||||
func _get_caller_extension_name() -> String:
|
||||
var stack = get_stack()
|
||||
for trace in stack:
|
||||
# Get extension name that called the action
|
||||
var arr: Array = trace["source"].split("/")
|
||||
var idx = arr.find("Extensions")
|
||||
if idx != -1:
|
||||
return _arr[idx + 1]
|
||||
return "Unknown"
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
for keys in _action_history.keys():
|
||||
check_sanity(keys)
|
||||
|
||||
|
||||
# The Api Methods Start Here
|
||||
func get_api_version() -> int:
|
||||
return ProjectSettings.get_setting("application/config/ExtensionsAPI_Version")
|
||||
|
||||
|
||||
func get_pixelorama_version() -> String:
|
||||
return ProjectSettings.get_setting("application/config/Version")
|
||||
class GeneralAPI:
|
||||
# Version And Config
|
||||
func get_pixelorama_version() -> String:
|
||||
return ProjectSettings.get_setting("application/config/Version")
|
||||
|
||||
func get_config_file() -> ConfigFile:
|
||||
return Global.config_cache
|
||||
|
||||
# Nodes
|
||||
func get_global() -> Global:
|
||||
return Global
|
||||
|
||||
func get_extensions_node() -> Node:
|
||||
return Global.control.get_node("Extensions")
|
||||
|
||||
func get_canvas() -> Canvas:
|
||||
return Global.canvas
|
||||
|
||||
|
||||
func dialog_open(open: bool) -> void:
|
||||
Global.dialog_open(open)
|
||||
class MenuAPI:
|
||||
enum { FILE, EDIT, SELECT, IMAGE, VIEW, WINDOW, HELP }
|
||||
|
||||
# Menu methods
|
||||
func _get_popup_menu(menu_type: int) -> PopupMenu:
|
||||
match menu_type:
|
||||
FILE:
|
||||
return Global.top_menu_container.file_menu_button.get_popup()
|
||||
EDIT:
|
||||
return Global.top_menu_container.edit_menu_button.get_popup()
|
||||
SELECT:
|
||||
return Global.top_menu_container.select_menu_button.get_popup()
|
||||
IMAGE:
|
||||
return Global.top_menu_container.image_menu_button.get_popup()
|
||||
VIEW:
|
||||
return Global.top_menu_container.view_menu_button.get_popup()
|
||||
WINDOW:
|
||||
return Global.top_menu_container.window_menu_button.get_popup()
|
||||
HELP:
|
||||
return Global.top_menu_container.help_menu_button.get_popup()
|
||||
return null
|
||||
|
||||
func add_menu_item(menu_type: int, item_name: String, item_metadata, item_id := -1) -> int:
|
||||
# item_metadata is usually a popup node you want to appear when you click the item_name
|
||||
# that popup should also have an (menu_item_clicked) function inside it's script
|
||||
var popup_menu: PopupMenu = _get_popup_menu(menu_type)
|
||||
if not popup_menu:
|
||||
return -1
|
||||
popup_menu.add_item(item_name, item_id)
|
||||
var idx := item_id
|
||||
if item_id == -1:
|
||||
idx = popup_menu.get_item_count() - 1
|
||||
popup_menu.set_item_metadata(idx, item_metadata)
|
||||
ExtensionsApi.add_action("add_menu")
|
||||
return idx
|
||||
|
||||
func remove_menu_item(menu_type: int, item_idx: int) -> void:
|
||||
var popup_menu: PopupMenu = _get_popup_menu(menu_type)
|
||||
if not popup_menu:
|
||||
return
|
||||
popup_menu.remove_item(item_idx)
|
||||
ExtensionsApi.remove_action("add_menu")
|
||||
|
||||
|
||||
func get_current_project() -> Project:
|
||||
return Global.current_project
|
||||
class DialogAPI:
|
||||
func show_error(text: String) -> void:
|
||||
# useful for displaying messages like "Incompatible API" etc...
|
||||
Global.error_dialog.set_text(text)
|
||||
Global.error_dialog.popup_centered()
|
||||
Global.dialog_open(true)
|
||||
|
||||
func get_dialogs_parent_node() -> Node:
|
||||
return Global.control.get_node("Dialogs")
|
||||
|
||||
func dialog_open(open: bool) -> void:
|
||||
Global.dialog_open(open)
|
||||
|
||||
|
||||
func get_current_cel_info() -> Dictionary:
|
||||
# As types of cel are added to Pixelorama,
|
||||
# then the old extension would have no idea how to identify the types they use
|
||||
# E.g the extension may try to use a GroupCel as a PixelCel (if it doesn't know the difference)
|
||||
# So it's encouraged to use this function to access cels
|
||||
var project = get_current_project()
|
||||
var cel = project.get_current_cel()
|
||||
# Add cel types as we have more and more cels
|
||||
if cel is PixelCel:
|
||||
return {"cel": cel, "type": "PixelCel"}
|
||||
elif cel is GroupCel:
|
||||
return {"cel": cel, "type": "GroupCel"}
|
||||
else:
|
||||
return {"cel": cel, "type": "BaseCel"}
|
||||
class PanelAPI:
|
||||
func set_tabs_visible(visible: bool) -> void:
|
||||
var dockable := _get_dockable_container_ui()
|
||||
dockable.set_tabs_visible(visible)
|
||||
|
||||
func get_tabs_visible() -> bool:
|
||||
var dockable := _get_dockable_container_ui()
|
||||
return dockable.get_tabs_visible()
|
||||
|
||||
func add_node_as_tab(node: Node, alongside_node: String) -> void:
|
||||
var dockable := _get_dockable_container_ui()
|
||||
dockable.add_child(node)
|
||||
var tab = _find_tab_with_node(alongside_node, dockable)
|
||||
if not tab:
|
||||
push_error("Tab not found")
|
||||
return
|
||||
tab.insert_node(0, node) # Insert at the beginning
|
||||
ExtensionsApi.add_action("add_tab")
|
||||
# INSTRUCTION
|
||||
# After this check if tabs are invisible, if they are, then make tabs visible
|
||||
# and after doing yield(get_tree(), "idle_frame") twice make them invisible again
|
||||
|
||||
func remove_node_from_tab(node: Node) -> void:
|
||||
if node == null:
|
||||
return
|
||||
var dockable = Global.control.find_node("DockableContainer")
|
||||
var tab = _find_tab_with_node(node.name, dockable)
|
||||
if not tab:
|
||||
push_error("Tab not found")
|
||||
return
|
||||
tab.remove_node(node)
|
||||
node.get_parent().remove_child(node)
|
||||
node.queue_free()
|
||||
ExtensionsApi.remove_action("add_tab")
|
||||
|
||||
# PRIVATE METHODS
|
||||
func _get_dockable_container_ui() -> Node:
|
||||
return Global.control.find_node("DockableContainer")
|
||||
|
||||
func _find_tab_with_node(node_name: String, dockable_container):
|
||||
var root = dockable_container.layout.root
|
||||
var tabs = _get_tabs_in_root(root)
|
||||
for tab in tabs:
|
||||
var idx = tab.find_name(node_name)
|
||||
if idx != -1:
|
||||
return tab
|
||||
return null
|
||||
|
||||
func _get_tabs_in_root(parent_resource):
|
||||
var parents := [] # Resources have no get_parent_resource() so this is an alternative
|
||||
var scanned := [] # To keep track of already discovered layout_split resources
|
||||
var child_number := 0
|
||||
parents.append(parent_resource)
|
||||
var scan_target = parent_resource
|
||||
var tabs := []
|
||||
# Get children in the parent, the initial parent is the node we entered as "parent"
|
||||
while child_number < 2:
|
||||
# If parent isn't a (layout_split) resource then there is no point
|
||||
# in continuing (This is just a Sanity Check and should always pass)
|
||||
if !scan_target.has_method("get_first"):
|
||||
break
|
||||
|
||||
var child_resource
|
||||
if child_number == 0:
|
||||
child_resource = scan_target.get_first() # First child
|
||||
elif child_number == 1:
|
||||
child_resource = scan_target.get_second() # Second child
|
||||
|
||||
# If the child resource is a tab and it wasn't discovered before, add it to "paths"
|
||||
if child_resource.has_method("get_current_tab"):
|
||||
if !tabs.has(child_resource):
|
||||
tabs.append(child_resource)
|
||||
# If "child_resource" is another layout_split resource then we need to scan it too
|
||||
elif child_resource.has_method("get_first") and !scanned.has(child_resource):
|
||||
scanned.append(child_resource)
|
||||
parents.append(child_resource)
|
||||
scan_target = parents[-1] # Set this as the next scan target
|
||||
# Reset child_number by setting it to -1, because later it will
|
||||
# get added by "child_number += 1" to make it 0
|
||||
child_number = -1
|
||||
child_number += 1
|
||||
# If we have reached the bottom, then make the child's parent as
|
||||
# the next parent and move on to the next child in the parent
|
||||
if child_number == 2:
|
||||
scan_target = parents.pop_back()
|
||||
child_number = 0
|
||||
# If there is no parent left to get scanned
|
||||
if scan_target == null:
|
||||
return tabs
|
||||
|
||||
|
||||
func get_global() -> Global:
|
||||
return Global
|
||||
class ThemeAPI:
|
||||
func add_theme(theme: Theme) -> void:
|
||||
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
|
||||
themes.themes.append(theme)
|
||||
themes.add_theme(theme)
|
||||
ExtensionsApi.add_action("add_theme")
|
||||
|
||||
func find_theme_index(theme: Theme) -> int:
|
||||
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
|
||||
return themes.themes.find(theme)
|
||||
|
||||
func get_theme() -> Theme:
|
||||
return Global.control.theme
|
||||
|
||||
func set_theme(idx: int) -> bool:
|
||||
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
|
||||
if idx >= 0 and idx < themes.themes.size():
|
||||
themes.buttons_container.get_child(idx).emit_signal("pressed")
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func remove_theme(theme: Theme) -> void:
|
||||
Global.preferences_dialog.themes.remove_theme(theme)
|
||||
ExtensionsApi.remove_action("add_theme")
|
||||
|
||||
|
||||
func get_extensions_node() -> Node:
|
||||
return Global.control.get_node("Extensions")
|
||||
class ToolAPI:
|
||||
# Tool methods
|
||||
func add_tool(
|
||||
tool_name: String,
|
||||
display_name: String,
|
||||
shortcut: String,
|
||||
scene: PackedScene,
|
||||
extra_hint := "",
|
||||
extra_shortucts := []
|
||||
) -> void:
|
||||
var tool_class := Tools.Tool.new(
|
||||
tool_name, display_name, shortcut, scene, extra_hint, extra_shortucts
|
||||
)
|
||||
Tools.tools[tool_name] = tool_class
|
||||
Tools.add_tool_button(tool_class)
|
||||
ExtensionsApi.add_action("add_tool")
|
||||
|
||||
func remove_tool(tool_name: String) -> void:
|
||||
# Re-assigning the tools in case the tool to be removed is also Active
|
||||
Tools.assign_tool("Pencil", BUTTON_LEFT)
|
||||
Tools.assign_tool("Eraser", BUTTON_RIGHT)
|
||||
var tool_class: Tools.Tool = Tools.tools[tool_name]
|
||||
if tool_class:
|
||||
Tools.remove_tool(tool_class)
|
||||
ExtensionsApi.remove_action("add_tool")
|
||||
|
||||
|
||||
func get_dockable_container_ui() -> Node:
|
||||
return Global.control.find_node("DockableContainer")
|
||||
class ProjectAPI:
|
||||
func get_current_project() -> Project:
|
||||
return Global.current_project
|
||||
|
||||
|
||||
func get_config_file() -> ConfigFile:
|
||||
return Global.config_cache
|
||||
|
||||
|
||||
func get_canvas() -> Canvas:
|
||||
return Global.canvas
|
||||
|
||||
|
||||
func get_dialogs_parent_node() -> Node:
|
||||
return Global.control.get_node("Dialogs")
|
||||
|
||||
|
||||
# Dockable container methods
|
||||
# Adds a node as a tab next to an already existing panel to the dockable container
|
||||
func add_node_as_tab(node: Node, alongside_node: String) -> void:
|
||||
var dockable := get_dockable_container_ui()
|
||||
dockable.add_child(node)
|
||||
var tab = _find_tab_with_node(alongside_node, dockable)
|
||||
if not tab:
|
||||
push_error("Tab not found")
|
||||
return
|
||||
|
||||
tab.insert_node(0, node) # Insert at the beginning
|
||||
if !dockable.get_tabs_visible():
|
||||
dockable.set_tabs_visible(true)
|
||||
# A hacky way to fix tabs that sometimes are not visible when an extension is loaded
|
||||
yield(get_tree(), "idle_frame")
|
||||
yield(get_tree(), "idle_frame")
|
||||
dockable.set_tabs_visible(false)
|
||||
|
||||
|
||||
# Removes a node from the dockable container
|
||||
func remove_node_from_tab(node: Node) -> void:
|
||||
var dockable = Global.control.find_node("DockableContainer")
|
||||
var tab = _find_tab_with_node(node.name, dockable)
|
||||
if not tab:
|
||||
push_error("Tab not found")
|
||||
return
|
||||
tab.remove_node(node)
|
||||
node.get_parent().remove_child(node)
|
||||
node.queue_free()
|
||||
|
||||
|
||||
func _find_tab_with_node(node_name: String, dockable_container):
|
||||
var root = dockable_container.layout.root
|
||||
var tabs = _get_tabs_in_root(root)
|
||||
for tab in tabs:
|
||||
var idx = tab.find_name(node_name)
|
||||
if idx != -1:
|
||||
return tab
|
||||
return null
|
||||
|
||||
|
||||
# Returns all existing tabs inside Split resource
|
||||
func _get_tabs_in_root(parent_resource):
|
||||
var parents := [] # Resources have no get_parent_resource() so this is an alternative
|
||||
var scanned := [] # To keep track of already discovered layout_split resources
|
||||
var child_number := 0
|
||||
parents.append(parent_resource)
|
||||
var scan_target = parent_resource
|
||||
|
||||
var tabs := []
|
||||
|
||||
# Get children in the parent, the initial parent is the node we entered as "parent"
|
||||
while child_number < 2:
|
||||
# If parent isn't a (layout_split) resource then there is no point
|
||||
# in continuing (This is just a Sanity Check and should always pass)
|
||||
if !scan_target.has_method("get_first"):
|
||||
break
|
||||
|
||||
var child_resource
|
||||
if child_number == 0:
|
||||
child_resource = scan_target.get_first() # First child
|
||||
elif child_number == 1:
|
||||
child_resource = scan_target.get_second() # Second child
|
||||
|
||||
# If the child resource is a tab and it wasn't discovered before, add it to "paths"
|
||||
if child_resource.has_method("get_current_tab"):
|
||||
if !tabs.has(child_resource):
|
||||
tabs.append(child_resource)
|
||||
# If "child_resource" is another layout_split resource then we need to scan it too
|
||||
elif child_resource.has_method("get_first") and !scanned.has(child_resource):
|
||||
scanned.append(child_resource)
|
||||
parents.append(child_resource)
|
||||
scan_target = parents[-1] # Set this as the next scan target
|
||||
# Reset child_number by setting it to -1, because later it will
|
||||
# get added by "child_number += 1" to make it 0
|
||||
child_number = -1
|
||||
child_number += 1
|
||||
# If we have reached the bottom, then make the child's parent as
|
||||
# the next parent and move on to the next child in the parent
|
||||
if child_number == 2:
|
||||
scan_target = parents.pop_back()
|
||||
child_number = 0
|
||||
# If there is no parent left to get scanned
|
||||
if scan_target == null:
|
||||
return tabs
|
||||
|
||||
|
||||
# Menu methods
|
||||
func _get_popup_menu(menu_type: int) -> PopupMenu:
|
||||
match menu_type:
|
||||
FILE:
|
||||
return Global.top_menu_container.file_menu_button.get_popup()
|
||||
EDIT:
|
||||
return Global.top_menu_container.edit_menu_button.get_popup()
|
||||
SELECT:
|
||||
return Global.top_menu_container.select_menu_button.get_popup()
|
||||
IMAGE:
|
||||
return Global.top_menu_container.image_menu_button.get_popup()
|
||||
VIEW:
|
||||
return Global.top_menu_container.view_menu_button.get_popup()
|
||||
WINDOW:
|
||||
return Global.top_menu_container.window_menu_button.get_popup()
|
||||
HELP:
|
||||
return Global.top_menu_container.help_menu_button.get_popup()
|
||||
return null
|
||||
|
||||
|
||||
func add_menu_item(menu_type: int, item_name: String, item_metadata, item_id := -1) -> int:
|
||||
var popup_menu: PopupMenu = _get_popup_menu(menu_type)
|
||||
if not popup_menu:
|
||||
return -1
|
||||
popup_menu.add_item(item_name, item_id)
|
||||
var idx := item_id
|
||||
if item_id == -1:
|
||||
idx = popup_menu.get_item_count() - 1
|
||||
popup_menu.set_item_metadata(idx, item_metadata)
|
||||
return idx
|
||||
|
||||
|
||||
func remove_menu_item(menu_type: int, item_idx: int) -> void:
|
||||
var popup_menu: PopupMenu = _get_popup_menu(menu_type)
|
||||
if not popup_menu:
|
||||
return
|
||||
popup_menu.remove_item(item_idx)
|
||||
|
||||
|
||||
# Tool methods
|
||||
func add_tool(
|
||||
tool_name: String,
|
||||
display_name: String,
|
||||
shortcut: String,
|
||||
scene: PackedScene,
|
||||
extra_hint := "",
|
||||
extra_shortucts := []
|
||||
) -> void:
|
||||
var tool_class := Tools.Tool.new(
|
||||
tool_name, display_name, shortcut, scene, extra_hint, extra_shortucts
|
||||
)
|
||||
Tools.tools[tool_name] = tool_class
|
||||
Tools.add_tool_button(tool_class)
|
||||
|
||||
|
||||
func remove_tool(tool_name: String) -> void:
|
||||
# Re-assigning the tools in case the tool to be removed is also Active
|
||||
Tools.assign_tool("Pencil", BUTTON_LEFT)
|
||||
Tools.assign_tool("Eraser", BUTTON_RIGHT)
|
||||
var tool_class: Tools.Tool = Tools.tools[tool_name]
|
||||
if tool_class:
|
||||
Tools.remove_tool(tool_class)
|
||||
|
||||
|
||||
# Theme methods
|
||||
func add_theme(theme: Theme) -> void:
|
||||
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
|
||||
themes.themes.append(theme)
|
||||
themes.add_theme(theme)
|
||||
|
||||
|
||||
func find_theme_index(theme: Theme) -> int:
|
||||
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
|
||||
return themes.themes.find(theme)
|
||||
|
||||
|
||||
func get_theme() -> Theme:
|
||||
return Global.control.theme
|
||||
|
||||
|
||||
func set_theme(idx: int) -> bool:
|
||||
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
|
||||
if idx >= 0 and idx < themes.themes.size():
|
||||
themes.buttons_container.get_child(idx).emit_signal("pressed")
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
|
||||
func remove_theme(theme: Theme) -> void:
|
||||
Global.preferences_dialog.themes.remove_theme(theme)
|
||||
func get_current_cel_info() -> Dictionary:
|
||||
# As types of cel are added to Pixelorama,
|
||||
# then the old extension would have no idea how to identify the types they use
|
||||
# E.g the extension may try to use a GroupCel as a PixelCel (if it doesn't know the difference)
|
||||
# So it's encouraged to use this function to access cels
|
||||
var project = get_current_project()
|
||||
var cel = project.get_current_cel()
|
||||
# Add cel types as we have more and more cels
|
||||
if cel is PixelCel:
|
||||
return {"cel": cel, "type": "PixelCel"}
|
||||
elif cel is GroupCel:
|
||||
return {"cel": cel, "type": "GroupCel"}
|
||||
else:
|
||||
return {"cel": cel, "type": "BaseCel"}
|
||||
|
|
|
@ -165,6 +165,8 @@ func _add_extension(file_name: String) -> void:
|
|||
Global.error_dialog.popup_centered()
|
||||
Global.dialog_open(true)
|
||||
print("Incompatible API")
|
||||
# Don't put it in faulty, (it's merely incompatible)
|
||||
remover_directory.remove(EXTENSIONS_PATH.plus_file("Faulty.txt"))
|
||||
return
|
||||
|
||||
var extension := Extension.new()
|
||||
|
@ -192,6 +194,7 @@ func _enable_extension(extension: Extension, save_to_config := true) -> void:
|
|||
var id: String = extension.file_name
|
||||
|
||||
if extension.enabled:
|
||||
ExtensionsApi.clear_history(extension.file_name)
|
||||
for node in extension.nodes:
|
||||
var scene_path: String = extension_path.plus_file(node)
|
||||
var extension_scene: PackedScene = load(scene_path)
|
||||
|
@ -206,6 +209,7 @@ func _enable_extension(extension: Extension, save_to_config := true) -> void:
|
|||
if ext_node.is_in_group(id): # Node for extention found
|
||||
extension_parent.remove_child(ext_node)
|
||||
ext_node.queue_free()
|
||||
ExtensionsApi.check_sanity(extension.file_name)
|
||||
|
||||
if save_to_config:
|
||||
Global.config_cache.set_value("extensions", extension.file_name, extension.enabled)
|
||||
|
|
Loading…
Reference in a new issue