1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-19 01:29:49 +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":
cel.size_changed(project.size)
canvas.update_texture(layer_index, frame_index, project, undo)
cel.on_undo_redo(undo)
else:
for i in project.frames.size():
for j in project.layers.size():
@ -992,7 +991,6 @@ func undo_or_redo(
if action_name == "Scale":
cel.size_changed(project.size)
canvas.update_texture(j, i, project, undo)
cel.on_undo_redo(undo)
canvas.selection.queue_redraw()
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(
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()
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_height := image.get_size().y / vert
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 xx in range(horiz):
var cropped_image := image.get_region(
Rect2i(frame_width * xx, frame_height * yy, frame_width, frame_height)
)
@warning_ignore("int_as_enum_without_cast")
tileset.add_tile(cropped_image, null, 2)
tileset.add_tile(cropped_image, null)
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)
tile_size = image.get_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:
var offset: Vector2 = (0.5 * (tile_size - rect.size)).floor()
var cropped_image := Image.create(
@ -874,7 +874,7 @@ func open_image_as_tileset_smart(
)
cropped_image.blit_rect(image, rect, offset)
@warning_ignore("int_as_enum_without_cast")
tileset.add_tile(cropped_image, null, 2)
tileset.add_tile(cropped_image, null)
project.tilesets.append(tileset)

View file

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

View file

@ -29,9 +29,6 @@ var cells: Array[Cell]
var horizontal_cells: int
## The amount of vertical cells.
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].
## 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
@ -147,14 +144,9 @@ func transform_tile(
return transformed_tile
func update_tileset(undo: bool) -> void:
func update_tileset() -> void:
editing_images.clear()
var undos := tileset.project.undos
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)
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)
@ -171,7 +163,7 @@ func update_tileset(undo: bool) -> void:
if index == 0:
# If the tileset is empty, only then add a new tile.
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
continue
if not tiles_equal(i, image_portion, current_tile.image):
@ -189,12 +181,8 @@ func update_tileset(undo: bool) -> void:
found_tile = true
break
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
if undo:
var tile_removed := tileset.remove_unused_tiles(self)
if tile_removed:
_re_index_all_cells()
## Cases:[br]
@ -256,7 +244,7 @@ func _handle_auto_editing_mode(i: int, image_portion: Image) -> void:
else:
# Case 2: The cell is not mapped already,
# 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
else: # If the cell is already mapped.
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,
# and the currently mapped tile still exists in the tileset.
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
else:
# Case 7: The cell is mapped and it does not
@ -349,15 +337,6 @@ func _is_redo() -> bool:
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.
func _on_tileset_updated(cel: CelTileMap) -> void:
if cel == self or not is_instance_valid(cel):
@ -415,36 +394,29 @@ func size_changed(new_size: Vector2i) -> void:
_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:
var dict := {}
var cells_serialized := []
cells_serialized.resize(cells.size())
for i in cells.size():
cells_serialized[i] = cells[i].serialize()
dict["cells_data"] = cells_serialized
var cell_indices := []
cell_indices.resize(cells.size())
for i in cell_indices.size():
cell_indices[i] = cells[i].serialize()
dict["cell_indices"] = cell_indices
dict["tileset"] = tileset.serialize_undo_data()
return dict
func deserialize_undo_data(dict: Dictionary, undo_redo: UndoRedo, undo: bool) -> void:
var cells_data = dict["cells_data"]
for i in cells_data.size():
var cell_data: Dictionary = cells_data[i]
if undo:
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))
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(tileset.deserialize_undo_data.bind(dict.get("tileset"), self))
func serialize() -> Dictionary:

View file

@ -352,7 +352,7 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces
if dict.has("tilesets"):
for saved_tileset in dict["tilesets"]:
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)
tilesets.append(tileset)
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.
signal updated(cel: CelTileMap)
## The [Project] the tileset is being used by.
var project: Project
## The tileset's name.
var name := ""
## The size of each individual tile.
@ -24,45 +22,30 @@ var tiles: Array[Tile] = []
class Tile:
## The [Image] tile itself.
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.
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(
_image: Image, _mode_added: TileSetPanel.TileEditingMode, _undo_step_added := 0
) -> void:
func _init(_image: Image) -> void:
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.
## Returns [code]true[/code] if the current undo step is less than [member undo_step_added],
## which essentially means that the tile always gets removed if the user undos to the point
## the tile was added to the tileset.
## 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
## Returns [code]true[/code] if the amount of [member times_used] is 0.
func can_be_removed() -> bool:
return times_used <= 0
func _init(_tile_size: Vector2i, _project: Project, _name := "") -> void:
func _init(_tile_size: Vector2i, _name := "") -> void:
tile_size = _tile_size
project = _project
name = _name
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.
## 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.
func add_tile(image: Image, cel: CelTileMap, edit_mode: TileSetPanel.TileEditingMode) -> void:
var tile := Tile.new(image, edit_mode, project.undos)
func add_tile(image: Image, cel: CelTileMap) -> void:
var tile := Tile.new(image)
tiles.append(tile)
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.
## 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.
func insert_tile(
image: Image, position: int, cel: CelTileMap, edit_mode: TileSetPanel.TileEditingMode
) -> void:
var tile := Tile.new(image, edit_mode, project.undos)
func insert_tile(image: Image, position: int, cel: CelTileMap) -> void:
var tile := Tile.new(image)
tiles.insert(position, tile)
updated.emit(cel)
@ -86,7 +67,7 @@ func insert_tile(
## The [param cel] parameter references the [CelTileMap] that this change is coming from.
func unuse_tile_at_index(index: int, cel: CelTileMap) -> bool:
tiles[index].times_used -= 1
if tiles[index].can_be_removed(project):
if tiles[index].can_be_removed():
remove_tile_at_index(index, cel)
return true
return false
@ -122,7 +103,7 @@ func remove_unused_tiles(cel: CelTileMap) -> bool:
var tile_removed := false
for i in range(tiles.size() - 1, 0, -1):
var tile := tiles[i]
if tile.can_be_removed(project):
if tile.can_be_removed():
remove_tile_at_index(i, cel)
tile_removed = true
return tile_removed
@ -139,3 +120,25 @@ func serialize() -> Dictionary:
func deserialize(dict: Dictionary) -> void:
name = dict.get("name", name)
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:
for cel in _undo_data:
if cel is CelTileMap:
(cel as CelTileMap).update_tileset()
var redo_data := _get_undo_data()
var project := Global.current_project
var frame := -1

View file

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