diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 7be6ae0fc..65716285f 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -37,7 +37,7 @@ func handle_loading_file(file: String) -> void: Palettes.import_palette_from_path(file, true) elif file_ext in ["pck", "zip"]: # Godot resource pack file - Global.preferences_dialog.extensions.install_extension(file) + Global.control.get_node("Extensions").install_extension(file) elif file_ext == "shader" or file_ext == "gdshader": # Godot shader file var shader := load(file) diff --git a/src/Preferences/HandleExtensions.gd b/src/HandleExtensions.gd similarity index 63% rename from src/Preferences/HandleExtensions.gd rename to src/HandleExtensions.gd index 211a3e3a9..05acd8b9f 100644 --- a/src/Preferences/HandleExtensions.gd +++ b/src/HandleExtensions.gd @@ -1,23 +1,19 @@ +class_name Extensions extends Control +signal extension_loaded(extension: Extension, extension_name: String) +signal extension_uninstalled(file_name: String) + enum UninstallMode { KEEP_FILE, FILE_TO_BIN, REMOVE_PERMANENT } const EXTENSIONS_PATH := "user://extensions" const BUG_EXTENSIONS_PATH := "user://give_in_bug_report" const BIN_ACTION := "trash" -@export var add_extension_file_dialog: FileDialog - var extensions := {} ## Extension name: Extension class var extension_selected := -1 var damaged_extension: String -@onready var extension_list: ItemList = $InstalledExtensions -@onready var enable_button: Button = $HBoxContainer/EnableButton -@onready var uninstall_button: Button = $HBoxContainer/UninstallButton -@onready var extension_parent: Node = Global.control.get_node("Extensions") -@onready var delete_confirmation: ConfirmationDialog = %DeleteConfirmation - class Extension: var file_name := "" @@ -52,11 +48,6 @@ class Extension: func _ready() -> void: - delete_confirmation.add_button(tr("Move to Trash"), false, BIN_ACTION) - if OS.get_name() == "Web": - $HBoxContainer/AddExtensionButton.disabled = true - $HBoxContainer/OpenFolderButton.visible = false - _add_internal_extensions() var file_names: PackedStringArray = [] @@ -82,7 +73,7 @@ func _ready() -> void: ## This is an empty function at the moment, but internal extensions here should be added here ## For example: -## [code]read_extension("ExtensionName", true)[/code] +## [code]_load_extension("ExtensionName", true)[/code] func _add_internal_extensions() -> void: pass @@ -96,33 +87,6 @@ func install_extension(path: String) -> void: _add_extension(file_name) -func _uninstall_extension( - file_name := "", remove_mode := UninstallMode.REMOVE_PERMANENT, item := extension_selected -) -> void: - var err := OK - match remove_mode: - UninstallMode.FILE_TO_BIN: - err = OS.move_to_trash( - ProjectSettings.globalize_path(EXTENSIONS_PATH).path_join(file_name) - ) - UninstallMode.REMOVE_PERMANENT: - err = DirAccess.remove_absolute(EXTENSIONS_PATH.path_join(file_name)) - if remove_mode != UninstallMode.KEEP_FILE: - if err != OK: - print(err) - return - - var extension: Extension = extensions[file_name] - extension.enabled = false - _enable_extension(extension, false) - - extensions.erase(file_name) - extension_list.remove_item(item) - extension_selected = -1 - enable_button.disabled = true - uninstall_button.disabled = true - - func _add_extension(file_name: String) -> void: var tester_file: FileAccess # For testing and deleting damaged extensions # Remove any extension that was proven guilty before this extension is loaded @@ -149,19 +113,11 @@ func _add_extension(file_name: String) -> void: # The new (about to load) extension will be considered guilty till it's proven innocent tester_file = FileAccess.open(EXTENSIONS_PATH.path_join("Faulty.txt"), FileAccess.WRITE) - tester_file.store_string(file_name) # Guilty till proven innocent ;) + tester_file.store_string(file_name) tester_file.close() if extensions.has(file_name): - var item := -1 - for i in extension_list.get_item_count(): - if extension_list.get_item_metadata(i) == file_name: - item = i - break - if item == -1: - print("Failed to find %s" % file_name) - return - _uninstall_extension(file_name, UninstallMode.KEEP_FILE, item) + uninstall_extension(file_name, UninstallMode.KEEP_FILE) # Wait two frames so the previous nodes can get freed await get_tree().process_frame await get_tree().process_frame @@ -171,16 +127,15 @@ func _add_extension(file_name: String) -> void: if !success: # Don't delete the extension # Context: pixelorama deletes v0.11.x extensions when you open v1.0, this will prevent it. -# OS.move_to_trash(file_path) print("EXTENSION ERROR: Failed loading resource pack %s." % file_name) - print(" There may be errors in extension code or extension is incompatible") - # Delete the faulty.txt, (it's fate has already been decided) + print("There may be errors in extension code or extension is incompatible") + # Delete the faulty.txt, its fate has already been decided DirAccess.remove_absolute(EXTENSIONS_PATH.path_join("Faulty.txt")) return - read_extension(file_name) + _load_extension(file_name) -func read_extension(extension_file_or_folder_name: StringName, internal := false) -> void: +func _load_extension(extension_file_or_folder_name: StringName, internal := false) -> void: var file_name_no_ext := extension_file_or_folder_name.get_basename() var extension_path := "res://src/Extensions/%s/" % file_name_no_ext var extension_config_file_path := extension_path.path_join("extension.json") @@ -216,8 +171,8 @@ func read_extension(extension_file_or_folder_name: StringName, internal := false ) Global.popup_error(str(err_text, required_text)) print("Incompatible API") - if !internal: # the file isn't created for internal extensions, no need for removal - # Don't put it in faulty, (it's merely incompatible) + if !internal: # The file isn't created for internal extensions, no need for removal + # Don't put it in faulty, it's merely incompatible DirAccess.remove_absolute(EXTENSIONS_PATH.path_join("Faulty.txt")) return @@ -225,16 +180,11 @@ func read_extension(extension_file_or_folder_name: StringName, internal := false extension.serialize(extension_json) extension.internal = internal extensions[extension_file_or_folder_name] = extension - extension_list.add_item(extension.display_name) - var item_count := extension_list.get_item_count() - 1 - extension_list.set_item_tooltip(item_count, extension.description) - extension_list.set_item_metadata(item_count, extension_file_or_folder_name) - if internal: # enable internal extensions if it is for the first time - extension.enabled = Global.config_cache.get_value("extensions", extension.file_name, true) - else: - extension.enabled = Global.config_cache.get_value("extensions", extension.file_name, false) + extension_loaded.emit(extension, extension_file_or_folder_name) + # Enable internal extensions if it is the first time they are being loaded + extension.enabled = Global.config_cache.get_value("extensions", extension.file_name, internal) if extension.enabled: - _enable_extension(extension) + enable_extension(extension) # If an extension doesn't crash pixelorama then it is proven innocent # And we should now delete its "Faulty.txt" file @@ -242,7 +192,7 @@ func read_extension(extension_file_or_folder_name: StringName, internal := false DirAccess.remove_absolute(EXTENSIONS_PATH.path_join("Faulty.txt")) -func _enable_extension(extension: Extension, save_to_config := true) -> void: +func enable_extension(extension: Extension, save_to_config := true) -> void: var extension_path: String = "res://src/Extensions/%s/" % extension.file_name # A unique id for the extension (currently set to file_name). More parameters (version etc.) @@ -257,14 +207,14 @@ func _enable_extension(extension: Extension, save_to_config := true) -> void: var extension_scene: PackedScene = load(scene_path) if extension_scene: var extension_node: Node = extension_scene.instantiate() - extension_parent.add_child(extension_node) + add_child(extension_node) extension_node.add_to_group(id) # Keep track of what to remove later else: print("Failed to load extension %s" % id) else: - for ext_node in extension_parent.get_children(): + for ext_node in get_children(): if ext_node.is_in_group(id): # Node for extension found - extension_parent.remove_child(ext_node) + remove_child(ext_node) ext_node.queue_free() ExtensionsApi.check_sanity(extension.file_name) @@ -273,66 +223,24 @@ func _enable_extension(extension: Extension, save_to_config := true) -> void: Global.config_cache.save("user://cache.ini") -func _on_InstalledExtensions_item_selected(index: int) -> void: - extension_selected = index - var file_name: String = extension_list.get_item_metadata(extension_selected) +func uninstall_extension(file_name := "", remove_mode := UninstallMode.REMOVE_PERMANENT) -> void: + var err := OK + match remove_mode: + UninstallMode.FILE_TO_BIN: + err = OS.move_to_trash( + ProjectSettings.globalize_path(EXTENSIONS_PATH).path_join(file_name) + ) + UninstallMode.REMOVE_PERMANENT: + err = DirAccess.remove_absolute(EXTENSIONS_PATH.path_join(file_name)) + if remove_mode != UninstallMode.KEEP_FILE: + if err != OK: + print(err) + return + var extension: Extension = extensions[file_name] - if extension.enabled: - enable_button.text = "Disable" - else: - enable_button.text = "Enable" - enable_button.disabled = false - if !extension.internal: - uninstall_button.disabled = false - else: - uninstall_button.disabled = true + extension.enabled = false + enable_extension(extension, false) - -func _on_InstalledExtensions_empty_clicked(_position: Vector2, _button_index: int) -> void: - enable_button.disabled = true - uninstall_button.disabled = true - - -func _on_AddExtensionButton_pressed() -> void: - add_extension_file_dialog.popup_centered() - - -func _on_EnableButton_pressed() -> void: - var file_name: String = extension_list.get_item_metadata(extension_selected) - var extension: Extension = extensions[file_name] - extension.enabled = !extension.enabled - # Don't allow disabling internal extensions through this button. - if extension.internal and extension.enabled_once: - Global.preferences_dialog.preference_update(true) - else: - _enable_extension(extension) - - if extension.enabled: - enable_button.text = "Disable" - else: - enable_button.text = "Enable" - - -func _on_UninstallButton_pressed() -> void: - delete_confirmation.popup_centered() - - -func _on_OpenFolderButton_pressed() -> void: - OS.shell_open(ProjectSettings.globalize_path(EXTENSIONS_PATH)) - - -func _on_AddExtensionFileDialog_files_selected(paths: PackedStringArray) -> void: - for path in paths: - install_extension(path) - - -func _on_delete_confirmation_custom_action(action: StringName) -> void: - if action == BIN_ACTION: - _uninstall_extension( - extension_list.get_item_metadata(extension_selected), UninstallMode.FILE_TO_BIN - ) - delete_confirmation.hide() - - -func _on_delete_confirmation_confirmed() -> void: - _uninstall_extension(extension_list.get_item_metadata(extension_selected)) + extensions.erase(file_name) + extension_selected = -1 + extension_uninstalled.emit(file_name) diff --git a/src/Main.tscn b/src/Main.tscn index 49fd2b34a..627b4657c 100644 --- a/src/Main.tscn +++ b/src/Main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=15 format=3 uid="uid://dbylw5k04ulp8"] +[gd_scene load_steps=16 format=3 uid="uid://dbylw5k04ulp8"] [ext_resource type="Theme" uid="uid://cngbvfpwjoimv" path="res://assets/themes/dark/theme.tres" id="1"] [ext_resource type="Script" path="res://src/Main.gd" id="2"] @@ -11,6 +11,7 @@ [ext_resource type="PackedScene" uid="uid://d4euwo633u33b" path="res://src/UI/Dialogs/SaveSprite.tscn" id="11"] [ext_resource type="PackedScene" uid="uid://b3aeqj2k58wdk" path="res://src/UI/Dialogs/OpenSprite.tscn" id="12"] [ext_resource type="PackedScene" uid="uid://c0nuukjakmai2" path="res://src/UI/Dialogs/TileModeOffsetsDialog.tscn" id="14"] +[ext_resource type="Script" path="res://src/HandleExtensions.gd" id="15_v0k2h"] [ext_resource type="PackedScene" uid="uid://clbjfkdupw52l" path="res://src/UI/Timeline/CelProperties.tscn" id="17_ucs64"] [ext_resource type="PackedScene" uid="uid://b3hkjj3s6pe4x" path="res://src/Preferences/PreferencesDialog.tscn" id="32"] [ext_resource type="PackedScene" uid="uid://clgu8wb5o6oup" path="res://src/UI/Dialogs/ExportDialog.tscn" id="39"] @@ -90,6 +91,7 @@ dialog_autowrap = true [node name="Extensions" type="Control" parent="."] anchors_preset = 0 +script = ExtResource("15_v0k2h") [node name="LeftCursor" type="Sprite2D" parent="."] visible = false diff --git a/src/Preferences/ExtensionsPreferences.gd b/src/Preferences/ExtensionsPreferences.gd new file mode 100644 index 000000000..520f4c14e --- /dev/null +++ b/src/Preferences/ExtensionsPreferences.gd @@ -0,0 +1,104 @@ +extends VBoxContainer + +@export var preferences_dialog: AcceptDialog +@export var add_extension_file_dialog: FileDialog + +@onready var extensions := Global.control.get_node("Extensions") as Extensions +@onready var extension_list := $InstalledExtensions as ItemList +@onready var enable_button := $HBoxContainer/EnableButton as Button +@onready var uninstall_button := $HBoxContainer/UninstallButton as Button +@onready var delete_confirmation := %DeleteConfirmation as ConfirmationDialog + + +func _ready() -> void: + extensions.extension_loaded.connect(_extension_loaded) + extensions.extension_uninstalled.connect(_extension_uninstalled) + delete_confirmation.add_button(tr("Move to Trash"), false, Extensions.BIN_ACTION) + if OS.get_name() == "Web": + $HBoxContainer/AddExtensionButton.disabled = true + $HBoxContainer/OpenFolderButton.visible = false + + +func _extension_loaded(extension: Extensions.Extension, extension_name: String) -> void: + extension_list.add_item(extension.display_name) + var item_count := extension_list.get_item_count() - 1 + extension_list.set_item_tooltip(item_count, extension.description) + extension_list.set_item_metadata(item_count, extension_name) + + +func _extension_uninstalled(extension_name: String) -> void: + var item := -1 + for i in extension_list.get_item_count(): + if extension_list.get_item_metadata(i) == extension_name: + item = i + break + if item == -1: + print("Failed to find extension %s" % extension_name) + return + extension_list.remove_item(item) + enable_button.disabled = true + uninstall_button.disabled = true + + +func _on_InstalledExtensions_item_selected(index: int) -> void: + extensions.extension_selected = index + var file_name: String = extension_list.get_item_metadata(extensions.extension_selected) + var extension: Extensions.Extension = extensions.extensions[file_name] + if extension.enabled: + enable_button.text = "Disable" + else: + enable_button.text = "Enable" + enable_button.disabled = false + if !extension.internal: + uninstall_button.disabled = false + else: + uninstall_button.disabled = true + + +func _on_InstalledExtensions_empty_clicked(_position: Vector2, _button_index: int) -> void: + enable_button.disabled = true + uninstall_button.disabled = true + + +func _on_AddExtensionButton_pressed() -> void: + add_extension_file_dialog.popup_centered() + + +func _on_EnableButton_pressed() -> void: + var file_name: String = extension_list.get_item_metadata(extensions.extension_selected) + var extension: Extensions.Extension = extensions.extensions[file_name] + extension.enabled = !extension.enabled + # Don't allow disabling internal extensions through this button. + if extension.internal and extension.enabled_once: + preferences_dialog.preference_update(true) + else: + extensions.enable_extension(extension) + + if extension.enabled: + enable_button.text = "Disable" + else: + enable_button.text = "Enable" + + +func _on_UninstallButton_pressed() -> void: + delete_confirmation.popup_centered() + + +func _on_OpenFolderButton_pressed() -> void: + OS.shell_open(ProjectSettings.globalize_path(extensions.EXTENSIONS_PATH)) + + +func _on_AddExtensionFileDialog_files_selected(paths: PackedStringArray) -> void: + for path in paths: + extensions.install_extension(path) + + +func _on_delete_confirmation_custom_action(action: StringName) -> void: + if action == Extensions.BIN_ACTION: + var extension_name: String = extension_list.get_item_metadata(extensions.extension_selected) + extensions.uninstall_extension(extension_name, Extensions.UninstallMode.FILE_TO_BIN) + delete_confirmation.hide() + + +func _on_delete_confirmation_confirmed() -> void: + extensions.uninstall_extension(extension_list.get_item_metadata(extensions.extension_selected)) diff --git a/src/Preferences/PreferencesDialog.tscn b/src/Preferences/PreferencesDialog.tscn index f0dc5e888..ecdbfb85d 100644 --- a/src/Preferences/PreferencesDialog.tscn +++ b/src/Preferences/PreferencesDialog.tscn @@ -1,11 +1,11 @@ [gd_scene load_steps=10 format=3 uid="uid://b3hkjj3s6pe4x"] [ext_resource type="Script" path="res://src/Preferences/PreferencesDialog.gd" id="1"] -[ext_resource type="Script" path="res://src/Preferences/HandleExtensions.gd" id="2"] [ext_resource type="PackedScene" uid="uid://bq7ibhm0txl5p" path="res://addons/keychain/ShortcutEdit.tscn" id="3"] [ext_resource type="Script" path="res://src/Preferences/HandleLanguages.gd" id="4"] [ext_resource type="Script" path="res://src/Preferences/HandleThemes.gd" id="5"] [ext_resource type="PackedScene" path="res://src/UI/Nodes/ValueSliderV2.tscn" id="7"] +[ext_resource type="Script" path="res://src/Preferences/ExtensionsPreferences.gd" id="7_8ume5"] [ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="8"] [ext_resource type="PackedScene" uid="uid://chy5d42l72crk" path="res://src/UI/ExtensionExplorer/Store.tscn" id="8_jmnx8"] @@ -1116,11 +1116,12 @@ size_flags_horizontal = 3 tooltip_text = "Specifies the tablet driver being used on Windows. If you have Windows Ink enabled, select winink." mouse_default_cursor_shape = 2 -[node name="Extensions" type="VBoxContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide" node_paths=PackedStringArray("add_extension_file_dialog")] +[node name="Extensions" type="VBoxContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide" node_paths=PackedStringArray("preferences_dialog", "add_extension_file_dialog")] unique_name_in_owner = true visible = false layout_mode = 2 -script = ExtResource("2") +script = ExtResource("7_8ume5") +preferences_dialog = NodePath("../../../../..") add_extension_file_dialog = NodePath("../../../../../AddExtensionFileDialog") [node name="ExtensionsHeader" type="HBoxContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions"] diff --git a/src/UI/ExtensionExplorer/Entry/ExtensionEntry.gd b/src/UI/ExtensionExplorer/Entry/ExtensionEntry.gd index 22ba2be16..331db3686 100644 --- a/src/UI/ExtensionExplorer/Entry/ExtensionEntry.gd +++ b/src/UI/ExtensionExplorer/Entry/ExtensionEntry.gd @@ -1,7 +1,6 @@ class_name ExtensionEntry extends Panel -var extension_container: VBoxContainer var thumbnail := "" var download_link := "" var download_path := "" @@ -9,6 +8,7 @@ var tags := PackedStringArray() var is_update := false ## An update instead of download # node references used in this script +@onready var extensions := Global.control.get_node("Extensions") as Extensions @onready var ext_name := %ExtensionName as Label @onready var ext_discription := %ExtensionDescription as TextEdit @onready var small_picture := %Picture as TextureButton @@ -85,7 +85,7 @@ func _on_DownloadRequest_request_completed( ) -> void: if result == HTTPRequest.RESULT_SUCCESS: # Add extension - extension_container.install_extension(download_path) + extensions.install_extension(download_path) if is_update: is_update = false announce_done(true) @@ -130,9 +130,9 @@ func tags_match(tag_array: PackedStringArray) -> bool: ## Updates the entry node's UI if it has an update available func change_button_if_updatable(extension_name: String, new_version: float) -> void: - for extension in extension_container.extensions.keys(): - if extension_container.extensions[extension].file_name == extension_name: - var old_version = str_to_var(extension_container.extensions[extension].version) + for extension in extensions.extensions.keys(): + if extensions.extensions[extension].file_name == extension_name: + var old_version = str_to_var(extensions.extensions[extension].version) if typeof(old_version) == TYPE_FLOAT: if new_version > old_version: down_button.text = "Update" diff --git a/src/UI/ExtensionExplorer/Store.gd b/src/UI/ExtensionExplorer/Store.gd index 64abf36f3..cbdb3253b 100644 --- a/src/UI/ExtensionExplorer/Store.gd +++ b/src/UI/ExtensionExplorer/Store.gd @@ -12,7 +12,6 @@ const STORE_INFORMATION_FILE := STORE_NAME + ".md" const EXTENSION_ENTRY_TSCN := preload("res://src/UI/ExtensionExplorer/Entry/ExtensionEntry.tscn") # Variables placed here due to their frequent use -var extension_container: VBoxContainer var extension_path: String ## The path where extensions will be stored (obtained from pixelorama) var custom_links_remaining: int ## Remaining custom links to be processed var redirects: Array[String] @@ -34,10 +33,9 @@ var faulty_custom_links: Array[String] func _ready() -> void: # Basic setup - extension_container = Global.preferences_dialog.find_child("Extensions") main_store_link.text = STORE_LINK # Get the path that pixelorama uses to store extensions - extension_path = ProjectSettings.globalize_path(extension_container.EXTENSIONS_PATH) + extension_path = ProjectSettings.globalize_path(Extensions.EXTENSIONS_PATH) # tell the downloader where to download the store information store_info_downloader.download_file = extension_path.path_join(STORE_INFORMATION_FILE) @@ -128,7 +126,6 @@ func _on_CopyCommand_pressed() -> void: ## Adds a new extension entry to the "content" func add_entry(info: Dictionary) -> void: var entry := EXTENSION_ENTRY_TSCN.instantiate() - entry.extension_container = extension_container content.add_child(entry) entry.set_info(info, extension_path)