diff --git a/src/Classes/Cels/CelTileMap.gd b/src/Classes/Cels/CelTileMap.gd index fd266828b49..9fbb369bb7d 100644 --- a/src/Classes/Cels/CelTileMap.gd +++ b/src/Classes/Cels/CelTileMap.gd @@ -11,16 +11,7 @@ extends PixelCel ## such as horizontal flipping, vertical flipping, or if it's transposed. ## The [TileSetCustom] that this cel uses, passed down from the cel's [LayerTileMap]. -var tileset: TileSetCustom: - set(value): - if is_instance_valid(tileset): - if tileset.updated.is_connected(_on_tileset_updated): - tileset.updated.disconnect(_on_tileset_updated) - tileset = value - if is_instance_valid(tileset): - _resize_cells(get_image().get_size()) - if not tileset.updated.is_connected(_on_tileset_updated): - tileset.updated.connect(_on_tileset_updated) +var tileset: TileSetCustom ## The [Array] of type [CelTileMap.Cell] that contains data for each cell of the tilemap. ## The array's size is equal to [member horizontal_cells] * [member vertical_cells]. @@ -76,7 +67,20 @@ class Cell: func _init(_tileset: TileSetCustom, _image := ImageExtended.new(), _opacity := 1.0) -> void: super._init(_image, _opacity) - tileset = _tileset + set_tileset(_tileset) + + +func set_tileset(new_tileset: TileSetCustom, reset_indices := true) -> void: + if tileset == new_tileset: + return + if is_instance_valid(tileset): + if tileset.updated.is_connected(_on_tileset_updated): + tileset.updated.disconnect(_on_tileset_updated) + tileset = new_tileset + if is_instance_valid(tileset): + _resize_cells(get_image().get_size(), reset_indices) + if not tileset.updated.is_connected(_on_tileset_updated): + tileset.updated.connect(_on_tileset_updated) ## Maps the cell at position [param cell_position] to @@ -84,8 +88,9 @@ func _init(_tileset: TileSetCustom, _image := ImageExtended.new(), _opacity := 1 func set_index(cell_position: int, index: int) -> void: index = clampi(index, 0, tileset.tiles.size() - 1) var previous_index := cells[cell_position].index + if previous_index != index: - if previous_index > 0: + if previous_index > 0 and previous_index < tileset.tiles.size(): tileset.tiles[previous_index].times_used -= 1 tileset.tiles[index].times_used += 1 cells[cell_position].index = index @@ -215,15 +220,14 @@ func update_tilemap( tile_editing_mode := TileSetPanel.tile_editing_mode, source_image := image ) -> void: editing_images.clear() + var tileset_size_before_update := tileset.tiles.size() for i in cells.size(): var coords := get_cell_coords_in_image(i) var rect := Rect2i(coords, tileset.tile_size) 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!") index = 0 - cells[i].index = 0 var current_tile := tileset.tiles[index] if tile_editing_mode == TileSetPanel.TileEditingMode.MANUAL: if image_portion.is_invisible(): @@ -237,7 +241,7 @@ func update_tilemap( if not tiles_equal(i, image_portion, current_tile.image): tileset.replace_tile_at(image_portion, index, self) elif tile_editing_mode == TileSetPanel.TileEditingMode.AUTO: - _handle_auto_editing_mode(i, image_portion) + _handle_auto_editing_mode(i, image_portion, tileset_size_before_update) else: # Stack if image_portion.is_invisible(): continue @@ -254,6 +258,23 @@ func update_tilemap( tileset.add_tile(image_portion, self) cells[i].index = tileset.tiles.size() - 1 cells[i].remove_transformations() + # Updates transparent cells that have indices higher than 0. + # This can happen when switching to another tileset which has less tiles + # than the previous one. + for i in cells.size(): + var coords := get_cell_coords_in_image(i) + var rect := Rect2i(coords, tileset.tile_size) + var image_portion := source_image.get_region(rect) + if not image_portion.is_invisible(): + continue + var index := cells[i].index + if index == 0: + continue + if index >= tileset.tiles.size(): + index = 0 + var current_tile := tileset.tiles[index] + if not tiles_equal(i, image_portion, current_tile.image): + set_index(i, cells[i].index) ## Gets called by [method update_tilemap]. This method is responsible for handling @@ -294,11 +315,17 @@ func update_tilemap( ## 7) Cell mapped, does not exist in the tileset. ## The mapped tile does not exist in the tileset anymore. ## Simply replace the old tile with the new one, do not change its index. -func _handle_auto_editing_mode(i: int, image_portion: Image) -> void: +func _handle_auto_editing_mode( + i: int, image_portion: Image, tileset_size_before_update: int +) -> void: var index := cells[i].index + if index >= tileset.tiles.size(): + index = 0 var current_tile := tileset.tiles[index] if image_portion.is_invisible(): # Case 0: The cell is transparent. + if cells[i].index >= tileset_size_before_update: + return cells[i].index = 0 cells[i].remove_transformations() if index > 0: @@ -374,10 +401,7 @@ func _update_cell(cell_position: int) -> void: var cell_data := cells[cell_position] var index := cell_data.index if index >= tileset.tiles.size(): - printerr( - "Cell at position ", cell_position + 1, ", mapped to ", index, " is out of bounds!" - ) - return + index = 0 var current_tile := tileset.tiles[index].image var transformed_tile := transform_tile( current_tile, cell_data.flip_h, cell_data.flip_v, cell_data.transpose @@ -389,7 +413,7 @@ func _update_cell(cell_position: int) -> void: ## Calls [method _update_cell] for all [member cells]. -func _update_cel_portions() -> void: +func update_cel_portions() -> void: for i in cells.size(): _update_cell(i) @@ -402,7 +426,11 @@ func _re_index_all_cells() -> void: var rect := Rect2i(coords, tileset.tile_size) var image_portion := image.get_region(rect) if image_portion.is_invisible(): - cells[i].index = 0 + var index := cells[i].index + if index > 0 and index < tileset.tiles.size(): + var current_tile := tileset.tiles[index] + if not tiles_equal(i, image_portion, current_tile.image): + set_index(i, cells[i].index) continue for j in range(1, tileset.tiles.size()): var tile := tileset.tiles[j] @@ -412,12 +440,16 @@ func _re_index_all_cells() -> void: ## Resizes the [member cells] array based on [param new_size]. -func _resize_cells(new_size: Vector2i) -> void: +func _resize_cells(new_size: Vector2i, reset_indices := true) -> void: horizontal_cells = ceili(float(new_size.x) / tileset.tile_size.x) vertical_cells = ceili(float(new_size.y) / tileset.tile_size.y) cells.resize(horizontal_cells * vertical_cells) for i in cells.size(): - cells[i] = Cell.new() + if reset_indices: + cells[i] = Cell.new() + else: + if not is_instance_valid(cells[i]): + cells[i] = Cell.new() ## Returns [code]true[/code] if the user just did a Redo. @@ -429,7 +461,7 @@ func _is_redo() -> bool: ## make sure to also update it here. ## If [param replace_index] is larger than -1, it means that manual mode ## has been used to replace a tile in the tileset in another cel, -## so call [method _update_cel_portions] to update it in this cel as well. +## so call [method update_cel_portions] to update it in this cel as well. ## Otherwise, call [method _re_index_all_cells] to ensure that the cells have correct indices. func _on_tileset_updated(cel: CelTileMap, replace_index: int) -> void: if cel == self or not is_instance_valid(cel): @@ -437,7 +469,7 @@ func _on_tileset_updated(cel: CelTileMap, replace_index: int) -> void: if link_set != null and cel in link_set["cels"]: return if replace_index > -1: # Manual mode - _update_cel_portions() + update_cel_portions() else: _re_index_all_cells() Global.canvas.update_all_layers = true @@ -469,6 +501,8 @@ func update_texture(undo := false) -> void: for i in cells.size(): var cell_data := cells[i] var index := cell_data.index + if index >= tileset.tiles.size(): + index = 0 var coords := get_cell_coords_in_image(i) var rect := Rect2i(coords, tileset.tile_size) var image_portion := image.get_region(rect) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 7b9fd9ef4e0..38788311383 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -662,10 +662,7 @@ func get_all_pixel_cels() -> Array[PixelCel]: ## and calls [method CelTileMap.serialize_undo_data] for [CelTileMap]s. func serialize_cel_undo_data(cels: Array[BaseCel], data: Dictionary) -> void: var cels_to_serialize := cels - if ( - TileSetPanel.tile_editing_mode == TileSetPanel.TileEditingMode.MANUAL - and not TileSetPanel.placing_tiles - ): + if not TileSetPanel.placing_tiles: cels_to_serialize = find_same_tileset_tilemap_cels(cels) for cel in cels_to_serialize: if not cel is PixelCel: diff --git a/src/UI/Timeline/LayerProperties.gd b/src/UI/Timeline/LayerProperties.gd index f8753ac850d..fd4dfd22957 100644 --- a/src/UI/Timeline/LayerProperties.gd +++ b/src/UI/Timeline/LayerProperties.gd @@ -8,13 +8,15 @@ var layer_indices: PackedInt32Array @onready var opacity_slider := $GridContainer/OpacitySlider as ValueSlider @onready var blend_modes_button := $GridContainer/BlendModeOptionButton as OptionButton @onready var user_data_text_edit := $GridContainer/UserDataTextEdit as TextEdit +@onready var tileset_option_button := $GridContainer/TilesetOptionButton as OptionButton func _on_visibility_changed() -> void: if layer_indices.size() == 0: return Global.dialog_open(visible) - var first_layer := Global.current_project.layers[layer_indices[0]] + var project := Global.current_project + var first_layer := project.layers[layer_indices[0]] if visible: _fill_blend_modes_option_button() name_line_edit.text = first_layer.name @@ -22,6 +24,14 @@ func _on_visibility_changed() -> void: var blend_mode_index := blend_modes_button.get_item_index(first_layer.blend_mode) blend_modes_button.selected = blend_mode_index user_data_text_edit.text = first_layer.user_data + get_tree().set_group(&"TilemapLayers", "visible", first_layer is LayerTileMap) + tileset_option_button.clear() + if first_layer is LayerTileMap: + for i in project.tilesets.size(): + var tileset := project.tilesets[i] + tileset_option_button.add_item(tileset.get_text_info(i)) + if tileset == first_layer.tileset: + tileset_option_button.select(i) else: layer_indices = [] @@ -86,6 +96,7 @@ func _on_blend_mode_option_button_item_selected(index: BaseLayer.BlendModes) -> Global.canvas.update_all_layers = true var project := Global.current_project var current_mode := blend_modes_button.get_item_id(index) + project.undos += 1 project.undo_redo.create_action("Set Blend Mode") for layer_index in layer_indices: var layer := project.layers[layer_index] @@ -109,3 +120,32 @@ func _on_user_data_text_edit_text_changed() -> void: func _emit_layer_property_signal() -> void: layer_property_changed.emit() + + +func _on_tileset_option_button_item_selected(index: int) -> void: + var project := Global.current_project + var new_tileset := project.tilesets[index] + project.undos += 1 + project.undo_redo.create_action("Set Tileset") + for layer_index in layer_indices: + var layer := project.layers[layer_index] + if layer is not LayerTileMap: + continue + var previous_tileset := (layer as LayerTileMap).tileset + project.undo_redo.add_do_property(layer, "tileset", new_tileset) + project.undo_redo.add_undo_property(layer, "tileset", previous_tileset) + for frame in project.frames: + for i in frame.cels.size(): + var cel := frame.cels[i] + if cel is CelTileMap and i == layer_index: + project.undo_redo.add_do_method(cel.set_tileset.bind(new_tileset, false)) + project.undo_redo.add_do_method(cel.update_cel_portions) + project.undo_redo.add_undo_method(cel.set_tileset.bind(previous_tileset, false)) + project.undo_redo.add_undo_method(cel.update_cel_portions) + project.undo_redo.add_do_method(Global.undo_or_redo.bind(false)) + project.undo_redo.add_do_method(Global.canvas.draw_layers) + project.undo_redo.add_do_method(func(): Global.cel_switched.emit()) + project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true)) + project.undo_redo.add_undo_method(Global.canvas.draw_layers) + project.undo_redo.add_undo_method(func(): Global.cel_switched.emit()) + project.undo_redo.commit_action() diff --git a/src/UI/Timeline/LayerProperties.tscn b/src/UI/Timeline/LayerProperties.tscn index 7979e7eb4fb..74ac0b68264 100644 --- a/src/UI/Timeline/LayerProperties.tscn +++ b/src/UI/Timeline/LayerProperties.tscn @@ -5,11 +5,14 @@ [node name="LayerProperties" type="AcceptDialog"] title = "Layer properties" +size = Vector2i(300, 208) script = ExtResource("1_54q1t") [node name="GridContainer" type="GridContainer" parent="."] -offset_right = 40.0 -offset_bottom = 40.0 +offset_left = 8.0 +offset_top = 8.0 +offset_right = 292.0 +offset_bottom = 159.0 columns = 2 [node name="NameLabel" type="Label" parent="GridContainer"] @@ -60,8 +63,19 @@ layout_mode = 2 size_flags_horizontal = 3 scroll_fit_content_height = true +[node name="TilesetLabel" type="Label" parent="GridContainer" groups=["TilemapLayers"]] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 0 +text = "Tileset:" + +[node name="TilesetOptionButton" type="OptionButton" parent="GridContainer" groups=["TilemapLayers"]] +layout_mode = 2 +mouse_default_cursor_shape = 2 + [connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] [connection signal="text_changed" from="GridContainer/NameLineEdit" to="." method="_on_name_line_edit_text_changed"] [connection signal="value_changed" from="GridContainer/OpacitySlider" to="." method="_on_opacity_slider_value_changed"] [connection signal="item_selected" from="GridContainer/BlendModeOptionButton" to="." method="_on_blend_mode_option_button_item_selected"] [connection signal="text_changed" from="GridContainer/UserDataTextEdit" to="." method="_on_user_data_text_edit_text_changed"] +[connection signal="item_selected" from="GridContainer/TilesetOptionButton" to="." method="_on_tileset_option_button_item_selected"]