1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-18 17:19:50 +00:00

Integrate Extension Explorer (#910)

* Integration of ExtensionExplorer

---------

Co-authored-by: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com>
Co-authored-by: Emmanouil Papadeas <manoschool@yahoo.gr>
This commit is contained in:
Variable 2024-01-23 06:25:37 +05:00 committed by GitHub
parent ddce0393dd
commit f43f80cee0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 906 additions and 4 deletions

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=9 format=3 uid="uid://b3hkjj3s6pe4x"]
[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"]
@ -7,11 +7,13 @@
[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/UI/Nodes/ValueSlider.gd" id="8"]
[ext_resource type="PackedScene" uid="uid://chy5d42l72crk" path="res://src/UI/ExtensionExplorer/Store.tscn" id="8_jmnx8"]
[sub_resource type="ButtonGroup" id="ButtonGroup_8vsfb"]
[node name="PreferencesDialog" type="AcceptDialog"]
title = "Preferences"
position = Vector2i(0, 36)
size = Vector2i(800, 500)
exclusive = false
popup_window = true
@ -28,7 +30,7 @@ offset_bottom = -49.0
size_flags_horizontal = 3
theme_override_constants/separation = 20
theme_override_constants/autohide = 0
split_offset = 150
split_offset = 125
[node name="List" type="ItemList" parent="HSplitContainer"]
custom_minimum_size = Vector2(85, 0)
@ -1076,6 +1078,7 @@ tooltip_text = "Specifies the tablet driver being used on Windows. If you have W
mouse_default_cursor_shape = 2
[node name="Extensions" type="VBoxContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide"]
unique_name_in_owner = true
visible = false
layout_mode = 2
script = ExtResource("2")
@ -1093,6 +1096,10 @@ text = "Extensions"
layout_mode = 2
size_flags_horizontal = 3
[node name="Explore" type="Button" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/ExtensionsHeader"]
layout_mode = 2
text = "Explore Online"
[node name="InstalledExtensions" type="ItemList" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions"]
layout_mode = 2
auto_height = true
@ -1293,7 +1300,18 @@ layout_mode = 2
layout_mode = 2
text = "Pixelorama must be restarted for changes to take effect."
[node name="Popups" type="Node" parent="."]
[node name="Popups" type="Control" parent="."]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="AddExtensionFileDialog" type="FileDialog" parent="Popups"]
mode = 1
@ -1307,6 +1325,9 @@ access = 2
filters = PackedStringArray("*.pck ; Godot Resource Pack File", "*.zip ;")
show_hidden_files = true
[node name="Store" parent="Popups" instance=ExtResource("8_jmnx8")]
transient = true
[node name="DeleteConfirmation" type="ConfirmationDialog" parent="."]
unique_name_in_owner = true
position = Vector2i(0, 36)
@ -1329,6 +1350,7 @@ vertical_alignment = 1
[connection signal="item_selected" from="HSplitContainer/List" to="." method="_on_List_item_selected"]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Language/System Language" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Language" method="_on_Language_pressed" binds= [1]]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/ShrinkContainer/ShrinkApplyButton" to="." method="_on_ShrinkApplyButton_pressed"]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/ExtensionsHeader/Explore" to="Popups/Store" method="_on_explore_pressed"]
[connection signal="empty_clicked" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_empty_clicked"]
[connection signal="item_selected" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_item_selected"]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/HBoxContainer/AddExtensionButton" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_AddExtensionButton_pressed"]

View file

@ -0,0 +1,185 @@
class_name ExtensionEntry
extends Panel
var extension_container: VBoxContainer
var thumbnail := ""
var download_link := ""
var download_path := ""
var tags := PackedStringArray()
var is_update := false ## An update instead of download
# node references used in this script
@onready var ext_name := %ExtensionName as Label
@onready var ext_discription := %ExtensionDescription as TextEdit
@onready var small_picture := %Picture as TextureButton
@onready var enlarged_picture := %Enlarged as TextureRect
@onready var request_delay := %RequestDelay as Timer
@onready var thumbnail_request := %ImageRequest as HTTPRequest
@onready var extension_downloader := %DownloadRequest as HTTPRequest
@onready var down_button := %DownloadButton as Button
@onready var progress_bar := %ProgressBar as ProgressBar
@onready var done_label := %Done as Label
@onready var alert_dialog := %Alert as AcceptDialog
func set_info(info: Dictionary, extension_path: String) -> void:
if "name" in info.keys() and "version" in info.keys():
ext_name.text = str(info["name"], "-v", info["version"])
# check for updates
change_button_if_updatable(info["name"], info["version"])
# Setting path extension will be "temporarily" downloaded to before install
DirAccess.make_dir_recursive_absolute(str(extension_path, "Download/"))
download_path = str(extension_path, "Download/", info["name"], ".pck")
if "description" in info.keys():
ext_discription.text = info["description"]
ext_discription.tooltip_text = ext_discription.text
if "thumbnail" in info.keys():
thumbnail = info["thumbnail"]
if "download_link" in info.keys():
download_link = info["download_link"]
if "tags" in info.keys():
tags.append_array(info["tags"])
# Adding a tiny delay to prevent sending bulk requests
request_delay.wait_time = randf() * 2
request_delay.start()
func _on_RequestDelay_timeout() -> void:
request_delay.queue_free() # node no longer needed
thumbnail_request.request(thumbnail) # image
func _on_ImageRequest_request_completed(
_result, _response_code, _headers, body: PackedByteArray
) -> void:
# Update the received image
thumbnail_request.queue_free()
var image := Image.new()
# for images on internet there is a hagh chance that extension is wrong
# so check all of them even if they give error
var err := image.load_png_from_buffer(body)
if err != OK:
var err_a := image.load_jpg_from_buffer(body)
if err_a != OK:
var err_b := image.load_webp_from_buffer(body)
if err_b != OK:
var err_c := image.load_tga_from_buffer(body)
if err_c != OK:
image.load_bmp_from_buffer(body)
var texture := ImageTexture.create_from_image(image)
small_picture.texture_normal = texture
small_picture.pressed.connect(enlarge_thumbnail.bind(texture))
func _on_Download_pressed() -> void:
down_button.disabled = true
extension_downloader.download_file = download_path
extension_downloader.request(download_link)
prepare_progress()
## Called after the extension downloader has finished its job
func _on_DownloadRequest_request_completed(
result: int, _response_code: int, _headers: PackedStringArray, _body: PackedByteArray
) -> void:
if result == HTTPRequest.RESULT_SUCCESS:
# Add extension
extension_container.install_extension(download_path)
if is_update:
is_update = false
announce_done(true)
else:
alert_dialog.get_node("Text").text = (
str(
"Unable to Download extension...\nHttp Code: ",
result,
" (",
error_string(result),
")"
)
. c_unescape()
)
alert_dialog.popup_centered()
announce_done(false)
DirAccess.remove_absolute(download_path)
## Updates the entry node's UI
func announce_done(success: bool) -> void:
close_progress()
down_button.disabled = false
if success:
done_label.visible = true
down_button.text = "Re-Download"
done_label.get_node("DoneDelay").start()
## Returns true if entry contains ALL tags in tag_array
func tags_match(tag_array: PackedStringArray) -> bool:
if tags.size() > 0:
for tag in tag_array:
if !tag in tags:
return false
return true
else:
if tag_array.size() > 0:
return false
return true
## 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)
if typeof(old_version) == TYPE_FLOAT:
if new_version > old_version:
down_button.text = "Update"
is_update = true
elif new_version == old_version:
down_button.text = "Re-Download"
## Show an enlarged version of the thumbnail
func enlarge_thumbnail(texture: ImageTexture) -> void:
enlarged_picture.texture = texture
enlarged_picture.get_parent().popup_centered()
## A beautification function that hides the "Done" label bar after some time
func _on_DoneDelay_timeout() -> void:
done_label.visible = false
## Progress bar method
func prepare_progress() -> void:
progress_bar.visible = true
progress_bar.value = 0
progress_bar.get_node("ProgressTimer").start()
## Progress bar method
func update_progress() -> void:
var down := extension_downloader.get_downloaded_bytes()
var total := extension_downloader.get_body_size()
progress_bar.value = (float(down) / float(total)) * 100.0
## Progress bar method
func close_progress() -> void:
progress_bar.visible = false
progress_bar.get_node("ProgressTimer").stop()
## Progress bar method
func _on_ProgressTimer_timeout() -> void:
update_progress()
func _manage_enlarded_thumbnail_close() -> void:
enlarged_picture.get_parent().hide()
func _manage_alert_close() -> void:
alert_dialog.hide()

View file

@ -0,0 +1,125 @@
[gd_scene load_steps=3 format=3 uid="uid://dnjpemuehkxsn"]
[ext_resource type="Script" path="res://src/UI/ExtensionExplorer/Entry/ExtensionEntry.gd" id="1_3no3v"]
[ext_resource type="Texture2D" uid="uid://b47r0c6auaqk6" path="res://assets/graphics/icons/icon.png" id="2_qhsve"]
[node name="ExtensionEntry" type="Panel"]
self_modulate = Color(0.411765, 0.411765, 0.411765, 1)
custom_minimum_size = Vector2(300, 150)
offset_right = 284.0
offset_bottom = 150.0
size_flags_horizontal = 3
script = ExtResource("1_3no3v")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="Picture" type="TextureButton" parent="MarginContainer/HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 120)
layout_mode = 2
mouse_default_cursor_shape = 2
texture_normal = ExtResource("2_qhsve")
ignore_texture_size = true
stretch_mode = 5
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ExtensionName" type="Label" parent="MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Extension Name..."
[node name="ExtensionDescription" type="TextEdit" parent="MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
placeholder_text = "Description"
editable = false
wrap_mode = 1
[node name="Done" type="Label" parent="MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
self_modulate = Color(0.337255, 1, 0, 1)
layout_mode = 2
text = "Done!!!"
[node name="DoneDelay" type="Timer" parent="MarginContainer/HBoxContainer/VBoxContainer/Done"]
wait_time = 2.0
[node name="ProgressBar" type="ProgressBar" parent="MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
[node name="ProgressTimer" type="Timer" parent="MarginContainer/HBoxContainer/VBoxContainer/ProgressBar"]
wait_time = 0.1
[node name="DownloadButton" type="Button" parent="MarginContainer/HBoxContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Download"
[node name="RequestDelay" type="Timer" parent="."]
unique_name_in_owner = true
one_shot = true
autostart = true
[node name="ImageRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
[node name="DownloadRequest" type="HTTPRequest" parent="."]
unique_name_in_owner = true
[node name="Alert" type="AcceptDialog" parent="."]
unique_name_in_owner = true
size = Vector2i(421, 106)
[node name="Text" type="Label" parent="Alert"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
horizontal_alignment = 1
[node name="EnlardedThumbnail" type="Window" parent="."]
position = Vector2i(0, 36)
size = Vector2i(440, 360)
visible = false
transient = true
exclusive = true
[node name="Enlarged" type="TextureRect" parent="EnlardedThumbnail"]
unique_name_in_owner = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
stretch_mode = 5
[connection signal="timeout" from="MarginContainer/HBoxContainer/VBoxContainer/Done/DoneDelay" to="." method="_on_DoneDelay_timeout"]
[connection signal="timeout" from="MarginContainer/HBoxContainer/VBoxContainer/ProgressBar/ProgressTimer" to="." method="_on_ProgressTimer_timeout"]
[connection signal="pressed" from="MarginContainer/HBoxContainer/VBoxContainer/DownloadButton" to="." method="_on_Download_pressed"]
[connection signal="timeout" from="RequestDelay" to="." method="_on_RequestDelay_timeout"]
[connection signal="request_completed" from="ImageRequest" to="." method="_on_ImageRequest_request_completed"]
[connection signal="request_completed" from="DownloadRequest" to="." method="_on_DownloadRequest_request_completed"]
[connection signal="close_requested" from="Alert" to="." method="_manage_alert_close"]
[connection signal="focus_exited" from="Alert" to="." method="_manage_alert_close"]
[connection signal="close_requested" from="EnlardedThumbnail" to="." method="_manage_enlarded_thumbnail_close"]
[connection signal="focus_exited" from="EnlardedThumbnail" to="." method="_manage_enlarded_thumbnail_close"]

View file

@ -0,0 +1,214 @@
extends Window
## Usage:
## Change the "STORE_NAME" and "STORE_LINK"
## Don't touch anything else
const STORE_NAME := "Extension Explorer"
# gdlint: ignore=max-line-length
const STORE_LINK := "https://raw.githubusercontent.com/Orama-Interactive/Pixelorama/master/src/UI/ExtensionExplorer/store_info.md"
## File that will contain information about extensions available for download
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]
var faulty_custom_links: Array[String]
# node references used in this script
@onready var content: VBoxContainer = $"%Content"
@onready var store_info_downloader: HTTPRequest = %StoreInformationDownloader
@onready var main_store_link: LineEdit = %MainStoreLink
@onready var custom_store_links: VBoxContainer = %CustomStoreLinks
@onready var search_manager: LineEdit = %SearchManager
@onready var tab_container: TabContainer = %TabContainer
@onready var progress_bar: ProgressBar = %ProgressBar
@onready var update_timer: Timer = %UpdateTimer
@onready var faulty_links_label: Label = %FaultyLinks
@onready var custom_link_error: AcceptDialog = %ErrorCustom
@onready var error_get_info: AcceptDialog = %Error
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)
# tell the downloader where to download the store information
store_info_downloader.download_file = extension_path.path_join(STORE_INFORMATION_FILE)
func _on_Store_about_to_show() -> void:
# Clear old tags
search_manager.available_tags = PackedStringArray()
for tag in search_manager.tag_list.get_children():
tag.queue_free()
# Clear old entries
for entry in content.get_children():
entry.queue_free()
faulty_custom_links.clear()
custom_links_remaining = custom_store_links.custom_links.size()
fetch_info(STORE_LINK)
func _on_close_requested() -> void:
hide()
func fetch_info(link: String) -> void:
if extension_path != "": # Did everything went smoothly in _ready() function?
# everything is ready, now request the store information
# so that available extensions could be displayed
var error := store_info_downloader.request(link)
if error == OK:
prepare_progress()
else:
printerr("Unable to get info from remote repository.")
error_getting_info(error)
## When downloading is finished
func _on_StoreInformation_request_completed(
result: int, _response_code: int, _headers: PackedStringArray, _body: PackedByteArray
) -> void:
if result == HTTPRequest.RESULT_SUCCESS:
# process the info contained in the file
var file := FileAccess.open(
extension_path.path_join(STORE_INFORMATION_FILE), FileAccess.READ
)
while not file.eof_reached():
process_line(file.get_line())
file.close()
DirAccess.remove_absolute(extension_path.path_join(STORE_INFORMATION_FILE))
# Hide the progress bar because it's no longer required
close_progress()
else:
printerr("Unable to get info from remote repository...")
error_getting_info(result)
func close_progress() -> void:
progress_bar.get_parent().visible = false
tab_container.visible = true
update_timer.stop()
if redirects.size() > 0:
var next_link := redirects.pop_front() as String
fetch_info(next_link)
else:
# no more redirects, jump to the next store
custom_links_remaining -= 1
if custom_links_remaining >= 0:
var next_link: String = custom_store_links.custom_links[custom_links_remaining]
fetch_info(next_link)
else:
if faulty_custom_links.size() > 0: # manage custom faulty links
faulty_links_label.text = ""
for link in faulty_custom_links:
faulty_links_label.text += str(link, "\n")
custom_link_error.popup_centered()
## Signal connected from StoreButton.tscn
func _on_explore_pressed() -> void:
popup_centered()
## Function related to error dialog
func _on_CopyCommand_pressed() -> void:
DisplayServer.clipboard_set(
"sudo flatpak override com.orama_interactive.Pixelorama --share=network"
)
## 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)
## Gets called when data couldn't be fetched from remote repository
func error_getting_info(result: int) -> void:
# Shows a popup if error is from main link (i-e MainStore)
# Popups for errors in custom_links are handled in close_progress()
if custom_links_remaining == custom_store_links.custom_links.size():
error_get_info.popup_centered()
error_get_info.title = error_string(result)
else:
faulty_custom_links.append(custom_store_links.custom_links[custom_links_remaining])
close_progress()
## Progress bar method
func prepare_progress() -> void:
progress_bar.get_parent().visible = true
tab_container.visible = false
progress_bar.value = 0
update_timer.start()
## Progress bar method
func update_progress() -> void:
var down := store_info_downloader.get_downloaded_bytes()
var total := store_info_downloader.get_body_size()
progress_bar.value = (float(down) / float(total)) * 100.0
## Progress bar method
func _on_UpdateTimer_timeout() -> void:
update_progress()
# DATA PROCESSORS
func process_line(line: String):
# If the line isn't a comment, we will check data type
var raw_data
line = line.strip_edges()
if !line.begins_with("#") and !line.begins_with("//") and line != "":
# attempting to convert to a variable other than a string
raw_data = str_to_var(line)
if !raw_data: # attempt failed, using it as string
raw_data = line
# Determine action based on data type
match typeof(raw_data):
TYPE_ARRAY:
var extension_data: Dictionary = parse_extension_data(raw_data)
add_entry(extension_data)
TYPE_STRING:
# it's most probably a store link
var link: String = raw_data.strip_edges()
if !link in redirects:
redirects.append(link)
func parse_extension_data(raw_data: Array) -> Dictionary:
DirAccess.make_dir_recursive_absolute(str(extension_path, "Download/"))
var result := {}
# Check for non-compulsory things if they exist
for item in raw_data:
if typeof(item) == TYPE_ARRAY:
# first array element should always be an identifier text type
var identifier = item.pop_front()
if typeof(identifier) == TYPE_STRING and item.size() > 0:
match identifier:
"name":
result["name"] = item[0]
"version":
result["version"] = item[0]
"description":
result["description"] = item[0]
"thumbnail":
result["thumbnail"] = item[0]
"download_link":
result["download_link"] = item[0]
"tags": # (this should remain as an array)
result["tags"] = item
search_manager.add_new_tags(item)
return result

View file

@ -0,0 +1,230 @@
[gd_scene load_steps=5 format=3 uid="uid://chy5d42l72crk"]
[ext_resource type="Script" path="res://src/UI/ExtensionExplorer/Store.gd" id="1_pwcwi"]
[ext_resource type="Script" path="res://src/UI/ExtensionExplorer/Subscripts/SearchManager.gd" id="2_uqsvm"]
[ext_resource type="Script" path="res://src/UI/ExtensionExplorer/Subscripts/CustomStoreLinks.gd" id="3_dk1xf"]
[ext_resource type="Texture2D" uid="uid://d1urikaf1lxwl" path="res://assets/graphics/timeline/new_frame.png" id="4_ntl7p"]
[node name="Store" type="Window"]
title = "Explore Online"
position = Vector2i(0, 36)
size = Vector2i(760, 470)
visible = false
wrap_controls = true
exclusive = true
script = ExtResource("1_pwcwi")
[node name="TabContainer" type="TabContainer" parent="."]
unique_name_in_owner = true
clip_contents = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 7.0
offset_top = 8.0
offset_right = -7.0
offset_bottom = -8.0
[node name="Store" type="MarginContainer" parent="TabContainer"]
layout_mode = 2
[node name="StoreMainContainer" type="HSplitContainer" parent="TabContainer/Store"]
layout_mode = 2
[node name="Parameters" type="VBoxContainer" parent="TabContainer/Store/StoreMainContainer"]
custom_minimum_size = Vector2(150, 0)
layout_mode = 2
[node name="SearchManager" type="LineEdit" parent="TabContainer/Store/StoreMainContainer/Parameters"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "Search..."
script = ExtResource("2_uqsvm")
[node name="Header" type="Label" parent="TabContainer/Store/StoreMainContainer/Parameters"]
layout_mode = 2
theme_type_variation = &"HeaderSmall"
text = "Tags:"
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/Store/StoreMainContainer/Parameters"]
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 0
[node name="TagList" type="VBoxContainer" parent="TabContainer/Store/StoreMainContainer/Parameters/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
alignment = 1
[node name="ContentScrollContainer" type="ScrollContainer" parent="TabContainer/Store/StoreMainContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Content" type="VBoxContainer" parent="TabContainer/Store/StoreMainContainer/ContentScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="Options" type="MarginContainer" parent="TabContainer"]
visible = false
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/Options"]
layout_mode = 2
[node name="CustomStoreLinks" type="VBoxContainer" parent="TabContainer/Options/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
script = ExtResource("3_dk1xf")
[node name="Header" type="Label" parent="TabContainer/Options/ScrollContainer/CustomStoreLinks"]
layout_mode = 2
theme_type_variation = &"HeaderSmall"
text = "Store Links:"
[node name="MainStoreLink" type="LineEdit" parent="TabContainer/Options/ScrollContainer/CustomStoreLinks"]
unique_name_in_owner = true
layout_mode = 2
editable = false
[node name="Links" type="VBoxContainer" parent="TabContainer/Options/ScrollContainer/CustomStoreLinks"]
layout_mode = 2
[node name="ButtonContainer" type="HBoxContainer" parent="TabContainer/Options/ScrollContainer/CustomStoreLinks"]
layout_mode = 2
[node name="Guide" type="Button" parent="TabContainer/Options/ScrollContainer/CustomStoreLinks/ButtonContainer"]
visible = false
layout_mode = 2
text = "Guide to making a Store File"
[node name="NewLink" type="Button" parent="TabContainer/Options/ScrollContainer/CustomStoreLinks/ButtonContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
size_flags_horizontal = 8
[node name="TextureRect" type="TextureRect" parent="TabContainer/Options/ScrollContainer/CustomStoreLinks/ButtonContainer/NewLink"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 5.0
offset_top = 5.0
offset_right = -5.0
offset_bottom = -5.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("4_ntl7p")
stretch_mode = 5
[node name="ProgressContainer" type="VBoxContainer" parent="."]
unique_name_in_owner = true
visible = false
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 51.0
offset_top = 21.0
offset_right = -37.0
offset_bottom = -5.0
alignment = 1
[node name="ProgressBar" type="ProgressBar" parent="ProgressContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
[node name="Label" type="Label" parent="ProgressContainer"]
layout_mode = 2
text = "Fetching data from Remote Repository
Please Wait"
horizontal_alignment = 1
[node name="UpdateTimer" type="Timer" parent="ProgressContainer"]
unique_name_in_owner = true
wait_time = 0.1
[node name="StoreInformationDownloader" type="HTTPRequest" parent="."]
unique_name_in_owner = true
[node name="Error" type="AcceptDialog" parent="."]
unique_name_in_owner = true
position = Vector2i(0, 36)
size = Vector2i(511, 326)
unresizable = true
popup_window = true
[node name="Content" type="VBoxContainer" parent="Error"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
[node name="Label" type="Label" parent="Error/Content"]
custom_minimum_size = Vector2(495, 180)
layout_mode = 2
text = "Unable to get info from remote repository.
Possible Solutions:
- Make sure you are connected to the internet.
- If you are using the Flatpak version of Pixelorama, you need to grant it permission to connect to the internet. To do that, you can run the following command on your terminal:"
autowrap_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Error/Content"]
layout_mode = 2
[node name="LineEdit" type="LineEdit" parent="Error/Content/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "sudo flatpak override com.orama_interactive.Pixelorama --share=network"
editable = false
[node name="CopyCommand" type="Button" parent="Error/Content/HBoxContainer"]
layout_mode = 2
text = "Copy"
[node name="Label2" type="Label" parent="Error/Content"]
custom_minimum_size = Vector2(495, 50)
layout_mode = 2
size_flags_horizontal = 3
text = "Alternatively, you download Flatseal and set permissions for Flatpak apps there."
autowrap_mode = 3
[node name="ErrorCustom" type="AcceptDialog" parent="."]
unique_name_in_owner = true
position = Vector2i(0, 36)
size = Vector2i(357, 110)
popup_window = true
[node name="Content" type="VBoxContainer" parent="ErrorCustom"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
[node name="Label" type="Label" parent="ErrorCustom/Content"]
layout_mode = 2
text = "Unable to get info from remote repository."
[node name="FaultyLinks" type="Label" parent="ErrorCustom/Content"]
unique_name_in_owner = true
layout_mode = 2
[connection signal="about_to_popup" from="." to="." method="_on_Store_about_to_show"]
[connection signal="close_requested" from="." to="." method="_on_close_requested"]
[connection signal="text_changed" from="TabContainer/Store/StoreMainContainer/Parameters/SearchManager" to="TabContainer/Store/StoreMainContainer/Parameters/SearchManager" method="_on_SearchManager_text_changed"]
[connection signal="visibility_changed" from="TabContainer/Options" to="TabContainer/Options/ScrollContainer/CustomStoreLinks" method="_on_Options_visibility_changed"]
[connection signal="pressed" from="TabContainer/Options/ScrollContainer/CustomStoreLinks/ButtonContainer/Guide" to="TabContainer/Options/ScrollContainer/CustomStoreLinks" method="_on_Guide_pressed"]
[connection signal="pressed" from="TabContainer/Options/ScrollContainer/CustomStoreLinks/ButtonContainer/NewLink" to="TabContainer/Options/ScrollContainer/CustomStoreLinks" method="_on_NewLink_pressed"]
[connection signal="timeout" from="ProgressContainer/UpdateTimer" to="." method="_on_UpdateTimer_timeout"]
[connection signal="request_completed" from="StoreInformationDownloader" to="." method="_on_StoreInformation_request_completed"]
[connection signal="pressed" from="Error/Content/HBoxContainer/CopyCommand" to="." method="_on_CopyCommand_pressed"]

View file

@ -0,0 +1,47 @@
extends VBoxContainer
var custom_links := []
func _ready() -> void:
custom_links = Global.config_cache.get_value("ExtensionExplorer", "custom_links", [])
for link in custom_links:
add_field(link)
func update_links() -> void:
custom_links.clear()
for child in $Links.get_children():
if child.text != "":
custom_links.append(child.text)
Global.config_cache.set_value("ExtensionExplorer", "custom_links", custom_links)
func _on_NewLink_pressed() -> void:
add_field()
func add_field(link := "") -> void:
var link_field := LineEdit.new()
# gdlint: ignore=max-line-length
link_field.placeholder_text = "Paste Store link, given by the store owner (will automatically be removed if left empty)"
link_field.text = link
$Links.add_child(link_field)
link_field.text_changed.connect(field_text_changed)
func field_text_changed(_text: String) -> void:
update_links()
func _on_Options_visibility_changed() -> void:
for child in $Links.get_children():
if child.text == "":
child.queue_free()
# Uncomment it when we have a proper guide for writing a store_info file
func _on_Guide_pressed() -> void:
pass
# gdlint: ignore=max-line-length
# OS.shell_open("https://github.com/Variable-Interactive/Variable-Store/tree/master#rules-for-writing-a-store_info-file")

View file

@ -0,0 +1,50 @@
extends LineEdit
var available_tags := PackedStringArray()
@onready var tag_list: VBoxContainer = $"%TagList"
func _on_SearchManager_text_changed(_new_text: String) -> void:
tag_text_search()
func tag_text_search() -> void:
var result := text_search(text)
var tags := PackedStringArray([])
for tag: Button in tag_list.get_children():
if tag.button_pressed:
tags.append(tag.text)
for entry in result:
if !entry.tags_match(tags):
entry.visible = false
func text_search(text_to_search: String) -> Array[ExtensionEntry]:
var result: Array[ExtensionEntry] = []
for entry: ExtensionEntry in $"%Content".get_children():
var visibility := true
if text_to_search != "":
var extension_name := entry.ext_name.text.to_lower()
var extension_description := entry.ext_discription.text.to_lower()
if not text_to_search.to_lower() in extension_name:
if not text_to_search.to_lower() in extension_description:
visibility = false
if visibility == true:
result.append(entry)
entry.visible = visibility
return result
func add_new_tags(tag_array: PackedStringArray) -> void:
for tag in tag_array:
if !tag in available_tags:
available_tags.append(tag)
var tag_checkbox := CheckBox.new()
tag_checkbox.text = tag
tag_list.add_child(tag_checkbox)
tag_checkbox.toggled.connect(start_tag_search)
func start_tag_search(_button_pressed: bool) -> void:
tag_text_search()

View file

@ -0,0 +1,27 @@
// This file is for online use.<br>
## Rules for writing a (store_info) file:
// 1. The Store Entry is one large Array (referred to as "entry") consisting of sub-arrays (referred to as "data")<br>
// e.g `[[keyword, ....], [keyword, ....], [keyword, ....], .......]`<br>
// 2. Each data must have a keyword of type `String` at it's first index which helps in identifying what the data represents.<br>
// e.g, ["name", "name of extension"] is the data giving information about "name".<br>
// Valid keywords are `name`, `version`, `description`, `tags`, `thumbnail`, `download_link`<br>
// Put quotation marks ("") to make it a string, otherwise error will occur.<br>
// 3. One store entry must occupy only one line (and vice-versa).<br>
// 4. Comments are supported. you can comment an entire line by placing `#` or `//` at the start of the line (comments between or at end of line are not allowed).<br>
// 5. links to another store_info file can be placed inside another store_info file (it will get detected as a custom store file).<br>
## TIPS:
// - `thumbnail` is the link you get by right clicking an image (uploaded somewhere on the internet) and selecting Copy Image Link.<br>
// - `download_link` is ususlly od the form `{repo}/raw/{Path of extension within repo}`<br>
// e.g, if `https://github.com/Variable-ind/Pixelorama-Extensions/blob/master/Extensions/Example.pck` is the URL path to your extension then replace "blob" with "raw"
// and the link becomes `"https://github.com/Variable-ind/Pixelorama-Extensions/raw/master/Extensions/Example.pck"`<br>
// For further help see the entries below for reference of how it's done
## Entries:
// Put Official Extensions Here
## Other Store Links:
### VariableStore
https://raw.githubusercontent.com/Variable-ind/Pixelorama-Extensions/4.0/store_info.md

View file

@ -953,7 +953,7 @@ mouse_filter = 2
color = Color(0, 0.741176, 1, 0.501961)
[node name="PasteTagPopup" type="Popup" parent="."]
size = Vector2i(250, 574)
size = Vector2i(250, 335)
min_size = Vector2i(250, 0)
script = ExtResource("12")
@ -992,6 +992,7 @@ layout_mode = 2
text = "Create new tags"
[node name="Instructions" type="Label" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
custom_minimum_size = Vector2(250, 23)
layout_mode = 2
text = "Available tags:"
autowrap_mode = 3
@ -1004,6 +1005,7 @@ size_flags_vertical = 3
[node name="StartFrame" type="Label" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(250, 23)
layout_mode = 2
horizontal_alignment = 1
autowrap_mode = 3