mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-19 01:29:49 +00:00
Re-implement the ability to add custom export formats from extensions and allow preview display (#924)
* Fixed ExportApi * Added ability for custom exporters to show their preview * renamed a function * Formatting * Linting
This commit is contained in:
parent
26e843301f
commit
5e64491bee
|
@ -10,6 +10,7 @@ enum FileFormat { PNG, WEBP, JPEG, GIF, APNG }
|
||||||
var animated_formats := [FileFormat.GIF, FileFormat.APNG]
|
var animated_formats := [FileFormat.GIF, FileFormat.APNG]
|
||||||
|
|
||||||
## A dictionary of custom exporter generators (received from extensions)
|
## A dictionary of custom exporter generators (received from extensions)
|
||||||
|
var custom_file_formats := {}
|
||||||
var custom_exporter_generators := {}
|
var custom_exporter_generators := {}
|
||||||
|
|
||||||
var current_tab := ExportTab.IMAGE
|
var current_tab := ExportTab.IMAGE
|
||||||
|
@ -51,16 +52,43 @@ func _multithreading_enabled() -> bool:
|
||||||
return ProjectSettings.get_setting("rendering/driver/threads/thread_model") == 2
|
return ProjectSettings.get_setting("rendering/driver/threads/thread_model") == 2
|
||||||
|
|
||||||
|
|
||||||
func add_file_format(_format: String) -> int:
|
func add_custom_file_format(
|
||||||
var id := FileFormat.size()
|
format_name: String, extension: String, exporter_generator: Object, tab: int, is_animated: bool
|
||||||
# FileFormat.merge({format: id})
|
) -> int:
|
||||||
|
# Obtain a unique id
|
||||||
|
var id := Export.FileFormat.size()
|
||||||
|
for i in Export.custom_file_formats.size():
|
||||||
|
var format_id = id + i
|
||||||
|
if !Export.custom_file_formats.values().has(i):
|
||||||
|
id = format_id
|
||||||
|
# Add to custom_file_formats
|
||||||
|
custom_file_formats.merge({format_name: id})
|
||||||
|
custom_exporter_generators.merge({id: [exporter_generator, extension]})
|
||||||
|
if is_animated:
|
||||||
|
Export.animated_formats.append(id)
|
||||||
|
# Add to export dialog
|
||||||
|
match tab:
|
||||||
|
ExportTab.IMAGE:
|
||||||
|
Global.export_dialog.image_exports.append(id)
|
||||||
|
ExportTab.SPRITESHEET:
|
||||||
|
Global.export_dialog.spritesheet_exports.append(id)
|
||||||
|
_: # Both
|
||||||
|
Global.export_dialog.image_exports.append(id)
|
||||||
|
Global.export_dialog.spritesheet_exports.append(id)
|
||||||
return id
|
return id
|
||||||
|
|
||||||
|
|
||||||
func remove_file_format(id: int) -> void:
|
func remove_custom_file_format(id: int) -> void:
|
||||||
for key in Export.FileFormat.keys():
|
for key in custom_file_formats.keys():
|
||||||
if Export.FileFormat[key] == id:
|
if custom_file_formats[key] == id:
|
||||||
# Export.FileFormat.erase(key)
|
custom_file_formats.erase(key)
|
||||||
|
# remove exporter generator
|
||||||
|
Export.custom_exporter_generators.erase(id)
|
||||||
|
# remove from animated (if it is present there)
|
||||||
|
Export.animated_formats.erase(id)
|
||||||
|
# remove from export dialog
|
||||||
|
Global.export_dialog.image_exports.erase(id)
|
||||||
|
Global.export_dialog.spritesheet_exports.erase(id)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@ -392,8 +420,8 @@ func file_format_description(format_enum: int) -> String:
|
||||||
return "APNG Image"
|
return "APNG Image"
|
||||||
_:
|
_:
|
||||||
# If a file format description is not found, try generating one
|
# If a file format description is not found, try generating one
|
||||||
for key in FileFormat.keys():
|
for key in custom_file_formats.keys():
|
||||||
if FileFormat[key] == format_enum:
|
if custom_file_formats[key] == format_enum:
|
||||||
return str(key.capitalize())
|
return str(key.capitalize())
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
|
@ -476,57 +476,37 @@ class ExportAPI:
|
||||||
var ExportTab := Export.ExportTab
|
var ExportTab := Export.ExportTab
|
||||||
|
|
||||||
func add_export_option(
|
func add_export_option(
|
||||||
format_info: Dictionary, exporter_generator, tab := ExportTab.IMAGE, is_animated := true
|
format_info: Dictionary,
|
||||||
|
exporter_generator: Object,
|
||||||
|
tab := ExportTab.IMAGE,
|
||||||
|
is_animated := true
|
||||||
) -> int:
|
) -> int:
|
||||||
# separate enum name and file name
|
# Separate enum name and file name
|
||||||
var extension = ""
|
var extension = ""
|
||||||
var format_name = ""
|
var format_name = ""
|
||||||
if format_info.has("extension"):
|
if format_info.has("extension"):
|
||||||
extension = format_info["extension"]
|
extension = format_info["extension"]
|
||||||
if format_info.has("description"):
|
if format_info.has("description"):
|
||||||
format_name = format_info["description"].to_upper().replace(" ", "_")
|
format_name = format_info["description"].strip_edges().to_upper().replace(" ", "_")
|
||||||
# change format name if another one uses the same name
|
# Change format name if another one uses the same name
|
||||||
for i in range(Export.FileFormat.size()):
|
var existing_format_names = Export.FileFormat.keys() + Export.custom_file_formats.keys()
|
||||||
|
for i in range(existing_format_names.size()):
|
||||||
var test_name = format_name
|
var test_name = format_name
|
||||||
if i != 0:
|
if i != 0:
|
||||||
test_name = str(test_name, "_", i)
|
test_name = str(test_name, "_", i)
|
||||||
if !Export.FileFormat.keys().has(test_name):
|
if !existing_format_names.has(test_name):
|
||||||
format_name = test_name
|
format_name = test_name
|
||||||
break
|
break
|
||||||
# add to FileFormat enum
|
# Setup complete, add the exporter
|
||||||
var id := Export.FileFormat.size()
|
var id = Export.add_custom_file_format(
|
||||||
for i in Export.FileFormat.size(): # use an empty id if it's available
|
format_name, extension, exporter_generator, tab, is_animated
|
||||||
if !Export.FileFormat.values().has(i):
|
)
|
||||||
id = i
|
|
||||||
# Export.FileFormat.merge({format_name: id})
|
|
||||||
# add exporter generator
|
|
||||||
Export.custom_exporter_generators.merge({id: [exporter_generator, extension]})
|
|
||||||
# add to animated (or not)
|
|
||||||
if is_animated:
|
|
||||||
Export.animated_formats.append(id)
|
|
||||||
# add to export dialog
|
|
||||||
match tab:
|
|
||||||
ExportTab.IMAGE:
|
|
||||||
Global.export_dialog.image_exports.append(id)
|
|
||||||
ExportTab.SPRITESHEET:
|
|
||||||
Global.export_dialog.spritesheet_exports.append(id)
|
|
||||||
_: # Both
|
|
||||||
Global.export_dialog.image_exports.append(id)
|
|
||||||
Global.export_dialog.spritesheet_exports.append(id)
|
|
||||||
ExtensionsApi.add_action("add_exporter")
|
ExtensionsApi.add_action("add_exporter")
|
||||||
return id
|
return id
|
||||||
|
|
||||||
func remove_export_option(id: int):
|
func remove_export_option(id: int):
|
||||||
if Export.custom_exporter_generators.has(id):
|
if Export.custom_exporter_generators.has(id):
|
||||||
# remove enum
|
Export.remove_custom_file_format(id)
|
||||||
Export.remove_file_format(id)
|
|
||||||
# remove exporter generator
|
|
||||||
Export.custom_exporter_generators.erase(id)
|
|
||||||
# remove from animated (or not)
|
|
||||||
Export.animated_formats.erase(id)
|
|
||||||
# add to export dialog
|
|
||||||
Global.export_dialog.image_exports.erase(id)
|
|
||||||
Global.export_dialog.spritesheet_exports.erase(id)
|
|
||||||
ExtensionsApi.remove_action("add_exporter")
|
ExtensionsApi.remove_action("add_exporter")
|
||||||
|
|
||||||
|
|
||||||
|
@ -623,3 +603,12 @@ class SignalsAPI:
|
||||||
func disconnect_current_cel_texture_changed(callable: Callable):
|
func disconnect_current_cel_texture_changed(callable: Callable):
|
||||||
texture_changed.disconnect(callable)
|
texture_changed.disconnect(callable)
|
||||||
ExtensionsApi.remove_action("texture_changed")
|
ExtensionsApi.remove_action("texture_changed")
|
||||||
|
|
||||||
|
# Export dialog signals
|
||||||
|
func connect_export_about_to_preview(target: Object, method: String):
|
||||||
|
Global.export_dialog.about_to_preview.connect(Callable(target, method))
|
||||||
|
ExtensionsApi.add_action("export_about_to_preview")
|
||||||
|
|
||||||
|
func disconnect_export_about_to_preview(target: Object, method: String):
|
||||||
|
Global.export_dialog.about_to_preview.disconnect(Callable(target, method))
|
||||||
|
ExtensionsApi.remove_action("export_about_to_preview")
|
||||||
|
|
|
@ -166,7 +166,11 @@ func _add_extension(file_name: String) -> void:
|
||||||
"The extension %s will not work on this version of Pixelorama \n"
|
"The extension %s will not work on this version of Pixelorama \n"
|
||||||
% file_name_no_ext
|
% file_name_no_ext
|
||||||
)
|
)
|
||||||
var required_text := "Requires API version: %s" % str(supported_api_versions)
|
var required_text := str(
|
||||||
|
"Extension works on API versions: %s" % str(supported_api_versions),
|
||||||
|
"\n",
|
||||||
|
"But Pixelorama's API version is: %s" % ExtensionsApi.get_api_version()
|
||||||
|
)
|
||||||
Global.error_dialog.set_text(str(err_text, required_text))
|
Global.error_dialog.set_text(str(err_text, required_text))
|
||||||
Global.error_dialog.popup_centered()
|
Global.error_dialog.popup_centered()
|
||||||
Global.dialog_open(true)
|
Global.dialog_open(true)
|
||||||
|
|
|
@ -2,6 +2,7 @@ extends ConfirmationDialog
|
||||||
|
|
||||||
## Called when user resumes export after filename collision
|
## Called when user resumes export after filename collision
|
||||||
signal resume_export_function
|
signal resume_export_function
|
||||||
|
signal about_to_preview(Dictionary)
|
||||||
|
|
||||||
var preview_current_frame := 0
|
var preview_current_frame := 0
|
||||||
var preview_frames: Array[Texture2D] = []
|
var preview_frames: Array[Texture2D] = []
|
||||||
|
@ -18,6 +19,9 @@ var spritesheet_exports: Array[Export.FileFormat] = [
|
||||||
Export.FileFormat.PNG, Export.FileFormat.WEBP, Export.FileFormat.JPEG
|
Export.FileFormat.PNG, Export.FileFormat.WEBP, Export.FileFormat.JPEG
|
||||||
]
|
]
|
||||||
|
|
||||||
|
var _preview_images: Array[Image]
|
||||||
|
var _preview_durations: PackedFloat32Array
|
||||||
|
|
||||||
@onready var tabs: TabBar = $VBoxContainer/TabBar
|
@onready var tabs: TabBar = $VBoxContainer/TabBar
|
||||||
@onready var checker: ColorRect = $"%TransparentChecker"
|
@onready var checker: ColorRect = $"%TransparentChecker"
|
||||||
@onready var previews: GridContainer = $"%Previews"
|
@onready var previews: GridContainer = $"%Previews"
|
||||||
|
@ -91,18 +95,27 @@ func show_tab() -> void:
|
||||||
|
|
||||||
|
|
||||||
func set_preview() -> void:
|
func set_preview() -> void:
|
||||||
|
_preview_images = Export.processed_images.duplicate()
|
||||||
|
_preview_durations = Export.durations.duplicate()
|
||||||
|
var preview_data = {
|
||||||
|
"exporter_id": Global.current_project.file_format,
|
||||||
|
"export_tab": Export.current_tab,
|
||||||
|
"preview_images": _preview_images,
|
||||||
|
"durations": _preview_durations
|
||||||
|
}
|
||||||
|
about_to_preview.emit(preview_data)
|
||||||
remove_previews()
|
remove_previews()
|
||||||
if Export.processed_images.size() == 1:
|
if _preview_images.size() == 1:
|
||||||
previews.columns = 1
|
previews.columns = 1
|
||||||
add_image_preview(Export.processed_images[0])
|
add_image_preview(_preview_images[0])
|
||||||
else:
|
else:
|
||||||
if Export.is_single_file_format():
|
if Export.is_single_file_format():
|
||||||
previews.columns = 1
|
previews.columns = 1
|
||||||
add_animated_preview()
|
add_animated_preview()
|
||||||
else:
|
else:
|
||||||
previews.columns = ceili(sqrt(Export.processed_images.size()))
|
previews.columns = ceili(sqrt(_preview_images.size()))
|
||||||
for i in range(Export.processed_images.size()):
|
for i in range(_preview_images.size()):
|
||||||
add_image_preview(Export.processed_images[i], i + 1)
|
add_image_preview(_preview_images[i], i + 1)
|
||||||
|
|
||||||
if Global.current_project.file_format == Export.FileFormat.GIF:
|
if Global.current_project.file_format == Export.FileFormat.GIF:
|
||||||
$"%GifWarning".visible = true
|
$"%GifWarning".visible = true
|
||||||
|
@ -133,7 +146,7 @@ func add_animated_preview() -> void:
|
||||||
preview_current_frame = 0
|
preview_current_frame = 0
|
||||||
preview_frames = []
|
preview_frames = []
|
||||||
|
|
||||||
for processed_image in Export.processed_images:
|
for processed_image in _preview_images:
|
||||||
var texture := ImageTexture.create_from_image(processed_image)
|
var texture := ImageTexture.create_from_image(processed_image)
|
||||||
preview_frames.push_back(texture)
|
preview_frames.push_back(texture)
|
||||||
|
|
||||||
|
@ -146,7 +159,7 @@ func add_animated_preview() -> void:
|
||||||
|
|
||||||
previews.add_child(container)
|
previews.add_child(container)
|
||||||
frame_timer.set_one_shot(true) # wait_time can't change correctly if the timer is playing
|
frame_timer.set_one_shot(true) # wait_time can't change correctly if the timer is playing
|
||||||
frame_timer.wait_time = Export.durations[preview_current_frame]
|
frame_timer.wait_time = _preview_durations[preview_current_frame]
|
||||||
frame_timer.start()
|
frame_timer.start()
|
||||||
|
|
||||||
|
|
||||||
|
@ -226,8 +239,8 @@ func create_layer_list() -> void:
|
||||||
|
|
||||||
|
|
||||||
func update_dimensions_label() -> void:
|
func update_dimensions_label() -> void:
|
||||||
if Export.processed_images.size() > 0:
|
if _preview_images.size() > 0:
|
||||||
var new_size: Vector2 = Export.processed_images[0].get_size() * (Export.resize / 100.0)
|
var new_size: Vector2 = _preview_images[0].get_size() * (Export.resize / 100.0)
|
||||||
dimension_label.text = str(new_size.x, "×", new_size.y)
|
dimension_label.text = str(new_size.x, "×", new_size.y)
|
||||||
|
|
||||||
|
|
||||||
|
@ -390,7 +403,7 @@ func _on_FrameTimer_timeout() -> void:
|
||||||
else:
|
else:
|
||||||
preview_current_frame += 1
|
preview_current_frame += 1
|
||||||
|
|
||||||
frame_timer.wait_time = Export.durations[preview_current_frame - 1]
|
frame_timer.wait_time = _preview_durations[preview_current_frame - 1]
|
||||||
frame_timer.start()
|
frame_timer.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
[node name="ExportDialog" type="ConfirmationDialog"]
|
[node name="ExportDialog" type="ConfirmationDialog"]
|
||||||
canvas_item_default_texture_filter = 0
|
canvas_item_default_texture_filter = 0
|
||||||
title = "Export..."
|
title = "Export..."
|
||||||
|
position = Vector2i(0, 36)
|
||||||
size = Vector2i(700, 600)
|
size = Vector2i(700, 600)
|
||||||
exclusive = false
|
exclusive = false
|
||||||
dialog_hide_on_ok = false
|
dialog_hide_on_ok = false
|
||||||
|
@ -17,8 +18,8 @@ script = ExtResource("1")
|
||||||
custom_minimum_size = Vector2(330, 0)
|
custom_minimum_size = Vector2(330, 0)
|
||||||
offset_left = 8.0
|
offset_left = 8.0
|
||||||
offset_top = 8.0
|
offset_top = 8.0
|
||||||
offset_right = 524.0
|
offset_right = 692.0
|
||||||
offset_bottom = 494.0
|
offset_bottom = 551.0
|
||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
|
|
||||||
[node name="TabBar" type="TabBar" parent="VBoxContainer"]
|
[node name="TabBar" type="TabBar" parent="VBoxContainer"]
|
||||||
|
@ -300,8 +301,8 @@ layout_mode = 3
|
||||||
anchors_preset = 0
|
anchors_preset = 0
|
||||||
offset_left = 8.0
|
offset_left = 8.0
|
||||||
offset_top = 8.0
|
offset_top = 8.0
|
||||||
offset_right = 524.0
|
offset_right = 692.0
|
||||||
offset_bottom = 494.0
|
offset_bottom = 551.0
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|
||||||
[node name="PathDialog" type="FileDialog" parent="Popups"]
|
[node name="PathDialog" type="FileDialog" parent="Popups"]
|
||||||
|
|
Loading…
Reference in a new issue