mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00:00
Improved the Paste tag system (#946)
* cloned frames only keep original selected cels as selected * Fixed PasteTagPopup dialog, fixed tag created immediately after starting pixelorama not placed correctly * formatting * typo * Pase tag popup 2.0 * improved code * formatting * Update PasteTagPopup.gd * fixed code messed up while resolving conflict * group sync achieved * linting * quality of life additions * fixed projects not updating --------- Co-authored-by: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com>
This commit is contained in:
parent
bcdefe66ce
commit
87b5a818bb
|
@ -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
|
## 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
|
## [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
|
## 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
|
## [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
|
var project := Global.current_project
|
||||||
|
|
||||||
if indices.size() == 0:
|
if indices.size() == 0:
|
||||||
|
@ -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_cel.selected = new_cel.get_object_from_id(selected_id)
|
||||||
new_frame.cels.append(new_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
|
# 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:
|
if copied_indices[0] >= tag.from && copied_indices[0] <= tag.to:
|
||||||
tag.to += 1
|
tag.to += 1
|
||||||
elif copied_indices[0] < tag.from:
|
elif copied_indices[0] < tag.from:
|
||||||
tag.from += 1
|
tag.from += 1
|
||||||
tag.to += 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_do_method(Global.undo_or_redo.bind(false))
|
||||||
project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
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)
|
# Note: temporarily set the selected cels to an empty array (needed for undo/redo)
|
||||||
|
|
|
@ -952,9 +952,63 @@ offset_bottom = 40.0
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
color = Color(0, 0.741176, 1, 0.501961)
|
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")
|
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/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/RemoveLayer" to="." method="_on_RemoveLayer_pressed"]
|
||||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/LayerSettingsContainer/LayerButtons/MoveUpLayer" to="." method="change_layer_order" binds= [true]]
|
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/LayerSettingsContainer/LayerButtons/MoveUpLayer" to="." method="change_layer_order" binds= [true]]
|
||||||
|
|
|
@ -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:
|
func _ready() -> void:
|
||||||
id_pressed.connect(_on_TagList_id_pressed)
|
var tag_container: Control = Global.animation_timeline.find_child("TagContainer")
|
||||||
tag_container.gui_input.connect(_on_TagContainer_gui_input)
|
# 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:
|
||||||
|
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:
|
func _on_TagContainer_gui_input(event: InputEvent) -> void:
|
||||||
if !event is InputEventMouseButton:
|
if !event is InputEventMouseButton:
|
||||||
return
|
return
|
||||||
if Input.is_action_just_released("right_mouse"):
|
if Input.is_action_just_released("right_mouse"):
|
||||||
clear()
|
# Reset UI
|
||||||
if Global.current_project.animation_tags.is_empty():
|
from_project_list.clear()
|
||||||
return
|
if Global.projects.find(from_project) < 0:
|
||||||
add_separator("Paste content from tag:")
|
from_project = Global.current_project
|
||||||
for tag in Global.current_project.animation_tags:
|
# Populate project list
|
||||||
var img := Image.create(5, 5, true, Image.FORMAT_RGBA8)
|
for project in Global.projects:
|
||||||
img.fill(tag.color)
|
from_project_list.add_item(project.name)
|
||||||
var tex := ImageTexture.create_from_image(img)
|
from_project_list.select(Global.projects.find(from_project))
|
||||||
var tag_name := tag.name
|
# Populate tag list
|
||||||
if tag_name == "":
|
refresh_list()
|
||||||
tag_name = "(Untitled)"
|
|
||||||
add_icon_item(tex, tag_name)
|
|
||||||
var frame_idx := Global.current_project.current_frame + 2
|
var frame_idx := Global.current_project.current_frame + 2
|
||||||
add_separator(str("The pasted frames will start at (Frame ", frame_idx, ")"))
|
start_frame.text = str("The pasted frames will start at (Frame ", frame_idx, ")")
|
||||||
popup(Rect2i(tag_container.get_global_mouse_position(), Vector2.ONE))
|
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:
|
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 = []
|
var frames = []
|
||||||
for i in range(tag.from - 1, tag.to):
|
for i in range(tag.from - 1, tag.to):
|
||||||
frames.append(i)
|
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()
|
||||||
|
|
Loading…
Reference in a new issue