Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved the Paste tag system #946

Merged
merged 20 commits into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/UI/Timeline/AnimationTimeline.gd
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,12 @@ func _on_CopyFrame_pressed() -> void:
## When [param destination] is -1, the new frames will be placed right next to the last frame in
## [param destination]. if [param select_all_cels] is [code]true[/code] then all of the new copied
## cels will be selected, otherwise only the cels corresponding to the original selected cels will
## get selected.
## get selected. if [param tag_name_from] holds an animation tag then a tag of it's name will be
## created over the new frames.
## [br]Note: [param indices] must be in ascending order
func copy_frames(indices := [], destination := -1, select_all_cels := true) -> void:
func copy_frames(
indices := [], destination := -1, select_all_cels := true, tag_name_from: AnimationTag = null
) -> void:
var project := Global.current_project

if indices.size() == 0:
Expand Down Expand Up @@ -423,12 +426,22 @@ func copy_frames(indices := [], destination := -1, select_all_cels := true) -> v
new_cel.selected = new_cel.get_object_from_id(selected_id)
new_frame.cels.append(new_cel)

for tag in new_animation_tags: # Loop through the tags to see if the frame is in one
# After adding one frame, loop through the tags to see if the frame was in an animation tag
for tag in new_animation_tags:
if copied_indices[0] >= tag.from && copied_indices[0] <= tag.to:
tag.to += 1
elif copied_indices[0] < tag.from:
tag.from += 1
tag.to += 1
if tag_name_from:
new_animation_tags.append(
AnimationTag.new(
tag_name_from.name,
tag_name_from.color,
copied_indices[0] + 1,
copied_indices[-1] + 1
)
)
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
# Note: temporarily set the selected cels to an empty array (needed for undo/redo)
Expand Down
56 changes: 55 additions & 1 deletion src/UI/Timeline/AnimationTimeline.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -952,9 +952,63 @@ offset_bottom = 40.0
mouse_filter = 2
color = Color(0, 0.741176, 1, 0.501961)

[node name="PasteTagPopup" type="PopupMenu" parent="."]
[node name="PasteTagPopup" type="Popup" parent="."]
size = Vector2i(250, 307)
visible = true
min_size = Vector2i(250, 0)
script = ExtResource("12")

