mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +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]
|
||||
|
||||
## A dictionary of custom exporter generators (received from extensions)
|
||||
var custom_file_formats := {}
|
||||
var custom_exporter_generators := {}
|
||||
|
||||
var current_tab := ExportTab.IMAGE
|
||||
|
@ -51,16 +52,43 @@ func _multithreading_enabled() -> bool:
|
|||
return ProjectSettings.get_setting("rendering/driver/threads/thread_model") == 2
|
||||
|
||||
|
||||
func add_file_format(_format: String) -> int:
|
||||
var id := FileFormat.size()
|
||||
# FileFormat.merge({format: id})
|
||||
func add_custom_file_format(
|
||||
format_name: String, extension: String, exporter_generator: Object, tab: int, is_animated: bool
|
||||
) -> 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
|
||||
|
||||
|
||||
func remove_file_format(id: int) -> void:
|
||||
for key in Export.FileFormat.keys():
|
||||
if Export.FileFormat[key] == id:
|
||||
# Export.FileFormat.erase(key)
|
||||
func remove_custom_file_format(id: int) -> void:
|
||||
for key in custom_file_formats.keys():
|
||||
if custom_file_formats[key] == id:
|
||||
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
|
||||
|
||||
|
||||
|
@ -392,8 +420,8 @@ func file_format_description(format_enum: int) -> String:
|
|||
return "APNG Image"
|
||||
_:
|
||||
# If a file format description is not found, try generating one
|
||||
for key in FileFormat.keys():
|
||||
if FileFormat[key] == format_enum:
|
||||
for key in custom_file_formats.keys():
|
||||
if custom_file_formats[key] == format_enum:
|
||||
return str(key.capitalize())
|
||||
return ""
|
||||
|
||||
|
|
|
@ -476,57 +476,37 @@ class ExportAPI:
|
|||
var ExportTab := Export.ExportTab
|
||||
|
||||
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:
|
||||
# separate enum name and file name
|
||||
# Separate enum name and file name
|
||||
var extension = ""
|
||||
var format_name = ""
|
||||
if format_info.has("extension"):
|
||||
extension = format_info["extension"]
|
||||
if format_info.has("description"):
|
||||
format_name = format_info["description"].to_upper().replace(" ", "_")
|
||||
# change format name if another one uses the same name
|
||||
for i in range(Export.FileFormat.size()):
|
||||
format_name = format_info["description"].strip_edges().to_upper().replace(" ", "_")
|
||||
# Change format name if another one uses the same name
|
||||
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
|
||||
if i != 0:
|
||||
test_name = str(test_name, "_", i)
|
||||
if !Export.FileFormat.keys().has(test_name):
|
||||
if !existing_format_names.has(test_name):
|
||||
format_name = test_name
|
||||
break
|
||||
# add to FileFormat enum
|
||||
var id := Export.FileFormat.size()
|
||||
for i in Export.FileFormat.size(): # use an empty id if it's available
|
||||
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)
|
||||
# Setup complete, add the exporter
|
||||
var id = Export.add_custom_file_format(
|
||||
format_name, extension, exporter_generator, tab, is_animated
|
||||
)
|
||||
ExtensionsApi.add_action("add_exporter")
|
||||
return id
|
||||
|
||||
func remove_export_option(id: int):
|
||||
if Export.custom_exporter_generators.has(id):
|
||||
# remove enum
|
||||
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)
|
||||
Export.remove_custom_file_format(id)
|
||||
ExtensionsApi.remove_action("add_exporter")
|
||||
|
||||
|
||||
|
@ -623,3 +603,12 @@ class SignalsAPI:
|
|||
func disconnect_current_cel_texture_changed(callable: Callable):
|
||||
texture_changed.disconnect(callable)
|
||||
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"
|
||||
% 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.popup_centered()
|
||||
Global.dialog_open(true)
|
||||
|
|
|
@ -2,6 +2,7 @@ extends ConfirmationDialog
|
|||
|
||||
## Called when user resumes export after filename collision
|
||||
signal resume_export_function
|
||||
signal about_to_preview(Dictionary)
|
||||
|
||||
var preview_current_frame := 0
|
||||
var preview_frames: Array[Texture2D] = []
|
||||
|
@ -18,6 +19,9 @@ var spritesheet_exports: Array[Export.FileFormat] = [
|
|||
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 checker: ColorRect = $"%TransparentChecker"
|
||||
@onready var previews: GridContainer = $"%Previews"
|
||||
|
@ -91,18 +95,27 @@ func show_tab() -> 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()
|
||||
if Export.processed_images.size() == 1:
|
||||
if _preview_images.size() == 1:
|
||||
previews.columns = 1
|
||||
add_image_preview(Export.processed_images[0])
|
||||
add_image_preview(_preview_images[0])
|
||||
else:
|
||||
if Export.is_single_file_format():
|
||||
previews.columns = 1
|
||||
add_animated_preview()
|
||||
else:
|
||||
previews.columns = ceili(sqrt(Export.processed_images.size()))
|
||||
for i in range(Export.processed_images.size()):
|
||||
add_image_preview(Export.processed_images[i], i + 1)
|
||||
previews.columns = ceili(sqrt(_preview_images.size()))
|
||||
for i in range(_preview_images.size()):
|
||||
add_image_preview(_preview_images[i], i + 1)
|
||||
|
||||
if Global.current_project.file_format == Export.FileFormat.GIF:
|
||||
$"%GifWarning".visible = true
|
||||
|
@ -133,7 +146,7 @@ func add_animated_preview() -> void:
|
|||
preview_current_frame = 0
|
||||
preview_frames = []
|
||||
|
||||
for processed_image in Export.processed_images:
|
||||
for processed_image in _preview_images:
|
||||
var texture := ImageTexture.create_from_image(processed_image)
|
||||
preview_frames.push_back(texture)
|
||||
|
||||
|
@ -146,7 +159,7 @@ func add_animated_preview() -> void:
|
|||
|
||||
previews.add_child(container)
|
||||
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()
|
||||
|
||||
|
||||
|
@ -226,8 +239,8 @@ func create_layer_list() -> void:
|
|||
|
||||
|
||||
func update_dimensions_label() -> void:
|
||||
if Export.processed_images.size() > 0:
|
||||
var new_size: Vector2 = Export.processed_images[0].get_size() * (Export.resize / 100.0)
|
||||
if _preview_images.size() > 0:
|
||||
var new_size: Vector2 = _preview_images[0].get_size() * (Export.resize / 100.0)
|
||||
dimension_label.text = str(new_size.x, "×", new_size.y)
|
||||
|
||||
|
||||
|
@ -390,7 +403,7 @@ func _on_FrameTimer_timeout() -> void:
|
|||
else:
|
||||
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()
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
[node name="ExportDialog" type="ConfirmationDialog"]
|
||||
canvas_item_default_texture_filter = 0
|
||||
title = "Export..."
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(700, 600)
|
||||
exclusive = false
|
||||
dialog_hide_on_ok = false
|
||||
|
@ -17,8 +18,8 @@ script = ExtResource("1")
|
|||
custom_minimum_size = Vector2(330, 0)
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 524.0
|
||||
offset_bottom = 494.0
|
||||
offset_right = 692.0
|
||||
offset_bottom = 551.0
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="TabBar" type="TabBar" parent="VBoxContainer"]
|
||||
|
@ -300,8 +301,8 @@ layout_mode = 3
|
|||
anchors_preset = 0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 524.0
|
||||
offset_bottom = 494.0
|
||||
offset_right = 692.0
|
||||
offset_bottom = 551.0
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="PathDialog" type="FileDialog" parent="Popups"]
|
||||
|
|
Loading…
Reference in a new issue