1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-30 23:19:49 +00:00

Separate extension handling code from the Preferences UI

The preferences code only handles the UI related stuff, while HandleExtensions is now solely responsible for extension enabling, loading and uninstalling. This makes it possible to handle extensions without having the preferences dialog be in the middle.
This commit is contained in:
Emmanouil Papadeas 2024-03-24 01:20:51 +02:00
parent ebfc3c04fe
commit f3ca6d1c33
7 changed files with 158 additions and 146 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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))

View file

@ -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"]

View file

@ -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"

View file

@ -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)