1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-19 09:39:48 +00:00

Make undo/redo store tilemap cell indices and tileset tiles

Fixes issues with cases 0.5 and 5 of auto mode, and should be a better system overall. Only works with BaseDraw tools, needs to be applied everywhere as well.
This commit is contained in:
Emmanouil Papadeas 2024-11-28 16:27:30 +02:00
parent 8d1652dc09
commit 903ea5134a
8 changed files with 65 additions and 93 deletions

View file

@ -984,7 +984,6 @@ func undo_or_redo(
if action_name == "Scale": if action_name == "Scale":
cel.size_changed(project.size) cel.size_changed(project.size)
canvas.update_texture(layer_index, frame_index, project, undo) canvas.update_texture(layer_index, frame_index, project, undo)
cel.on_undo_redo(undo)
else: else:
for i in project.frames.size(): for i in project.frames.size():
for j in project.layers.size(): for j in project.layers.size():
@ -992,7 +991,6 @@ func undo_or_redo(
if action_name == "Scale": if action_name == "Scale":
cel.size_changed(project.size) cel.size_changed(project.size)
canvas.update_texture(j, i, project, undo) canvas.update_texture(j, i, project, undo)
cel.on_undo_redo(undo)
canvas.selection.queue_redraw() canvas.selection.queue_redraw()
if action_name == "Scale": if action_name == "Scale":

View file

@ -269,7 +269,7 @@ func open_pxo_file(path: String, is_backup := false, replace_empty := true) -> v
var image := Image.create_from_data( var image := Image.create_from_data(
tile_size.x, tile_size.y, false, new_project.get_image_format(), image_data tile_size.x, tile_size.y, false, new_project.get_image_format(), image_data
) )
tileset.add_tile(image, null, 2) tileset.add_tile(image, null)
zip_reader.close() zip_reader.close()
new_project.export_directory_path = path.get_base_dir() new_project.export_directory_path = path.get_base_dir()
@ -844,14 +844,14 @@ func open_image_as_tileset(
var frame_width := image.get_size().x / horiz var frame_width := image.get_size().x / horiz
var frame_height := image.get_size().y / vert var frame_height := image.get_size().y / vert
var tile_size := Vector2i(frame_width, frame_height) var tile_size := Vector2i(frame_width, frame_height)
var tileset := TileSetCustom.new(tile_size, project, path.get_basename().get_file()) var tileset := TileSetCustom.new(tile_size, path.get_basename().get_file())
for yy in range(vert): for yy in range(vert):
for xx in range(horiz): for xx in range(horiz):
var cropped_image := image.get_region( var cropped_image := image.get_region(
Rect2i(frame_width * xx, frame_height * yy, frame_width, frame_height) Rect2i(frame_width * xx, frame_height * yy, frame_width, frame_height)
) )
@warning_ignore("int_as_enum_without_cast") @warning_ignore("int_as_enum_without_cast")
tileset.add_tile(cropped_image, null, 2) tileset.add_tile(cropped_image, null)
project.tilesets.append(tileset) project.tilesets.append(tileset)
@ -866,7 +866,7 @@ func open_image_as_tileset_smart(
if sliced_rects.size() == 0: # Image is empty sprite (manually set data to be consistent) if sliced_rects.size() == 0: # Image is empty sprite (manually set data to be consistent)
tile_size = image.get_size() tile_size = image.get_size()
sliced_rects.append(Rect2i(Vector2i.ZERO, tile_size)) sliced_rects.append(Rect2i(Vector2i.ZERO, tile_size))
var tileset := TileSetCustom.new(tile_size, project, path.get_basename().get_file()) var tileset := TileSetCustom.new(tile_size, path.get_basename().get_file())
for rect in sliced_rects: for rect in sliced_rects:
var offset: Vector2 = (0.5 * (tile_size - rect.size)).floor() var offset: Vector2 = (0.5 * (tile_size - rect.size)).floor()
var cropped_image := Image.create( var cropped_image := Image.create(
@ -874,7 +874,7 @@ func open_image_as_tileset_smart(
) )
cropped_image.blit_rect(image, rect, offset) cropped_image.blit_rect(image, rect, offset)
@warning_ignore("int_as_enum_without_cast") @warning_ignore("int_as_enum_without_cast")
tileset.add_tile(cropped_image, null, 2) tileset.add_tile(cropped_image, null)
project.tilesets.append(tileset) project.tilesets.append(tileset)

View file

@ -77,10 +77,6 @@ func update_texture(_undo := false) -> void:
cel.texture_changed.emit() cel.texture_changed.emit()
func on_undo_redo(_undo: bool) -> void:
pass
## Returns a curated [Dictionary] containing the cel data. ## Returns a curated [Dictionary] containing the cel data.
func serialize() -> Dictionary: func serialize() -> Dictionary:
var dict := {"opacity": opacity, "z_index": z_index} var dict := {"opacity": opacity, "z_index": z_index}

View file

@ -29,9 +29,6 @@ var cells: Array[Cell]
var horizontal_cells: int var horizontal_cells: int
## The amount of vertical cells. ## The amount of vertical cells.
var vertical_cells: int var vertical_cells: int
## Dictionary of [int] and an [Array] of [bool] ([member TileSetPanel.placing_tiles])
## and [enum TileSetPanel.TileEditingMode].
var undo_redo_modes := {}
## Dictionary of [int] and [Array]. ## Dictionary of [int] and [Array].
## The key is the index of the tile in the tileset, ## The key is the index of the tile in the tileset,
## and the value is the index of the tilemap tile that changed first, along with ## and the value is the index of the tilemap tile that changed first, along with
@ -147,14 +144,9 @@ func transform_tile(
return transformed_tile return transformed_tile
func update_tileset(undo: bool) -> void: func update_tileset() -> void:
editing_images.clear() editing_images.clear()
var undos := tileset.project.undos var tile_editing_mode := TileSetPanel.tile_editing_mode
if not undo and not _is_redo():
undo_redo_modes[undos] = [TileSetPanel.placing_tiles, TileSetPanel.tile_editing_mode]
if undo:
undos += 1
var tile_editing_mode := _get_tile_editing_mode(undos)
for i in cells.size(): for i in cells.size():
var coords := get_cell_coords_in_image(i) var coords := get_cell_coords_in_image(i)
var rect := Rect2i(coords, tileset.tile_size) var rect := Rect2i(coords, tileset.tile_size)
@ -171,7 +163,7 @@ func update_tileset(undo: bool) -> void:
if index == 0: if index == 0:
# If the tileset is empty, only then add a new tile. # If the tileset is empty, only then add a new tile.
if tileset.tiles.size() <= 1: if tileset.tiles.size() <= 1:
tileset.add_tile(image_portion, self, tile_editing_mode) tileset.add_tile(image_portion, self)
cells[i].index = tileset.tiles.size() - 1 cells[i].index = tileset.tiles.size() - 1
continue continue
if not tiles_equal(i, image_portion, current_tile.image): if not tiles_equal(i, image_portion, current_tile.image):
@ -189,12 +181,8 @@ func update_tileset(undo: bool) -> void:
found_tile = true found_tile = true
break break
if not found_tile: if not found_tile:
tileset.add_tile(image_portion, self, tile_editing_mode) tileset.add_tile(image_portion, self)
cells[i].index = tileset.tiles.size() - 1 cells[i].index = tileset.tiles.size() - 1
if undo:
var tile_removed := tileset.remove_unused_tiles(self)
if tile_removed:
_re_index_all_cells()
## Cases:[br] ## Cases:[br]
@ -256,7 +244,7 @@ func _handle_auto_editing_mode(i: int, image_portion: Image) -> void:
else: else:
# Case 2: The cell is not mapped already, # Case 2: The cell is not mapped already,
# and it does not exist in the tileset. # and it does not exist in the tileset.
tileset.add_tile(image_portion, self, TileSetPanel.TileEditingMode.AUTO) tileset.add_tile(image_portion, self)
cells[i].index = tileset.tiles.size() - 1 cells[i].index = tileset.tiles.size() - 1
else: # If the cell is already mapped. else: # If the cell is already mapped.
if tiles_equal(i, image_portion, current_tile.image): if tiles_equal(i, image_portion, current_tile.image):
@ -284,7 +272,7 @@ func _handle_auto_editing_mode(i: int, image_portion: Image) -> void:
# exist in the tileset as a tile, # exist in the tileset as a tile,
# and the currently mapped tile still exists in the tileset. # and the currently mapped tile still exists in the tileset.
tileset.unuse_tile_at_index(index, self) tileset.unuse_tile_at_index(index, self)
tileset.add_tile(image_portion, self, TileSetPanel.TileEditingMode.AUTO) tileset.add_tile(image_portion, self)
cells[i].index = tileset.tiles.size() - 1 cells[i].index = tileset.tiles.size() - 1
else: else:
# Case 7: The cell is mapped and it does not # Case 7: The cell is mapped and it does not
@ -349,15 +337,6 @@ func _is_redo() -> bool:
return Global.control.redone return Global.control.redone
func _get_tile_editing_mode(undos: int) -> TileSetPanel.TileEditingMode:
var tile_editing_mode: TileSetPanel.TileEditingMode
if undo_redo_modes.has(undos):
tile_editing_mode = undo_redo_modes[undos][1]
else:
tile_editing_mode = TileSetPanel.tile_editing_mode
return tile_editing_mode
## If the tileset has been modified by another tile, make sure to also update it here. ## If the tileset has been modified by another tile, make sure to also update it here.
func _on_tileset_updated(cel: CelTileMap) -> void: func _on_tileset_updated(cel: CelTileMap) -> void:
if cel == self or not is_instance_valid(cel): if cel == self or not is_instance_valid(cel):
@ -415,36 +394,29 @@ func size_changed(new_size: Vector2i) -> void:
_re_index_all_cells() _re_index_all_cells()
func on_undo_redo(undo: bool) -> void:
var undos := tileset.project.undos
if undo:
undos += 1
if (undo or _is_redo()) and undo_redo_modes.has(undos):
var placing_tiles: bool = undo_redo_modes[undos][0]
if placing_tiles:
_re_index_all_cells()
return
update_tileset(undo)
func serialize_undo_data() -> Dictionary: func serialize_undo_data() -> Dictionary:
var dict := {} var dict := {}
var cells_serialized := [] var cell_indices := []
cells_serialized.resize(cells.size()) cell_indices.resize(cells.size())
for i in cells.size(): for i in cell_indices.size():
cells_serialized[i] = cells[i].serialize() cell_indices[i] = cells[i].serialize()
dict["cells_data"] = cells_serialized dict["cell_indices"] = cell_indices
dict["tileset"] = tileset.serialize_undo_data()
return dict return dict
func deserialize_undo_data(dict: Dictionary, undo_redo: UndoRedo, undo: bool) -> void: func deserialize_undo_data(dict: Dictionary, undo_redo: UndoRedo, undo: bool) -> void:
var cells_data = dict["cells_data"] var cell_indices = dict["cell_indices"]
for i in cells_data.size(): if undo:
var cell_data: Dictionary = cells_data[i] for i in cell_indices.size():
if undo: var cell_data: Dictionary = cell_indices[i]
undo_redo.add_undo_method(cells[i].deserialize.bind(cell_data)) undo_redo.add_undo_method(cells[i].deserialize.bind(cell_data))
else: undo_redo.add_undo_method(tileset.deserialize_undo_data.bind(dict.get("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(cells[i].deserialize.bind(cell_data))
undo_redo.add_do_method(tileset.deserialize_undo_data.bind(dict.get("tileset"), self))
func serialize() -> Dictionary: func serialize() -> Dictionary:

View file

@ -352,7 +352,7 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces
if dict.has("tilesets"): if dict.has("tilesets"):
for saved_tileset in dict["tilesets"]: for saved_tileset in dict["tilesets"]:
var tile_size = str_to_var("Vector2i" + saved_tileset.get("tile_size")) var tile_size = str_to_var("Vector2i" + saved_tileset.get("tile_size"))
var tileset := TileSetCustom.new(tile_size, self) var tileset := TileSetCustom.new(tile_size)
tileset.deserialize(saved_tileset) tileset.deserialize(saved_tileset)
tilesets.append(tileset) tilesets.append(tileset)
if dict.has("frames") and dict.has("layers"): if dict.has("frames") and dict.has("layers"):

View file

@ -10,8 +10,6 @@ extends RefCounted
## The [CelTileMap] that the changes are coming from is referenced in the [param cel] parameter. ## The [CelTileMap] that the changes are coming from is referenced in the [param cel] parameter.
signal updated(cel: CelTileMap) signal updated(cel: CelTileMap)
## The [Project] the tileset is being used by.
var project: Project
## The tileset's name. ## The tileset's name.
var name := "" var name := ""
## The size of each individual tile. ## The size of each individual tile.
@ -24,45 +22,30 @@ var tiles: Array[Tile] = []
class Tile: class Tile:
## The [Image] tile itself. ## The [Image] tile itself.
var image: Image var image: Image
## The mode that was used when this tile was added to the tileset.
var mode_added: TileSetPanel.TileEditingMode
## The amount of tiles this tile is being used in tilemaps. ## The amount of tiles this tile is being used in tilemaps.
var times_used := 1 var times_used := 1
## The step number of undo/redo when this tile was added to the tileset.
var undo_step_added := 0
func _init( func _init(_image: Image) -> void:
_image: Image, _mode_added: TileSetPanel.TileEditingMode, _undo_step_added := 0
) -> void:
image = _image image = _image
mode_added = _mode_added
undo_step_added = _undo_step_added
## A method that checks if the tile should be removed from the tileset. ## A method that checks if the tile should be removed from the tileset.
## Returns [code]true[/code] if the current undo step is less than [member undo_step_added], ## Returns [code]true[/code] if the amount of [member times_used] is 0.
## which essentially means that the tile always gets removed if the user undos to the point func can_be_removed() -> bool:
## the tile was added to the tileset. return times_used <= 0
## Otherwise, it returns [code]true[/code] if [member mode_added] is not equal to
## [enum TileSetPanel.TileEditingMode.STACK] and the amount of [member times_used] is 0.
func can_be_removed(project: Project) -> bool:
if project.undos < undo_step_added:
return true
return mode_added != TileSetPanel.TileEditingMode.STACK and times_used <= 0
func _init(_tile_size: Vector2i, _project: Project, _name := "") -> void: func _init(_tile_size: Vector2i, _name := "") -> void:
tile_size = _tile_size tile_size = _tile_size
project = _project
name = _name name = _name
var empty_image := Image.create_empty(tile_size.x, tile_size.y, false, Image.FORMAT_RGBA8) var empty_image := Image.create_empty(tile_size.x, tile_size.y, false, Image.FORMAT_RGBA8)
tiles.append(Tile.new(empty_image, TileSetPanel.tile_editing_mode)) tiles.append(Tile.new(empty_image))
## Adds a new [param image] as a tile to the tileset. ## Adds a new [param image] as a tile to the tileset.
## The [param cel] parameter references the [CelTileMap] that this change is coming from, ## The [param cel] parameter references the [CelTileMap] that this change is coming from,
## and the [param edit_mode] parameter contains the tile editing mode at the time of this change. ## and the [param edit_mode] parameter contains the tile editing mode at the time of this change.
func add_tile(image: Image, cel: CelTileMap, edit_mode: TileSetPanel.TileEditingMode) -> void: func add_tile(image: Image, cel: CelTileMap) -> void:
var tile := Tile.new(image, edit_mode, project.undos) var tile := Tile.new(image)
tiles.append(tile) tiles.append(tile)
updated.emit(cel) updated.emit(cel)
@ -70,10 +53,8 @@ func add_tile(image: Image, cel: CelTileMap, edit_mode: TileSetPanel.TileEditing
## Adds a new [param image] as a tile in a given [param position] in the tileset. ## Adds a new [param image] as a tile in a given [param position] in the tileset.
## The [param cel] parameter references the [CelTileMap] that this change is coming from, ## The [param cel] parameter references the [CelTileMap] that this change is coming from,
## and the [param edit_mode] parameter contains the tile editing mode at the time of this change. ## and the [param edit_mode] parameter contains the tile editing mode at the time of this change.
func insert_tile( func insert_tile(image: Image, position: int, cel: CelTileMap) -> void:
image: Image, position: int, cel: CelTileMap, edit_mode: TileSetPanel.TileEditingMode var tile := Tile.new(image)
) -> void:
var tile := Tile.new(image, edit_mode, project.undos)
tiles.insert(position, tile) tiles.insert(position, tile)
updated.emit(cel) updated.emit(cel)
@ -86,7 +67,7 @@ func insert_tile(
## The [param cel] parameter references the [CelTileMap] that this change is coming from. ## The [param cel] parameter references the [CelTileMap] that this change is coming from.
func unuse_tile_at_index(index: int, cel: CelTileMap) -> bool: func unuse_tile_at_index(index: int, cel: CelTileMap) -> bool:
tiles[index].times_used -= 1 tiles[index].times_used -= 1
if tiles[index].can_be_removed(project): if tiles[index].can_be_removed():
remove_tile_at_index(index, cel) remove_tile_at_index(index, cel)
return true return true
return false return false
@ -122,7 +103,7 @@ func remove_unused_tiles(cel: CelTileMap) -> bool:
var tile_removed := false var tile_removed := false
for i in range(tiles.size() - 1, 0, -1): for i in range(tiles.size() - 1, 0, -1):
var tile := tiles[i] var tile := tiles[i]
if tile.can_be_removed(project): if tile.can_be_removed():
remove_tile_at_index(i, cel) remove_tile_at_index(i, cel)
tile_removed = true tile_removed = true
return tile_removed return tile_removed
@ -139,3 +120,25 @@ func serialize() -> Dictionary:
func deserialize(dict: Dictionary) -> void: func deserialize(dict: Dictionary) -> void:
name = dict.get("name", name) name = dict.get("name", name)
tile_size = str_to_var("Vector2i" + dict.get("tile_size")) tile_size = str_to_var("Vector2i" + dict.get("tile_size"))
func serialize_undo_data() -> Dictionary:
var dict := {}
for tile in tiles:
var image_data := tile.image.get_data()
dict[tile.image] = [image_data.compress(), image_data.size(), tile.times_used]
return dict
func deserialize_undo_data(dict: Dictionary, cel: CelTileMap) -> void:
tiles.resize(dict.size())
var i := 0
for image: Image in dict:
var tile_data = dict[image]
var buffer_size := tile_data[1] as int
var image_data := (tile_data[0] as PackedByteArray).decompress(buffer_size)
image.set_data(tile_size.x, tile_size.y, false, image.get_format(), image_data)
tiles[i] = Tile.new(image)
tiles[i].times_used = tile_data[2]
i += 1
updated.emit(cel)

View file

@ -272,6 +272,9 @@ func prepare_undo(action: String) -> void:
func commit_undo() -> void: func commit_undo() -> void:
for cel in _undo_data:
if cel is CelTileMap:
(cel as CelTileMap).update_tileset()
var redo_data := _get_undo_data() var redo_data := _get_undo_data()
var project := Global.current_project var project := Global.current_project
var frame := -1 var frame := -1

View file

@ -14,7 +14,7 @@ func _on_confirmed() -> void:
var tile_size := tile_size_slider.value var tile_size := tile_size_slider.value
var tileset: TileSetCustom var tileset: TileSetCustom
if tileset_option_button.selected == 0: if tileset_option_button.selected == 0:
tileset = TileSetCustom.new(tile_size, project, tileset_name) tileset = TileSetCustom.new(tile_size, tileset_name)
else: else:
tileset = project.tilesets[tileset_option_button.selected - 1] tileset = project.tilesets[tileset_option_button.selected - 1]
var layer := LayerTileMap.new(project, tileset, layer_name) var layer := LayerTileMap.new(project, tileset, layer_name)