1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-19 01:29:49 +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:
Variable 2023-01-10 22:26:13 +05:00 committed by GitHub
parent 512c85ac64
commit e1facda618
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 294 additions and 224 deletions

View file

@ -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/windows_native_icon="res://assets/graphics/icons/icon.ico"
config/custom_user_dir_name.X11="pixelorama" config/custom_user_dir_name.X11="pixelorama"
config/Version="v0.11-dev" config/Version="v0.11-dev"
config/ExtensionsAPI_Version=2 config/ExtensionsAPI_Version=3
[audio] [audio]

View file

@ -1,93 +1,178 @@
# gdlint: ignore=max-public-methods # gdlint: ignore=max-public-methods
extends Node extends Node
# 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 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")
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
class MenuAPI:
enum { FILE, EDIT, SELECT, IMAGE, VIEW, WINDOW, HELP } 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")
class DialogAPI:
func show_error(text: String) -> void: func show_error(text: String) -> void:
# useful for displaying messages like "Incompatible API" etc... # useful for displaying messages like "Incompatible API" etc...
Global.error_dialog.set_text(text) Global.error_dialog.set_text(text)
Global.error_dialog.popup_centered() Global.error_dialog.popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
func get_dialogs_parent_node() -> Node:
func get_api_version() -> int: return Global.control.get_node("Dialogs")
return ProjectSettings.get_setting("application/config/ExtensionsAPI_Version")
func get_pixelorama_version() -> String:
return ProjectSettings.get_setting("application/config/Version")
func dialog_open(open: bool) -> void: func dialog_open(open: bool) -> void:
Global.dialog_open(open) Global.dialog_open(open)
func get_current_project() -> Project: class PanelAPI:
return Global.current_project 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 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"}
func get_global() -> Global:
return Global
func get_extensions_node() -> Node:
return Global.control.get_node("Extensions")
func get_dockable_container_ui() -> Node:
return Global.control.find_node("DockableContainer")
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: func add_node_as_tab(node: Node, alongside_node: String) -> void:
var dockable := get_dockable_container_ui() var dockable := _get_dockable_container_ui()
dockable.add_child(node) dockable.add_child(node)
var tab = _find_tab_with_node(alongside_node, dockable) var tab = _find_tab_with_node(alongside_node, dockable)
if not tab: if not tab:
push_error("Tab not found") push_error("Tab not found")
return return
tab.insert_node(0, node) # Insert at the beginning tab.insert_node(0, node) # Insert at the beginning
if !dockable.get_tabs_visible(): ExtensionsApi.add_action("add_tab")
dockable.set_tabs_visible(true) # INSTRUCTION
# A hacky way to fix tabs that sometimes are not visible when an extension is loaded # After this check if tabs are invisible, if they are, then make tabs visible
yield(get_tree(), "idle_frame") # and after doing yield(get_tree(), "idle_frame") twice make them invisible again
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: func remove_node_from_tab(node: Node) -> void:
if node == null:
return
var dockable = Global.control.find_node("DockableContainer") var dockable = Global.control.find_node("DockableContainer")
var tab = _find_tab_with_node(node.name, dockable) var tab = _find_tab_with_node(node.name, dockable)
if not tab: if not tab:
@ -96,7 +181,11 @@ func remove_node_from_tab(node: Node) -> void:
tab.remove_node(node) tab.remove_node(node)
node.get_parent().remove_child(node) node.get_parent().remove_child(node)
node.queue_free() 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): func _find_tab_with_node(node_name: String, dockable_container):
var root = dockable_container.layout.root var root = dockable_container.layout.root
@ -107,17 +196,13 @@ func _find_tab_with_node(node_name: String, dockable_container):
return tab return tab
return null return null
# Returns all existing tabs inside Split resource
func _get_tabs_in_root(parent_resource): func _get_tabs_in_root(parent_resource):
var parents := [] # Resources have no get_parent_resource() so this is an alternative 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 scanned := [] # To keep track of already discovered layout_split resources
var child_number := 0 var child_number := 0
parents.append(parent_resource) parents.append(parent_resource)
var scan_target = parent_resource var scan_target = parent_resource
var tabs := [] var tabs := []
# Get children in the parent, the initial parent is the node we entered as "parent" # Get children in the parent, the initial parent is the node we entered as "parent"
while child_number < 2: while child_number < 2:
# If parent isn't a (layout_split) resource then there is no point # If parent isn't a (layout_split) resource then there is no point
@ -154,45 +239,34 @@ func _get_tabs_in_root(parent_resource):
return tabs return tabs
# Menu methods class ThemeAPI:
func _get_popup_menu(menu_type: int) -> PopupMenu: func add_theme(theme: Theme) -> void:
match menu_type: var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
FILE: themes.themes.append(theme)
return Global.top_menu_container.file_menu_button.get_popup() themes.add_theme(theme)
EDIT: ExtensionsApi.add_action("add_theme")
return Global.top_menu_container.edit_menu_button.get_popup()
SELECT: func find_theme_index(theme: Theme) -> int:
return Global.top_menu_container.select_menu_button.get_popup() var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
IMAGE: return themes.themes.find(theme)
return Global.top_menu_container.image_menu_button.get_popup()
VIEW: func get_theme() -> Theme:
return Global.top_menu_container.view_menu_button.get_popup() return Global.control.theme
WINDOW:
return Global.top_menu_container.window_menu_button.get_popup() func set_theme(idx: int) -> bool:
HELP: var themes: BoxContainer = Global.preferences_dialog.find_node("Themes")
return Global.top_menu_container.help_menu_button.get_popup() if idx >= 0 and idx < themes.themes.size():
return null themes.buttons_container.get_child(idx).emit_signal("pressed")
return true
else:
func add_menu_item(menu_type: int, item_name: String, item_metadata, item_id := -1) -> int: return false
var popup_menu: PopupMenu = _get_popup_menu(menu_type)
if not popup_menu: func remove_theme(theme: Theme) -> void:
return -1 Global.preferences_dialog.themes.remove_theme(theme)
popup_menu.add_item(item_name, item_id) ExtensionsApi.remove_action("add_theme")
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)
class ToolAPI:
# Tool methods # Tool methods
func add_tool( func add_tool(
tool_name: String, tool_name: String,
@ -207,7 +281,7 @@ func add_tool(
) )
Tools.tools[tool_name] = tool_class Tools.tools[tool_name] = tool_class
Tools.add_tool_button(tool_class) Tools.add_tool_button(tool_class)
ExtensionsApi.add_action("add_tool")
func remove_tool(tool_name: String) -> void: func remove_tool(tool_name: String) -> void:
# Re-assigning the tools in case the tool to be removed is also Active # Re-assigning the tools in case the tool to be removed is also Active
@ -216,32 +290,24 @@ func remove_tool(tool_name: String) -> void:
var tool_class: Tools.Tool = Tools.tools[tool_name] var tool_class: Tools.Tool = Tools.tools[tool_name]
if tool_class: if tool_class:
Tools.remove_tool(tool_class) Tools.remove_tool(tool_class)
ExtensionsApi.remove_action("add_tool")
# Theme methods class ProjectAPI:
func add_theme(theme: Theme) -> void: func get_current_project() -> Project:
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes") return Global.current_project
themes.themes.append(theme)
themes.add_theme(theme)
func get_current_cel_info() -> Dictionary:
func find_theme_index(theme: Theme) -> int: # As types of cel are added to Pixelorama,
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes") # then the old extension would have no idea how to identify the types they use
return themes.themes.find(theme) # 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()
func get_theme() -> Theme: var cel = project.get_current_cel()
return Global.control.theme # Add cel types as we have more and more cels
if cel is PixelCel:
return {"cel": cel, "type": "PixelCel"}
func set_theme(idx: int) -> bool: elif cel is GroupCel:
var themes: BoxContainer = Global.preferences_dialog.find_node("Themes") return {"cel": cel, "type": "GroupCel"}
if idx >= 0 and idx < themes.themes.size():
themes.buttons_container.get_child(idx).emit_signal("pressed")
return true
else: else:
return false return {"cel": cel, "type": "BaseCel"}
func remove_theme(theme: Theme) -> void:
Global.preferences_dialog.themes.remove_theme(theme)

View file

@ -165,6 +165,8 @@ func _add_extension(file_name: String) -> void:
Global.error_dialog.popup_centered() Global.error_dialog.popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
print("Incompatible API") print("Incompatible API")
# Don't put it in faulty, (it's merely incompatible)
remover_directory.remove(EXTENSIONS_PATH.plus_file("Faulty.txt"))
return return
var extension := Extension.new() var extension := Extension.new()
@ -192,6 +194,7 @@ func _enable_extension(extension: Extension, save_to_config := true) -> void:
var id: String = extension.file_name var id: String = extension.file_name
if extension.enabled: if extension.enabled:
ExtensionsApi.clear_history(extension.file_name)
for node in extension.nodes: for node in extension.nodes:
var scene_path: String = extension_path.plus_file(node) var scene_path: String = extension_path.plus_file(node)
var extension_scene: PackedScene = load(scene_path) 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 if ext_node.is_in_group(id): # Node for extention found
extension_parent.remove_child(ext_node) extension_parent.remove_child(ext_node)
ext_node.queue_free() ext_node.queue_free()
ExtensionsApi.check_sanity(extension.file_name)
if save_to_config: if save_to_config:
Global.config_cache.set_value("extensions", extension.file_name, extension.enabled) Global.config_cache.set_value("extensions", extension.file_name, extension.enabled)