[node name="PanelContainer" type="PanelContainer" parent="PasteTagPopup"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

[node name="VBoxContainer" type="VBoxContainer" parent="PasteTagPopup/PanelContainer"]
layout_mode = 2

[node name="Title" type="Label" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
layout_mode = 2
theme_type_variation = &"HeaderSmall"
text = "Import Tags"
horizontal_alignment = 1

[node name="HBoxContainer" type="HBoxContainer" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
layout_mode = 2

[node name="Label" type="Label" parent="PasteTagPopup/PanelContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "From: "

[node name="ProjectList" type="OptionButton" parent="PasteTagPopup/PanelContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
alignment = 1

[node name="CreateTags" type="CheckButton" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Create new tags"

[node name="Instructions" type="Label" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
layout_mode = 2
text = "Available tags:"
autowrap_mode = 3

[node name="TagList" type="ItemList" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 150)
layout_mode = 2
size_flags_vertical = 3

[node name="StartFrame" type="Label" parent="PasteTagPopup/PanelContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
horizontal_alignment = 1
autowrap_mode = 3

[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/LayerSettingsContainer/LayerButtons/AddLayer" to="." method="add_layer"]
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/LayerSettingsContainer/LayerButtons/RemoveLayer" to="." method="_on_RemoveLayer_pressed"]
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/LayerSettingsContainer/LayerButtons/MoveUpLayer" to="." method="change_layer_order" binds= [true]]
Expand Down
240 changes: 220 additions & 20 deletions src/UI/Timeline/PasteTagPopup.gd
Original file line number Diff line number Diff line change
@@ -1,37 +1,237 @@
extends PopupMenu
extends Popup

@onready var tag_container: Control = Global.animation_timeline.find_child("TagContainer")
var from_project: Project
var create_new_tags := false

@onready var from_project_list: OptionButton = %ProjectList
@onready var create_tags: CheckButton = %CreateTags
@onready var animation_tags_list: ItemList = %TagList
@onready var start_frame: Label = %StartFrame


func _ready() -> void:
id_pressed.connect(_on_TagList_id_pressed)
tag_container.gui_input.connect(_on_TagContainer_gui_input)
var tag_container: Control = Global.animation_timeline.find_child("TagContainer")
# connect signals
tag_container.connect("gui_input", _on_TagContainer_gui_input)
from_project_list.connect("item_selected", _on_FromProject_changed)
animation_tags_list.connect("item_selected", _on_TagList_id_pressed)
create_tags.connect("toggled", _on_CreateTags_toggled)


func refresh_list() -> void:
Variable-ind marked this conversation as resolved.
Show resolved Hide resolved
animation_tags_list.clear()
for tag in from_project.animation_tags:
var img = Image.create(5, 5, true, Image.FORMAT_RGBA8)
img.fill(tag.color)
var tex = ImageTexture.create_from_image(img)
var tag_title = tag.name
if tag_title == "":
tag_title = "(Untitled)"
animation_tags_list.add_item(tag_title, tex)


func _on_CreateTags_toggled(pressed: bool) -> void:
create_new_tags = pressed


func _on_TagContainer_gui_input(event: InputEvent) -> void:
if !event is InputEventMouseButton:
return
if Input.is_action_just_released("right_mouse"):
clear()
if Global.current_project.animation_tags.is_empty():
return
add_separator("Paste content from tag:")
for tag in Global.current_project.animation_tags:
var img := Image.create(5, 5, true, Image.FORMAT_RGBA8)
img.fill(tag.color)
var tex := ImageTexture.create_from_image(img)
var tag_name := tag.name
if tag_name == "":
tag_name = "(Untitled)"
add_icon_item(tex, tag_name)
# Reset UI
from_project_list.clear()
if Global.projects.find(from_project) < 0:
from_project = Global.current_project
# Populate project list
for project in Global.projects:
from_project_list.add_item(project.name)
from_project_list.select(Global.projects.find(from_project))
# Populate tag list
refresh_list()
var frame_idx := Global.current_project.current_frame + 2
add_separator(str("The pasted frames will start at (Frame ", frame_idx, ")"))
popup(Rect2i(tag_container.get_global_mouse_position(), Vector2.ONE))
start_frame.text = str("The pasted frames will start at (Frame ", frame_idx, ")")
popup(Rect2i(Global.control.get_global_mouse_position(), Vector2i.ONE))


func _on_FromProject_changed(id: int) -> void:
from_project = Global.projects[id]
refresh_list()


func _on_TagList_id_pressed(id: int) -> void:
var tag: AnimationTag = Global.current_project.animation_tags[id - 1]
var tag: AnimationTag = from_project.animation_tags[id]
var frames = []
for i in range(tag.from - 1, tag.to):
frames.append(i)
Global.animation_timeline.copy_frames(frames, Global.current_project.current_frame)
if create_new_tags:
add_animation(frames, Global.current_project.current_frame, tag)
else:
add_animation(frames, Global.current_project.current_frame)
hide()


## Gets frame indices of [member from_project] and dumps it in the current project.
func add_animation(indices: Array, destination: int, from_tag: AnimationTag = null):
var project: Project = Global.current_project
if from_project == project: ## If we are copying tags within project
Global.animation_timeline.copy_frames(indices, destination, true, from_tag)
return
var new_animation_tags := project.animation_tags.duplicate()
# Loop through the tags to create new classes for them, so that they won't be the same
# as project.animation_tags's classes. Needed for undo/redo to work properly.
for i in new_animation_tags.size():
new_animation_tags[i] = AnimationTag.new(
new_animation_tags[i].name,
new_animation_tags[i].color,
new_animation_tags[i].from,
new_animation_tags[i].to
)
var imported_frames: Array[Frame] = [] # The copied frames
# the indices of newly copied frames
var copied_indices: PackedInt32Array = range(
destination + 1, (destination + 1) + indices.size()
)
project.undos += 1
project.undo_redo.create_action("Import Tag")
# Step 1: calculate layers to generate
var layer_to_names := PackedStringArray() # names of currently existing layers
for l in project.layers:
layer_to_names.append(l.name)

# the goal of this section is to mark existing layers with their indices else with -1
var layer_from_to := {} # indices of layers from and to
for from in from_project.layers.size():
var to = layer_to_names.find(from_project.layers[from].name)
if project.layers[to].get_layer_type() != from_project.layers[from].get_layer_type():
to = -1
if to in layer_from_to.values(): # from_project has layers with duplicate frames
to = -1
layer_from_to[from] = to

# Step 2: generate required layers
var combined_copy := Array() # Makes calculations easy
combined_copy.append_array(project.layers)
var added_layers := Array() # Array of layers
var added_idx := Array() # Array of indices to add the respective layers (in added_layers) to
var added_cels := Array() # Array of an Array of cels (added in same order as their layer)

if layer_from_to.values().count(-1) > 0:
# As it is extracted from a dictionary, so i assume the keys aren't sorted
var from_layers_size = layer_from_to.keys().duplicate(true)
from_layers_size.sort() # it's values should now be from (layer size - 1) to zero
for i in from_layers_size:
if layer_from_to[i] == -1:
var type = from_project.layers[i].get_layer_type()
var l: BaseLayer
match type:
Global.LayerTypes.PIXEL:
l = PixelLayer.new(project)
Global.LayerTypes.GROUP:
l = GroupLayer.new(project)
Global.LayerTypes.THREE_D:
l = Layer3D.new(project)
var cels := []
for f in project.frames:
cels.append(l.new_empty_cel())
l.name = from_project.layers[i].name # this will set it to the required layer name

# Set an appropriate parent
var new_layer_idx = combined_copy.size()
layer_from_to[i] = new_layer_idx
var from_children = from_project.layers[i].get_children(false)
for from_child in from_children: # If this layer had children
var child_to_idx = layer_from_to[from_project.layers.find(from_child)]
var to_child = combined_copy[child_to_idx]
if to_child in added_layers: # if child was added recently
to_child.parent = l

combined_copy.insert(new_layer_idx, l)
added_layers.append(l) # layer is now added
added_idx.append(new_layer_idx) # at index new_layer_idx
added_cels.append(cels) # with cels

# Now initiate import
for f in indices:
var src_frame: Frame = from_project.frames[f]
var new_frame := Frame.new()
imported_frames.append(new_frame)
new_frame.duration = src_frame.duration
for to in combined_copy.size():
var new_cel: BaseCel
if to in layer_from_to.values():
var from = layer_from_to.find_key(to)
# Cel we're copying from, the source
var src_cel: BaseCel = from_project.frames[f].cels[from]
var selected_id := -1
if src_cel is Cel3D:
new_cel = src_cel.get_script().new(
project.size, false, src_cel.object_properties, src_cel.scene_properties
)
if src_cel.selected != null:
selected_id = src_cel.selected.id
else:
new_cel = src_cel.get_script().new()

# add more types here if they have a copy_content() method
if src_cel is PixelCel:
var src_img = src_cel.copy_content()
var copy := Image.create(
project.size.x, project.size.y, false, Image.FORMAT_RGBA8
)
copy.blit_rect(
src_img, Rect2(Vector2.ZERO, src_img.get_size()), Vector2.ZERO
)
new_cel.set_content(copy)
new_cel.opacity = src_cel.opacity

if new_cel is Cel3D:
if selected_id in new_cel.object_properties.keys():
if selected_id != -1:
new_cel.selected = new_cel.get_object_from_id(selected_id)
else:
new_cel = combined_copy[to].new_empty_cel()
new_frame.cels.append(new_cel)

for tag in new_animation_tags: # Loop through the tags to see if the frame is in one
if copied_indices[0] >= tag.from && copied_indices[0] <= tag.to:
tag.to += 1
elif copied_indices[0] < tag.from:
tag.from += 1
tag.to += 1
if from_tag:
new_animation_tags.append(
AnimationTag.new(
from_tag.name, from_tag.color, copied_indices[0] + 1, copied_indices[-1] + 1
)
)
project.undo_redo.add_undo_method(project.remove_frames.bind(copied_indices))
project.undo_redo.add_do_method(project.add_layers.bind(added_layers, added_idx, added_cels))
project.undo_redo.add_do_method(project.add_frames.bind(imported_frames, copied_indices))
project.undo_redo.add_undo_method(project.remove_layers.bind(added_idx))
# Note: temporarily set the selected cels to an empty array (needed for undo/redo)
project.undo_redo.add_do_property(Global.current_project, "selected_cels", [])
project.undo_redo.add_undo_property(Global.current_project, "selected_cels", [])

var all_new_cels = []
# Select all the new frames so that it is easier to move/offset collectively if user wants
# To ease animation workflow, new current frame is the first copied frame instead of the last
var range_start: int = copied_indices[-1]
var range_end: int = copied_indices[0]
var frame_diff_sign := signi(range_end - range_start)
if frame_diff_sign == 0:
frame_diff_sign = 1
for i in range(range_start, range_end + frame_diff_sign, frame_diff_sign):
for j in range(0, combined_copy.size()):
var frame_layer := [i, j]
if !all_new_cels.has(frame_layer):
all_new_cels.append(frame_layer)
project.undo_redo.add_do_property(Global.current_project, "selected_cels", all_new_cels)
project.undo_redo.add_undo_method(
project.change_cel.bind(project.current_frame, project.current_layer)
)
project.undo_redo.add_do_method(project.change_cel.bind(range_end))
project.undo_redo.add_do_property(project, "animation_tags", new_animation_tags)
project.undo_redo.add_undo_property(project, "animation_tags", project.animation_tags)
project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
project.undo_redo.commit_action()