@tool ## Tool for installing icons and setting up the configuration of the input icon mapper. extends Node ## Sent when the user selects to cancel the installation process. signal canceled ## Sent when the installation process has completed. signal completed const RELATIVE_PATH_TO_CONFIGURE_SCENE = "scenes/menus/options_menu/input/input_options_menu.tscn" const REIMPORT_CHECK_DELAY : float = 0.5 const OPEN_SCENE_DELAY : float = 0.5 const REGEX_PREFIX = """\\[node name="InputIconMapper" parent="." index="0"\\][\\s\\S]*""" const FILLED_WHITE_CONFIGURATION = """ [node name="InputIconMapper" parent="." index="0"] replace_strings = { "Capslock": "Caps Lock", "Generic Stick": "Generic Left Stick", "Guide": "Home", "Slash Back": "Back Slash", "Slash Forward": "Slash", "Stick L": "Left Stick", "Stick R": "Right Stick", "Trigger L 1": "Left Shoulder", "Trigger L 2": "Left Trigger", "Trigger R 1": "Right Shoulder", "Trigger R 2": "Right Trigger" } filtered_strings = Array[String](["keyboard", "color", "button", "arrow"]) directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"]) filter = "color" ends_with = ".png" not_ends_with = "outline.png" """ const FILLED_COLOR_CONFIGURATION = """ [node name="InputIconMapper" parent="." index="0"] prioritized_strings = Array[String](["color"]) replace_strings = { "Capslock": "Caps Lock", "Generic Stick": "Generic Left Stick", "Guide": "Home", "Slash Back": "Back Slash", "Slash Forward": "Slash", "Stick L": "Left Stick", "Stick R": "Right Stick", "Trigger L 1": "Left Shoulder", "Trigger L 2": "Left Trigger", "Trigger R 1": "Right Shoulder", "Trigger R 2": "Right Trigger" } filtered_strings = Array[String](["keyboard", "color", "button", "arrow"]) directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"]) ends_with = ".png" not_ends_with = "outline.png" """ const OUTLINED_WHITE_CONFIGURATION = """ [node name="InputIconMapper" parent="." index="0"] prioritized_strings = Array[String](["outline"]) replace_strings = { "Capslock": "Caps Lock", "Generic Stick": "Generic Left Stick", "Guide": "Home", "Slash Back": "Back Slash", "Slash Forward": "Slash", "Stick L": "Left Stick", "Stick R": "Right Stick", "Trigger L 1": "Left Shoulder", "Trigger L 2": "Left Trigger", "Trigger R 1": "Right Shoulder", "Trigger R 2": "Right Trigger" } filtered_strings = Array[String](["keyboard", "color", "button", "arrow", "outline"]) directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"]) filter = "color" ends_with = ".png" """ const OUTLINED_COLOR_CONFIGURATION = """ [node name="InputIconMapper" parent="." index="0"] prioritized_strings = Array[String](["outline", "color"]) replace_strings = { "Capslock": "Caps Lock", "Generic Stick": "Generic Left Stick", "Guide": "Home", "Slash Back": "Back Slash", "Slash Forward": "Slash", "Stick L": "Left Stick", "Stick R": "Right Stick", "Trigger L 1": "Left Shoulder", "Trigger L 2": "Left Trigger", "Trigger R 1": "Right Shoulder", "Trigger R 2": "Right Trigger" } filtered_strings = Array[String](["keyboard", "color", "button", "arrow", "outline"]) directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"]) ends_with = ".png" """ const PACKAGE_EXTRA_DIRECTORIES := [ "Flairs", "Nintendo Gamecube", "Nintendo Switch 2", "Nintendo Wii", "Nintendo WiiU", "Playdate", "Steam Controller", "Touch", ] const PACKAGE_EXTRA_FILES := [ "Preview", ] ## Path start where the project examples have been copied. @export_dir var copy_dir_path : String ## Path end where the zipped files are to be extracted. @export var extract_extension : String @onready var _download_and_extract_node : DownloadAndExtract = $DownloadAndExtract @onready var _skip_installation_dialog : ConfirmationDialog = $SkipInstallationDialog @onready var _kenney_input_prompts_dialog : ConfirmationDialog = $KenneyInputPromptsDialog @onready var _installing_dialog : AcceptDialog = $InstallingDialog @onready var _clean_up_dialog : ConfirmationDialog = $CleanUpDialog @onready var _error_dialog : AcceptDialog = $ErrorDialog @onready var _stage_label : Label = %StageLabel @onready var _progress_bar : ProgressBar = %ProgressBar var _configuration_index : int = -1 ## State flag of whether the tool is waiting for the filesystem to finish scanning. var scanning : bool = false ## State flag for whether the tool is waiting for the filesystem to finish reimporting. var reimporting : bool = false ## Flag for whether the tool will force a download and extraction, even if the contents exist. var force : bool = false func _download_and_extract() -> void: _installing_dialog.show() _download_and_extract_node.run.call_deferred() func _run_complete() -> void: completed.emit() queue_free() func _clean_up_or_complete() -> void: if _has_extras(): _clean_up_dialog.show() else: _run_complete() func _process(_delta : float) -> void: if _installing_dialog.visible: _progress_bar.value = _download_and_extract_node.get_progress() match _download_and_extract_node.stage: DownloadAndExtract.Stage.DOWNLOAD: _stage_label.text = "Downloading..." DownloadAndExtract.Stage.SAVE: _stage_label.text = "Saving..." DownloadAndExtract.Stage.EXTRACT: _stage_label.text = "Extracting..." DownloadAndExtract.Stage.DELETE: _stage_label.text = "Cleaning up..." DownloadAndExtract.Stage.NONE: _installing_dialog.hide() elif scanning: var file_system := EditorInterface.get_resource_filesystem() if not file_system.is_scanning(): scanning = false await get_tree().create_timer(REIMPORT_CHECK_DELAY).timeout if reimporting: await file_system.resources_reimported reimporting = false _configure_and_complete() func _delete_recursive(path : String) -> void: if not path.ends_with("/"): path += "/" var dir_access := DirAccess.open(path) if dir_access == null: return var directories := dir_access.get_directories() for directory in directories: _delete_recursive(path + directory) DirAccess.remove_absolute(path + directory) var files := dir_access.get_files() for file in files: DirAccess.remove_absolute(path + file) func get_full_path() -> String: var full_path := copy_dir_path if not full_path.ends_with("/"): full_path += "/" full_path += extract_extension if not full_path.ends_with("/"): full_path += "/" return full_path func _has_extras() -> bool: var full_path := get_full_path() var directories := DirAccess.get_directories_at(full_path) for directory in directories: for key in PACKAGE_EXTRA_DIRECTORIES: if directory.contains(key): return true var files := DirAccess.get_files_at(full_path) for file in files: for key in PACKAGE_EXTRA_FILES: if file.contains(key): return true return false func _delete_extras() -> void: var full_path := get_full_path() var directories := DirAccess.get_directories_at(full_path) for directory in directories: for key in PACKAGE_EXTRA_DIRECTORIES: if directory.contains(key): _delete_recursive(full_path + directory) DirAccess.remove_absolute(full_path + directory) continue var files := DirAccess.get_files_at(full_path) for file in files: for key in PACKAGE_EXTRA_FILES: if file.contains(key): DirAccess.remove_absolute(full_path + file) continue EditorInterface.get_resource_filesystem().scan() func _configure_icons() -> void: var input_options_menu_path := copy_dir_path + RELATIVE_PATH_TO_CONFIGURE_SCENE var input_options_menu := FileAccess.get_file_as_string(input_options_menu_path) var regex := RegEx.new() regex.compile(REGEX_PREFIX + """\\[node""") var result = regex.sub(input_options_menu, "[node") if result == input_options_menu: regex.clear() regex.compile(REGEX_PREFIX) result = regex.sub(input_options_menu, "") input_options_menu = result match(_configuration_index % 4): 0: input_options_menu += FILLED_COLOR_CONFIGURATION 1: input_options_menu += FILLED_WHITE_CONFIGURATION 2: input_options_menu += OUTLINED_COLOR_CONFIGURATION 3: input_options_menu += OUTLINED_WHITE_CONFIGURATION match(_configuration_index / 4): 0: input_options_menu = input_options_menu.replace("Default", "Vector").replace(".png", ".svg") 1: pass 2: input_options_menu = input_options_menu.replace("Default", "Double") var file_rewrite := FileAccess.open(input_options_menu_path, FileAccess.WRITE) file_rewrite.store_string(input_options_menu) file_rewrite.close() if input_options_menu_path in EditorInterface.get_open_scenes(): EditorInterface.reload_scene_from_path(input_options_menu_path) else: EditorInterface.open_scene_from_path(input_options_menu_path) await get_tree().create_timer(OPEN_SCENE_DELAY).timeout EditorInterface.save_scene() await get_tree().create_timer(REIMPORT_CHECK_DELAY).timeout _clean_up_or_complete() func _configure_and_complete() -> void: if _configuration_index >= 0: _configure_icons() return _clean_up_or_complete() func _scan_filesystem_and_reimport() -> void: var file_system := EditorInterface.get_resource_filesystem() file_system.scan() scanning = true await file_system.resources_reimporting reimporting = true func _enable_forced_install() -> void: force = true _download_and_extract_node.force = true _kenney_input_prompts_dialog.show.call_deferred() func _enable_skipped_install() -> void: _kenney_input_prompts_dialog.set_short_description() _kenney_input_prompts_dialog.show.call_deferred() func _show_error_dialog(error : String) -> void: _installing_dialog.hide() _error_dialog.show() _error_dialog.dialog_text = "%s!" % error func _ready() -> void: _skip_installation_dialog.hide() _kenney_input_prompts_dialog.hide() _installing_dialog.hide() _installing_dialog.get_ok_button().hide() _clean_up_dialog.hide() _error_dialog.hide() _download_and_extract_node.extract_path = get_full_path() if _download_and_extract_node.extract_path_exists(): _skip_installation_dialog.show() else: _kenney_input_prompts_dialog.show() func _on_kenney_input_prompts_dialog_canceled() -> void: canceled.emit() queue_free() func _on_kenney_input_prompts_dialog_configuration_selected(index: int) -> void: _configuration_index = index func _on_kenney_input_prompts_dialog_confirmed() -> void: if _download_and_extract_node.extract_path_exists() and not force: _configure_and_complete() return _download_and_extract() func _on_skip_installation_dialog_canceled() -> void: _enable_forced_install() func _on_skip_installation_dialog_confirmed() -> void: _enable_skipped_install() func _on_error_dialog_confirmed() -> void: queue_free() func _on_error_dialog_canceled() -> void: queue_free() func _on_download_and_extract_run_completed() -> void: _scan_filesystem_and_reimport() func _on_download_and_extract_run_failed(error : String) -> void: _show_error_dialog(error) func _on_clean_up_dialog_confirmed() -> void: _delete_extras() _run_complete() func _on_clean_up_dialog_canceled() -> void: _run_complete()