diff --git a/src/Classes/AnimationTag.gd b/src/Classes/AnimationTag.gd index 52833f5a5..bb5f2b834 100644 --- a/src/Classes/AnimationTag.gd +++ b/src/Classes/AnimationTag.gd @@ -49,6 +49,7 @@ var name: String ## Name of tag var color: Color ## Color of tag var from: int ## First frame number in the tag (first frame in timeline is numbered 1) var to: int ## First frame number in the tag (first frame in timeline is numbered 1) +var user_data := "" ## User defined data, set in the tag properties. ## Class Constructor (used as [code]AnimationTag.new(name, color, from, to)[/code]) @@ -60,7 +61,10 @@ func _init(_name: String, _color: Color, _from: int, _to: int) -> void: func serialize() -> Dictionary: - return {"name": name, "color": color.to_html(), "from": from, "to": to} + var dict := {"name": name, "color": color.to_html(), "from": from, "to": to} + if not user_data.is_empty(): + dict["user_data"] = user_data + return dict func get_size() -> int: diff --git a/src/Classes/Cels/BaseCel.gd b/src/Classes/Cels/BaseCel.gd index 923cf808c..8dedefba4 100644 --- a/src/Classes/Cels/BaseCel.gd +++ b/src/Classes/Cels/BaseCel.gd @@ -17,6 +17,7 @@ var transformed_content: Image ## Used in transformations (moving, scaling etc ## Used for individual cel ordering. Used for when cels need to be drawn above or below ## their corresponding layer. var z_index := 0 +var user_data := "" ## User defined data, set in the cel properties. func get_final_opacity(layer: BaseLayer) -> float: @@ -78,14 +79,17 @@ func update_texture() -> void: ## Returns a curated [Dictionary] containing the cel data. func serialize() -> Dictionary: - return {"opacity": opacity, "z_index": z_index} + var dict := {"opacity": opacity, "z_index": z_index} + if not user_data.is_empty(): + dict["user_data"] = user_data + return dict ## Sets the cel data according to a curated [Dictionary] obtained from [method serialize]. func deserialize(dict: Dictionary) -> void: opacity = dict["opacity"] - if dict.has("z_index"): - z_index = dict["z_index"] + z_index = dict.get("z_index", z_index) + user_data = dict.get("user_data", user_data) ## Used to perform cleanup after a cel is removed. diff --git a/src/Classes/Frame.gd b/src/Classes/Frame.gd index c21044661..57f9611f5 100644 --- a/src/Classes/Frame.gd +++ b/src/Classes/Frame.gd @@ -3,8 +3,9 @@ extends RefCounted ## A class for frame properties. ## A frame is a collection of cels, for each layer. -var cels: Array[BaseCel] -var duration := 1.0 +var cels: Array[BaseCel] ## The array containing all of the frame's [BaseCel]s. One for each layer. +var duration := 1.0 ## The duration multiplier. This allows for individual frame timing. +var user_data := "" ## User defined data, set in the frame properties. func _init(_cels: Array[BaseCel] = [], _duration := 1.0) -> void: diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 0969ce00a..d93e6756b 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -232,9 +232,12 @@ func serialize() -> Dictionary: 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 current_frame_data := { + "cels": cel_data, "duration": frame.duration, "metadata": _serialize_metadata(frame) + } + if not frame.user_data.is_empty(): + current_frame_data["user_data"] = frame.user_data + frame_data.append(current_frame_data) var brush_data := [] for brush in brushes: brush_data.append({"size_x": brush.get_size().x, "size_y": brush.get_size().y}) @@ -335,6 +338,7 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces duration = dict.frame_duration[frame_i] var frame_class := Frame.new(cels, duration) + frame_class.user_data = frame.get("user_data", "") _deserialize_metadata(frame_class, frame) frames.append(frame_class) frame_i += 1 @@ -347,7 +351,9 @@ func deserialize(dict: Dictionary, zip_reader: ZIPReader = null, file: FileAcces _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)) + var new_tag := AnimationTag.new(tag.name, Color(tag.color), tag.from, tag.to) + new_tag.user_data = tag.get("user_data", "") + animation_tags.append(new_tag) animation_tags = animation_tags if dict.has("guides"): for g in dict.guides: diff --git a/src/UI/Timeline/CelProperties.gd b/src/UI/Timeline/CelProperties.gd index b3fbe5236..b35e06ba3 100644 --- a/src/UI/Timeline/CelProperties.gd +++ b/src/UI/Timeline/CelProperties.gd @@ -6,6 +6,7 @@ var cel_indices: Array @onready var layer_num := $GridContainer/LayerNum as Label @onready var opacity_slider := $GridContainer/OpacitySlider as ValueSlider @onready var z_index_slider := $GridContainer/ZIndexSlider as ValueSlider +@onready var user_data_text_edit := $GridContainer/UserDataTextEdit as TextEdit func _on_visibility_changed() -> void: @@ -25,6 +26,7 @@ func _on_visibility_changed() -> void: layer_num.text = "[%s...%s]" % [first_layer.name, last_layer.name] opacity_slider.value = first_cel.opacity * 100.0 z_index_slider.value = first_cel.z_index + user_data_text_edit.text = first_cel.user_data else: cel_indices = [] @@ -47,3 +49,9 @@ func _on_z_index_slider_value_changed(value: float) -> void: Global.current_project.order_layers() Global.canvas.update_all_layers = true Global.canvas.queue_redraw() + + +func _on_user_data_text_edit_text_changed() -> void: + for cel_index in cel_indices: + var cel := Global.current_project.frames[cel_index[0]].cels[cel_index[1]] + cel.user_data = user_data_text_edit.text diff --git a/src/UI/Timeline/CelProperties.tscn b/src/UI/Timeline/CelProperties.tscn index 2ed73ab78..b8eac8d41 100644 --- a/src/UI/Timeline/CelProperties.tscn +++ b/src/UI/Timeline/CelProperties.tscn @@ -5,7 +5,7 @@ [node name="CelProperties" type="AcceptDialog"] title = "Cel properties" -size = Vector2i(300, 161) +size = Vector2i(300, 188) exclusive = false popup_window = true script = ExtResource("1_lyy7i") @@ -14,7 +14,7 @@ script = ExtResource("1_lyy7i") offset_left = 8.0 offset_top = 8.0 offset_right = 292.0 -offset_bottom = 112.0 +offset_bottom = 139.0 columns = 2 [node name="Frame" type="Label" parent="GridContainer"] @@ -76,6 +76,15 @@ stretch_margin_right = 3 stretch_margin_bottom = 3 script = ExtResource("1_85pb7") +[node name="UserDataLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "User data:" + +[node name="UserDataTextEdit" type="TextEdit" parent="GridContainer"] +layout_mode = 2 +scroll_fit_content_height = true + [connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] [connection signal="value_changed" from="GridContainer/OpacitySlider" to="." method="_on_opacity_slider_value_changed"] [connection signal="value_changed" from="GridContainer/ZIndexSlider" to="." method="_on_z_index_slider_value_changed"] +[connection signal="text_changed" from="GridContainer/UserDataTextEdit" to="." method="_on_user_data_text_edit_text_changed"] diff --git a/src/UI/Timeline/FrameProperties.gd b/src/UI/Timeline/FrameProperties.gd index f25cd6847..ea4b61fb1 100644 --- a/src/UI/Timeline/FrameProperties.gd +++ b/src/UI/Timeline/FrameProperties.gd @@ -3,6 +3,7 @@ extends ConfirmationDialog var frame_indices := [] @onready var frame_num := $GridContainer/FrameNum @onready var frame_dur := $GridContainer/FrameTime +@onready var user_data_text_edit := $GridContainer/UserDataTextEdit as TextEdit func _on_FrameProperties_about_to_show() -> void: @@ -13,8 +14,10 @@ func _on_FrameProperties_about_to_show() -> void: frame_num.set_text(str(frame_indices[0] + 1)) else: frame_num.set_text("[%s...%s]" % [frame_indices[0] + 1, frame_indices[-1] + 1]) - var duration: float = Global.current_project.frames[frame_indices[0]].duration + var frame := Global.current_project.frames[frame_indices[0]] + var duration := frame.duration frame_dur.set_value(duration) + user_data_text_edit.text = frame.user_data func _on_FrameProperties_visibility_changed() -> void: @@ -24,13 +27,15 @@ func _on_FrameProperties_visibility_changed() -> void: func _on_FrameProperties_confirmed() -> void: var project := Global.current_project var new_duration: float = frame_dur.get_value() + var new_user_data := user_data_text_edit.text project.undos += 1 project.undo_redo.create_action("Change frame duration") - for frame in frame_indices: - project.undo_redo.add_do_property(project.frames[frame], "duration", new_duration) - project.undo_redo.add_undo_property( - project.frames[frame], "duration", project.frames[frame].duration - ) + for frame_idx in frame_indices: + var frame := project.frames[frame_idx] + project.undo_redo.add_do_property(frame, "duration", new_duration) + project.undo_redo.add_do_property(frame, "user_data", new_user_data) + project.undo_redo.add_undo_property(frame, "duration", frame.duration) + project.undo_redo.add_undo_property(frame, "user_data", frame.user_data) 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() diff --git a/src/UI/Timeline/FrameProperties.tscn b/src/UI/Timeline/FrameProperties.tscn index 7b70d8ecf..ec9abec24 100644 --- a/src/UI/Timeline/FrameProperties.tscn +++ b/src/UI/Timeline/FrameProperties.tscn @@ -38,6 +38,14 @@ value = 1.0 allow_greater = true suffix = "x" +[node name="UserDataLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "User data:" + +[node name="UserDataTextEdit" type="TextEdit" parent="GridContainer"] +layout_mode = 2 +scroll_fit_content_height = true + [connection signal="about_to_popup" from="." to="." method="_on_FrameProperties_about_to_show"] [connection signal="confirmed" from="." to="." method="_on_FrameProperties_confirmed"] [connection signal="visibility_changed" from="." to="." method="_on_FrameProperties_visibility_changed"] diff --git a/src/UI/Timeline/FrameTagDialog.gd b/src/UI/Timeline/FrameTagDialog.gd index 19a969965..97900654a 100644 --- a/src/UI/Timeline/FrameTagDialog.gd +++ b/src/UI/Timeline/FrameTagDialog.gd @@ -7,6 +7,11 @@ var delete_tag_button: Button @onready var main_vbox_cont: VBoxContainer = $VBoxContainer/ScrollContainer/VBoxTagContainer @onready var add_tag_button: Button = $VBoxContainer/ScrollContainer/VBoxTagContainer/AddTag @onready var options_dialog := $TagOptions +@onready var name_line_edit := $TagOptions/GridContainer/NameLineEdit as LineEdit +@onready var color_picker_button := $TagOptions/GridContainer/ColorPickerButton as ColorPickerButton +@onready var from_spinbox := $TagOptions/GridContainer/FromSpinBox as SpinBox +@onready var to_spinbox := $TagOptions/GridContainer/ToSpinBox as SpinBox +@onready var user_data_text_edit := $TagOptions/GridContainer/UserDataTextEdit as TextEdit func _ready() -> void: @@ -71,11 +76,10 @@ func _on_AddTag_pressed() -> void: frames.append(cel[0]) frames.sort() - options_dialog.get_node("GridContainer/FromSpinBox").value = (frames[0] + 1) - options_dialog.get_node("GridContainer/ToSpinBox").value = (frames[-1] + 1) - options_dialog.get_node("GridContainer/ColorPickerButton").color = Color( - randf(), randf(), randf() - ) + from_spinbox.value = (frames[0] + 1) + to_spinbox.value = (frames[-1] + 1) + color_picker_button.color = Color(randf(), randf(), randf()) + user_data_text_edit.text = "" func _on_EditButton_pressed(_tag_id: int, edit_button: Button) -> void: @@ -83,11 +87,12 @@ func _on_EditButton_pressed(_tag_id: int, edit_button: Button) -> void: var y_pos := edit_button.global_position.y + 2 * edit_button.size.y options_dialog.popup(Rect2i(position + Vector2i(x_pos, y_pos), options_dialog.size)) current_tag_id = _tag_id - var animation_tag: AnimationTag = Global.current_project.animation_tags[_tag_id] - options_dialog.get_node("GridContainer/NameLineEdit").text = animation_tag.name - options_dialog.get_node("GridContainer/ColorPickerButton").color = animation_tag.color - options_dialog.get_node("GridContainer/FromSpinBox").value = animation_tag.from - options_dialog.get_node("GridContainer/ToSpinBox").value = animation_tag.to + var animation_tag := Global.current_project.animation_tags[_tag_id] + name_line_edit.text = animation_tag.name + color_picker_button.color = animation_tag.color + from_spinbox.value = animation_tag.from + to_spinbox.value = animation_tag.to + user_data_text_edit.text = animation_tag.user_data if !delete_tag_button: delete_tag_button = options_dialog.add_button("Delete", true, "delete_tag") else: @@ -95,10 +100,11 @@ func _on_EditButton_pressed(_tag_id: int, edit_button: Button) -> void: func _on_TagOptions_confirmed() -> void: - var tag_name: String = options_dialog.get_node("GridContainer/NameLineEdit").text - var tag_color: Color = options_dialog.get_node("GridContainer/ColorPickerButton").color - var tag_from: int = options_dialog.get_node("GridContainer/FromSpinBox").value - var tag_to: int = options_dialog.get_node("GridContainer/ToSpinBox").value + var tag_name := name_line_edit.text + var tag_color := color_picker_button.color + var tag_from := from_spinbox.value + var tag_to := to_spinbox.value + var user_data := user_data_text_edit.text if tag_to > Global.current_project.frames.size(): tag_to = Global.current_project.frames.size() @@ -110,20 +116,22 @@ func _on_TagOptions_confirmed() -> void: # Loop through the tags to create new classes for them, so that they won't be the same # as Global.current_project.animation_tags's classes. Needed for undo/redo to work properly. for i in new_animation_tags.size(): + var prev_tag: AnimationTag = new_animation_tags[i] 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 + prev_tag.name, prev_tag.color, prev_tag.from, prev_tag.to ) + new_animation_tags[i].user_data = prev_tag.user_data if current_tag_id == Global.current_project.animation_tags.size(): - new_animation_tags.append(AnimationTag.new(tag_name, tag_color, tag_from, tag_to)) + var new_tag := AnimationTag.new(tag_name, tag_color, tag_from, tag_to) + new_tag.user_data = user_data + new_animation_tags.append(new_tag) else: new_animation_tags[current_tag_id].name = tag_name new_animation_tags[current_tag_id].color = tag_color new_animation_tags[current_tag_id].from = tag_from new_animation_tags[current_tag_id].to = tag_to + new_animation_tags[current_tag_id].user_data = user_data # Handle Undo/Redo Global.current_project.undos += 1 @@ -141,24 +149,25 @@ func _on_TagOptions_confirmed() -> void: func _on_TagOptions_custom_action(action: String) -> void: - if action == "delete_tag": - var new_animation_tags := Global.current_project.animation_tags.duplicate() - new_animation_tags.remove_at(current_tag_id) - # Handle Undo/Redo - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Delete Frame Tag") - Global.current_project.undo_redo.add_do_method(Global.general_redo) - Global.current_project.undo_redo.add_undo_method(Global.general_undo) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "animation_tags", new_animation_tags - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "animation_tags", Global.current_project.animation_tags - ) - Global.current_project.undo_redo.commit_action() + if action != "delete_tag": + return + var new_animation_tags := Global.current_project.animation_tags.duplicate() + new_animation_tags.remove_at(current_tag_id) + # Handle Undo/Redo + Global.current_project.undos += 1 + Global.current_project.undo_redo.create_action("Delete Frame Tag") + Global.current_project.undo_redo.add_do_method(Global.general_redo) + Global.current_project.undo_redo.add_undo_method(Global.general_undo) + Global.current_project.undo_redo.add_do_property( + Global.current_project, "animation_tags", new_animation_tags + ) + Global.current_project.undo_redo.add_undo_property( + Global.current_project, "animation_tags", Global.current_project.animation_tags + ) + Global.current_project.undo_redo.commit_action() - options_dialog.hide() - _on_FrameTagDialog_about_to_show() + options_dialog.hide() + _on_FrameTagDialog_about_to_show() func _on_TagOptions_visibility_changed() -> void: diff --git a/src/UI/Timeline/FrameTagDialog.tscn b/src/UI/Timeline/FrameTagDialog.tscn index 691cd0a04..e9f8d4d15 100644 --- a/src/UI/Timeline/FrameTagDialog.tscn +++ b/src/UI/Timeline/FrameTagDialog.tscn @@ -63,7 +63,7 @@ button_pressed = true text = "Animation plays only on frames of the same tag" [node name="TagOptions" type="ConfirmationDialog" parent="."] -size = Vector2i(303, 127) +size = Vector2i(303, 240) exclusive = false popup_window = true @@ -71,35 +71,41 @@ popup_window = true offset_left = 8.0 offset_top = 8.0 offset_right = 295.0 -offset_bottom = 78.0 +offset_bottom = 191.0 theme_override_constants/h_separation = 8 theme_override_constants/v_separation = 8 -columns = 4 +columns = 2 [node name="NameLabel" type="Label" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 text = "Name:" [node name="NameLineEdit" type="LineEdit" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 caret_blink = true caret_blink_interval = 0.5 [node name="ColorLabel" type="Label" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 text = "Color:" [node name="ColorPickerButton" type="ColorPickerButton" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 mouse_default_cursor_shape = 2 color = Color(1, 0, 0, 1) [node name="FromLabel" type="Label" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 text = "From:" [node name="FromSpinBox" type="SpinBox" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 mouse_default_cursor_shape = 2 min_value = 1.0 value = 1.0 @@ -107,15 +113,25 @@ allow_greater = true [node name="ToLabel" type="Label" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 text = "To:" [node name="ToSpinBox" type="SpinBox" parent="TagOptions/GridContainer"] layout_mode = 2 +size_flags_horizontal = 3 mouse_default_cursor_shape = 2 min_value = 1.0 value = 1.0 allow_greater = true +[node name="UserDataLabel" type="Label" parent="TagOptions/GridContainer"] +layout_mode = 2 +text = "User data:" + +[node name="UserDataTextEdit" type="TextEdit" parent="TagOptions/GridContainer"] +layout_mode = 2 +scroll_fit_content_height = true + [connection signal="about_to_popup" from="." to="." method="_on_FrameTagDialog_about_to_show"] [connection signal="visibility_changed" from="." to="." method="_on_FrameTagDialog_visibility_changed"] [connection signal="pressed" from="VBoxContainer/ScrollContainer/VBoxTagContainer/AddTag" to="." method="_on_AddTag_pressed"]