1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-31 15:39:49 +00:00
Pixelorama/src/Classes/Project.gd
Emmanouil Papadeas 8ab4d05327 Don't save the project's name inside the pxo
Instead, get it from the file's name
2023-07-08 17:28:44 +03:00

878 lines
30 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# gdlint: ignore=max-public-methods
class_name Project
extends Reference
# A class for project properties.
var name := "" setget _name_changed
var size: Vector2 setget _size_changed
var undo_redo := UndoRedo.new()
var tiles: Tiles
var undos := 0 # The number of times we added undo properties
var can_undo = true
var fill_color := Color(0)
var has_changed := false setget _has_changed_changed
# frames and layers Arrays should generally only be modified directly when
# opening/creating a project. When modifying the current project, use
# the add/remove/move/swap_frames/layers methods
var frames := [] # Array of Frames (that contain Cels)
var layers := [] # Array of Layers
var current_frame := 0
var current_layer := 0
var selected_cels := [[0, 0]] # Array of Arrays of 2 integers (frame & layer)
var animation_tags := [] setget _animation_tags_changed # Array of AnimationTags
var guides := [] # Array of Guides
var brushes := [] # Array of Images
var reference_images := [] # Array of ReferenceImages
var vanishing_points := [] # Array of Vanishing Points
var fps := 6.0
var x_symmetry_point
var y_symmetry_point
var x_symmetry_axis := SymmetryGuide.new()
var y_symmetry_axis := SymmetryGuide.new()
var selection_map := SelectionMap.new()
# This is useful for when the selection is outside of the canvas boundaries,
# on the left and/or above (negative coords)
var selection_offset := Vector2.ZERO setget _selection_offset_changed
var has_selection := false
# For every camera (currently there are 3)
var cameras_rotation := [0.0, 0.0, 0.0] # Array of float
var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)]
var cameras_offset := [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO]
var cameras_zoom_max := [Vector2.ONE, Vector2.ONE, Vector2.ONE]
# Export directory path and export file name
var directory_path := ""
var file_name := "untitled"
var file_format: int = Export.FileFormat.PNG
var was_exported := false
var export_overwrite := false
var animation_tag_node = preload("res://src/UI/Timeline/AnimationTagUI.tscn")
func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> void:
frames = _frames
name = _name
size = _size
tiles = Tiles.new(size)
selection_map.create(size.x, size.y, false, Image.FORMAT_LA8)
Global.tabs.add_tab(name)
OpenSave.current_save_paths.append("")
OpenSave.backup_save_paths.append("")
x_symmetry_point = size.x - 1
y_symmetry_point = size.y - 1
x_symmetry_axis.type = x_symmetry_axis.Types.HORIZONTAL
x_symmetry_axis.project = self
x_symmetry_axis.add_point(Vector2(-19999, y_symmetry_point / 2 + 0.5))
x_symmetry_axis.add_point(Vector2(19999, y_symmetry_point / 2 + 0.5))
Global.canvas.add_child(x_symmetry_axis)
y_symmetry_axis.type = y_symmetry_axis.Types.VERTICAL
y_symmetry_axis.project = self
y_symmetry_axis.add_point(Vector2(x_symmetry_point / 2 + 0.5, -19999))
y_symmetry_axis.add_point(Vector2(x_symmetry_point / 2 + 0.5, 19999))
Global.canvas.add_child(y_symmetry_axis)
if OS.get_name() == "HTML5":
directory_path = "user://"
else:
directory_path = Global.config_cache.get_value(
"data", "current_dir", OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP)
)
func remove() -> void:
undo_redo.free()
for ri in reference_images:
ri.queue_free()
if self == Global.current_project:
# If the project is not current_project then the points need not be removed
for point_idx in vanishing_points.size():
var editor = Global.perspective_editor
for c in editor.vanishing_point_container.get_children():
c.queue_free()
for guide in guides:
guide.queue_free()
for frame in frames:
for l in layers.size():
var cel: BaseCel = frame.cels[l]
cel.on_remove()
# Prevents memory leak (due to the layers' project reference stopping ref counting from freeing)
layers.clear()
Global.projects.erase(self)
func commit_undo() -> void:
if not can_undo:
return
if Global.canvas.selection.is_moving_content:
Global.canvas.selection.transform_content_cancel()
else:
undo_redo.undo()
func commit_redo() -> void:
if not can_undo:
return
Global.control.redone = true
undo_redo.redo()
Global.control.redone = false
func new_empty_frame() -> Frame:
var frame := Frame.new()
var bottom_layer := true
for l in layers: # Create as many cels as there are layers
var cel: BaseCel = l.new_empty_cel()
if cel is PixelCel and bottom_layer and fill_color.a > 0:
cel.image.fill(fill_color)
frame.cels.append(cel)
bottom_layer = false
return frame
func get_current_cel() -> BaseCel:
return frames[current_frame].cels[current_layer]
func selection_map_changed() -> void:
var image_texture := ImageTexture.new()
has_selection = !selection_map.is_invisible()
if has_selection:
image_texture.create_from_image(selection_map, 0)
Global.canvas.selection.marching_ants_outline.texture = image_texture
var edit_menu_popup: PopupMenu = Global.top_menu_container.edit_menu_button.get_popup()
edit_menu_popup.set_item_disabled(Global.EditMenu.NEW_BRUSH, !has_selection)
func _selection_offset_changed(value: Vector2) -> void:
selection_offset = value
Global.canvas.selection.marching_ants_outline.offset = selection_offset
func change_project() -> void:
Global.animation_timeline.project_changed()
Global.current_frame_mark_label.text = "%s/%s" % [str(current_frame + 1), frames.size()]
Global.disable_button(Global.remove_frame_button, frames.size() == 1)
Global.disable_button(Global.move_left_frame_button, frames.size() == 1 or current_frame == 0)
Global.disable_button(
Global.move_right_frame_button, frames.size() == 1 or current_frame == frames.size() - 1
)
toggle_layer_buttons()
self.animation_tags = animation_tags
# Change the guides
for guide in Global.canvas.get_children():
if guide is Guide:
if guide in guides:
guide.visible = Global.show_guides
if guide is SymmetryGuide:
if guide.type == Guide.Types.HORIZONTAL:
guide.visible = Global.show_x_symmetry_axis and Global.show_guides
else:
guide.visible = Global.show_y_symmetry_axis and Global.show_guides
else:
guide.visible = false
# Change the project brushes
Brushes.clear_project_brush()
for brush in brushes:
Brushes.add_project_brush(brush)
Global.canvas.update()
Global.canvas.grid.update()
Global.canvas.tile_mode.update()
Global.transparent_checker.update_rect()
Global.animation_timeline.fps_spinbox.value = fps
Global.horizontal_ruler.update()
Global.vertical_ruler.update()
Global.references_panel.project_changed()
Global.perspective_editor.update_points()
Global.cursor_position_label.text = "[%s×%s]" % [size.x, size.y]
Global.window_title = "%s - Pixelorama %s" % [name, Global.current_version]
if has_changed:
Global.window_title = Global.window_title + "(*)"
var save_path = OpenSave.current_save_paths[Global.current_project_index]
if save_path != "":
Global.open_sprites_dialog.current_path = save_path
Global.save_sprites_dialog.current_path = save_path
Global.top_menu_container.file_menu.set_item_text(
Global.FileMenu.SAVE, tr("Save") + " %s" % save_path.get_file()
)
else:
Global.top_menu_container.file_menu.set_item_text(Global.FileMenu.SAVE, tr("Save"))
if !was_exported:
Global.top_menu_container.file_menu.set_item_text(Global.FileMenu.EXPORT, tr("Export"))
else:
if export_overwrite:
Global.top_menu_container.file_menu.set_item_text(
Global.FileMenu.EXPORT,
tr("Overwrite") + " %s" % (file_name + Export.file_format_string(file_format))
)
else:
Global.top_menu_container.file_menu.set_item_text(
Global.FileMenu.EXPORT,
tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))
)
for j in Tiles.MODE.values():
Global.top_menu_container.tile_mode_submenu.set_item_checked(j, j == tiles.mode)
# Change selection effect & bounding rectangle
Global.canvas.selection.marching_ants_outline.offset = selection_offset
selection_map_changed()
Global.canvas.selection.big_bounding_rectangle = selection_map.get_used_rect()
Global.canvas.selection.big_bounding_rectangle.position += selection_offset
Global.canvas.selection.update()
var edit_menu_popup: PopupMenu = Global.top_menu_container.edit_menu_button.get_popup()
edit_menu_popup.set_item_disabled(Global.EditMenu.NEW_BRUSH, !has_selection)
var i := 0
for camera in Global.cameras:
camera.zoom_max = cameras_zoom_max[i]
if camera == Global.camera_preview:
Global.preview_zoom_slider.disconnect(
"value_changed",
Global.canvas_preview_container,
"_on_PreviewZoomSlider_value_changed"
)
Global.preview_zoom_slider.min_value = -camera.zoom_max.x
Global.preview_zoom_slider.connect(
"value_changed",
Global.canvas_preview_container,
"_on_PreviewZoomSlider_value_changed"
)
if camera == Global.camera:
camera.set_zoom_max_value()
camera.rotation = cameras_rotation[i]
camera.zoom = cameras_zoom[i]
camera.offset = cameras_offset[i]
camera.emit_signal("rotation_changed")
camera.emit_signal("zoom_changed")
i += 1
Global.tile_mode_offset_dialog.change_mask()
func serialize() -> Dictionary:
var layer_data := []
for layer in layers:
layer_data.append(layer.serialize())
layer_data[-1]["metadata"] = _serialize_metadata(layer)
var tag_data := []
for tag in animation_tags:
tag_data.append(
{
"name": tag.name,
"color": tag.color.to_html(),
"from": tag.from,
"to": tag.to,
}
)
var guide_data := []
for guide in guides:
if guide is SymmetryGuide:
continue
if !is_instance_valid(guide):
continue
var coords = guide.points[0].x
if guide.type == Guide.Types.HORIZONTAL:
coords = guide.points[0].y
guide_data.append({"type": guide.type, "pos": coords})
var frame_data := []
for frame in frames:
var cel_data := []
for cel in frame.cels:
cel_data.append(cel.serialize())
cel_data[-1]["metadata"] = _serialize_metadata(cel)
frame_data.append(
{"cels": cel_data, "duration": frame.duration, "metadata": _serialize_metadata(frame)}
)
var brush_data := []
for brush in brushes:
brush_data.append({"size_x": brush.get_size().x, "size_y": brush.get_size().y})
var reference_image_data := []
for reference_image in reference_images:
reference_image_data.append(reference_image.serialize())
var metadata := _serialize_metadata(self)
var project_data := {
"pixelorama_version": Global.current_version,
"pxo_version": ProjectSettings.get_setting("application/config/Pxo_Version"),
"size_x": size.x,
"size_y": size.y,
"tile_mode_x_basis_x": tiles.x_basis.x,
"tile_mode_x_basis_y": tiles.x_basis.y,
"tile_mode_y_basis_x": tiles.y_basis.x,
"tile_mode_y_basis_y": tiles.y_basis.y,
"save_path": OpenSave.current_save_paths[Global.projects.find(self)],
"layers": layer_data,
"tags": tag_data,
"guides": guide_data,
"symmetry_points": [x_symmetry_point, y_symmetry_point],
"frames": frame_data,
"brushes": brush_data,
"reference_images": reference_image_data,
"vanishing_points": vanishing_points,
"export_directory_path": directory_path,
"export_file_name": file_name,
"export_file_format": file_format,
"fps": fps,
"metadata": metadata
}
return project_data
func deserialize(dict: Dictionary) -> void:
if dict.has("size_x") and dict.has("size_y"):
size.x = dict.size_x
size.y = dict.size_y
tiles.tile_size = size
selection_map.crop(size.x, size.y)
if dict.has("tile_mode_x_basis_x") and dict.has("tile_mode_x_basis_y"):
tiles.x_basis.x = dict.tile_mode_x_basis_x
tiles.x_basis.y = dict.tile_mode_x_basis_y
if dict.has("tile_mode_y_basis_x") and dict.has("tile_mode_y_basis_y"):
tiles.y_basis.x = dict.tile_mode_y_basis_x
tiles.y_basis.y = dict.tile_mode_y_basis_y
if dict.has("save_path"):
OpenSave.current_save_paths[Global.projects.find(self)] = dict.save_path
if dict.has("frames") and dict.has("layers"):
for saved_layer in dict.layers:
match int(saved_layer.get("type", Global.LayerTypes.PIXEL)):
Global.LayerTypes.PIXEL:
layers.append(PixelLayer.new(self))
Global.LayerTypes.GROUP:
layers.append(GroupLayer.new(self))
Global.LayerTypes.THREE_D:
layers.append(Layer3D.new(self))
var frame_i := 0
for frame in dict.frames:
var cels := []
var cel_i := 0
for cel in frame.cels:
match int(dict.layers[cel_i].get("type", Global.LayerTypes.PIXEL)):
Global.LayerTypes.PIXEL:
cels.append(PixelCel.new(Image.new()))
Global.LayerTypes.GROUP:
cels.append(GroupCel.new())
Global.LayerTypes.THREE_D:
cels.append(Cel3D.new(size, true))
cels[cel_i].deserialize(cel)
_deserialize_metadata(cels[cel_i], cel)
cel_i += 1
var duration := 1.0
if frame.has("duration"):
duration = frame.duration
elif dict.has("frame_duration"):
duration = dict.frame_duration[frame_i]
var frame_class := Frame.new(cels, duration)
_deserialize_metadata(frame_class, frame)
frames.append(frame_class)
frame_i += 1
# Parent references to other layers are created when deserializing
# a layer, so loop again after creating them:
for layer_i in dict.layers.size():
layers[layer_i].index = layer_i
layers[layer_i].deserialize(dict.layers[layer_i])
_deserialize_metadata(layers[layer_i], dict.layers[layer_i])
if dict.has("tags"):
for tag in dict.tags:
animation_tags.append(AnimationTag.new(tag.name, Color(tag.color), tag.from, tag.to))
self.animation_tags = animation_tags
if dict.has("guides"):
for g in dict.guides:
var guide := Guide.new()
guide.type = g.type
if guide.type == Guide.Types.HORIZONTAL:
guide.add_point(Vector2(-99999, g.pos))
guide.add_point(Vector2(99999, g.pos))
else:
guide.add_point(Vector2(g.pos, -99999))
guide.add_point(Vector2(g.pos, 99999))
guide.has_focus = false
guide.project = self
Global.canvas.add_child(guide)
if dict.has("reference_images"):
for g in dict.reference_images:
var ri := ReferenceImage.new()
ri.project = self
ri.deserialize(g)
Global.canvas.add_child(ri)
if dict.has("vanishing_points"):
vanishing_points = dict.vanishing_points
Global.perspective_editor.update()
if dict.has("symmetry_points"):
x_symmetry_point = dict.symmetry_points[0]
y_symmetry_point = dict.symmetry_points[1]
for point in x_symmetry_axis.points.size():
x_symmetry_axis.points[point].y = floor(y_symmetry_point / 2 + 1)
for point in y_symmetry_axis.points.size():
y_symmetry_axis.points[point].x = floor(x_symmetry_point / 2 + 1)
if dict.has("export_directory_path"):
directory_path = dict.export_directory_path
if dict.has("export_file_name"):
file_name = dict.export_file_name
if dict.has("export_file_format"):
file_format = dict.export_file_format
if dict.has("fps"):
fps = dict.fps
_deserialize_metadata(self, dict)
func _serialize_metadata(object: Object) -> Dictionary:
var metadata := {}
for meta in object.get_meta_list():
metadata[meta] = object.get_meta(meta)
return metadata
func _deserialize_metadata(object: Object, dict: Dictionary) -> void:
if not dict.has("metadata"):
return
var metadata: Dictionary = dict["metadata"]
for meta in metadata.keys():
object.set_meta(meta, metadata[meta])
func _name_changed(value: String) -> void:
name = value
Global.tabs.set_tab_title(Global.tabs.current_tab, name)
func _size_changed(value: Vector2) -> void:
if size.x != 0:
tiles.x_basis = (tiles.x_basis * value.x / size.x).round()
else:
tiles.x_basis = Vector2(value.x, 0)
if size.y != 0:
tiles.y_basis = (tiles.y_basis * value.y / size.y).round()
else:
tiles.y_basis = Vector2(0, value.y)
tiles.tile_size = value
Global.tile_mode_offset_dialog.change_mask()
size = value
Global.canvas.crop_rect.reset()
func change_cel(new_frame: int, new_layer := -1) -> void:
if new_frame < 0:
new_frame = current_frame
if new_layer < 0:
new_layer = current_layer
Global.canvas.selection.transform_content_confirm()
# Unpress all buttons
for i in frames.size():
var frame_button: BaseButton = Global.frame_hbox.get_child(i)
frame_button.pressed = false # Unpress all frame buttons
for cel_hbox in Global.cel_vbox.get_children():
if i < cel_hbox.get_child_count():
cel_hbox.get_child(i).pressed = false # Unpress all cel buttons
for layer_button in Global.layer_vbox.get_children():
layer_button.pressed = false # Unpress all layer buttons
if selected_cels.empty():
selected_cels.append([new_frame, new_layer])
for cel in selected_cels: # Press selected buttons
var frame: int = cel[0]
var layer: int = cel[1]
if frame < Global.frame_hbox.get_child_count():
var frame_button: BaseButton = Global.frame_hbox.get_child(frame)
frame_button.pressed = true # Press selected frame buttons
var layer_vbox_child_count: int = Global.layer_vbox.get_child_count()
if layer < layer_vbox_child_count:
var layer_button = Global.layer_vbox.get_child(layer_vbox_child_count - 1 - layer)
layer_button.pressed = true # Press selected layer buttons
var cel_vbox_child_count: int = Global.cel_vbox.get_child_count()
if layer < cel_vbox_child_count:
var cel_hbox: Container = Global.cel_vbox.get_child(cel_vbox_child_count - 1 - layer)
if frame < cel_hbox.get_child_count():
var cel_button: BaseButton = cel_hbox.get_child(frame)
cel_button.pressed = true # Press selected cel buttons
if new_frame != current_frame: # If the frame has changed
current_frame = new_frame
Global.current_frame_mark_label.text = "%s/%s" % [str(current_frame + 1), frames.size()]
toggle_frame_buttons()
if new_layer != current_layer: # If the layer has changed
current_layer = new_layer
toggle_layer_buttons()
if current_frame < frames.size(): # Set opacity slider
var cel_opacity: float = frames[current_frame].cels[current_layer].opacity
Global.layer_opacity_slider.value = cel_opacity * 100
Global.canvas.update()
Global.transparent_checker.update_rect()
Global.emit_signal("cel_changed")
func toggle_frame_buttons() -> void:
Global.disable_button(Global.remove_frame_button, frames.size() == 1)
Global.disable_button(Global.move_left_frame_button, frames.size() == 1 or current_frame == 0)
Global.disable_button(
Global.move_right_frame_button, frames.size() == 1 or current_frame == frames.size() - 1
)
func toggle_layer_buttons() -> void:
if layers.empty() or current_layer >= layers.size():
return
var child_count: int = layers[current_layer].get_child_count(true)
Global.disable_button(
Global.remove_layer_button,
layers[current_layer].is_locked_in_hierarchy() or layers.size() == child_count + 1
)
Global.disable_button(Global.move_up_layer_button, current_layer == layers.size() - 1)
Global.disable_button(
Global.move_down_layer_button,
current_layer == child_count and not is_instance_valid(layers[current_layer].parent)
)
Global.disable_button(
Global.merge_down_layer_button,
(
current_layer == child_count
or layers[current_layer] is GroupLayer
or layers[current_layer - 1] is GroupLayer
or layers[current_layer - 1] is Layer3D
)
)
func _animation_tags_changed(value: Array) -> void:
animation_tags = value
for child in Global.tag_container.get_children():
child.queue_free()
for tag in animation_tags:
var tag_base_size = Global.animation_timeline.cel_size + 4
var tag_c: Container = animation_tag_node.instance()
Global.tag_container.add_child(tag_c)
tag_c.tag = tag
var tag_position: int = Global.tag_container.get_child_count() - 1
Global.tag_container.move_child(tag_c, tag_position)
tag_c.get_node("Label").text = tag.name
tag_c.get_node("Label").modulate = tag.color
tag_c.get_node("Line2D").default_color = tag.color
# Added 1 to answer to get starting position of next cel
tag_c.rect_position.x = (tag.from - 1) * tag_base_size + 1
var tag_size: int = tag.to - tag.from
# We dont need the 4 pixels at the end of last cel
tag_c.rect_min_size.x = (tag_size + 1) * tag_base_size - 8
tag_c.rect_position.y = 1 # To make top line of tag visible
tag_c.get_node("Line2D").points[2] = Vector2(tag_c.rect_min_size.x, 0)
tag_c.get_node("Line2D").points[3] = Vector2(tag_c.rect_min_size.x, 32)
_set_timeline_first_and_last_frames()
func _set_timeline_first_and_last_frames() -> void:
# This is useful in case tags get modified DURING the animation is playing
# otherwise, this code is useless in this context, since these values are being set
# when the play buttons get pressed anyway
Global.animation_timeline.first_frame = 0
Global.animation_timeline.last_frame = frames.size() - 1
if Global.play_only_tags:
for tag in animation_tags:
if current_frame + 1 >= tag.from && current_frame + 1 <= tag.to:
Global.animation_timeline.first_frame = tag.from - 1
Global.animation_timeline.last_frame = min(frames.size() - 1, tag.to - 1)
func _has_changed_changed(value: bool) -> void:
has_changed = value
if value:
Global.tabs.set_tab_title(Global.tabs.current_tab, name + "(*)")
else:
Global.tabs.set_tab_title(Global.tabs.current_tab, name)
func is_empty() -> bool:
return (
frames.size() == 1
and layers.size() == 1
and layers[0] is PixelLayer
and frames[0].cels[0].image.is_invisible()
and animation_tags.size() == 0
)
func can_pixel_get_drawn(
pixel: Vector2,
image: SelectionMap = selection_map,
selection_position: Vector2 = Global.canvas.selection.big_bounding_rectangle.position
) -> bool:
if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y:
return false
if tiles.mode != Tiles.MODE.NONE and !tiles.has_point(pixel):
return false
if has_selection:
if selection_position.x < 0:
pixel.x -= selection_position.x
if selection_position.y < 0:
pixel.y -= selection_position.y
return image.is_pixel_selected(pixel)
else:
return true
# Timeline modifications
# Modifying layers or frames Arrays on the current project should generally only be done
# through these methods.
# These allow you to add/remove/move/swap frames/layers/cels. It updates the Animation Timeline
# UI, and updates indices. These are designed to be reversible, meaning that to undo an add, you
# use remove, and vice versa. To undo a move or swap, use move or swap with the parameters swapped.
func add_frames(new_frames: Array, indices: Array) -> void: # indices should be in ascending order
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
for i in new_frames.size():
# For each linked cel in the frame, update its layer's cel_link_sets
for l in layers.size():
var cel: BaseCel = new_frames[i].cels[l]
if cel.link_set != null:
if not layers[l].cel_link_sets.has(cel.link_set):
layers[l].cel_link_sets.append(cel.link_set)
cel.link_set["cels"].append(cel)
# Add frame
frames.insert(indices[i], new_frames[i])
Global.animation_timeline.project_frame_added(indices[i])
_update_frame_ui()
func remove_frames(indices: Array) -> void: # indices should be in ascending order
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
for i in indices.size():
# With each removed index, future indices need to be lowered, so subtract by i
# For each linked cel in the frame, update its layer's cel_link_sets
for l in layers.size():
var cel: BaseCel = frames[indices[i] - i].cels[l]
cel.on_remove()
if cel.link_set != null:
cel.link_set["cels"].erase(cel)
if cel.link_set["cels"].empty():
layers[l].cel_link_sets.erase(cel.link_set)
# Remove frame
frames.remove(indices[i] - i)
Global.animation_timeline.project_frame_removed(indices[i] - i)
_update_frame_ui()
func move_frame(from_index: int, to_index: int) -> void:
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
var frame = frames[from_index]
frames.remove(from_index)
Global.animation_timeline.project_frame_removed(from_index)
frames.insert(to_index, frame)
Global.animation_timeline.project_frame_added(to_index)
_update_frame_ui()
func swap_frame(a_index: int, b_index: int) -> void:
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
var temp: Frame = frames[a_index]
frames[a_index] = frames[b_index]
frames[b_index] = temp
Global.animation_timeline.project_frame_removed(a_index)
Global.animation_timeline.project_frame_added(a_index)
Global.animation_timeline.project_frame_removed(b_index)
Global.animation_timeline.project_frame_added(b_index)
_set_timeline_first_and_last_frames()
func reverse_frames(frame_indices: Array) -> void:
Global.canvas.selection.transform_content_confirm()
# warning-ignore:integer_division
for i in frame_indices.size() / 2:
var index: int = frame_indices[i]
var reverse_index: int = frame_indices[-i - 1]
var temp: Frame = frames[index]
frames[index] = frames[reverse_index]
frames[reverse_index] = temp
Global.animation_timeline.project_frame_removed(index)
Global.animation_timeline.project_frame_added(index)
Global.animation_timeline.project_frame_removed(reverse_index)
Global.animation_timeline.project_frame_added(reverse_index)
_set_timeline_first_and_last_frames()
change_cel(-1)
func add_layers(new_layers: Array, indices: Array, cels: Array) -> void: # cels is 2d Array of cels
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
for i in indices.size():
layers.insert(indices[i], new_layers[i])
for f in frames.size():
frames[f].cels.insert(indices[i], cels[i][f])
new_layers[i].project = self
Global.animation_timeline.project_layer_added(indices[i])
_update_layer_ui()
func remove_layers(indices: Array) -> void:
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
for i in indices.size():
# With each removed index, future indices need to be lowered, so subtract by i
layers.remove(indices[i] - i)
for frame in frames:
frame.cels[indices[i] - i].on_remove()
frame.cels.remove(indices[i] - i)
Global.animation_timeline.project_layer_removed(indices[i] - i)
_update_layer_ui()
# from_indices and to_indicies should be in ascending order
func move_layers(from_indices: Array, to_indices: Array, to_parents: Array) -> void:
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
var removed_layers := []
var removed_cels := [] # 2D array of cels (an array for each layer removed)
for i in from_indices.size():
# With each removed index, future indices need to be lowered, so subtract by i
removed_layers.append(layers.pop_at(from_indices[i] - i))
removed_layers[i].parent = to_parents[i] # parents must be set before UI created in next loop
removed_cels.append([])
for frame in frames:
removed_cels[i].append(frame.cels.pop_at(from_indices[i] - i))
Global.animation_timeline.project_layer_removed(from_indices[i] - i)
for i in to_indices.size():
layers.insert(to_indices[i], removed_layers[i])
for f in frames.size():
frames[f].cels.insert(to_indices[i], removed_cels[i][f])
Global.animation_timeline.project_layer_added(to_indices[i])
_update_layer_ui()
# "a" and "b" should both contain "from", "to", and "to_parents" arrays.
# (Using dictionaries because there seems to be a limit of 5 arguments for do/undo method calls)
func swap_layers(a: Dictionary, b: Dictionary) -> void:
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
var a_layers := []
var b_layers := []
var a_cels := [] # 2D array of cels (an array for each layer removed)
var b_cels := [] # 2D array of cels (an array for each layer removed)
for i in a.from.size():
a_layers.append(layers.pop_at(a.from[i] - i))
Global.animation_timeline.project_layer_removed(a.from[i] - i)
a_layers[i].parent = a.to_parents[i] # All parents must be set early, before creating buttons
a_cels.append([])
for frame in frames:
a_cels[i].append(frame.cels.pop_at(a.from[i] - i))
for i in b.from.size():
var index = (b.from[i] - i) if a.from[0] > b.from[0] else (b.from[i] - i - a.from.size())
b_layers.append(layers.pop_at(index))
Global.animation_timeline.project_layer_removed(index)
b_layers[i].parent = b.to_parents[i] # All parents must be set early, before creating buttons
b_cels.append([])
for frame in frames:
b_cels[i].append(frame.cels.pop_at(index))
for i in a_layers.size():
var index = a.to[i] if a.to[0] < b.to[0] else (a.to[i] - b.to.size())
layers.insert(index, a_layers[i])
for f in frames.size():
frames[f].cels.insert(index, a_cels[i][f])
Global.animation_timeline.project_layer_added(index)
for i in b_layers.size():
layers.insert(b.to[i], b_layers[i])
for f in frames.size():
frames[f].cels.insert(b.to[i], b_cels[i][f])
Global.animation_timeline.project_layer_added(b.to[i])
_update_layer_ui()
func move_cel(from_frame: int, to_frame: int, layer: int) -> void:
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
var cel: BaseCel = frames[from_frame].cels[layer]
if from_frame < to_frame:
for f in range(from_frame, to_frame): # Forward range
frames[f].cels[layer] = frames[f + 1].cels[layer] # Move left
else:
for f in range(from_frame, to_frame, -1): # Backward range
frames[f].cels[layer] = frames[f - 1].cels[layer] # Move right
frames[to_frame].cels[layer] = cel
Global.animation_timeline.project_cel_removed(from_frame, layer)
Global.animation_timeline.project_cel_added(to_frame, layer)
# Update the cel buttons for this layer:
var cel_hbox: HBoxContainer = Global.cel_vbox.get_child(layers.size() - 1 - layer)
for f in frames.size():
cel_hbox.get_child(f).frame = f
cel_hbox.get_child(f).button_setup()
func swap_cel(a_frame: int, a_layer: int, b_frame: int, b_layer: int) -> void:
Global.canvas.selection.transform_content_confirm()
selected_cels.clear()
var temp: BaseCel = frames[a_frame].cels[a_layer]
frames[a_frame].cels[a_layer] = frames[b_frame].cels[b_layer]
frames[b_frame].cels[b_layer] = temp
Global.animation_timeline.project_cel_removed(a_frame, a_layer)
Global.animation_timeline.project_cel_added(a_frame, a_layer)
Global.animation_timeline.project_cel_removed(b_frame, b_layer)
Global.animation_timeline.project_cel_added(b_frame, b_layer)
func _update_frame_ui() -> void:
for f in frames.size(): # Update the frames and frame buttons
Global.frame_hbox.get_child(f).frame = f
Global.frame_hbox.get_child(f).text = str(f + 1)
for l in layers.size(): # Update the cel buttons
var cel_hbox: HBoxContainer = Global.cel_vbox.get_child(layers.size() - 1 - l)
for f in frames.size():
cel_hbox.get_child(f).frame = f
cel_hbox.get_child(f).button_setup()
_set_timeline_first_and_last_frames()
## Update the layer indices and layer/cel buttons
func _update_layer_ui() -> void:
for l in layers.size():
layers[l].index = l
Global.layer_vbox.get_child(layers.size() - 1 - l).layer = l
var cel_hbox: HBoxContainer = Global.cel_vbox.get_child(layers.size() - 1 - l)
for f in frames.size():
cel_hbox.get_child(f).layer = l
cel_hbox.get_child(f).button_setup()
toggle_layer_buttons()