diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 6a668fea8..97f2c0f83 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -535,9 +535,11 @@ func center(indices: Array) -> void: tmp_centered.blend_rect(cel.image, used_rect, offset) var centered := ImageExtended.new() centered.copy_from_custom(tmp_centered, cel_image.is_indexed) + if cel is CelTileMap: + (cel as CelTileMap).serialize_undo_data_source_image(centered, redo_data, undo_data) centered.add_data_to_dictionary(redo_data, cel_image) cel_image.add_data_to_dictionary(undo_data) - Global.undo_redo_compress_images(redo_data, undo_data) + project.deserialize_cel_undo_data(redo_data, undo_data) project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true)) project.undo_redo.add_do_method(Global.undo_or_redo.bind(false)) project.undo_redo.commit_action() @@ -546,15 +548,15 @@ func center(indices: Array) -> void: func scale_project(width: int, height: int, interpolation: int) -> void: var redo_data := {} var undo_data := {} - for f in Global.current_project.frames: - for i in range(f.cels.size() - 1, -1, -1): - var cel := f.cels[i] - if not cel is PixelCel: - continue - var cel_image := (cel as PixelCel).get_image() - var sprite := _resize_image(cel_image, width, height, interpolation) as ImageExtended - sprite.add_data_to_dictionary(redo_data, cel_image) - cel_image.add_data_to_dictionary(undo_data) + for cel in Global.current_project.get_all_pixel_cels(): + if not cel is PixelCel: + continue + var cel_image := (cel as PixelCel).get_image() + var sprite := _resize_image(cel_image, width, height, interpolation) as ImageExtended + if cel is CelTileMap: + (cel as CelTileMap).serialize_undo_data_source_image(sprite, redo_data, undo_data) + sprite.add_data_to_dictionary(redo_data, cel_image) + cel_image.add_data_to_dictionary(undo_data) general_do_and_undo_scale(width, height, redo_data, undo_data) @@ -596,9 +598,9 @@ func _resize_image( func crop_to_selection() -> void: if not Global.current_project.has_selection: return + Global.canvas.selection.transform_content_confirm() var redo_data := {} var undo_data := {} - Global.canvas.selection.transform_content_confirm() var rect: Rect2i = Global.canvas.selection.big_bounding_rectangle # Loop through all the cels to crop them for cel in Global.current_project.get_all_pixel_cels(): @@ -606,6 +608,8 @@ func crop_to_selection() -> void: var tmp_cropped := cel_image.get_region(rect) var cropped := ImageExtended.new() cropped.copy_from_custom(tmp_cropped, cel_image.is_indexed) + if cel is CelTileMap: + (cel as CelTileMap).serialize_undo_data_source_image(cropped, redo_data, undo_data) cropped.add_data_to_dictionary(redo_data, cel_image) cel_image.add_data_to_dictionary(undo_data) @@ -617,18 +621,17 @@ func crop_to_selection() -> void: func crop_to_content() -> void: Global.canvas.selection.transform_content_confirm() var used_rect := Rect2i() - for f in Global.current_project.frames: - for cel in f.cels: - if not cel is PixelCel: - continue - var cel_used_rect := cel.get_image().get_used_rect() - if cel_used_rect == Rect2i(0, 0, 0, 0): # If the cel has no content - continue + for cel in Global.current_project.get_all_pixel_cels(): + if not cel is PixelCel: + continue + var cel_used_rect := cel.get_image().get_used_rect() + if cel_used_rect == Rect2i(0, 0, 0, 0): # If the cel has no content + continue - if used_rect == Rect2i(0, 0, 0, 0): # If we still haven't found the first cel with content - used_rect = cel_used_rect - else: - used_rect = used_rect.merge(cel_used_rect) + if used_rect == Rect2i(0, 0, 0, 0): # If we still haven't found the first cel with content + used_rect = cel_used_rect + else: + used_rect = used_rect.merge(cel_used_rect) # If no layer has any content, just return if used_rect == Rect2i(0, 0, 0, 0): @@ -644,6 +647,8 @@ func crop_to_content() -> void: var tmp_cropped := cel_image.get_region(used_rect) var cropped := ImageExtended.new() cropped.copy_from_custom(tmp_cropped, cel_image.is_indexed) + if cel is CelTileMap: + (cel as CelTileMap).serialize_undo_data_source_image(cropped, redo_data, undo_data) cropped.add_data_to_dictionary(redo_data, cel_image) cel_image.add_data_to_dictionary(undo_data) @@ -662,6 +667,8 @@ func resize_canvas(width: int, height: int, offset_x: int, offset_y: int) -> voi cel_image, Rect2i(Vector2i.ZERO, cel_image.get_size()), Vector2i(offset_x, offset_y) ) resized.convert_rgb_to_indexed() + if cel is CelTileMap: + (cel as CelTileMap).serialize_undo_data_source_image(resized, redo_data, undo_data) resized.add_data_to_dictionary(redo_data, cel_image) cel_image.add_data_to_dictionary(undo_data) @@ -698,7 +705,7 @@ func general_do_and_undo_scale( project.undo_redo.add_do_property(project, "y_symmetry_point", new_y_symmetry_point) project.undo_redo.add_do_property(project.x_symmetry_axis, "points", new_x_symmetry_axis_points) project.undo_redo.add_do_property(project.y_symmetry_axis, "points", new_y_symmetry_axis_points) - Global.undo_redo_compress_images(redo_data, undo_data) + project.deserialize_cel_undo_data(redo_data, undo_data) project.undo_redo.add_undo_property(project, "size", project.size) project.undo_redo.add_undo_property(project, "x_symmetry_point", project.x_symmetry_point) project.undo_redo.add_undo_property(project, "y_symmetry_point", project.y_symmetry_point) diff --git a/src/Classes/Cels/CelTileMap.gd b/src/Classes/Cels/CelTileMap.gd index 6889ecc50..02cd13035 100644 --- a/src/Classes/Cels/CelTileMap.gd +++ b/src/Classes/Cels/CelTileMap.gd @@ -156,13 +156,14 @@ func transform_tile( return transformed_tile -func update_tileset() -> void: +func update_tileset( + tile_editing_mode := TileSetPanel.tile_editing_mode, source_image := image +) -> void: editing_images.clear() - var tile_editing_mode := TileSetPanel.tile_editing_mode for i in cells.size(): var coords := get_cell_coords_in_image(i) var rect := Rect2i(coords, tileset.tile_size) - var image_portion := image.get_region(rect) + var image_portion := source_image.get_region(rect) var index := cells[i].index if index >= tileset.tiles.size(): printerr("Cell at position ", i + 1, ", mapped to ", index, " is out of bounds!") @@ -416,11 +417,6 @@ func update_texture(undo := false) -> void: super.update_texture(undo) -func size_changed(new_size: Vector2i) -> void: - _resize_cells(new_size) - _re_index_all_cells() - - func serialize_undo_data() -> Dictionary: var dict := {} var cell_indices := [] @@ -432,22 +428,39 @@ func serialize_undo_data() -> Dictionary: return dict +func serialize_undo_data_source_image( + source_image: ImageExtended, redo_data: Dictionary, undo_data: Dictionary +) -> void: + undo_data[self] = serialize_undo_data() + if source_image.get_size() != image.get_size(): + _resize_cells(source_image.get_size()) + tileset.clear_tileset(self) + var tile_editing_mode := TileSetPanel.tile_editing_mode + if tile_editing_mode == TileSetPanel.TileEditingMode.MANUAL: + tile_editing_mode = TileSetPanel.TileEditingMode.AUTO + update_tileset(tile_editing_mode, source_image) + redo_data[self] = serialize_undo_data() + + func deserialize_undo_data(dict: Dictionary, undo_redo: UndoRedo, undo: bool) -> void: var cell_indices = dict["cell_indices"] if undo: - for i in cell_indices.size(): - var cell_data: Dictionary = cell_indices[i] - undo_redo.add_undo_method(cells[i].deserialize.bind(cell_data)) + undo_redo.add_undo_method(deserialize_cell_data.bind(cell_indices)) if dict.has("tileset"): undo_redo.add_undo_method(tileset.deserialize_undo_data.bind(dict.tileset, self)) else: - for i in cell_indices.size(): - var cell_data: Dictionary = cell_indices[i] - undo_redo.add_do_method(cells[i].deserialize.bind(cell_data)) + undo_redo.add_do_method(deserialize_cell_data.bind(cell_indices)) if dict.has("tileset"): undo_redo.add_do_method(tileset.deserialize_undo_data.bind(dict.tileset, self)) +func deserialize_cell_data(cell_indices: Array) -> void: + _resize_cells(image.get_size()) + for i in cell_indices.size(): + var cell_data: Dictionary = cell_indices[i] + cells[i].deserialize(cell_data) + + func serialize() -> Dictionary: var dict := super.serialize() var cell_indices := [] diff --git a/src/Classes/TileSetCustom.gd b/src/Classes/TileSetCustom.gd index 49c89b714..830e9988e 100644 --- a/src/Classes/TileSetCustom.gd +++ b/src/Classes/TileSetCustom.gd @@ -2,8 +2,8 @@ class_name TileSetCustom extends RefCounted ## A Tileset is a collection of tiles, used by [LayerTileMap]s and [CelTileMap]s. -## The tileset contains the [Project] that it is being used by, its [member name]. -## the size of each individual tile, and the collection of [TileSetCustom.Tile]s itself. +## The tileset contains its [member name], the size of each individual tile, +## and the collection of [TileSetCustom.Tile]s itself. ## Not to be confused with [TileSet], which is a Godot class. ## Emitted every time the tileset changes, such as when a tile is added, removed or replaced. @@ -16,6 +16,11 @@ var name := "" var tile_size: Vector2i ## The collection of tiles in the form of an [Array] of type [TileSetCustom.Tile]. var tiles: Array[Tile] = [] +## If [code]true[/code], the code in [method clear_tileset] does not execute. +## This variable is used to prevent multiple cels from clearing the tileset at the same time. +## In [method clear_tileset], the variable is set to [code]true[/code], and then +## immediately set to [code]false[/code] in the next frame using [method Object.set_deferred]. +var _tileset_has_been_cleared := false ## An internal class of [TileSetCustom], which contains data used by individual tiles of a tileset. @@ -109,6 +114,19 @@ func remove_unused_tiles(cel: CelTileMap) -> bool: return tile_removed +## Clears the tileset. Usually called when the project gets resized, +## and tilemap cels are updating their size and clearing the tileset to re-create it. +func clear_tileset(cel: CelTileMap) -> void: + if _tileset_has_been_cleared: + return + tiles.clear() + var empty_image := Image.create_empty(tile_size.x, tile_size.y, false, Image.FORMAT_RGBA8) + tiles.append(Tile.new(empty_image)) + updated.emit(cel) + _tileset_has_been_cleared = true + set_deferred("_tileset_has_been_cleared", false) + + ## Serializes the data of this class into the form of a [Dictionary], ## which is used so the data can be stored in pxo files. func serialize() -> Dictionary: diff --git a/src/UI/Timeline/AnimationTimeline.gd b/src/UI/Timeline/AnimationTimeline.gd index 77611b8d7..2cf58ce6c 100644 --- a/src/UI/Timeline/AnimationTimeline.gd +++ b/src/UI/Timeline/AnimationTimeline.gd @@ -1107,9 +1107,9 @@ func _on_MergeDownLayer_pressed() -> void: var undo_data := {} var redo_data := {} if bottom_cel is CelTileMap: - undo_data[bottom_cel] = (bottom_cel as CelTileMap).serialize_undo_data() - (bottom_cel as CelTileMap).update_tileset(new_bottom_image) - redo_data[bottom_cel] = (bottom_cel as CelTileMap).serialize_undo_data() + (bottom_cel as CelTileMap).serialize_undo_data_source_image( + new_bottom_image, redo_data, undo_data + ) new_bottom_image.add_data_to_dictionary(redo_data, bottom_image) bottom_image.add_data_to_dictionary(undo_data) project.deserialize_cel_undo_data(redo_data, undo_data)