1
0
Fork 0
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:
Variable 2023-12-30 17:53:45 +05:00 committed by GitHub
parent bcdefe66ce
commit 87b5a818bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 291 additions and 24 deletions

View file

@ -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)

View file

@ -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]]

View file

@ -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()