mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00:00
Implement layer effects (#940)
* Basic logic for layer effects * Add an FX button and the ability to add effects, no way to remove or change properties of effects yet * Basic and ugly UI for adding and removing effects, no property changing yet * Swap effects * Fix preload shader paths * Change parameters for layer effects * Change gradient parameter in layer effect shaders, and other fixes * Use CollapsibleContainers for the shader properties * Set the correct gradient interpolation mode and color space in the UI * Make effects of group layers apply to children * Change `apply_fx` to `apply_effects`, formatting, some extra doc comments * Apply effects to other canvases, when merging layers and when exporting * Display humanized names of the shader unifrms * Some UI improvements to the LayerEffectsSettings * Add an Enabled button in the layer effects window, and change checkboxes to checkbuttons * Change BaseLayer.apply_effects() to take a cel as a parameter instead * Make layer effect buttons be affected by the modulate icon color * Add option in the View menu whether layer effects are displayed in the canvas or not * Rename `apply_effects()` to `display_effects()` * Add translation strings * Add nearest filter to the gradient map * Don't change Main.tscn * Fix more translations * Change the default cursor shape of the generated UI elements of the layer effects * Add undo/redo and effect application (apply effect destructively) There are some errors due to the usage of anonymous lambda methods in undo/redo, but it seems to be working well regardless. * Make layer effect application work on all cels
This commit is contained in:
parent
d532aee550
commit
08b03ae0e5
|
@ -311,6 +311,10 @@ msgstr ""
|
|||
msgid "Show Mouse Guides"
|
||||
msgstr ""
|
||||
|
||||
#. Found under the View menu. When enabled, non-destructive layer effects will be visible on the canvas.
|
||||
msgid "Display Layer Effects"
|
||||
msgstr ""
|
||||
|
||||
#. Found under the View menu.
|
||||
msgid "Snap To"
|
||||
msgstr ""
|
||||
|
@ -1620,6 +1624,9 @@ msgstr ""
|
|||
msgid "Zoom out"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Options:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2716,3 +2723,15 @@ msgstr ""
|
|||
|
||||
msgid "Blacks out the image and makes all opaque pixels a dark color."
|
||||
msgstr ""
|
||||
|
||||
#. Used in checkbuttons (like on/off switches) that enable/disable something.
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#. Refers to non-destructive effects (such as outline, drop shadow etc) that are applied to layers. Found in the title of the layer effects dialog.
|
||||
msgid "Layer effects"
|
||||
msgstr ""
|
||||
|
||||
#. A button that, when pressed, shows a list of effects to add. Found in the the layer effects dialog.
|
||||
msgid "Add effect"
|
||||
msgstr ""
|
||||
|
|
1
assets/graphics/misc/close.svg
Normal file
1
assets/graphics/misc/close.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3 3 10 10M3 13 13 3" fill="none" stroke="#fff" stroke-width="2" stroke-opacity=".898"/></svg>
|
After Width: | Height: | Size: 187 B |
37
assets/graphics/misc/close.svg.import
Normal file
37
assets/graphics/misc/close.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b4e44lb8k2pmt"
|
||||
path="res://.godot/imported/close.svg-11f3414f2f3de5550eee4cc42f4941c6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/close.svg"
|
||||
dest_files=["res://.godot/imported/close.svg-11f3414f2f3de5550eee4cc42f4941c6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.25
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
assets/graphics/misc/move_down_arrow.svg
Normal file
1
assets/graphics/misc/move_down_arrow.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path fill="#fff" fill-opacity=".784" d="m10 1043.4c-.26378.01-.5144.1165-.69726.3067l-3.293 3.2929-3.293-3.2929c-.18826-.1936-.44679-.3028-.7168-.3028-.89742.0002-1.3404 1.0909-.69727 1.7168l4 4c.39053.3904 1.0235.3904 1.4141 0l4-4c.65734-.6321.19491-1.7422-.7168-1.7207z" transform="translate(0 -1040.4)"/></svg>
|
After Width: | Height: | Size: 397 B |
37
assets/graphics/misc/move_down_arrow.svg.import
Normal file
37
assets/graphics/misc/move_down_arrow.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1qjs2ci67se4"
|
||||
path="res://.godot/imported/move_down_arrow.svg-76570684c2341024db5505cd94fb3ba5.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/move_down_arrow.svg"
|
||||
dest_files=["res://.godot/imported/move_down_arrow.svg-76570684c2341024db5505cd94fb3ba5.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.7
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
1
assets/graphics/misc/move_up_arrow.svg
Normal file
1
assets/graphics/misc/move_up_arrow.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path fill="#ffffff" fill-opacity="0.784" d="m 10,9.0131922 c -0.26378,-0.01 -0.5144,-0.1165 -0.69726,-0.3067 l -3.293,-3.2929 -3.293,3.2929 c -0.18826,0.1936 -0.44679,0.3028 -0.7168,0.3028 -0.89742,-2e-4 -1.3404,-1.0909 -0.69727,-1.7168 l 4,-4 c 0.39053,-0.3904 1.0235,-0.3904 1.4141,0 l 4,4 c 0.65734,0.6321 0.19491,1.7422 -0.7168,1.7207 z" /></svg>
|
After Width: | Height: | Size: 435 B |
37
assets/graphics/misc/move_up_arrow.svg.import
Normal file
37
assets/graphics/misc/move_up_arrow.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cxj0mtixk466v"
|
||||
path="res://.godot/imported/move_up_arrow.svg-01a22b2c21ca40bb8e863f736d2606de.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/misc/move_up_arrow.svg"
|
||||
dest_files=["res://.godot/imported/move_up_arrow.svg-01a22b2c21ca40bb8e863f736d2606de.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.7
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
|
@ -683,6 +683,10 @@ posterize={
|
|||
"deadzone": 0.5,
|
||||
"events": []
|
||||
}
|
||||
display_layer_effects={
|
||||
"deadzone": 0.5,
|
||||
"events": []
|
||||
}
|
||||
view_splash_screen={
|
||||
"deadzone": 0.5,
|
||||
"events": []
|
||||
|
|
|
@ -20,11 +20,13 @@ func blend_all_layers(
|
|||
for i in Global.current_project.layers.size():
|
||||
if current_cels[i] is GroupCel:
|
||||
continue
|
||||
if not Global.current_project.layers[i].is_visible_in_hierarchy():
|
||||
var layer := Global.current_project.layers[i]
|
||||
if not layer.is_visible_in_hierarchy():
|
||||
continue
|
||||
textures.append(current_cels[i].get_image())
|
||||
var cel_image := layer.display_effects(current_cels[i])
|
||||
textures.append(cel_image)
|
||||
opacities.append(current_cels[i].opacity)
|
||||
blend_modes.append(Global.current_project.layers[i].blend_mode)
|
||||
blend_modes.append(layer.blend_mode)
|
||||
var texture_array := Texture2DArray.new()
|
||||
texture_array.create_from_images(textures)
|
||||
var params := {
|
||||
|
@ -51,12 +53,14 @@ func blend_selected_cels(
|
|||
continue
|
||||
if frame.cels[cel_ind] is GroupCel:
|
||||
continue
|
||||
if not project.layers[cel_ind].is_visible_in_hierarchy():
|
||||
var layer := project.layers[cel_ind]
|
||||
if not layer.is_visible_in_hierarchy():
|
||||
continue
|
||||
var cel := frame.cels[cel_ind]
|
||||
textures.append(cel.get_image())
|
||||
var cel_image := layer.display_effects(cel)
|
||||
textures.append(cel_image)
|
||||
opacities.append(cel.opacity)
|
||||
blend_modes.append(Global.current_project.layers[cel_ind].blend_mode)
|
||||
blend_modes.append(layer.blend_mode)
|
||||
var texture_array := Texture2DArray.new()
|
||||
texture_array.create_from_images(textures)
|
||||
var params := {
|
||||
|
|
|
@ -34,6 +34,7 @@ enum ViewMenu {
|
|||
SHOW_RULERS,
|
||||
SHOW_GUIDES,
|
||||
SHOW_MOUSE_GUIDES,
|
||||
DISPLAY_LAYER_EFFECTS,
|
||||
SNAP_TO,
|
||||
}
|
||||
## Enumeration of items present in the Window Menu.
|
||||
|
@ -69,6 +70,8 @@ const OVERRIDE_FILE := "override.cfg"
|
|||
const HOME_SUBDIR_NAME := "pixelorama"
|
||||
## The name of folder that contains subdirectories for users to place brushes, palettes, patterns.
|
||||
const CONFIG_SUBDIR_NAME := "pixelorama_data"
|
||||
const VALUE_SLIDER_V2_TSCN := preload("res://src/UI/Nodes/ValueSliderV2.tscn")
|
||||
const GRADIENT_EDIT_TSCN := preload("res://src/UI/Nodes/GradientEdit.tscn")
|
||||
|
||||
## It is path to the executable's base drectory.
|
||||
var root_directory := "."
|
||||
|
@ -392,6 +395,12 @@ var show_rulers := true
|
|||
var show_guides := true
|
||||
## If [code]true[/code], the mouse guides are visible.
|
||||
var show_mouse_guides := false
|
||||
var display_layer_effects := true:
|
||||
set(value):
|
||||
display_layer_effects = value
|
||||
if is_instance_valid(top_menu_container):
|
||||
top_menu_container.view_menu.set_item_checked(ViewMenu.DISPLAY_LAYER_EFFECTS, value)
|
||||
canvas.queue_redraw()
|
||||
## If [code]true[/code], cursor snaps to the boundary of rectangular grid boxes.
|
||||
var snap_to_rectangular_grid_boundary := false
|
||||
## If [code]true[/code], cursor snaps to the center of rectangular grid boxes.
|
||||
|
@ -607,6 +616,7 @@ func _initialize_keychain() -> void:
|
|||
"show_pixel_grid": Keychain.InputAction.new("", "View menu", true),
|
||||
"show_guides": Keychain.InputAction.new("", "View menu", true),
|
||||
"show_rulers": Keychain.InputAction.new("", "View menu", true),
|
||||
&"display_layer_effects": Keychain.InputAction.new("", "View menu", true),
|
||||
"moveable_panels": Keychain.InputAction.new("", "Window menu", true),
|
||||
"zen_mode": Keychain.InputAction.new("", "Window menu", true),
|
||||
"toggle_fullscreen": Keychain.InputAction.new("", "Window menu", true),
|
||||
|
@ -900,3 +910,220 @@ func undo_redo_move(diff: Vector2i, images: Array[Image]) -> void:
|
|||
image_copy.copy_from(image)
|
||||
image.fill(Color(0, 0, 0, 0))
|
||||
image.blit_rect(image_copy, Rect2i(Vector2i.ZERO, image.get_size()), diff)
|
||||
|
||||
|
||||
func create_ui_for_shader_uniforms(
|
||||
shader: Shader,
|
||||
params: Dictionary,
|
||||
parent_node: Control,
|
||||
value_changed: Callable,
|
||||
file_selected: Callable
|
||||
) -> void:
|
||||
var code := shader.code.split("\n")
|
||||
var uniforms: PackedStringArray = []
|
||||
for line in code:
|
||||
if line.begins_with("uniform"):
|
||||
uniforms.append(line)
|
||||
|
||||
for uniform in uniforms:
|
||||
# Example uniform:
|
||||
# uniform float parameter_name : hint_range(0, 255) = 100.0;
|
||||
var uniform_split := uniform.split("=")
|
||||
var u_value := ""
|
||||
if uniform_split.size() > 1:
|
||||
u_value = uniform_split[1].replace(";", "").strip_edges()
|
||||
else:
|
||||
uniform_split[0] = uniform_split[0].replace(";", "").strip_edges()
|
||||
|
||||
var u_left_side := uniform_split[0].split(":")
|
||||
var u_hint := ""
|
||||
if u_left_side.size() > 1:
|
||||
u_hint = u_left_side[1].strip_edges()
|
||||
u_hint = u_hint.replace(";", "")
|
||||
|
||||
var u_init := u_left_side[0].split(" ")
|
||||
var u_type := u_init[1]
|
||||
var u_name := u_init[2]
|
||||
var humanized_u_name := Keychain.humanize_snake_case(u_name) + ":"
|
||||
|
||||
if u_type == "float" or u_type == "int":
|
||||
var label := Label.new()
|
||||
label.text = humanized_u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var slider := ValueSlider.new()
|
||||
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var min_value := 0.0
|
||||
var max_value := 255.0
|
||||
var step := 1.0
|
||||
var range_values_array: PackedStringArray
|
||||
if "hint_range" in u_hint:
|
||||
var range_values: String = u_hint.replace("hint_range(", "")
|
||||
range_values = range_values.replace(")", "").strip_edges()
|
||||
range_values_array = range_values.split(",")
|
||||
|
||||
if u_type == "float":
|
||||
if range_values_array.size() >= 1:
|
||||
min_value = float(range_values_array[0])
|
||||
else:
|
||||
min_value = 0.01
|
||||
|
||||
if range_values_array.size() >= 2:
|
||||
max_value = float(range_values_array[1])
|
||||
|
||||
if range_values_array.size() >= 3:
|
||||
step = float(range_values_array[2])
|
||||
else:
|
||||
step = 0.01
|
||||
|
||||
if u_value != "":
|
||||
slider.value = float(u_value)
|
||||
else:
|
||||
if range_values_array.size() >= 1:
|
||||
min_value = int(range_values_array[0])
|
||||
|
||||
if range_values_array.size() >= 2:
|
||||
max_value = int(range_values_array[1])
|
||||
|
||||
if range_values_array.size() >= 3:
|
||||
step = int(range_values_array[2])
|
||||
|
||||
if u_value != "":
|
||||
slider.value = int(u_value)
|
||||
if params.has(u_name):
|
||||
slider.value = params[u_name]
|
||||
else:
|
||||
params[u_name] = slider.value
|
||||
slider.min_value = min_value
|
||||
slider.max_value = max_value
|
||||
slider.step = step
|
||||
slider.value_changed.connect(value_changed.bind(u_name))
|
||||
slider.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(slider)
|
||||
parent_node.add_child(hbox)
|
||||
elif u_type == "vec2":
|
||||
var label := Label.new()
|
||||
label.text = humanized_u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var vector2 := _vec2str_to_vector2(u_value)
|
||||
var slider := VALUE_SLIDER_V2_TSCN.instantiate() as ValueSliderV2
|
||||
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
slider.value = vector2
|
||||
if params.has(u_name):
|
||||
slider.value = params[u_name]
|
||||
else:
|
||||
params[u_name] = slider.value
|
||||
slider.value_changed.connect(value_changed.bind(u_name))
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(slider)
|
||||
parent_node.add_child(hbox)
|
||||
elif u_type == "vec4":
|
||||
if "source_color" in u_hint:
|
||||
var label := Label.new()
|
||||
label.text = humanized_u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var color := _vec4str_to_color(u_value)
|
||||
var color_button := ColorPickerButton.new()
|
||||
color_button.custom_minimum_size = Vector2(20, 20)
|
||||
color_button.color = color
|
||||
if params.has(u_name):
|
||||
color_button.color = params[u_name]
|
||||
else:
|
||||
params[u_name] = color_button.color
|
||||
color_button.color_changed.connect(value_changed.bind(u_name))
|
||||
color_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
color_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(color_button)
|
||||
parent_node.add_child(hbox)
|
||||
elif u_type == "sampler2D":
|
||||
if u_name == "selection":
|
||||
continue
|
||||
var label := Label.new()
|
||||
label.text = humanized_u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
if u_name.begins_with("gradient_"):
|
||||
var gradient_edit := GRADIENT_EDIT_TSCN.instantiate() as GradientEditNode
|
||||
gradient_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
if params.has(u_name) and params[u_name] is GradientTexture2D:
|
||||
gradient_edit.set_gradient_texture(params[u_name])
|
||||
else:
|
||||
params[u_name] = gradient_edit.texture
|
||||
value_changed.call(gradient_edit.get_node("TextureRect").texture, u_name)
|
||||
gradient_edit.updated.connect(
|
||||
func(_gradient, _cc): value_changed.call(gradient_edit.texture, u_name)
|
||||
)
|
||||
hbox.add_child(gradient_edit)
|
||||
else:
|
||||
var file_dialog := FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
file_dialog.size = Vector2(384, 281)
|
||||
file_dialog.file_selected.connect(file_selected.bind(u_name))
|
||||
var button := Button.new()
|
||||
button.text = "Load texture"
|
||||
button.pressed.connect(file_dialog.popup_centered)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
hbox.add_child(button)
|
||||
parent_node.add_child(file_dialog)
|
||||
parent_node.add_child(hbox)
|
||||
|
||||
elif u_type == "bool":
|
||||
var label := Label.new()
|
||||
label.text = humanized_u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var checkbox := CheckBox.new()
|
||||
checkbox.text = "On"
|
||||
if u_value == "true":
|
||||
checkbox.button_pressed = true
|
||||
if params.has(u_name):
|
||||
checkbox.button_pressed = params[u_name]
|
||||
else:
|
||||
params[u_name] = checkbox.button_pressed
|
||||
checkbox.toggled.connect(value_changed.bind(u_name))
|
||||
checkbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
checkbox.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(checkbox)
|
||||
parent_node.add_child(hbox)
|
||||
|
||||
|
||||
func _vec2str_to_vector2(vec2: String) -> Vector2:
|
||||
vec2 = vec2.replace("vec2(", "")
|
||||
vec2 = vec2.replace(")", "")
|
||||
var vec_values := vec2.split(",")
|
||||
if vec_values.size() == 0:
|
||||
return Vector2.ZERO
|
||||
var y := float(vec_values[0])
|
||||
if vec_values.size() == 2:
|
||||
y = float(vec_values[1])
|
||||
var vector2 := Vector2(float(vec_values[0]), y)
|
||||
return vector2
|
||||
|
||||
|
||||
func _vec4str_to_color(vec4: String) -> Color:
|
||||
vec4 = vec4.replace("vec4(", "")
|
||||
vec4 = vec4.replace(")", "")
|
||||
var rgba_values := vec4.split(",")
|
||||
var red := float(rgba_values[0])
|
||||
|
||||
var green := float(rgba_values[0])
|
||||
if rgba_values.size() >= 2:
|
||||
green = float(rgba_values[1])
|
||||
|
||||
var blue := float(rgba_values[0])
|
||||
if rgba_values.size() >= 3:
|
||||
blue = float(rgba_values[2])
|
||||
|
||||
var alpha := float(rgba_values[0])
|
||||
if rgba_values.size() == 4:
|
||||
alpha = float(rgba_values[3])
|
||||
var color := Color(red, green, blue, alpha)
|
||||
return color
|
||||
|
|
|
@ -29,7 +29,7 @@ enum BlendModes {
|
|||
}
|
||||
|
||||
var name := "" ## Name of the layer.
|
||||
var project: Project ## Project, the layer belongs to.
|
||||
var project: Project ## The project the layer belongs to.
|
||||
var index: int ## Index of layer in the timeline.
|
||||
var parent: BaseLayer ## Parent of the layer.
|
||||
var visible := true ## Sets visibility of the layer.
|
||||
|
@ -37,6 +37,8 @@ var locked := false ## Images of a locked layer won't be overritten.
|
|||
var new_cels_linked := false ## Determines if new cel of the layer should be linked or not.
|
||||
var blend_mode := BlendModes.NORMAL ## Blend mode of the current layer.
|
||||
var cel_link_sets: Array[Dictionary] = [] ## Each Dictionary represents a cel's "link set"
|
||||
var effects: Array[LayerEffect] ## An array for non-destructive effects of the layer.
|
||||
var effects_enabled := true ## If [code]true[/code], the effects are being applied.
|
||||
|
||||
|
||||
## Returns true if this is a direct or indirect parent of layer
|
||||
|
@ -48,7 +50,7 @@ func is_ancestor_of(layer: BaseLayer) -> bool:
|
|||
return false
|
||||
|
||||
|
||||
## Returns an [Array] of layers that are children of this layer.
|
||||
## Returns an [Array] of [BaseLayer]s that are children of this layer.
|
||||
## The process is recursive if [param recursive] is [code]true[/code].
|
||||
func get_children(recursive: bool) -> Array[BaseLayer]:
|
||||
var children: Array[BaseLayer] = []
|
||||
|
@ -110,6 +112,16 @@ func is_locked_in_hierarchy() -> bool:
|
|||
return locked
|
||||
|
||||
|
||||
## Returns an [Array] of [BaseLayer]s that are ancestors of this layer.
|
||||
## If there are no ancestors, returns an empty array.
|
||||
func get_ancestors() -> Array[BaseLayer]:
|
||||
var ancestors: Array[BaseLayer] = []
|
||||
if is_instance_valid(parent):
|
||||
ancestors.append(parent)
|
||||
ancestors.append_array(parent.get_ancestors())
|
||||
return ancestors
|
||||
|
||||
|
||||
## Returns the number of parents above this layer.
|
||||
func get_hierarchy_depth() -> int:
|
||||
if is_instance_valid(parent):
|
||||
|
@ -117,7 +129,7 @@ func get_hierarchy_depth() -> int:
|
|||
return 0
|
||||
|
||||
|
||||
## Returns the path of the layer in the timeline as a [String]
|
||||
## Returns the path of the layer in the timeline as a [String].
|
||||
func get_layer_path() -> String:
|
||||
if is_instance_valid(parent):
|
||||
return str(parent.get_layer_path(), "/", name)
|
||||
|
@ -162,6 +174,32 @@ func link_cel(cel: BaseCel, link_set = null) -> void:
|
|||
cel_link_sets.append(link_set)
|
||||
|
||||
|
||||
## Returns a copy of the [param cel]'s [Image] with all of the effects applied to it.
|
||||
## This method is not destructive as it does NOT change the data of the image,
|
||||
## it just returns a copy.
|
||||
func display_effects(cel: BaseCel) -> Image:
|
||||
var image := Image.new()
|
||||
image.copy_from(cel.get_image())
|
||||
if not effects_enabled:
|
||||
return image
|
||||
var image_size := image.get_size()
|
||||
for effect in effects:
|
||||
if not effect.enabled:
|
||||
continue
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
shader_image_effect.generate_image(image, effect.shader, effect.params, image_size)
|
||||
# Inherit effects from the parents
|
||||
for ancestor in get_ancestors():
|
||||
if not ancestor.effects_enabled:
|
||||
continue
|
||||
for effect in ancestor.effects:
|
||||
if not effect.enabled:
|
||||
continue
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
shader_image_effect.generate_image(image, effect.shader, effect.params, image_size)
|
||||
return image
|
||||
|
||||
|
||||
# Methods to Override:
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ func _about_to_popup() -> void:
|
|||
|
||||
# prepares "animate_panel.frames" according to affect
|
||||
func prepare_animator(project: Project) -> void:
|
||||
if not is_instance_valid(animate_panel):
|
||||
return
|
||||
var frames: PackedInt32Array = []
|
||||
if affect == SELECTED_CELS:
|
||||
for frame_layer in project.selected_cels:
|
||||
|
|
17
src/Classes/LayerEffect.gd
Normal file
17
src/Classes/LayerEffect.gd
Normal file
|
@ -0,0 +1,17 @@
|
|||
class_name LayerEffect
|
||||
extends RefCounted
|
||||
|
||||
var name := ""
|
||||
var shader: Shader
|
||||
var params := {}
|
||||
var enabled := true
|
||||
|
||||
|
||||
func _init(_name: String, _shader: Shader, _params := {}) -> void:
|
||||
name = _name
|
||||
shader = _shader
|
||||
params = _params
|
||||
|
||||
|
||||
func duplicate() -> LayerEffect:
|
||||
return LayerEffect.new(name, shader, params.duplicate())
|
|
@ -1,10 +1,10 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
uniform bool red;
|
||||
uniform bool blue;
|
||||
uniform bool green;
|
||||
uniform bool alpha;
|
||||
uniform bool red = true;
|
||||
uniform bool blue = true;
|
||||
uniform bool green = true;
|
||||
uniform bool alpha = false;
|
||||
uniform sampler2D selection;
|
||||
|
||||
float stolChannel(float x) {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
uniform vec2 shadow_offset; // Offset, in pixel coordinate [0, 1, 2, and so on]
|
||||
uniform vec4 shadow_color;
|
||||
uniform vec2 offset = vec2(5.0, 5.0); // Offset, in pixel coordinate [0, 1, 2, and so on]
|
||||
uniform vec4 shadow_color : source_color = vec4(0.08, 0.08, 0.08, 0.63);
|
||||
uniform sampler2D selection;
|
||||
|
||||
|
||||
void fragment() {
|
||||
vec2 offset = shadow_offset * TEXTURE_PIXEL_SIZE; // Normalize shadow_offset to [0..1]
|
||||
vec2 normalized_offset = offset * TEXTURE_PIXEL_SIZE; // Normalize offset to [0..1]
|
||||
vec4 original = texture(TEXTURE, UV); // Original texture
|
||||
float shadow = texture(TEXTURE, UV - offset).a; // Shadow, alpha only
|
||||
float shadow = texture(TEXTURE, UV - normalized_offset).a; // Shadow, alpha only
|
||||
shadow *= shadow_color.a; // Multiply this mask by shadow alpha
|
||||
shadow = mix(0.0, shadow, texture(selection, UV).a); // Clip shadow by selection mask
|
||||
shadow = mix(shadow, 0.0, original.a); // Erase shadow alpha on original area
|
||||
|
||||
// Make a border to prevent stretching pixels on the edge
|
||||
vec2 border_uv = UV - offset;
|
||||
vec2 border_uv = UV - normalized_offset;
|
||||
border_uv -= 0.5;
|
||||
border_uv *= 2.0;
|
||||
border_uv = abs(border_uv);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
uniform sampler2D map; // GradientTexture
|
||||
uniform sampler2D gradient_map : filter_nearest; // GradientTexture
|
||||
uniform sampler2D selection;
|
||||
|
||||
void fragment() {
|
||||
|
@ -9,7 +9,7 @@ void fragment() {
|
|||
vec4 selection_color = texture(selection, UV);
|
||||
vec4 output = original_color;
|
||||
float value = (0.2126 * original_color.r) + (0.7152 * original_color.g) + (0.0722 * original_color.b);
|
||||
vec4 gradient_color = texture(map, vec2(value, 0.0));
|
||||
vec4 gradient_color = texture(gradient_map, vec2(value, 0.0));
|
||||
output.rgb = gradient_color.rgb;
|
||||
output.a *= gradient_color.a;
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
uniform float hue_shift : hint_range(-1, 1);
|
||||
uniform float sat_shift : hint_range(-1, 1);
|
||||
uniform float val_shift : hint_range(-1, 1);
|
||||
uniform float hue : hint_range(-1, 1);
|
||||
uniform float saturation : hint_range(-1, 1);
|
||||
uniform float value : hint_range(-1, 1);
|
||||
uniform sampler2D selection;
|
||||
|
||||
vec3 rgb2hsb(vec3 c){
|
||||
|
@ -40,20 +40,20 @@ void fragment() {
|
|||
// If not greyscale
|
||||
if(col[0] != col[1] || col[1] != col[2]) {
|
||||
// Shift the color by shift_amount, but rolling over the value goes over 1
|
||||
hsb.x = mod(hsb.x + hue_shift, 1.0);
|
||||
hsb.x = mod(hsb.x + hue, 1.0);
|
||||
}
|
||||
if(sat_shift > 0.0) {
|
||||
hsb.y = mix(hsb.y, 1 , sat_shift);
|
||||
if(saturation > 0.0) {
|
||||
hsb.y = mix(hsb.y, 1 , saturation);
|
||||
}
|
||||
else if (sat_shift < 0.0) {
|
||||
hsb.y = mix(0, hsb.y , 1.0 - abs(sat_shift));
|
||||
else if (saturation < 0.0) {
|
||||
hsb.y = mix(0, hsb.y , 1.0 - abs(saturation));
|
||||
}
|
||||
|
||||
if(val_shift > 0.0) {
|
||||
hsb.z = mix(hsb.z, 1 , val_shift);
|
||||
if(value > 0.0) {
|
||||
hsb.z = mix(hsb.z, 1 , value);
|
||||
}
|
||||
else if (val_shift < 0.0) {
|
||||
hsb.z = mix(0, hsb.z , 1.0 - abs(val_shift));
|
||||
else if (value < 0.0) {
|
||||
hsb.z = mix(0, hsb.z , 1.0 - abs(value));
|
||||
}
|
||||
|
||||
col = hsb2rgb(hsb);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
uniform bool red;
|
||||
uniform bool blue;
|
||||
uniform bool green;
|
||||
uniform bool alpha;
|
||||
uniform bool red = true;
|
||||
uniform bool blue = true;
|
||||
uniform bool green = true;
|
||||
uniform bool alpha = false;
|
||||
uniform sampler2D selection;
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ shader_type canvas_item;
|
|||
|
||||
uniform sampler2D selection;
|
||||
uniform float colors : hint_range(1.0, 255.0) = 2.0;
|
||||
uniform float dither : hint_range(0.0, 0.5) = 0.0;
|
||||
uniform float dither_intensity : hint_range(0.0, 0.5) = 0.0;
|
||||
|
||||
void fragment()
|
||||
{
|
||||
|
@ -14,13 +14,13 @@ void fragment()
|
|||
float b = floor(mod(UV.y / TEXTURE_PIXEL_SIZE.y, 2.0));
|
||||
float c = mod(a + b, 2.0);
|
||||
vec4 col;
|
||||
col.r = (round(color.r * colors + dither) / colors) * c;
|
||||
col.g = (round(color.g * colors + dither) / colors) * c;
|
||||
col.b = (round(color.b * colors + dither) / colors) * c;
|
||||
col.r = (round(color.r * colors + dither_intensity) / colors) * c;
|
||||
col.g = (round(color.g * colors + dither_intensity) / colors) * c;
|
||||
col.b = (round(color.b * colors + dither_intensity) / colors) * c;
|
||||
c = 1.0 - c;
|
||||
col.r += (round(color.r * colors - dither) / colors) * c;
|
||||
col.g += (round(color.g * colors - dither) / colors) * c;
|
||||
col.b += (round(color.b * colors - dither) / colors) * c;
|
||||
col.r += (round(color.r * colors - dither_intensity) / colors) * c;
|
||||
col.g += (round(color.g * colors - dither_intensity) / colors) * c;
|
||||
col.b += (round(color.b * colors - dither_intensity) / colors) * c;
|
||||
col.a = color.a;
|
||||
vec4 output = mix(color.rgba, col, selection_color.a);
|
||||
COLOR = output;
|
||||
|
|
|
@ -120,7 +120,8 @@ func update_selected_cels_textures(project := Global.current_project) -> void:
|
|||
|
||||
|
||||
func draw_layers() -> void:
|
||||
var current_cels := Global.current_project.frames[Global.current_project.current_frame].cels
|
||||
var current_frame := Global.current_project.frames[Global.current_project.current_frame]
|
||||
var current_cels := current_frame.cels
|
||||
var textures: Array[Image] = []
|
||||
var opacities := PackedFloat32Array()
|
||||
var blend_modes := PackedInt32Array()
|
||||
|
@ -131,7 +132,11 @@ func draw_layers() -> void:
|
|||
continue
|
||||
var layer := Global.current_project.layers[i]
|
||||
if layer.is_visible_in_hierarchy():
|
||||
var cel_image := current_cels[i].get_image()
|
||||
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)
|
||||
opacities.append(current_cels[i].opacity)
|
||||
if [Global.current_project.current_frame, i] in Global.current_project.selected_cels:
|
||||
|
|
|
@ -72,7 +72,8 @@ func _draw() -> void:
|
|||
|
||||
|
||||
func _draw_layers() -> void:
|
||||
var current_cels := Global.current_project.frames[frame_index].cels
|
||||
var current_frame := Global.current_project.frames[frame_index]
|
||||
var current_cels := current_frame.cels
|
||||
var textures: Array[Image] = []
|
||||
var opacities := PackedFloat32Array()
|
||||
var blend_modes := PackedInt32Array()
|
||||
|
@ -80,10 +81,16 @@ func _draw_layers() -> void:
|
|||
for i in Global.current_project.layers.size():
|
||||
if current_cels[i] is GroupCel:
|
||||
continue
|
||||
if Global.current_project.layers[i].is_visible_in_hierarchy():
|
||||
textures.append(current_cels[i].get_image())
|
||||
var layer := Global.current_project.layers[i]
|
||||
if layer.is_visible_in_hierarchy():
|
||||
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)
|
||||
opacities.append(current_cels[i].opacity)
|
||||
blend_modes.append(Global.current_project.layers[i].blend_mode)
|
||||
blend_modes.append(layer.blend_mode)
|
||||
var texture_array := Texture2DArray.new()
|
||||
texture_array.create_from_images(textures)
|
||||
material.set_shader_parameter("layers", texture_array)
|
||||
|
|
|
@ -32,9 +32,7 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
|
|||
selection_tex = ImageTexture.create_from_image(project.selection_map)
|
||||
|
||||
var params := {
|
||||
"shadow_offset": Vector2(offset_x, offset_y),
|
||||
"shadow_color": color,
|
||||
"selection": selection_tex,
|
||||
"offset": Vector2(offset_x, offset_y), "shadow_color": color, "selection": selection_tex
|
||||
}
|
||||
if !has_been_confirmed:
|
||||
for param in params:
|
||||
|
|
|
@ -15,7 +15,7 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
|
|||
if selection_checkbox.button_pressed and project.has_selection:
|
||||
selection_tex = ImageTexture.create_from_image(project.selection_map)
|
||||
|
||||
var params := {"selection": selection_tex, "map": $VBoxContainer/GradientEdit.texture}
|
||||
var params := {"selection": selection_tex, "gradient_map": $VBoxContainer/GradientEdit.texture}
|
||||
|
||||
if !has_been_confirmed:
|
||||
for param in params:
|
||||
|
|
|
@ -32,7 +32,7 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
|
|||
if selection_checkbox.button_pressed and project.has_selection:
|
||||
selection_tex = ImageTexture.create_from_image(project.selection_map)
|
||||
|
||||
var params := {"hue_shift": hue, "sat_shift": sat, "val_shift": val, "selection": selection_tex}
|
||||
var params := {"hue": hue, "saturation": sat, "value": val, "selection": selection_tex}
|
||||
if !has_been_confirmed:
|
||||
for param in params:
|
||||
preview.material.set_shader_parameter(param, params[param])
|
||||
|
|
|
@ -17,7 +17,7 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
|
|||
if selection_checkbox.button_pressed and project.has_selection:
|
||||
selection_tex = ImageTexture.create_from_image(project.selection_map)
|
||||
|
||||
var params := {"colors": levels, "dither": dither, "selection": selection_tex}
|
||||
var params := {"colors": levels, "dither_intensity": dither, "selection": selection_tex}
|
||||
|
||||
if !has_been_confirmed:
|
||||
for param in params:
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
|
||||
[node name="Posterize" instance=ExtResource("1")]
|
||||
title = "Posterize"
|
||||
position = Vector2i(0, 36)
|
||||
size = Vector2i(360, 348)
|
||||
script = ExtResource("3")
|
||||
|
||||
[node name="VBoxContainer" parent="." index="3"]
|
||||
offset_bottom = 316.0
|
||||
offset_bottom = 299.0
|
||||
|
||||
[node name="ShowAnimate" parent="VBoxContainer" index="0"]
|
||||
visible = false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
extends ImageEffect
|
||||
|
||||
var shader: Shader
|
||||
var param_names: PackedStringArray = []
|
||||
var params := {}
|
||||
|
||||
@onready var shader_loaded_label: Label = $VBoxContainer/ShaderLoadedLabel
|
||||
@onready var shader_params: BoxContainer = $VBoxContainer/ShaderParams
|
||||
|
@ -21,10 +21,6 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
|
|||
if !shader:
|
||||
return
|
||||
|
||||
var params := {}
|
||||
for param in param_names:
|
||||
var param_data = preview.material.get_shader_parameter(param)
|
||||
params[param] = param_data
|
||||
var gen := ShaderImageEffect.new()
|
||||
gen.generate_image(cel, shader, params, project.size)
|
||||
|
||||
|
@ -51,210 +47,19 @@ func change_shader(shader_tmp: Shader, shader_name: String) -> void:
|
|||
shader = shader_tmp
|
||||
preview.material.shader = shader_tmp
|
||||
shader_loaded_label.text = tr("Shader loaded:") + " " + shader_name
|
||||
param_names.clear()
|
||||
params.clear()
|
||||
for child in shader_params.get_children():
|
||||
child.queue_free()
|
||||
|
||||
var code := shader.code.split("\n")
|
||||
var uniforms: PackedStringArray = []
|
||||
for line in code:
|
||||
if line.begins_with("uniform"):
|
||||
uniforms.append(line)
|
||||
|
||||
for uniform in uniforms:
|
||||
# Example uniform:
|
||||
# uniform float parameter_name : hint_range(0, 255) = 100.0;
|
||||
var uniform_split := uniform.split("=")
|
||||
var u_value := ""
|
||||
if uniform_split.size() > 1:
|
||||
u_value = uniform_split[1].replace(";", "").strip_edges()
|
||||
else:
|
||||
uniform_split[0] = uniform_split[0].replace(";", "").strip_edges()
|
||||
|
||||
var u_left_side := uniform_split[0].split(":")
|
||||
var u_hint := ""
|
||||
if u_left_side.size() > 1:
|
||||
u_hint = u_left_side[1].strip_edges()
|
||||
u_hint = u_hint.replace(";", "")
|
||||
|
||||
var u_init := u_left_side[0].split(" ")
|
||||
var u_type := u_init[1]
|
||||
var u_name := u_init[2]
|
||||
param_names.append(u_name)
|
||||
|
||||
if u_type == "float" or u_type == "int":
|
||||
var label := Label.new()
|
||||
label.text = u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var slider := ValueSlider.new()
|
||||
var min_value := 0.0
|
||||
var max_value := 255.0
|
||||
var step := 1.0
|
||||
var range_values_array: PackedStringArray
|
||||
if "hint_range" in u_hint:
|
||||
var range_values: String = u_hint.replace("hint_range(", "")
|
||||
range_values = range_values.replace(")", "").strip_edges()
|
||||
range_values_array = range_values.split(",")
|
||||
|
||||
if u_type == "float":
|
||||
if range_values_array.size() >= 1:
|
||||
min_value = float(range_values_array[0])
|
||||
else:
|
||||
min_value = 0.01
|
||||
|
||||
if range_values_array.size() >= 2:
|
||||
max_value = float(range_values_array[1])
|
||||
|
||||
if range_values_array.size() >= 3:
|
||||
step = float(range_values_array[2])
|
||||
else:
|
||||
step = 0.01
|
||||
|
||||
if u_value != "":
|
||||
slider.value = float(u_value)
|
||||
else:
|
||||
if range_values_array.size() >= 1:
|
||||
min_value = int(range_values_array[0])
|
||||
|
||||
if range_values_array.size() >= 2:
|
||||
max_value = int(range_values_array[1])
|
||||
|
||||
if range_values_array.size() >= 3:
|
||||
step = int(range_values_array[2])
|
||||
|
||||
if u_value != "":
|
||||
slider.value = int(u_value)
|
||||
slider.min_value = min_value
|
||||
slider.max_value = max_value
|
||||
slider.step = step
|
||||
slider.value_changed.connect(set_shader_parameter.bind(u_name))
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(slider)
|
||||
shader_params.add_child(hbox)
|
||||
elif u_type == "vec2":
|
||||
var label := Label.new()
|
||||
label.text = u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var vector2 := _vec2str_to_vector2(u_value)
|
||||
var slider1 := ValueSlider.new()
|
||||
slider1.value = vector2.x
|
||||
slider1.value_changed.connect(_set_vector2_shader_param.bind(u_name, true))
|
||||
var slider2 := ValueSlider.new()
|
||||
slider2.value = vector2.y
|
||||
slider2.value_changed.connect(_set_vector2_shader_param.bind(u_name, false))
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(slider1)
|
||||
hbox.add_child(slider2)
|
||||
shader_params.add_child(hbox)
|
||||
elif u_type == "vec4":
|
||||
if "source_color" in u_hint:
|
||||
var label := Label.new()
|
||||
label.text = u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var color := _vec4str_to_color(u_value)
|
||||
var color_button := ColorPickerButton.new()
|
||||
color_button.custom_minimum_size = Vector2(20, 20)
|
||||
color_button.color = color
|
||||
color_button.color_changed.connect(set_shader_parameter.bind(u_name))
|
||||
color_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(color_button)
|
||||
shader_params.add_child(hbox)
|
||||
elif u_type == "sampler2D":
|
||||
var label := Label.new()
|
||||
label.text = u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var file_dialog := FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
file_dialog.resizable = true
|
||||
file_dialog.custom_minimum_size = Vector2(200, 70)
|
||||
file_dialog.size = Vector2(384, 281)
|
||||
file_dialog.file_selected.connect(_load_texture.bind(u_name))
|
||||
var button := Button.new()
|
||||
button.text = "Load texture"
|
||||
button.pressed.connect(file_dialog.popup_centered)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(button)
|
||||
shader_params.add_child(hbox)
|
||||
shader_params.add_child(file_dialog)
|
||||
elif u_type == "bool":
|
||||
var label := Label.new()
|
||||
label.text = u_name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var checkbox := CheckBox.new()
|
||||
checkbox.text = "On"
|
||||
if u_value == "true":
|
||||
checkbox.button_pressed = true
|
||||
checkbox.toggled.connect(set_shader_parameter.bind(u_name))
|
||||
checkbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_child(label)
|
||||
hbox.add_child(checkbox)
|
||||
shader_params.add_child(hbox)
|
||||
Global.create_ui_for_shader_uniforms(
|
||||
shader_tmp, params, shader_params, _set_shader_parameter, _load_texture
|
||||
)
|
||||
|
||||
|
||||
# print("---")
|
||||
# print(uniform_split)
|
||||
# print(u_type)
|
||||
# print(u_name)
|
||||
# print(u_hint)
|
||||
# print(u_value)
|
||||
# print("--")
|
||||
|
||||
|
||||
func set_shader_parameter(value, param: String) -> void:
|
||||
func _set_shader_parameter(value, param: String) -> void:
|
||||
var mat: ShaderMaterial = preview.material
|
||||
mat.set_shader_parameter(param, value)
|
||||
|
||||
|
||||
func _set_vector2_shader_param(value: float, param: String, x: bool) -> void:
|
||||
var mat: ShaderMaterial = preview.material
|
||||
var vector2: Vector2 = mat.get_shader_parameter(param)
|
||||
if x:
|
||||
vector2.x = value
|
||||
else:
|
||||
vector2.y = value
|
||||
set_shader_parameter(vector2, param)
|
||||
|
||||
|
||||
func _vec2str_to_vector2(vec2: String) -> Vector2:
|
||||
vec2 = vec2.replace("vec2(", "")
|
||||
vec2 = vec2.replace(")", "")
|
||||
var vec_values: PackedStringArray = vec2.split(",")
|
||||
if vec_values.size() == 0:
|
||||
return Vector2.ZERO
|
||||
var y := float(vec_values[0])
|
||||
if vec_values.size() == 2:
|
||||
y = float(vec_values[1])
|
||||
var vector2 := Vector2(float(vec_values[0]), y)
|
||||
return vector2
|
||||
|
||||
|
||||
func _vec4str_to_color(vec4: String) -> Color:
|
||||
vec4 = vec4.replace("vec4(", "")
|
||||
vec4 = vec4.replace(")", "")
|
||||
var rgba_values: PackedStringArray = vec4.split(",")
|
||||
var red := float(rgba_values[0])
|
||||
|
||||
var green := float(rgba_values[0])
|
||||
if rgba_values.size() >= 2:
|
||||
green = float(rgba_values[1])
|
||||
|
||||
var blue := float(rgba_values[0])
|
||||
if rgba_values.size() >= 3:
|
||||
blue = float(rgba_values[2])
|
||||
|
||||
var alpha := float(rgba_values[0])
|
||||
if rgba_values.size() == 4:
|
||||
alpha = float(rgba_values[3])
|
||||
var color: Color = Color(red, green, blue, alpha)
|
||||
return color
|
||||
params[param] = value
|
||||
|
||||
|
||||
func _load_texture(path: String, param: String) -> void:
|
||||
|
@ -264,4 +69,4 @@ func _load_texture(path: String, param: String) -> void:
|
|||
print("Error loading texture")
|
||||
return
|
||||
var image_tex := ImageTexture.create_from_image(image)
|
||||
set_shader_parameter(image_tex, param)
|
||||
_set_shader_parameter(image_tex, param)
|
||||
|
|
|
@ -55,7 +55,7 @@ layout_mode = 2
|
|||
|
||||
[node name="FileDialog" type="FileDialog" parent="."]
|
||||
access = 2
|
||||
filters = PackedStringArray("*shader; Godot Shader File")
|
||||
filters = PackedStringArray("*gdshader; Godot Shader File")
|
||||
show_hidden_files = true
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/ChooseShader" to="." method="_on_ChooseShader_pressed"]
|
||||
|
|
|
@ -24,7 +24,7 @@ func _init() -> void:
|
|||
func _ready() -> void:
|
||||
_button.toggle_mode = true
|
||||
_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
_button.toggled.connect(_on_Button_toggled)
|
||||
_button.toggled.connect(set_visible_children)
|
||||
add_child(_button, false, Node.INTERNAL_MODE_FRONT)
|
||||
_texture_rect.anchor_top = 0.5
|
||||
_texture_rect.anchor_bottom = 0.5
|
||||
|
@ -53,11 +53,8 @@ func _notification(what: int) -> void:
|
|||
_texture_rect.texture = get_theme_icon("arrow_normal", "CollapsibleContainer")
|
||||
|
||||
|
||||
func _on_Button_toggled(button_pressed: bool) -> void:
|
||||
_set_visible(button_pressed)
|
||||
|
||||
|
||||
func _set_visible(pressed: bool) -> void:
|
||||
## Toggles whether the children of the container are visible or not
|
||||
func set_visible_children(pressed: bool) -> void:
|
||||
var angle := 0.0 if pressed else -90.0
|
||||
create_tween().tween_property(_texture_rect, "rotation_degrees", angle, 0.05)
|
||||
for child in get_children():
|
||||
|
|
|
@ -100,6 +100,8 @@ class GradientCursor:
|
|||
|
||||
func _ready() -> void:
|
||||
_create_cursors()
|
||||
%InterpolationOptionButton.select(gradient.interpolation_mode)
|
||||
%ColorSpaceOptionButton.select(gradient.interpolation_color_space)
|
||||
|
||||
|
||||
func _create_cursors() -> void:
|
||||
|
@ -163,12 +165,18 @@ func get_gradient_color(x: float) -> Color:
|
|||
return gradient.sample(x / x_offset)
|
||||
|
||||
|
||||
func set_gradient_texture(new_texture: GradientTexture2D) -> void:
|
||||
$TextureRect.texture = new_texture
|
||||
texture = new_texture
|
||||
gradient = texture.gradient
|
||||
|
||||
|
||||
func _on_ColorPicker_color_changed(color: Color) -> void:
|
||||
active_cursor.set_color(color)
|
||||
|
||||
|
||||
func _on_GradientEdit_resized() -> void:
|
||||
if not gradient:
|
||||
if not is_instance_valid(texture_rect):
|
||||
return
|
||||
x_offset = size.x - GradientCursor.WIDTH
|
||||
_create_cursors()
|
||||
|
@ -176,10 +184,12 @@ func _on_GradientEdit_resized() -> void:
|
|||
|
||||
func _on_InterpolationOptionButton_item_selected(index: Gradient.InterpolationMode) -> void:
|
||||
gradient.interpolation_mode = index
|
||||
updated.emit(gradient, continuous_change)
|
||||
|
||||
|
||||
func _on_color_space_option_button_item_selected(index: Gradient.ColorSpace) -> void:
|
||||
gradient.interpolation_color_space = index
|
||||
updated.emit(gradient, continuous_change)
|
||||
|
||||
|
||||
func _on_DivideButton_pressed() -> void:
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
[ext_resource type="Script" path="res://src/UI/Nodes/GradientEdit.gd" id="1"]
|
||||
|
||||
[sub_resource type="Gradient" id="1"]
|
||||
[sub_resource type="Gradient" id="Gradient_1k8kj"]
|
||||
resource_local_to_scene = true
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="2"]
|
||||
gradient = SubResource("1")
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_fau1l"]
|
||||
resource_local_to_scene = true
|
||||
gradient = SubResource("Gradient_1k8kj")
|
||||
|
||||
[node name="GradientEdit" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
|
@ -17,7 +19,7 @@ script = ExtResource("1")
|
|||
custom_minimum_size = Vector2(0, 30)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
texture = SubResource("2")
|
||||
texture = SubResource("GradientTexture2D_fau1l")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Value" type="Label" parent="TextureRect"]
|
||||
|
@ -34,10 +36,10 @@ offset_bottom = 7.0
|
|||
[node name="Popup" type="PopupPanel" parent="."]
|
||||
|
||||
[node name="ColorPicker" type="ColorPicker" parent="Popup"]
|
||||
offset_left = 12.0
|
||||
offset_top = 8.0
|
||||
offset_right = 298.0
|
||||
offset_bottom = 576.0
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = 294.0
|
||||
offset_bottom = 572.0
|
||||
|
||||
[node name="InterpolationContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
@ -48,6 +50,7 @@ size_flags_horizontal = 3
|
|||
text = "Interpolation:"
|
||||
|
||||
[node name="InterpolationOptionButton" type="OptionButton" parent="InterpolationContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
mouse_default_cursor_shape = 2
|
||||
|
@ -69,6 +72,7 @@ size_flags_horizontal = 3
|
|||
text = "Color space:"
|
||||
|
||||
[node name="ColorSpaceOptionButton" type="OptionButton" parent="ColorSpaceContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
mouse_default_cursor_shape = 2
|
||||
|
|
|
@ -907,9 +907,10 @@ func _on_MergeDownLayer_pressed() -> void:
|
|||
project.undo_redo.create_action("Merge Layer")
|
||||
|
||||
for frame in project.frames:
|
||||
top_cels.append(frame.cels[top_layer.index]) # Store for undo purposes
|
||||
var top_cel := frame.cels[top_layer.index]
|
||||
top_cels.append(top_cel) # Store for undo purposes
|
||||
|
||||
var top_image := frame.cels[top_layer.index].get_image()
|
||||
var top_image := top_layer.display_effects(top_cel)
|
||||
var bottom_cel := frame.cels[bottom_layer.index]
|
||||
var textures: Array[Image] = []
|
||||
var opacities := PackedFloat32Array()
|
||||
|
@ -1094,3 +1095,8 @@ func project_cel_removed(frame: int, layer: int) -> void:
|
|||
var cel_hbox := Global.cel_vbox.get_child(Global.cel_vbox.get_child_count() - 1 - layer)
|
||||
cel_hbox.get_child(frame).queue_free()
|
||||
cel_hbox.remove_child(cel_hbox.get_child(frame))
|
||||
|
||||
|
||||
func _on_layer_fx_pressed() -> void:
|
||||
$LayerEffectsSettings.popup_centered()
|
||||
Global.dialog_open(true)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=75 format=3 uid="uid://dbr6mulku2qju"]
|
||||
[gd_scene load_steps=76 format=3 uid="uid://dbr6mulku2qju"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/UI/Timeline/AnimationTimeline.gd" id="1"]
|
||||
[ext_resource type="Texture2D" uid="uid://d36mlbmq06q4e" path="res://assets/graphics/layers/new.png" id="2"]
|
||||
|
@ -20,6 +20,7 @@
|
|||
[ext_resource type="Texture2D" uid="uid://esistdjfbrc4" path="res://assets/graphics/timeline/play_backwards.png" id="24"]
|
||||
[ext_resource type="Texture2D" uid="uid://l4jj86y1hukm" path="res://assets/graphics/timeline/go_to_last_frame.png" id="25"]
|
||||
[ext_resource type="Texture2D" uid="uid://b2ndrc0cvy1m5" path="res://assets/graphics/timeline/next_frame.png" id="26"]
|
||||
[ext_resource type="PackedScene" uid="uid://dd1fkkc3vjh78" path="res://src/UI/Timeline/LayerEffects/LayerEffectsSettings.tscn" id="26_vbrbd"]
|
||||
[ext_resource type="Texture2D" uid="uid://cerkv5yx4cqeh" path="res://assets/graphics/timeline/copy_frame.png" id="27"]
|
||||
[ext_resource type="Texture2D" uid="uid://i13jhsg117kd" path="res://assets/graphics/timeline/tag.png" id="28"]
|
||||
[ext_resource type="Texture2D" uid="uid://dukip7mvotxsp" path="res://assets/graphics/timeline/onion_skinning_off.png" id="29"]
|
||||
|
@ -380,6 +381,11 @@ texture = ExtResource("5")
|
|||
[node name="HBoxContainer" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LayerFX" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "FX"
|
||||
|
||||
[node name="Label" type="Label" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Blend mode:"
|
||||
|
@ -922,6 +928,8 @@ autowrap_mode = 3
|
|||
|
||||
[node name="FrameTagDialog" parent="." instance=ExtResource("42")]
|
||||
|
||||
[node name="LayerEffectsSettings" parent="." instance=ExtResource("26_vbrbd")]
|
||||
|
||||
[node name="DragHighlight" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
|
@ -939,6 +947,7 @@ script = ExtResource("12")
|
|||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MoveDownLayer" to="." method="change_layer_order" binds= [false]]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/CloneLayer" to="." method="_on_CloneLayer_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MergeDownLayer" to="." method="_on_MergeDownLayer_pressed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/HBoxContainer/LayerFX" to="." method="_on_layer_fx_pressed"]
|
||||
[connection signal="item_selected" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/HBoxContainer/BlendModes" to="." method="_on_blend_modes_item_selected"]
|
||||
[connection signal="value_changed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/BlendingHBox/OpacitySlider" to="." method="_on_OpacitySlider_value_changed"]
|
||||
[connection signal="pressed" from="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer/AnimationTools/AnimationButtons/FrameButtons/AddFrame" to="." method="add_frame"]
|
||||
|
|
211
src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd
Normal file
211
src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd
Normal file
|
@ -0,0 +1,211 @@
|
|||
extends AcceptDialog
|
||||
|
||||
const DELETE_TEXTURE := preload("res://assets/graphics/misc/close.svg")
|
||||
const MOVE_UP_TEXTURE := preload("res://assets/graphics/misc/move_up_arrow.svg")
|
||||
const MOVE_DOWN_TEXTURE := preload("res://assets/graphics/misc/move_down_arrow.svg")
|
||||
|
||||
var effects: Array[LayerEffect] = [
|
||||
LayerEffect.new("Offset", preload("res://src/Shaders/Effects/OffsetPixels.gdshader")),
|
||||
LayerEffect.new("Outline", preload("res://src/Shaders/Effects/OutlineInline.gdshader")),
|
||||
LayerEffect.new("Drop Shadow", preload("res://src/Shaders/Effects/DropShadow.gdshader")),
|
||||
LayerEffect.new("Invert Colors", preload("res://src/Shaders/Effects/Invert.gdshader")),
|
||||
LayerEffect.new("Desaturation", preload("res://src/Shaders/Effects/Desaturate.gdshader")),
|
||||
LayerEffect.new(
|
||||
"Adjust Hue/Saturation/Value", preload("res://src/Shaders/Effects/HSV.gdshader")
|
||||
),
|
||||
LayerEffect.new("Posterize", preload("res://src/Shaders/Effects/Posterize.gdshader")),
|
||||
LayerEffect.new("Gradient Map", preload("res://src/Shaders/Effects/GradientMap.gdshader")),
|
||||
]
|
||||
|
||||
@onready var enabled_button: CheckButton = $VBoxContainer/HBoxContainer/EnabledButton
|
||||
@onready var effect_list: MenuButton = $VBoxContainer/HBoxContainer/EffectList
|
||||
@onready var effect_container: VBoxContainer = $VBoxContainer/ScrollContainer/EffectContainer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for effect in effects:
|
||||
effect_list.get_popup().add_item(effect.name)
|
||||
effect_list.get_popup().id_pressed.connect(_on_effect_list_id_pressed)
|
||||
|
||||
|
||||
func _on_about_to_popup() -> void:
|
||||
var layer := Global.current_project.layers[Global.current_project.current_layer]
|
||||
enabled_button.button_pressed = layer.effects_enabled
|
||||
for effect in layer.effects:
|
||||
_create_effect_ui(layer, effect)
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if not visible:
|
||||
Global.dialog_open(false)
|
||||
for child in effect_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func _on_effect_list_id_pressed(index: int) -> void:
|
||||
var layer := Global.current_project.layers[Global.current_project.current_layer]
|
||||
var effect := effects[index].duplicate()
|
||||
Global.current_project.undos += 1
|
||||
Global.current_project.undo_redo.create_action("Add layer effect")
|
||||
Global.current_project.undo_redo.add_do_method(func(): layer.effects.append(effect))
|
||||
Global.current_project.undo_redo.add_do_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
Global.current_project.undo_redo.add_undo_method(func(): layer.effects.erase(effect))
|
||||
Global.current_project.undo_redo.add_undo_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||
Global.current_project.undo_redo.commit_action()
|
||||
_create_effect_ui(layer, effect)
|
||||
|
||||
|
||||
func _create_effect_ui(layer: BaseLayer, effect: LayerEffect) -> void:
|
||||
var panel_container := PanelContainer.new()
|
||||
var vbox := VBoxContainer.new()
|
||||
var hbox := HBoxContainer.new()
|
||||
var enable_checkbox := CheckButton.new()
|
||||
enable_checkbox.button_pressed = effect.enabled
|
||||
enable_checkbox.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
enable_checkbox.toggled.connect(_enable_effect.bind(effect))
|
||||
var label := Label.new()
|
||||
label.text = effect.name
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var move_up_button := TextureButton.new()
|
||||
move_up_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
move_up_button.texture_normal = MOVE_UP_TEXTURE
|
||||
move_up_button.add_to_group(&"UIButtons")
|
||||
move_up_button.modulate = Global.modulate_icon_color
|
||||
move_up_button.pressed.connect(_re_order_effect.bind(effect, layer, panel_container, -1))
|
||||
var move_down_button := TextureButton.new()
|
||||
move_down_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
move_down_button.texture_normal = MOVE_DOWN_TEXTURE
|
||||
move_down_button.add_to_group(&"UIButtons")
|
||||
move_down_button.modulate = Global.modulate_icon_color
|
||||
move_down_button.pressed.connect(_re_order_effect.bind(effect, layer, panel_container, 1))
|
||||
var delete_button := TextureButton.new()
|
||||
delete_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
delete_button.texture_normal = DELETE_TEXTURE
|
||||
delete_button.add_to_group(&"UIButtons")
|
||||
delete_button.modulate = Global.modulate_icon_color
|
||||
delete_button.pressed.connect(_delete_effect.bind(effect))
|
||||
hbox.add_child(enable_checkbox)
|
||||
hbox.add_child(label)
|
||||
if layer is PixelLayer:
|
||||
var apply_button := Button.new()
|
||||
apply_button.text = "Apply"
|
||||
apply_button.pressed.connect(_apply_effect.bind(layer, effect))
|
||||
hbox.add_child(apply_button)
|
||||
hbox.add_child(move_up_button)
|
||||
hbox.add_child(move_down_button)
|
||||
hbox.add_child(delete_button)
|
||||
var parameter_vbox := CollapsibleContainer.new()
|
||||
parameter_vbox.text = "Options"
|
||||
Global.create_ui_for_shader_uniforms(
|
||||
effect.shader,
|
||||
effect.params,
|
||||
parameter_vbox,
|
||||
_set_parameter.bind(effect),
|
||||
_load_parameter_texture.bind(effect)
|
||||
)
|
||||
vbox.add_child(hbox)
|
||||
vbox.add_child(parameter_vbox)
|
||||
panel_container.add_child(vbox)
|
||||
effect_container.add_child(panel_container)
|
||||
parameter_vbox.set_visible_children(false)
|
||||
|
||||
|
||||
func _enable_effect(button_pressed: bool, effect: LayerEffect) -> void:
|
||||
effect.enabled = button_pressed
|
||||
Global.canvas.queue_redraw()
|
||||
|
||||
|
||||
func _re_order_effect(
|
||||
effect: LayerEffect, layer: BaseLayer, container: Container, direction: int
|
||||
) -> void:
|
||||
assert(layer.effects.size() == effect_container.get_child_count())
|
||||
var effect_index := container.get_index()
|
||||
var new_index := effect_index + direction
|
||||
if new_index < 0:
|
||||
return
|
||||
if new_index >= effect_container.get_child_count():
|
||||
return
|
||||
Global.current_project.undos += 1
|
||||
Global.current_project.undo_redo.create_action("Re-arrange layer effect")
|
||||
Global.current_project.undo_redo.add_do_method(
|
||||
swap_array.bind(layer.effects, effect_index, new_index, effect)
|
||||
)
|
||||
Global.current_project.undo_redo.add_do_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
Global.current_project.undo_redo.add_undo_method(
|
||||
swap_array.bind(layer.effects, new_index, effect_index, effect)
|
||||
)
|
||||
Global.current_project.undo_redo.add_undo_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||
Global.current_project.undo_redo.commit_action()
|
||||
effect_container.move_child(container, new_index)
|
||||
|
||||
|
||||
func swap_array(array: Array, old_index: int, new_index: int, new_item: Variant) -> void:
|
||||
var temp = array[new_index]
|
||||
array[new_index] = new_item
|
||||
array[old_index] = temp
|
||||
|
||||
|
||||
func _delete_effect(effect: LayerEffect) -> void:
|
||||
var layer := Global.current_project.layers[Global.current_project.current_layer]
|
||||
var index := layer.effects.find(effect)
|
||||
Global.current_project.undos += 1
|
||||
Global.current_project.undo_redo.create_action("Delete layer effect")
|
||||
Global.current_project.undo_redo.add_do_method(func(): layer.effects.erase(effect))
|
||||
Global.current_project.undo_redo.add_do_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
Global.current_project.undo_redo.add_undo_method(func(): layer.effects.insert(index, effect))
|
||||
Global.current_project.undo_redo.add_undo_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||
Global.current_project.undo_redo.commit_action()
|
||||
effect_container.get_child(index).queue_free()
|
||||
|
||||
|
||||
func _apply_effect(layer: BaseLayer, effect: LayerEffect) -> void:
|
||||
var index := layer.effects.find(effect)
|
||||
var redo_data := {}
|
||||
var undo_data := {}
|
||||
for frame in Global.current_project.frames:
|
||||
var cel := frame.cels[layer.index]
|
||||
var new_image := Image.new()
|
||||
new_image.copy_from(cel.get_image())
|
||||
var image_size := new_image.get_size()
|
||||
var shader_image_effect := ShaderImageEffect.new()
|
||||
shader_image_effect.generate_image(new_image, effect.shader, effect.params, image_size)
|
||||
redo_data[cel.image] = new_image.data
|
||||
undo_data[cel.image] = cel.image.data
|
||||
Global.current_project.undos += 1
|
||||
Global.current_project.undo_redo.create_action("Apply layer effect")
|
||||
Global.undo_redo_compress_images(redo_data, undo_data)
|
||||
Global.current_project.undo_redo.add_do_method(func(): layer.effects.erase(effect))
|
||||
Global.current_project.undo_redo.add_do_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_do_method(Global.undo_or_redo.bind(false))
|
||||
Global.current_project.undo_redo.add_undo_method(func(): layer.effects.insert(index, effect))
|
||||
Global.current_project.undo_redo.add_undo_method(Global.canvas.queue_redraw)
|
||||
Global.current_project.undo_redo.add_undo_method(Global.undo_or_redo.bind(true))
|
||||
Global.current_project.undo_redo.commit_action()
|
||||
effect_container.get_child(index).queue_free()
|
||||
|
||||
|
||||
func _set_parameter(value, param: String, effect: LayerEffect) -> void:
|
||||
effect.params[param] = value
|
||||
Global.canvas.queue_redraw()
|
||||
|
||||
|
||||
func _load_parameter_texture(path: String, effect: LayerEffect, param: String) -> void:
|
||||
var image := Image.new()
|
||||
image.load(path)
|
||||
if !image:
|
||||
print("Error loading texture")
|
||||
return
|
||||
var image_tex := ImageTexture.create_from_image(image)
|
||||
_set_parameter(image_tex, param, effect)
|
||||
|
||||
|
||||
func _on_enabled_button_toggled(button_pressed: bool) -> void:
|
||||
var layer := Global.current_project.layers[Global.current_project.current_layer]
|
||||
layer.effects_enabled = button_pressed
|
||||
Global.canvas.queue_redraw()
|
43
src/UI/Timeline/LayerEffects/LayerEffectsSettings.tscn
Normal file
43
src/UI/Timeline/LayerEffects/LayerEffectsSettings.tscn
Normal file
|
@ -0,0 +1,43 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://dd1fkkc3vjh78"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd" id="1_h6h7b"]
|
||||
|
||||
[node name="LayerEffectsSettings" type="AcceptDialog"]
|
||||
title = "Layer effects"
|
||||
size = Vector2i(400, 400)
|
||||
exclusive = false
|
||||
script = ExtResource("1_h6h7b")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 392.0
|
||||
offset_bottom = 351.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="EnabledButton" type="CheckButton" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Enabled"
|
||||
|
||||
[node name="EffectList" type="MenuButton" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 10
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "Add effect"
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="EffectContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[connection signal="about_to_popup" from="." to="." method="_on_about_to_popup"]
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
||||
[connection signal="toggled" from="VBoxContainer/HBoxContainer/EnabledButton" to="." method="_on_enabled_button_toggled"]
|
|
@ -129,6 +129,7 @@ func _setup_view_menu() -> void:
|
|||
"Show Rulers": "show_rulers",
|
||||
"Show Guides": "show_guides",
|
||||
"Show Mouse Guides": "",
|
||||
"Display Layer Effects": &"display_layer_effects",
|
||||
"Snap To": "",
|
||||
}
|
||||
view_menu = view_menu_button.get_popup()
|
||||
|
@ -145,6 +146,7 @@ func _setup_view_menu() -> void:
|
|||
_set_menu_shortcut(view_menu_items[item], view_menu, i)
|
||||
view_menu.set_item_checked(Global.ViewMenu.SHOW_RULERS, true)
|
||||
view_menu.set_item_checked(Global.ViewMenu.SHOW_GUIDES, true)
|
||||
view_menu.set_item_checked(Global.ViewMenu.DISPLAY_LAYER_EFFECTS, true)
|
||||
view_menu.hide_on_checkable_item_selection = false
|
||||
view_menu.id_pressed.connect(view_menu_id_pressed)
|
||||
|
||||
|
@ -346,7 +348,7 @@ func _setup_help_menu() -> void:
|
|||
help_menu.id_pressed.connect(help_menu_id_pressed)
|
||||
|
||||
|
||||
func _set_menu_shortcut(action: String, menu: PopupMenu, index: int) -> void:
|
||||
func _set_menu_shortcut(action: StringName, menu: PopupMenu, index: int) -> void:
|
||||
if action.is_empty():
|
||||
return
|
||||
var shortcut := Shortcut.new()
|
||||
|
@ -497,6 +499,8 @@ func view_menu_id_pressed(id: int) -> void:
|
|||
_toggle_show_guides()
|
||||
Global.ViewMenu.SHOW_MOUSE_GUIDES:
|
||||
_toggle_show_mouse_guides()
|
||||
Global.ViewMenu.DISPLAY_LAYER_EFFECTS:
|
||||
Global.display_layer_effects = not Global.display_layer_effects
|
||||
_:
|
||||
_handle_metadata(id, view_menu_button)
|
||||
|
||||
|
|
Loading…
Reference in a new issue