diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 1a02862a9..1c9d1c1fb 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -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 } diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 868d25ee1..84212ea3e 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -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 @@ -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) diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index 05abe63df..2811251ee 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -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: diff --git a/src/Classes/Layers/BaseLayer.gd b/src/Classes/Layers/BaseLayer.gd index bdc08e227..a63e4e8dd 100644 --- a/src/Classes/Layers/BaseLayer.gd +++ b/src/Classes/Layers/BaseLayer.gd @@ -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) diff --git a/src/UI/Dialogs/ImageEffects/ShaderEffect.gd b/src/UI/Dialogs/ImageEffects/ShaderEffect.gd index bd6524942..76f3687ac 100644 --- a/src/UI/Dialogs/ImageEffects/ShaderEffect.gd +++ b/src/UI/Dialogs/ImageEffects/ShaderEffect.gd @@ -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: @@ -17,36 +16,22 @@ func _about_to_popup() -> void: super._about_to_popup() -func commit_action(cel: Image, project := Global.current_project) -> void: - if !shader: - return +func set_nodes() -> void: + aspect_ratio_container = $VBoxContainer/AspectRatioContainer + preview = $VBoxContainer/AspectRatioContainer/Preview + +func commit_action(cel: Image, project := Global.current_project) -> void: + 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() diff --git a/src/UI/Dialogs/ImageEffects/ShaderEffect.tscn b/src/UI/Dialogs/ImageEffects/ShaderEffect.tscn index 037559f87..a42ddb3f7 100644 --- a/src/UI/Dialogs/ImageEffects/ShaderEffect.tscn +++ b/src/UI/Dialogs/ImageEffects/ShaderEffect.tscn @@ -1,4 +1,4 @@ -[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"] @@ -6,6 +6,8 @@ [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="."] @@ -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 @@ -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"] diff --git a/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd b/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd index 8e86da5d6..6979cb998 100644 --- a/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd +++ b/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd @@ -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) @@ -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: @@ -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() diff --git a/src/UI/TopMenuContainer/TopMenuContainer.gd b/src/UI/TopMenuContainer/TopMenuContainer.gd index df41698e8..b640dd254 100644 --- a/src/UI/TopMenuContainer/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer/TopMenuContainer.gd @@ -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") @@ -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") @@ -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() @@ -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 := { @@ -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 @@ -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)