INIT
This commit is contained in:
402
addons/gdfxr/editor/Editor.gd
Normal file
402
addons/gdfxr/editor/Editor.gd
Normal file
@@ -0,0 +1,402 @@
|
||||
@tool
|
||||
extends Container
|
||||
|
||||
enum ExtraOption { SAVE_AS, COPY, PASTE, PASTE_JSFXR, RECENT }
|
||||
enum DefaultFilename { EMPTY, GUESS_FOR_SAVE }
|
||||
|
||||
const SFXRConfig := preload("../SFXRConfig.gd")
|
||||
const SFXRGenerator := preload("../SFXRGenerator.gd")
|
||||
const Base58 := preload("../Base58.gd")
|
||||
const NUM_RECENTS := 4
|
||||
|
||||
class RecentEntry:
|
||||
var title: String
|
||||
var config := SFXRConfig.new()
|
||||
|
||||
var plugin: EditorPlugin:
|
||||
set(v):
|
||||
plugin = v
|
||||
for child in get_children():
|
||||
_hook_plugin(child)
|
||||
|
||||
var _config := SFXRConfig.new()
|
||||
var _config_defaults := SFXRConfig.new()
|
||||
var _config_clipboard: SFXRConfig
|
||||
var _config_recents: Array
|
||||
var _recents_id := 0
|
||||
var _generator := SFXRGenerator.new()
|
||||
var _path: String
|
||||
var _modified := false
|
||||
var _param_map := {}
|
||||
var _syncing_ui := false # a hack since Range set_value emits value_changed
|
||||
var _category_names := {}
|
||||
|
||||
@onready var audio_player := $AudioStreamPlayer as AudioStreamPlayer
|
||||
@onready var filename_label := find_child("Filename") as Label
|
||||
@onready var save_button := find_child("Save") as Button
|
||||
@onready var restore_button := find_child("Restore") as Button
|
||||
@onready var extra_button := find_child("Extra") as MenuButton
|
||||
@onready var version_button := find_child("VersionButton")
|
||||
@onready var translator := $PluginTranslator
|
||||
|
||||
|
||||
func _ready():
|
||||
if not plugin:
|
||||
return # Running in the edited scene instead of from Plugin
|
||||
|
||||
var popup := extra_button.get_popup()
|
||||
popup.add_item(translator.t("Save As..."), ExtraOption.SAVE_AS)
|
||||
popup.add_separator()
|
||||
popup.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), translator.t("Copy"), ExtraOption.COPY)
|
||||
popup.add_icon_item(get_theme_icon("ActionPaste", "EditorIcons"), translator.t("Paste"), ExtraOption.PASTE)
|
||||
popup.add_item(translator.t("Paste from jsfxr"), ExtraOption.PASTE_JSFXR)
|
||||
popup.add_separator(translator.t("Recently Generated"))
|
||||
popup.id_pressed.connect(_on_Extra_id_pressed)
|
||||
|
||||
_category_names = {
|
||||
SFXRConfig.Category.PICKUP_COIN: translator.t("Pickup/Coin"),
|
||||
SFXRConfig.Category.LASER_SHOOT: translator.t("Laser/Shoot"),
|
||||
SFXRConfig.Category.EXPLOSION: translator.t("Explosion"),
|
||||
SFXRConfig.Category.POWERUP: translator.t("Powerup"),
|
||||
SFXRConfig.Category.HIT_HURT: translator.t("Hit/Hurt"),
|
||||
SFXRConfig.Category.JUMP: translator.t("Jump"),
|
||||
SFXRConfig.Category.BLIP_SELECT: translator.t("Blip/Select"),
|
||||
}
|
||||
|
||||
var params := find_child("Params") as Container
|
||||
for category in params.get_children():
|
||||
for control in category.get_children():
|
||||
_param_map[control.parameter] = control
|
||||
control.param_changed.connect(_on_param_changed)
|
||||
control.param_reset.connect(_on_param_reset)
|
||||
control.param_submitted.connect(_on_param_submitted)
|
||||
|
||||
_set_editing_file("")
|
||||
|
||||
|
||||
func _notification(what: int):
|
||||
if not plugin:
|
||||
return # Running in the edited scene instead of from Plugin
|
||||
|
||||
match what:
|
||||
NOTIFICATION_ENTER_TREE, NOTIFICATION_THEME_CHANGED:
|
||||
find_child("ScrollContainer").add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Tree"))
|
||||
|
||||
if extra_button:
|
||||
var popup = extra_button.get_popup()
|
||||
popup.set_item_icon(popup.get_item_index(ExtraOption.COPY), get_theme_icon("ActionCopy", "EditorIcons"))
|
||||
popup.set_item_icon(popup.get_item_index(ExtraOption.PASTE), get_theme_icon("ActionPaste", "EditorIcons"))
|
||||
|
||||
|
||||
func edit(object: Variant) -> void:
|
||||
if not object:
|
||||
if not _modified:
|
||||
_set_editing_file("")
|
||||
# TODO: prompt?
|
||||
return
|
||||
|
||||
var path := object.resource_path as String
|
||||
if _modified:
|
||||
_popup_confirm(
|
||||
translator.t("There are unsaved changes.\nOpen '%s' anyway?") % path,
|
||||
_set_editing_file.bind(path)
|
||||
)
|
||||
else:
|
||||
_set_editing_file(path)
|
||||
|
||||
|
||||
func _hook_plugin(node: Node) -> void:
|
||||
if "plugin" in node:
|
||||
node.plugin = plugin
|
||||
for child in node.get_children():
|
||||
_hook_plugin(child)
|
||||
|
||||
|
||||
func _push_recent(title: String) -> void:
|
||||
var recent: RecentEntry
|
||||
if _config_recents.size() < NUM_RECENTS:
|
||||
recent = RecentEntry.new()
|
||||
else:
|
||||
recent = _config_recents.pop_back()
|
||||
|
||||
_recents_id += 1
|
||||
recent.title = "#%d %s" % [_recents_id, title]
|
||||
recent.config.copy_from(_config)
|
||||
|
||||
_config_recents.push_front(recent)
|
||||
|
||||
|
||||
func _popup_confirm(content: String, callback: Callable) -> void:
|
||||
var dialog := ConfirmationDialog.new()
|
||||
add_child(dialog)
|
||||
dialog.dialog_text = content
|
||||
dialog.title = translator.t("SFXR Editor")
|
||||
dialog.confirmed.connect(callback)
|
||||
dialog.popup_centered()
|
||||
dialog.visibility_changed.connect(dialog.queue_free)
|
||||
|
||||
|
||||
func _popup_message(content: String) -> void:
|
||||
var dialog := AcceptDialog.new()
|
||||
add_child(dialog)
|
||||
dialog.dialog_text = content
|
||||
dialog.title = translator.t("SFXR Editor")
|
||||
dialog.popup_centered()
|
||||
dialog.visibility_changed.connect(dialog.queue_free)
|
||||
|
||||
|
||||
func _popup_file_dialog(mode: int, callback: Callable, default_filename := DefaultFilename.EMPTY) -> void:
|
||||
var dialog := EditorFileDialog.new()
|
||||
add_child(dialog)
|
||||
dialog.access = EditorFileDialog.ACCESS_RESOURCES
|
||||
dialog.file_mode = mode
|
||||
|
||||
match default_filename:
|
||||
DefaultFilename.EMPTY:
|
||||
pass
|
||||
|
||||
DefaultFilename.GUESS_FOR_SAVE:
|
||||
if _path:
|
||||
dialog.current_path = _generate_serial_path(_path)
|
||||
|
||||
dialog.add_filter("*.sfxr; %s" % translator.t("SFXR Audio"))
|
||||
dialog.file_selected.connect(callback)
|
||||
dialog.popup_centered_ratio()
|
||||
dialog.visibility_changed.connect(dialog.queue_free)
|
||||
|
||||
|
||||
func _reset_defaults() -> void:
|
||||
_config_defaults.copy_from(_config)
|
||||
_set_modified(false)
|
||||
_sync_ui()
|
||||
|
||||
|
||||
func _restore_from_config(config: SFXRConfig) -> void:
|
||||
_config.copy_from(config)
|
||||
_sync_ui()
|
||||
_set_modified(not config.is_equal(_config_defaults))
|
||||
audio_player.stream = null
|
||||
|
||||
|
||||
func _set_editing_file(path: String) -> int: # Error
|
||||
if path.is_empty():
|
||||
_config.reset()
|
||||
audio_player.stream = null
|
||||
else:
|
||||
var err := _config.load(path)
|
||||
if err != OK:
|
||||
_popup_message(translator.t("'%s' is not a valid SFXR file.") % path)
|
||||
return err
|
||||
audio_player.stream = load(path)
|
||||
|
||||
_path = path
|
||||
_reset_defaults()
|
||||
return OK
|
||||
|
||||
|
||||
func _set_modified(value: bool) -> void:
|
||||
_modified = value
|
||||
|
||||
var has_file := not _path.is_empty()
|
||||
var base = _path if has_file else translator.t("Unsaved sound")
|
||||
if _modified:
|
||||
base += "(*)"
|
||||
filename_label.text = base
|
||||
restore_button.disabled = not _modified
|
||||
save_button.disabled = has_file and not _modified
|
||||
|
||||
|
||||
func _sync_ui() -> void:
|
||||
_syncing_ui = true
|
||||
for name in _param_map:
|
||||
var control = _param_map[name]
|
||||
var value = _config.get(name)
|
||||
control.set_value(value)
|
||||
control.set_resetable(value != _config_defaults.get(name))
|
||||
_syncing_ui = false
|
||||
|
||||
|
||||
func _generate_serial_path(path: String) -> String:
|
||||
var directory := DirAccess.open(path.get_base_dir())
|
||||
if not directory:
|
||||
return path
|
||||
|
||||
if not directory.file_exists(path.get_file()):
|
||||
return path
|
||||
|
||||
var basename := path.get_basename()
|
||||
var extension := path.get_extension()
|
||||
|
||||
# Extract trailing number.
|
||||
var num_string: String
|
||||
for i in range(basename.length() - 1, -1, -1):
|
||||
var c: String = basename[i]
|
||||
if "0" <= c and c <= "9":
|
||||
num_string = c + num_string
|
||||
else:
|
||||
break
|
||||
var number := num_string.to_int() if num_string else 0
|
||||
var name_string: String = basename.substr(0, basename.length() - num_string.length())
|
||||
|
||||
while true:
|
||||
number += 1
|
||||
var attemp := "%s%d.%s" % [name_string, number, extension]
|
||||
if not directory.file_exists(attemp):
|
||||
return attemp
|
||||
|
||||
return path # Unreachable
|
||||
|
||||
|
||||
func _on_param_submitted(_name):
|
||||
_on_Play_pressed(true)
|
||||
|
||||
|
||||
func _on_param_changed(name, value):
|
||||
if _syncing_ui:
|
||||
return
|
||||
|
||||
_config.set(name, value)
|
||||
|
||||
_param_map[name].set_resetable(value != _config_defaults.get(name))
|
||||
|
||||
_set_modified(not _config.is_equal(_config_defaults))
|
||||
audio_player.stream = null
|
||||
|
||||
|
||||
func _on_param_reset(name):
|
||||
var value = _config_defaults.get(name)
|
||||
_config.set(name, value)
|
||||
|
||||
_syncing_ui = true
|
||||
var control = _param_map[name]
|
||||
control.set_value(value)
|
||||
control.set_resetable(false)
|
||||
_syncing_ui = false
|
||||
|
||||
_set_modified(not _config.is_equal(_config_defaults))
|
||||
audio_player.stream = null
|
||||
_on_Play_pressed(true)
|
||||
|
||||
|
||||
func _on_Play_pressed(force_regenerate := false):
|
||||
if force_regenerate or audio_player.stream == null:
|
||||
audio_player.stream = _generator.generate_audio_stream(_config)
|
||||
audio_player.play()
|
||||
|
||||
|
||||
func _on_Randomize_pressed(category: int):
|
||||
if category == -1:
|
||||
_config.randomize()
|
||||
_push_recent(translator.t("Randomize"))
|
||||
else:
|
||||
_config.randomize_in_category(category)
|
||||
_push_recent(_category_names.get(category, "Unknown"))
|
||||
|
||||
_set_modified(true)
|
||||
_sync_ui()
|
||||
_on_Play_pressed(true)
|
||||
|
||||
|
||||
func _on_Mutate_pressed():
|
||||
_config.mutate()
|
||||
|
||||
_push_recent(translator.t("Mutate"))
|
||||
_set_modified(true)
|
||||
_sync_ui()
|
||||
_on_Play_pressed(true)
|
||||
|
||||
|
||||
func _on_Restore_pressed():
|
||||
_set_editing_file(_path)
|
||||
_on_Play_pressed(true)
|
||||
|
||||
|
||||
func _on_New_pressed():
|
||||
if _modified:
|
||||
_popup_confirm(
|
||||
translator.t("There are unsaved changes.\nCreate a new one anyway?"),
|
||||
_on_New_confirmed
|
||||
)
|
||||
else:
|
||||
_on_New_confirmed()
|
||||
|
||||
|
||||
func _on_New_confirmed() -> void:
|
||||
_set_editing_file("")
|
||||
|
||||
|
||||
func _on_Save_pressed():
|
||||
if _path.is_empty():
|
||||
_popup_file_dialog(EditorFileDialog.FILE_MODE_SAVE_FILE, _on_SaveAsDialog_confirmed)
|
||||
else:
|
||||
_config.save(_path)
|
||||
plugin.get_editor_interface().get_resource_filesystem().scan_sources()
|
||||
_reset_defaults()
|
||||
|
||||
|
||||
func _on_SaveAsDialog_confirmed(path: String):
|
||||
_path = path
|
||||
_config.save(path)
|
||||
plugin.get_editor_interface().get_resource_filesystem().scan()
|
||||
_reset_defaults()
|
||||
|
||||
|
||||
func _on_Load_pressed():
|
||||
if _modified:
|
||||
_popup_confirm(
|
||||
translator.t("There are unsaved changes.\nLoad anyway?"),
|
||||
_popup_file_dialog.bind(EditorFileDialog.FILE_MODE_OPEN_FILE, _set_editing_file)
|
||||
)
|
||||
else:
|
||||
_popup_file_dialog(EditorFileDialog.FILE_MODE_OPEN_FILE, _set_editing_file)
|
||||
|
||||
|
||||
func _on_Extra_about_to_show():
|
||||
var popup := extra_button.get_popup()
|
||||
popup.set_item_disabled(popup.get_item_index(ExtraOption.PASTE), _config_clipboard == null)
|
||||
popup.set_item_disabled(popup.get_item_index(ExtraOption.PASTE_JSFXR), not DisplayServer.clipboard_has())
|
||||
|
||||
# Rebuild recents menu everytime :)
|
||||
var first_recent_index := popup.get_item_index(ExtraOption.RECENT)
|
||||
if first_recent_index != -1:
|
||||
var count := popup.get_item_count()
|
||||
for i in count - first_recent_index:
|
||||
popup.remove_item(count - 1 - i)
|
||||
|
||||
if _config_recents.is_empty():
|
||||
popup.add_item(translator.t("None"), ExtraOption.RECENT)
|
||||
popup.set_item_disabled(popup.get_item_index(ExtraOption.RECENT), true)
|
||||
else:
|
||||
for i in _config_recents.size():
|
||||
popup.add_item(_config_recents[i].title, ExtraOption.RECENT + i)
|
||||
|
||||
|
||||
func _on_Extra_id_pressed(id: int) -> void:
|
||||
match id:
|
||||
ExtraOption.SAVE_AS:
|
||||
_popup_file_dialog(EditorFileDialog.FILE_MODE_SAVE_FILE, _on_SaveAsDialog_confirmed, DefaultFilename.GUESS_FOR_SAVE)
|
||||
|
||||
ExtraOption.COPY:
|
||||
if not _config_clipboard:
|
||||
_config_clipboard = SFXRConfig.new()
|
||||
_config_clipboard.copy_from(_config)
|
||||
|
||||
ExtraOption.PASTE:
|
||||
_restore_from_config(_config_clipboard)
|
||||
|
||||
ExtraOption.PASTE_JSFXR:
|
||||
var pasted := SFXRConfig.new()
|
||||
if pasted.load_from_base58(DisplayServer.clipboard_get()) == OK:
|
||||
_restore_from_config(pasted)
|
||||
else:
|
||||
_popup_message(translator.t("Clipboard does not contain code copied from jsfxr."))
|
||||
|
||||
_:
|
||||
var i := id - ExtraOption.RECENT as int
|
||||
if i < 0 or _config_recents.size() <= i:
|
||||
printerr("Bad index %d (%d in total)" % [i, _config_recents.size()])
|
||||
return
|
||||
var recent: RecentEntry = _config_recents[i]
|
||||
_restore_from_config(recent.config)
|
||||
_on_Play_pressed()
|
||||
|
||||
Reference in New Issue
Block a user