mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-30 23:19:49 +00:00
Add cel properties and z-index to individual cels
This commit is contained in:
parent
f893e68d59
commit
c0a8202145
|
@ -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 ""
|
||||
|
|
|
@ -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 := {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_image: Image
|
||||
if Global.display_layer_effects:
|
||||
cel_image = layer.display_effects(current_cels[i])
|
||||
else:
|
||||
cel_image = current_cels[i].get_image()
|
||||
textures.append(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))
|
||||
# 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))
|
||||
else:
|
||||
layer_metadata_image.set_pixel(i, 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))
|
||||
else:
|
||||
layer_metadata_image.set_pixel(i, 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:
|
||||
if not test_array in project.selected_cels:
|
||||
continue
|
||||
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))
|
||||
textures[ordered_index] = cel_image
|
||||
# Store the blend mode
|
||||
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(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())
|
||||
# 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(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():
|
||||
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, 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(
|
||||
ordered_index, 1, Color(cel.opacity, 0.0, 0.0, 0.0)
|
||||
)
|
||||
else:
|
||||
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(
|
||||
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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue