Skip to content

Commit

Permalink
Implement the ability to load custom shaders and image and layer effects
Browse files Browse the repository at this point in the history
Finally expose the feature of importing custom shaders as image effects, and implement custom shader loading for layer effects as well. To load a shader, drag and drop a .gdshader file into Pixelorama and it will get copied into `user://shaders`. Then, in the Effects menu, a new "Loaded" submenu will appear, and the new shaders will also be available in the layer effects dialog. Since they are stored on a persistent location, the shaders will also be available on the next times Pixelorama will launch as well.
  • Loading branch information
OverloadedOrama committed Dec 15, 2024
1 parent 8ceeba7 commit 39c85c3
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 66 deletions.
2 changes: 1 addition & 1 deletion src/Autoload/Global.gd
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ enum EffectsMenu {
GAUSSIAN_BLUR,
GRADIENT,
GRADIENT_MAP,
SHADER
LOADED_EFFECTS
}
## Enumeration of items present in the Select Menu.
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE, MODIFY }
Expand Down
10 changes: 7 additions & 3 deletions src/Autoload/OpenSave.gd
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ extends Node

signal project_saved
signal reference_image_imported
signal shader_copied(file_path: String)

const SHADERS_DIRECTORY := "user://shaders"

var preview_dialog_tscn := preload("res://src/UI/Dialogs/ImportPreviewDialog.tscn")
var preview_dialogs := [] ## Array of preview dialogs
Expand Down Expand Up @@ -39,12 +42,13 @@ func handle_loading_file(file: String) -> void:
elif file_ext in ["pck", "zip"]: # Godot resource pack file
Global.control.get_node("Extensions").install_extension(file)

elif file_ext == "shader" or file_ext == "gdshader": # Godot shader file
elif file_ext == "gdshader": # Godot shader file
var shader := load(file)
if not shader is Shader:
return
var file_name: String = file.get_file().get_basename()
Global.control.find_child("ShaderEffect").change_shader(shader, file_name)
var new_path := SHADERS_DIRECTORY.path_join(file.get_file())
DirAccess.copy_absolute(file, new_path)
shader_copied.emit(new_path)
elif file_ext == "mp3": # Audio file
open_audio_file(file)

Expand Down
6 changes: 4 additions & 2 deletions src/Classes/ImageEffect.gd
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,10 @@ func set_nodes() -> void:
selection_checkbox = $VBoxContainer/OptionsContainer/SelectionCheckBox
affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton
animate_panel = $"%AnimatePanel"
animate_panel.image_effect_node = self
live_checkbox.button_pressed = live_preview
if is_instance_valid(animate_panel):
animate_panel.image_effect_node = self
if is_instance_valid(live_checkbox):
live_checkbox.button_pressed = live_preview


func display_animate_dialog() -> void:
Expand Down
2 changes: 1 addition & 1 deletion src/Classes/Layers/BaseLayer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func display_effects(cel: BaseCel, image_override: Image = null) -> Image:
return image
var image_size := image.get_size()
for effect in effects:
if not effect.enabled:
if not effect.enabled or not is_instance_valid(effect.shader):
continue
var shader_image_effect := ShaderImageEffect.new()
shader_image_effect.generate_image(image, effect.shader, effect.params, image_size)
Expand Down
31 changes: 8 additions & 23 deletions src/UI/Dialogs/ImageEffects/ShaderEffect.gd
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ extends ImageEffect
var shader: Shader
var params := {}

@onready var shader_loaded_label: Label = $VBoxContainer/ShaderLoadedLabel
@onready var shader_params: BoxContainer = $VBoxContainer/ShaderParams
@onready var shader_params := $VBoxContainer/ShaderParams as VBoxContainer


func _about_to_popup() -> void:
Expand All @@ -17,36 +16,22 @@ func _about_to_popup() -> void:
super._about_to_popup()


func set_nodes() -> void:
aspect_ratio_container = $VBoxContainer/AspectRatioContainer
preview = $VBoxContainer/AspectRatioContainer/Preview


func commit_action(cel: Image, project := Global.current_project) -> void:
if !shader:
if not is_instance_valid(shader):
return

var gen := ShaderImageEffect.new()
gen.generate_image(cel, shader, params, project.size)


func _on_ChooseShader_pressed() -> void:
if OS.get_name() == "Web":
Html5FileExchange.load_shader()
else:
$FileDialog.popup_centered(Vector2(300, 340))


