extends Node var current_save_paths := [] # Array of strings # Stores a filename of a backup file in user:// until user saves manually var backup_save_paths := [] # Array of strings onready var autosave_timer : Timer func _ready() -> void: autosave_timer = Timer.new() autosave_timer.one_shot = false autosave_timer.process_mode = Timer.TIMER_PROCESS_IDLE autosave_timer.connect("timeout", self, "_on_Autosave_timeout") add_child(autosave_timer) update_autosave() func open_pxo_file(path : String, untitled_backup : bool = false) -> void: var file := File.new() var err := file.open_compressed(path, File.READ, File.COMPRESSION_ZSTD) if err == ERR_FILE_UNRECOGNIZED: err = file.open(path, File.READ) # If the file is not compressed open it raw (pre-v0.7) if err != OK: Global.notification_label("File failed to open") file.close() return var new_project := Project.new([], path.get_file()) var file_version := file.get_line() # Example, "v0.7.10-beta" var file_ver_splitted := file_version.split("-") var file_ver_splitted_numbers := file_ver_splitted[0].split(".") # In the above example, the major version would return "0", # the minor version would return "7", the patch "10" # and the status would return "beta" var file_major_version = int(file_ver_splitted_numbers[0].replace("v", "")) var file_minor_version = int(file_ver_splitted_numbers[1]) var file_patch_version := 0 var _file_status_version : String if file_ver_splitted_numbers.size() > 2: file_patch_version = int(file_ver_splitted_numbers[2]) if file_ver_splitted.size() > 1: _file_status_version = file_ver_splitted[1] if file_major_version == 0 and file_minor_version < 5: Global.notification_label("File is from an older version of Pixelorama, as such it might not work properly") var new_guides := true if file_major_version == 0: if file_minor_version < 7 or (file_minor_version == 7 and file_patch_version == 0): new_guides = false var frame := 0 var linked_cels := [] if file_major_version >= 0 and file_minor_version > 6: var global_layer_line := file.get_line() while global_layer_line == ".": var layer_name := file.get_line() var layer_visibility := file.get_8() var layer_lock := file.get_8() var layer_new_cels_linked := file.get_8() linked_cels.append(file.get_var()) var l := Layer.new(layer_name, layer_visibility, layer_lock, HBoxContainer.new(), layer_new_cels_linked, []) new_project.layers.append(l) global_layer_line = file.get_line() var frame_line := file.get_line() while frame_line == "--": # Load frames var frame_class := Frame.new() var width := file.get_16() var height := file.get_16() var layer_i := 0 var layer_line := file.get_line() while layer_line == "-": # Load layers var buffer := file.get_buffer(width * height * 4) if file_major_version == 0 and file_minor_version < 7: var layer_name_old_version = file.get_line() if frame == 0: var l := Layer.new(layer_name_old_version) new_project.layers.append(l) var cel_opacity := 1.0 if file_major_version >= 0 and file_minor_version > 5: cel_opacity = file.get_float() var image := Image.new() image.create_from_data(width, height, false, Image.FORMAT_RGBA8, buffer) image.lock() frame_class.cels.append(Cel.new(image, cel_opacity)) if file_major_version >= 0 and file_minor_version >= 7: if frame in linked_cels[layer_i]: new_project.layers[layer_i].linked_cels.append(frame_class) layer_i += 1 layer_line = file.get_line() if !new_guides: var guide_line := file.get_line() # "guideline" no pun intended while guide_line == "|": # Load guides var guide := Guide.new() guide.type = file.get_8() if guide.type == guide.Types.HORIZONTAL: guide.add_point(Vector2(-99999, file.get_16())) guide.add_point(Vector2(99999, file.get_16())) else: guide.add_point(Vector2(file.get_16(), -99999)) guide.add_point(Vector2(file.get_16(), 99999)) guide.has_focus = false Global.canvas.add_child(guide) guide_line = file.get_line() new_project.size = Vector2(width, height) new_project.frames.append(frame_class) frame_line = file.get_line() frame += 1 # new_project.frames = new_project.frames # Just to call Global.frames_changed # new_project.current_layer = new_project.layers.size() - 1 # new_project.current_frame = frame - 1 # new_project.layers = new_project.layers # Just to call Global.layers_changed if new_guides: var guide_line := file.get_line() # "guideline" no pun intended while guide_line == "|": # Load guides var guide := Guide.new() guide.type = file.get_8() if guide.type == guide.Types.HORIZONTAL: guide.add_point(Vector2(-99999, file.get_16())) guide.add_point(Vector2(99999, file.get_16())) else: guide.add_point(Vector2(file.get_16(), -99999)) guide.add_point(Vector2(file.get_16(), 99999)) guide.has_focus = false Global.canvas.add_child(guide) guide_line = file.get_line() # Load tool options Global.color_pickers[0].color = file.get_var() Global.color_pickers[1].color = file.get_var() Global.brush_sizes[0] = file.get_8() Global.brush_size_edits[0].value = Global.brush_sizes[0] Global.brush_sizes[1] = file.get_8() Global.brush_size_edits[1].value = Global.brush_sizes[1] if file_major_version == 0 and file_minor_version < 7: var left_palette = file.get_var() var right_palette = file.get_var() for color in left_palette: Global.color_pickers[0].get_picker().add_preset(color) for color in right_palette: Global.color_pickers[1].get_picker().add_preset(color) # Load custom brushes var brush_line := file.get_line() while brush_line == "/": var b_width := file.get_16() var b_height := file.get_16() var buffer := file.get_buffer(b_width * b_height * 4) var image := Image.new() image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer) new_project.brushes.append(image) Global.create_brush_button(image) brush_line = file.get_line() if file_major_version >= 0 and file_minor_version > 6: var tag_line := file.get_line() while tag_line == ".T/": var tag_name := file.get_line() var tag_color : Color = file.get_var() var tag_from := file.get_8() var tag_to := file.get_8() new_project.animation_tags.append(AnimationTag.new(tag_name, tag_color, tag_from, tag_to)) new_project.animation_tags = new_project.animation_tags # To execute animation_tags_changed() tag_line = file.get_line() file.close() Global.projects.append(new_project) Global.tabs.current_tab = Global.tabs.get_tab_count() - 1 Global.canvas.camera_zoom() if not untitled_backup: # Untitled backup should not change window title and save path current_save_paths[Global.current_project_index] = path Global.window_title = path.get_file() + " - Pixelorama " + Global.current_version func save_pxo_file(path : String, autosave : bool, project : Project = Global.current_project) -> void: var file := File.new() var err := file.open_compressed(path, File.WRITE, File.COMPRESSION_ZSTD) if err == OK: # Store Pixelorama version file.store_line(Global.current_version) # Store Global layers for layer in project.layers: file.store_line(".") file.store_line(layer.name) file.store_8(layer.visible) file.store_8(layer.locked) file.store_8(layer.new_cels_linked) var linked_cels := [] for frame in layer.linked_cels: linked_cels.append(project.frames.find(frame)) file.store_var(linked_cels) # Linked cels as cel numbers file.store_line("END_GLOBAL_LAYERS") # Store frames for frame in project.frames: file.store_line("--") file.store_16(project.size.x) file.store_16(project.size.y) for cel in frame.cels: # Store canvas layers file.store_line("-") file.store_buffer(cel.image.get_data()) file.store_float(cel.opacity) file.store_line("END_LAYERS") file.store_line("END_FRAMES") # Store guides for child in Global.canvas.get_children(): if child is Guide: file.store_line("|") file.store_8(child.type) if child.type == child.Types.HORIZONTAL: file.store_16(child.points[0].y) file.store_16(child.points[1].y) else: file.store_16(child.points[1].x) file.store_16(child.points[0].x) file.store_line("END_GUIDES") # Save tool options var left_color : Color = Global.color_pickers[0].color var right_color : Color = Global.color_pickers[1].color var left_brush_size : int = Global.brush_sizes[0] var right_brush_size : int = Global.brush_sizes[1] file.store_var(left_color) file.store_var(right_color) file.store_8(left_brush_size) file.store_8(right_brush_size) # Save custom brushes for i in range(project.brushes.size()): var brush = project.brushes[i] file.store_line("/") file.store_16(brush.get_size().x) file.store_16(brush.get_size().y) file.store_buffer(brush.get_data()) file.store_line("END_BRUSHES") # Store animation tags for tag in project.animation_tags: file.store_line(".T/") file.store_line(tag.name) file.store_var(tag.color) file.store_8(tag.from) file.store_8(tag.to) file.store_line("END_FRAME_TAGS") file.close() if project.has_changed and not autosave: project.has_changed = false if autosave: Global.notification_label("File autosaved") else: # First remove backup then set current save path remove_backup(Global.current_project_index) current_save_paths[Global.current_project_index] = path Global.notification_label("File saved") project.name = path.get_file() Global.window_title = path.get_file() + " - Pixelorama " + Global.current_version else: Global.notification_label("File failed to save") func update_autosave() -> void: autosave_timer.stop() autosave_timer.wait_time = Global.autosave_interval * 60 # Interval parameter is in minutes, wait_time is seconds if Global.enable_autosave: autosave_timer.start() func _on_Autosave_timeout() -> void: for i in range(backup_save_paths.size()): if backup_save_paths[i] == "": # Create a new backup file if it doesn't exist yet backup_save_paths[i] = "user://backup-" + String(OS.get_unix_time()) + "-%s" % i store_backup_path(i) save_pxo_file(backup_save_paths[i], true, Global.projects[i]) # Backup paths are stored in two ways: # 1) User already manually saved and defined a save path -> {current_save_path, backup_save_path} # 2) User didn't manually saved, "untitled" backup is stored -> {backup_save_path, backup_save_path} func store_backup_path(i : int) -> void: if current_save_paths[i] != "": # Remove "untitled" backup if it existed on this project instance if Global.config_cache.has_section_key("backups", backup_save_paths[i]): Global.config_cache.erase_section_key("backups", backup_save_paths[i]) Global.config_cache.set_value("backups", current_save_paths[i], backup_save_paths[i]) else: Global.config_cache.set_value("backups", backup_save_paths[i], backup_save_paths[i]) Global.config_cache.save("user://cache.ini") func remove_backup(i : int) -> void: # Remove backup file if backup_save_paths[i] != "": if current_save_paths[i] != "": remove_backup_by_path(current_save_paths[i], backup_save_paths[i]) else: # If manual save was not yet done - remove "untitled" backup remove_backup_by_path(backup_save_paths[i], backup_save_paths[i]) backup_save_paths[i] = "" func remove_backup_by_path(project_path : String, backup_path : String) -> void: Directory.new().remove(backup_path) Global.config_cache.erase_section_key("backups", project_path) Global.config_cache.save("user://cache.ini") func reload_backup_file(project_paths : Array, backup_paths : Array) -> void: for i in range(project_paths.size()): # If project path is the same as backup save path -> the backup was untitled open_pxo_file(backup_paths[i], project_paths[i] == backup_paths[i]) backup_save_paths[i] = backup_paths[i] if project_paths[i] != backup_paths[i]: current_save_paths[i] = project_paths[i] Global.window_title = project_paths[i].get_file() + " - Pixelorama(*) " + Global.current_version Global.current_project.has_changed = true Global.notification_label("Backup reloaded")