diff --git a/Translations/Translations.pot b/Translations/Translations.pot index ccdeaa68f..d8a6bd01f 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -1815,12 +1815,15 @@ msgstr "" msgid "Unlink Cels" msgstr "" -msgid "Frame Properties" +msgid "Properties" msgstr "" msgid "Frame properties" msgstr "" +msgid "Cel properties" +msgstr "" + #. Found on the popup menu that appears when a user right-clicks on a frame button. When clicked, the order of the selected frames is being reversed. msgid "Reverse Frames" msgstr "" diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 8c550e67c..e8ec9475f 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -21,22 +21,23 @@ func blend_layers( # the second are the opacities and the third are the origins var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8) for i in project.layers.size(): - var layer := project.layers[i] + var ordered_index := project.ordered_layers[i] + var layer := project.layers[ordered_index] var include := true if layer.is_visible_in_hierarchy() else false if only_selected and include: var test_array := [project.frames.find(frame), i] if not test_array in project.selected_cels: include = false - var cel := frame.cels[i] + var cel := frame.cels[ordered_index] var cel_image := layer.display_effects(cel) textures.append(cel_image) # Store the blend mode - metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)) + metadata_image.set_pixel(ordered_index, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)) # Store the opacity if include: - metadata_image.set_pixel(i, 1, Color(cel.opacity, 0.0, 0.0, 0.0)) + metadata_image.set_pixel(ordered_index, 1, Color(cel.opacity, 0.0, 0.0, 0.0)) else: - metadata_image.set_pixel(i, 1, Color()) + metadata_image.set_pixel(ordered_index, 1, Color()) var texture_array := Texture2DArray.new() texture_array.create_from_images(textures) var params := { diff --git a/src/Classes/Cels/BaseCel.gd b/src/Classes/Cels/BaseCel.gd index 8e0d08a8b..12d31739b 100644 --- a/src/Classes/Cels/BaseCel.gd +++ b/src/Classes/Cels/BaseCel.gd @@ -1,9 +1,9 @@ class_name BaseCel extends RefCounted ## Base class for cel properties. -## The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel). +## "Cel" is short for the term "celluloid" [url]https://en.wikipedia.org/wiki/Cel[/url]. -signal texture_changed ## Emitted whenever cel's tecture is changed +signal texture_changed ## Emitted whenever the cel's texture is changed var opacity := 1.0 ## Opacity/Transparency of the cel. ## The image stored in the cel. @@ -14,6 +14,7 @@ var image_texture: Texture2D: ## [br] If the cel is not linked then it is [code]null[/code]. var link_set = null # { "cels": Array, "hue": float } or null var transformed_content: Image ## Used in transformations (moving, scaling etc with selections). +var z_index := 0 # Methods to Override: @@ -70,12 +71,14 @@ func update_texture() -> void: ## Returns a curated [Dictionary] containing the cel data. func serialize() -> Dictionary: - return {"opacity": opacity} + return {"opacity": opacity, "z_index": z_index} ## 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"] ## Used to perform cleanup after a cel is removed. diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 2d5d10065..f649b5f79 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -33,14 +33,15 @@ var frames: Array[Frame] = [] var layers: Array[BaseLayer] = [] var current_frame := 0 var current_layer := 0 -var selected_cels := [[0, 0]] # Array of Arrays of 2 integers (frame & layer) +var selected_cels := [[0, 0]] ## Array of Arrays of 2 integers (frame & layer) +var ordered_layers: Array[int] = [0] var animation_tags: Array[AnimationTag] = []: set = _animation_tags_changed var guides: Array[Guide] = [] var brushes: Array[Image] = [] var reference_images: Array[ReferenceImage] = [] -var vanishing_points := [] # Array of Vanishing Points +var vanishing_points := [] ## Array of Vanishing Points var fps := 6.0 var x_symmetry_point: float @@ -49,15 +50,15 @@ 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) +## This is useful for when the selection is outside of the canvas boundaries, +## on the left and/or above (negative coords) var selection_offset := Vector2i.ZERO: set(value): selection_offset = value Global.canvas.selection.marching_ants_outline.offset = selection_offset var has_selection := false -# For every camera (currently there are 3) +## For every camera (currently there are 3) var cameras_rotation: PackedFloat32Array = [0.0, 0.0, 0.0] var cameras_zoom: PackedVector2Array = [ Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15) @@ -436,6 +437,7 @@ func deserialize(dict: Dictionary) -> void: if dict.has("fps"): fps = dict.fps _deserialize_metadata(self, dict) + order_layers() func _serialize_metadata(object: Object) -> Dictionary: @@ -613,6 +615,25 @@ func find_first_drawable_cel(frame := frames[current_frame]) -> BaseCel: return result +func order_layers(frame_index := current_frame) -> void: + ordered_layers = [] + for i in layers.size(): + ordered_layers.append(i) + ordered_layers.sort_custom(_z_index_sort.bind(frame_index)) + + +func _z_index_sort(a: int, b: int, frame_index: int) -> bool: + var z_index_a := frames[frame_index].cels[a].z_index + var z_index_b := frames[frame_index].cels[b].z_index + var layer_index_a := layers[a].index + z_index_a + var layer_index_b := layers[b].index + z_index_b + if layer_index_a < layer_index_b: + return true + if layer_index_a == layer_index_b and z_index_a < z_index_b: + return true + return false + + # Timeline modifications # Modifying layers or frames Arrays on the current project should generally only be done # through these methods. @@ -832,6 +853,7 @@ func _update_frame_ui() -> void: ## Update the layer indices and layer/cel buttons func _update_layer_ui() -> void: + order_layers() for l in layers.size(): layers[l].index = l Global.layer_vbox.get_child(layers.size() - 1 - l).layer_index = l diff --git a/src/UI/Canvas/Canvas.gd b/src/UI/Canvas/Canvas.gd index 89f560190..f38b8eaac 100644 --- a/src/UI/Canvas/Canvas.gd +++ b/src/UI/Canvas/Canvas.gd @@ -29,6 +29,8 @@ var layer_metadata_texture := ImageTexture.new() func _ready() -> void: + material.set_shader_parameter("layers", layer_texture_array) + material.set_shader_parameter("metadata", layer_metadata_texture) Global.project_changed.connect(queue_redraw) onion_past.type = onion_past.PAST onion_past.blue_red_color = Global.onion_skinning_past_color @@ -124,7 +126,7 @@ func update_texture(layer_i: int, frame_i := -1, project := Global.current_proje cel_image.get_size() == Vector2i(layer_texture_array.get_width(), layer_texture_array.get_height()) ): - layer_texture_array.update_layer(cel_image, layer_i) + layer_texture_array.update_layer(cel_image, project.ordered_layers[layer_i]) func update_selected_cels_textures(project := Global.current_project) -> void: @@ -146,60 +148,72 @@ func draw_layers() -> void: ) if recreate_texture_array: var textures: Array[Image] = [] + textures.resize(project.layers.size()) # Nx3 texture, where N is the number of layers and the first row are the blend modes, # the second are the opacities and the third are the origins layer_metadata_image = Image.create(project.layers.size(), 3, false, Image.FORMAT_RG8) # Draw current frame layers for i in project.layers.size(): + var ordered_index := project.ordered_layers[i] var layer := project.layers[i] + var cel := current_cels[i] var cel_image: Image if Global.display_layer_effects: - cel_image = layer.display_effects(current_cels[i]) + cel_image = layer.display_effects(cel) else: - cel_image = current_cels[i].get_image() - textures.append(cel_image) + cel_image = cel.get_image() + textures[ordered_index] = cel_image # Store the blend mode - layer_metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)) + layer_metadata_image.set_pixel( + ordered_index, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0) + ) # Store the opacity if layer.is_visible_in_hierarchy(): - layer_metadata_image.set_pixel(i, 1, Color(current_cels[i].opacity, 0.0, 0.0, 0.0)) + layer_metadata_image.set_pixel(ordered_index, 1, Color(cel.opacity, 0.0, 0.0, 0.0)) else: - layer_metadata_image.set_pixel(i, 1, Color()) + layer_metadata_image.set_pixel(ordered_index, 1, Color()) # Store the origin if [project.current_frame, i] in project.selected_cels: var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size()) - layer_metadata_image.set_pixel(i, 2, Color(origin.x, origin.y, 0.0, 0.0)) + layer_metadata_image.set_pixel( + ordered_index, 2, Color(origin.x, origin.y, 0.0, 0.0) + ) else: - layer_metadata_image.set_pixel(i, 2, Color()) + layer_metadata_image.set_pixel(ordered_index, 2, Color()) layer_texture_array.create_from_images(textures) layer_metadata_texture.set_image(layer_metadata_image) else: # Update the TextureArray if layer_texture_array.get_layers() > 0: for i in project.layers.size(): - var layer := project.layers[i] - var test_array := [project.current_frame, i] if not update_all_layers: + var test_array := [project.current_frame, i] if not test_array in project.selected_cels: continue + var ordered_index := project.ordered_layers[i] + var layer := project.layers[i] var cel := current_cels[i] var cel_image: Image if Global.display_layer_effects: cel_image = layer.display_effects(cel) else: cel_image = cel.get_image() - layer_texture_array.update_layer(cel_image, i) - layer_metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0)) + layer_texture_array.update_layer(cel_image, ordered_index) + layer_metadata_image.set_pixel( + ordered_index, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0) + ) if layer.is_visible_in_hierarchy(): - layer_metadata_image.set_pixel(i, 1, Color(cel.opacity, 0.0, 0.0, 0.0)) + layer_metadata_image.set_pixel( + ordered_index, 1, Color(cel.opacity, 0.0, 0.0, 0.0) + ) else: - layer_metadata_image.set_pixel(i, 1, Color()) + layer_metadata_image.set_pixel(ordered_index, 1, Color()) var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size()) - layer_metadata_image.set_pixel(i, 2, Color(origin.x, origin.y, 0.0, 0.0)) + layer_metadata_image.set_pixel( + ordered_index, 2, Color(origin.x, origin.y, 0.0, 0.0) + ) layer_metadata_texture.update(layer_metadata_image) - material.set_shader_parameter("layers", layer_texture_array) - material.set_shader_parameter("metadata", layer_metadata_texture) material.set_shader_parameter("origin_x_positive", move_preview_location.x > 0) material.set_shader_parameter("origin_y_positive", move_preview_location.y > 0) update_all_layers = false diff --git a/src/UI/Canvas/CanvasPreview.gd b/src/UI/Canvas/CanvasPreview.gd index 63d7ca7c4..888b7aff7 100644 --- a/src/UI/Canvas/CanvasPreview.gd +++ b/src/UI/Canvas/CanvasPreview.gd @@ -80,7 +80,7 @@ func _draw_layers() -> void: # the second are the opacities and the third are the origins var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8) # Draw current frame layers - for i in project.layers.size(): + for i in project.ordered_layers: if current_cels[i] is GroupCel: continue var layer := project.layers[i] diff --git a/src/UI/Timeline/CelButton.gd b/src/UI/Timeline/CelButton.gd index cd3a224eb..e0fc0861f 100644 --- a/src/UI/Timeline/CelButton.gd +++ b/src/UI/Timeline/CelButton.gd @@ -1,6 +1,6 @@ extends Button -enum MenuOptions { DELETE, LINK, UNLINK, PROPERTIES } +enum MenuOptions { PROPERTIES, DELETE, LINK, UNLINK } var frame := 0 var layer := 0 @@ -10,6 +10,7 @@ var cel: BaseCel @onready var linked_indicator: Polygon2D = get_node_or_null("LinkedIndicator") @onready var cel_texture: TextureRect = $CelTexture @onready var transparent_checker: ColorRect = $CelTexture/TransparentChecker +@onready var properties: AcceptDialog = $Properties func _ready() -> void: @@ -98,6 +99,8 @@ func _on_CelButton_pressed() -> void: func _on_PopupMenu_id_pressed(id: int) -> void: match id: + MenuOptions.PROPERTIES: + properties.popup_centered() MenuOptions.DELETE: _delete_cel_content() @@ -294,3 +297,14 @@ func _get_region_rect(x_begin: float, x_end: float) -> Rect2: rect.position.x += rect.size.x * x_begin rect.size.x *= x_end - x_begin return rect + + +func _on_z_index_slider_value_changed(value: float) -> void: + cel.z_index = value + Global.current_project.order_layers() + Global.canvas.update_all_layers = true + Global.canvas.queue_redraw() + + +func _on_properties_visibility_changed() -> void: + Global.dialog_open(properties.visible) diff --git a/src/UI/Timeline/CelButton.tscn b/src/UI/Timeline/CelButton.tscn index e068d78b1..66df574f7 100644 --- a/src/UI/Timeline/CelButton.tscn +++ b/src/UI/Timeline/CelButton.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=5 format=3 uid="uid://dw7ci3uixjuev"] +[gd_scene load_steps=6 format=3 uid="uid://dw7ci3uixjuev"] [ext_resource type="Script" path="res://src/UI/Timeline/CelButton.gd" id="1_iewgo"] [ext_resource type="PackedScene" uid="uid://3pmb60gpst7b" path="res://src/UI/Nodes/TransparentChecker.tscn" id="2_mi8wp"] [ext_resource type="Shader" path="res://src/Shaders/TransparentChecker.gdshader" id="3_qv21g"] +[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="4_wcpcc"] [sub_resource type="ShaderMaterial" id="1"] shader = ExtResource("3_qv21g") @@ -53,13 +54,15 @@ grow_horizontal = 2 grow_vertical = 2 [node name="PopupMenu" type="PopupMenu" parent="."] -item_count = 3 -item_0/text = "Delete" -item_0/id = -1 -item_1/text = "Link Cels to" -item_1/id = -1 -item_2/text = "Unlink Cels" +item_count = 4 +item_0/text = "Properties" +item_0/id = 0 +item_1/text = "Delete" +item_1/id = 1 +item_2/text = "Link Cels to" item_2/id = 2 +item_3/text = "Unlink Cels" +item_3/id = 3 [node name="LinkedIndicator" type="Polygon2D" parent="."] color = Color(0, 1, 0, 1) @@ -67,6 +70,43 @@ invert_enabled = true invert_border = 1.0 polygon = PackedVector2Array(0, 0, 36, 0, 36, 36, 0, 36) +[node name="Properties" type="AcceptDialog" parent="."] +title = "Cel properties" +size = Vector2i(300, 100) +exclusive = false +popup_window = true + +[node name="GridContainer" type="GridContainer" parent="Properties"] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 292.0 +offset_bottom = 55.0 +columns = 2 + +[node name="Label" type="Label" parent="Properties/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Z-Index:" + +[node name="ZIndexSlider" type="TextureProgressBar" parent="Properties/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +focus_mode = 2 +mouse_default_cursor_shape = 2 +theme_type_variation = &"ValueSlider" +min_value = -64.0 +max_value = 64.0 +allow_greater = true +allow_lesser = true +nine_patch_stretch = true +stretch_margin_left = 3 +stretch_margin_top = 3 +stretch_margin_right = 3 +stretch_margin_bottom = 3 +script = ExtResource("4_wcpcc") + [connection signal="pressed" from="." to="." method="_on_CelButton_pressed"] [connection signal="resized" from="." to="." method="_on_CelButton_resized"] [connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"] +[connection signal="visibility_changed" from="Properties" to="." method="_on_properties_visibility_changed"] +[connection signal="value_changed" from="Properties/GridContainer/ZIndexSlider" to="." method="_on_z_index_slider_value_changed"] diff --git a/src/UI/Timeline/FrameButton.gd b/src/UI/Timeline/FrameButton.gd index fe3f68074..f50074fa8 100644 --- a/src/UI/Timeline/FrameButton.gd +++ b/src/UI/Timeline/FrameButton.gd @@ -1,6 +1,6 @@ extends Button -enum { REMOVE, CLONE, MOVE_LEFT, MOVE_RIGHT, PROPERTIES, REVERSE, CENTER } +enum { PROPERTIES, REMOVE, CLONE, MOVE_LEFT, MOVE_RIGHT, REVERSE, CENTER } var frame := 0 diff --git a/src/UI/Timeline/FrameButton.tscn b/src/UI/Timeline/FrameButton.tscn index 1a822cbf1..39459e6c8 100644 --- a/src/UI/Timeline/FrameButton.tscn +++ b/src/UI/Timeline/FrameButton.tscn @@ -14,19 +14,19 @@ script = ExtResource("1") [node name="PopupMenu" type="PopupMenu" parent="."] item_count = 7 -item_0/text = "Remove Frame" +item_0/text = "Properties" item_0/id = -1 -item_0/disabled = true -item_1/text = "Clone Frame" +item_1/text = "Remove Frame" item_1/id = -1 -item_2/text = "Move Left" +item_1/disabled = true +item_2/text = "Clone Frame" item_2/id = -1 -item_2/disabled = true -item_3/text = "Move Right" +item_3/text = "Move Left" item_3/id = -1 item_3/disabled = true -item_4/text = "Frame Properties" +item_4/text = "Move Right" item_4/id = -1 +item_4/disabled = true item_5/text = "Reverse Frames" item_5/id = 5 item_5/disabled = true