func _on_FileDialog_file_selected(path: String) -> void:
var shader_tmp = load(path)
if !shader_tmp is Shader:
return
change_shader(shader_tmp, path.get_file().get_basename())


func set_nodes() -> void:
preview = $VBoxContainer/AspectRatioContainer/Preview


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
title = shader_name
params.clear()
for child in shader_params.get_children():
child.queue_free()
Expand Down
28 changes: 5 additions & 23 deletions src/UI/Dialogs/ImageEffects/ShaderEffect.tscn
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
[gd_scene load_steps=4 format=3 uid="uid://bkr47ocij684y"]
[gd_scene load_steps=4 format=3 uid="uid://b1ola6loro5m7"]

[ext_resource type="Script" path="res://src/UI/Dialogs/ImageEffects/ShaderEffect.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://3pmb60gpst7b" path="res://src/UI/Nodes/TransparentChecker.tscn" id="2"]

[sub_resource type="ShaderMaterial" id="1"]

[node name="ShaderEffect" type="ConfirmationDialog"]
position = Vector2i(0, 36)
size = Vector2i(612, 350)
script = ExtResource("1")

[node name="VBoxContainer" type="VBoxContainer" parent="."]
Expand All @@ -15,17 +17,14 @@ anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -36.0

[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "This is an experimental feature and may not be included in the stable version"
offset_bottom = -49.0

[node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3

[node name="Preview" type="TextureRect" parent="VBoxContainer/AspectRatioContainer"]
texture_filter = 1
material = SubResource("1")
custom_minimum_size = Vector2(200, 200)
layout_mode = 2
Expand All @@ -39,22 +38,5 @@ anchors_preset = 0
anchor_right = 1.0
anchor_bottom = 1.0

[node name="ChooseShader" type="Button" parent="VBoxContainer"]
layout_mode = 2
mouse_default_cursor_shape = 2
text = "Choose Shader"

[node name="ShaderLoadedLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "No shader loaded!"

[node name="ShaderParams" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2

[node name="FileDialog" type="FileDialog" parent="." groups=["FileDialogs"]]
access = 2
filters = PackedStringArray("*gdshader; Godot Shader File")
show_hidden_files = true

[connection signal="pressed" from="VBoxContainer/ChooseShader" to="." method="_on_ChooseShader_pressed"]
[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]
16 changes: 15 additions & 1 deletion src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ var effects: Array[LayerEffect] = [
func _ready() -> void:
for effect in effects:
effect_list.get_popup().add_item(effect.name)
if not DirAccess.dir_exists_absolute(OpenSave.SHADERS_DIRECTORY):
DirAccess.make_dir_recursive_absolute(OpenSave.SHADERS_DIRECTORY)
for file_name in DirAccess.get_files_at(OpenSave.SHADERS_DIRECTORY):
_load_shader_file(OpenSave.SHADERS_DIRECTORY.path_join(file_name))
OpenSave.shader_copied.connect(_load_shader_file)
effect_list.get_popup().id_pressed.connect(_on_effect_list_id_pressed)


Expand All @@ -49,7 +54,8 @@ 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)
if is_instance_valid(effect.shader):
_create_effect_ui(layer, effect)


func _on_visibility_changed() -> void:
Expand All @@ -59,6 +65,14 @@ func _on_visibility_changed() -> void:
child.queue_free()


func _load_shader_file(file_path: String) -> void:
var file := load(file_path)
if file is Shader:
var effect_name := file_path.get_file().get_basename()
effects.append(LayerEffect.new(effect_name, file))
effect_list.get_popup().add_item(effect_name)


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()
Expand Down
67 changes: 55 additions & 12 deletions src/UI/TopMenuContainer/TopMenuContainer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const HEART_ICON := preload("res://assets/graphics/misc/heart.svg")
var recent_projects := []
var selected_layout := 0
var zen_mode := false
var loaded_effects_submenu: PopupMenu

# Dialogs
var new_image_dialog := Dialog.new("res://src/UI/Dialogs/CreateNewImage.tscn")
Expand All @@ -40,7 +41,7 @@ var gradient_map_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/Gradien
var palettize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/PalettizeDialog.tscn")
var pixelize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/PixelizeDialog.tscn")
var posterize_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/Posterize.tscn")
var shader_effect_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn")
var loaded_effect_dialogs: Array[Dialog] = []
var manage_layouts_dialog := Dialog.new("res://src/UI/Dialogs/ManageLayouts.tscn")
var window_opacity_dialog := Dialog.new("res://src/UI/Dialogs/WindowOpacityDialog.tscn")
var about_dialog := Dialog.new("res://src/UI/Dialogs/AboutDialog.tscn")
Expand Down Expand Up @@ -79,21 +80,24 @@ class Dialog:

func popup(dialog_size := Vector2i.ZERO) -> void:
if not is_instance_valid(node):
var scene := load(scene_path)
if not scene is PackedScene:
return
node = scene.instantiate()
if not is_instance_valid(node):
return
Global.control.get_node("Dialogs").add_child(node)
instantiate_scene()
node.popup_centered(dialog_size)
var is_file_dialog := node is FileDialog
Global.dialog_open(true, is_file_dialog)

func instantiate_scene() -> void:
var scene := load(scene_path)
if not scene is PackedScene:
return
node = scene.instantiate()
if is_instance_valid(node):
Global.control.get_node("Dialogs").add_child(node)


func _ready() -> void:
Global.project_switched.connect(_project_switched)
Global.cel_switched.connect(_update_current_frame_mark)
OpenSave.shader_copied.connect(_load_shader_file)
_setup_file_menu()
_setup_edit_menu()
_setup_view_menu()
Expand Down Expand Up @@ -457,15 +461,45 @@ func _setup_effects_menu() -> void:
"Gaussian Blur": "gaussian_blur",
"Gradient": "gradient",
"Gradient Map": "gradient_map",
# "Shader": ""
"Loaded": ""
}
var i := 0
for item in menu_items:
_set_menu_shortcut(menu_items[item], effects_menu, i, item)
if item == "Loaded":
_setup_loaded_effects_submenu()
else:
_set_menu_shortcut(menu_items[item], effects_menu, i, item)
i += 1
effects_menu.id_pressed.connect(effects_menu_id_pressed)


func _setup_loaded_effects_submenu() -> void:
if not DirAccess.dir_exists_absolute(OpenSave.SHADERS_DIRECTORY):
DirAccess.make_dir_recursive_absolute(OpenSave.SHADERS_DIRECTORY)
var shader_files := DirAccess.get_files_at(OpenSave.SHADERS_DIRECTORY)
if shader_files.size() == 0:
return
for shader_file in shader_files:
_load_shader_file(OpenSave.SHADERS_DIRECTORY.path_join(shader_file))


func _load_shader_file(file_path: String) -> void:
var file := load(file_path)
if file is not Shader:
return
var effect_name := file_path.get_file().get_basename()
if not is_instance_valid(loaded_effects_submenu):
loaded_effects_submenu = PopupMenu.new()
loaded_effects_submenu.set_name("loaded_effects_submenu")
loaded_effects_submenu.id_pressed.connect(_loaded_effects_submenu_id_pressed)
effects_menu.add_child(loaded_effects_submenu)
effects_menu.add_submenu_item("Loaded", loaded_effects_submenu.get_name())
loaded_effects_submenu.add_item(effect_name)
var effect_index := loaded_effects_submenu.item_count - 1
loaded_effects_submenu.set_item_metadata(effect_index, file)
loaded_effect_dialogs.append(Dialog.new("res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn"))


func _setup_select_menu() -> void:
# Order as in Global.SelectMenu enum
var select_menu_items := {
Expand Down Expand Up @@ -770,6 +804,17 @@ func _snap_to_submenu_id_pressed(id: int) -> void:
snap_to_submenu.set_item_checked(id, Global.snap_to_perspective_guides)


func _loaded_effects_submenu_id_pressed(id: int) -> void:
var dialog := loaded_effect_dialogs[id]
if is_instance_valid(dialog.node):
dialog.popup()
else:
dialog.instantiate_scene()
var shader := loaded_effects_submenu.get_item_metadata(id) as Shader
dialog.node.change_shader(shader, loaded_effects_submenu.get_item_text(id))
dialog.popup()


func _panels_submenu_id_pressed(id: int) -> void:
if zen_mode:
return
Expand Down Expand Up @@ -950,8 +995,6 @@ func effects_menu_id_pressed(id: int) -> void:
pixelize_dialog.popup()
Global.EffectsMenu.POSTERIZE:
posterize_dialog.popup()
#Global.EffectsMenu.SHADER:
#shader_effect_dialog.popup()
_:
_handle_metadata(id, effects_menu)

Expand Down

0 comments on commit 39c85c3

Please sign in to comment.