diff --git a/Translations/Translations.pot b/Translations/Translations.pot index e7ceccf34..bc11eb007 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -277,6 +277,18 @@ msgstr "" msgid "Preferences" msgstr "" +#. A submenu of the Effects menu. Contains effects that generate new pixels, such as outline, drop shadow and gradient. +msgid "Procedural" +msgstr "" + +#. Refers to effects that blur the image. +msgid "Blur" +msgstr "" + +#. A submenu of the Effects menu. Contains effects that have been loaded by the user. +msgid "Loader" +msgstr "" + #. An option in the View menu. When selected, the canvas is being placed on the center of the screen. msgid "Center Canvas" msgstr "" diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index ca015a1db..efb064846 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -65,26 +65,6 @@ enum ImageMenu { CROP_TO_SELECTION, CROP_TO_CONTENT, } -## Enumeration of items present in the Effects menu. -enum EffectsMenu { - OFFSET_IMAGE, - FLIP, - ROTATE, - OUTLINE, - DROP_SHADOW, - INVERT_COLORS, - DESATURATION, - HSV, - BRIGHTNESS_SATURATION, - COLOR_CURVES, - PALETTIZE, - PIXELIZE, - POSTERIZE, - GAUSSIAN_BLUR, - GRADIENT, - GRADIENT_MAP, - LOADED_EFFECTS -} ## Enumeration of items present in the Select Menu. enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, WRAP_STROKES, MODIFY } ## Enumeration of items present in the Help Menu. diff --git a/src/Classes/LayerEffect.gd b/src/Classes/LayerEffect.gd index b28efd91b..7b6452c76 100644 --- a/src/Classes/LayerEffect.gd +++ b/src/Classes/LayerEffect.gd @@ -3,18 +3,20 @@ extends RefCounted var name := "" var shader: Shader +var category := "" var params := {} var enabled := true -func _init(_name := "", _shader: Shader = null, _params := {}) -> void: +func _init(_name := "", _shader: Shader = null, _category := "", _params := {}) -> void: name = _name shader = _shader + category = _category params = _params func duplicate() -> LayerEffect: - return LayerEffect.new(name, shader, params.duplicate()) + return LayerEffect.new(name, shader, category, params.duplicate()) func serialize() -> Dictionary: diff --git a/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd b/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd index c6fd70555..d58140744 100644 --- a/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd +++ b/src/UI/Timeline/LayerEffects/LayerEffectsSettings.gd @@ -5,28 +5,47 @@ const DELETE_TEXTURE := preload("res://assets/graphics/misc/close.svg") var effects: Array[LayerEffect] = [ LayerEffect.new( - "Convolution Matrix", preload("res://src/Shaders/Effects/ConvolutionMatrix.gdshader") + "Convolution Matrix", + preload("res://src/Shaders/Effects/ConvolutionMatrix.gdshader"), + "Color" ), - LayerEffect.new("Gaussian Blur", preload("res://src/Shaders/Effects/GaussianBlur.gdshader")), - 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") + "Gaussian Blur", preload("res://src/Shaders/Effects/GaussianBlur.gdshader"), "Blur" + ), + LayerEffect.new( + "Offset", preload("res://src/Shaders/Effects/OffsetPixels.gdshader"), "Transform" + ), + LayerEffect.new( + "Outline", preload("res://src/Shaders/Effects/OutlineInline.gdshader"), "Procedural" + ), + LayerEffect.new( + "Drop Shadow", preload("res://src/Shaders/Effects/DropShadow.gdshader"), "Procedural" + ), + LayerEffect.new("Invert Colors", preload("res://src/Shaders/Effects/Invert.gdshader"), "Color"), + LayerEffect.new( + "Desaturation", preload("res://src/Shaders/Effects/Desaturate.gdshader"), "Color" + ), + LayerEffect.new( + "Adjust Hue/Saturation/Value", preload("res://src/Shaders/Effects/HSV.gdshader"), "Color" ), LayerEffect.new( "Adjust Brightness/Contrast", - preload("res://src/Shaders/Effects/BrightnessContrast.gdshader") + preload("res://src/Shaders/Effects/BrightnessContrast.gdshader"), + "Color" ), - LayerEffect.new("Color Curves", preload("res://src/Shaders/Effects/ColorCurves.gdshader")), - LayerEffect.new("Palettize", preload("res://src/Shaders/Effects/Palettize.gdshader")), - LayerEffect.new("Pixelize", preload("res://src/Shaders/Effects/Pixelize.gdshader")), - LayerEffect.new("Posterize", preload("res://src/Shaders/Effects/Posterize.gdshader")), - LayerEffect.new("Gradient Map", preload("res://src/Shaders/Effects/GradientMap.gdshader")), - LayerEffect.new("Index Map", preload("res://src/Shaders/Effects/IndexMap.gdshader")), + LayerEffect.new( + "Color Curves", preload("res://src/Shaders/Effects/ColorCurves.gdshader"), "Color" + ), + LayerEffect.new("Palettize", preload("res://src/Shaders/Effects/Palettize.gdshader"), "Color"), + LayerEffect.new("Pixelize", preload("res://src/Shaders/Effects/Pixelize.gdshader"), "Blur"), + LayerEffect.new("Posterize", preload("res://src/Shaders/Effects/Posterize.gdshader"), "Color"), + LayerEffect.new( + "Gradient Map", preload("res://src/Shaders/Effects/GradientMap.gdshader"), "Color" + ), + LayerEffect.new("Index Map", preload("res://src/Shaders/Effects/IndexMap.gdshader"), "Color"), ] +## Dictionary of [String] and [PopupMenu], mapping each category to a PopupMenu. +var category_submenus := {} @onready var enabled_button: CheckButton = $VBoxContainer/HBoxContainer/EnabledButton @onready var effect_list: MenuButton = $VBoxContainer/HBoxContainer/EffectList @@ -35,14 +54,15 @@ var effects: Array[LayerEffect] = [ func _ready() -> void: - for effect in effects: - effect_list.get_popup().add_item(effect.name) + var effect_list_popup := effect_list.get_popup() + for i in effects.size(): + _add_effect_to_list(i) 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) + effect_list_popup.index_pressed.connect(_on_effect_list_pressed.bind(effect_list_popup)) func _notification(what: int) -> void: @@ -65,15 +85,38 @@ func _on_visibility_changed() -> void: child.queue_free() +func _add_effect_to_list(i: int) -> void: + var effect_list_popup := effect_list.get_popup() + var effect := effects[i] + if effect.category.is_empty(): + effect_list_popup.add_item(effect.name) + effect_list_popup.set_item_metadata(effect_list_popup.item_count - 1, i) + else: + if category_submenus.has(effect.category): + var submenu := category_submenus[effect.category] as PopupMenu + submenu.add_item(effect.name) + submenu.set_item_metadata(submenu.item_count - 1, i) + else: + var submenu := PopupMenu.new() + effect_list_popup.add_submenu_node_item(effect.category, submenu) + submenu.add_item(effect.name) + submenu.set_item_metadata(submenu.item_count - 1, i) + submenu.index_pressed.connect(_on_effect_list_pressed.bind(submenu)) + category_submenus[effect.category] = submenu + + 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) + var new_effect := LayerEffect.new(effect_name, file, "Loaded") + effects.append(new_effect) + _add_effect_to_list(effects.size() - 1) + #effect_list.get_popup().add_item(effect_name) -func _on_effect_list_id_pressed(index: int) -> void: +func _on_effect_list_pressed(menu_item_index: int, menu: PopupMenu) -> void: + var index: int = menu.get_item_metadata(menu_item_index) var layer := Global.current_project.layers[Global.current_project.current_layer] var effect := effects[index].duplicate() Global.current_project.undos += 1 diff --git a/src/UI/TopMenuContainer/TopMenuContainer.gd b/src/UI/TopMenuContainer/TopMenuContainer.gd index 7db21e726..a969ab26b 100644 --- a/src/UI/TopMenuContainer/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer/TopMenuContainer.gd @@ -14,7 +14,18 @@ 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 +var tile_mode_submenu := PopupMenu.new() +var selection_modify_submenu := PopupMenu.new() +var color_mode_submenu := PopupMenu.new() +var snap_to_submenu := PopupMenu.new() +var panels_submenu := PopupMenu.new() +var layouts_submenu := PopupMenu.new() +var recent_projects_submenu := PopupMenu.new() +var effects_transform_submenu := PopupMenu.new() +var effects_color_submenu := PopupMenu.new() +var effects_procedural_submenu := PopupMenu.new() +var effects_blur_submenu := PopupMenu.new() +var effects_loaded_submenu: PopupMenu # Dialogs var new_image_dialog := Dialog.new("res://src/UI/Dialogs/CreateNewImage.tscn") @@ -58,13 +69,6 @@ var about_dialog := Dialog.new("res://src/UI/Dialogs/AboutDialog.tscn") @onready var help_menu := $MarginContainer/HBoxContainer/MenuBar/Help as PopupMenu @onready var greyscale_vision: ColorRect = main_ui.find_child("GreyscaleVision") -@onready var tile_mode_submenu := PopupMenu.new() -@onready var selection_modify_submenu := PopupMenu.new() -@onready var color_mode_submenu := PopupMenu.new() -@onready var snap_to_submenu := PopupMenu.new() -@onready var panels_submenu := PopupMenu.new() -@onready var layouts_submenu := PopupMenu.new() -@onready var recent_projects_submenu := PopupMenu.new() @onready var current_frame_mark := %CurrentFrameMark as Label @@ -446,37 +450,45 @@ func _setup_color_mode_submenu(item: String) -> void: func _setup_effects_menu() -> void: - # Order as in Global.EffectMenu enum - var menu_items := { - "Offset Image": "offset_image", - "Mirror Image": "mirror_image", - "Rotate Image": "rotate_image", - "Outline": "outline", - "Drop Shadow": "drop_shadow", - "Invert Colors": "invert_colors", - "Desaturation": "desaturation", - "Adjust Hue/Saturation/Value": "adjust_hsv", - "Adjust Brightness/Contrast": "adjust_brightness_contrast", - "Color Curves": "color_curves", - "Palettize": "palettize", - "Pixelize": "pixelize", - "Posterize": "posterize", - "Gaussian Blur": "gaussian_blur", - "Gradient": "gradient", - "Gradient Map": "gradient_map", - "Loaded": "" - } - var i := 0 - for item in menu_items: - if item == "Loaded": - _setup_loaded_effects_submenu() - else: - _set_menu_shortcut(menu_items[item], effects_menu, i, item) - i += 1 + _set_menu_shortcut(&"offset_image", effects_transform_submenu, 0, "Offset Image") + _set_menu_shortcut(&"mirror_image", effects_transform_submenu, 1, "Mirror Image") + _set_menu_shortcut(&"rotate_image", effects_transform_submenu, 2, "Rotate Image") + effects_transform_submenu.id_pressed.connect(_on_effects_transform_submenu_id_pressed) + effects_menu.add_child(effects_transform_submenu) + effects_menu.add_submenu_node_item("Transform", effects_transform_submenu) + + _set_menu_shortcut(&"invert_colors", effects_color_submenu, 0, "Invert Colors") + _set_menu_shortcut(&"desaturation", effects_color_submenu, 1, "Desaturation") + _set_menu_shortcut(&"adjust_hsv", effects_color_submenu, 2, "Adjust Hue/Saturation/Value") + _set_menu_shortcut( + &"adjust_brightness_contrast", effects_color_submenu, 3, "Adjust Brightness/Contrast" + ) + _set_menu_shortcut(&"color_curves", effects_color_submenu, 4, "Color Curves") + _set_menu_shortcut(&"palettize", effects_color_submenu, 5, "Palettize") + _set_menu_shortcut(&"posterize", effects_color_submenu, 6, "Posterize") + _set_menu_shortcut(&"gradient_map", effects_color_submenu, 7, "Gradient Map") + effects_color_submenu.id_pressed.connect(_on_effects_color_submenu_id_pressed) + effects_menu.add_child(effects_color_submenu) + effects_menu.add_submenu_node_item("Color", effects_color_submenu) + + _set_menu_shortcut(&"outline", effects_procedural_submenu, 0, "Outline") + _set_menu_shortcut(&"drop_shadow", effects_procedural_submenu, 1, "Drop Shadow") + _set_menu_shortcut(&"gradient", effects_procedural_submenu, 2, "Gradient") + effects_procedural_submenu.id_pressed.connect(_on_effects_procedural_submenu_id_pressed) + effects_menu.add_child(effects_procedural_submenu) + effects_menu.add_submenu_node_item("Procedural", effects_procedural_submenu) + + _set_menu_shortcut(&"pixelize", effects_blur_submenu, 0, "Pixelize") + _set_menu_shortcut(&"gaussian_blur", effects_blur_submenu, 1, "Gaussian Blur") + effects_blur_submenu.id_pressed.connect(_on_effects_blur_submenu_id_pressed) + effects_menu.add_child(effects_blur_submenu) + effects_menu.add_submenu_node_item("Blur", effects_blur_submenu) + + _setup_effects_loaded_submenu() effects_menu.id_pressed.connect(effects_menu_id_pressed) -func _setup_loaded_effects_submenu() -> void: +func _setup_effects_loaded_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) @@ -491,15 +503,15 @@ func _load_shader_file(file_path: String) -> void: 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) + if not is_instance_valid(effects_loaded_submenu): + effects_loaded_submenu = PopupMenu.new() + effects_loaded_submenu.set_name("effects_loaded_submenu") + effects_loaded_submenu.id_pressed.connect(_effects_loaded_submenu_id_pressed) + effects_menu.add_child(effects_loaded_submenu) + effects_menu.add_submenu_node_item("Loaded", effects_loaded_submenu) + effects_loaded_submenu.add_item(effect_name) + var effect_index := effects_loaded_submenu.item_count - 1 + effects_loaded_submenu.set_item_metadata(effect_index, file) loaded_effect_dialogs.append(Dialog.new("res://src/UI/Dialogs/ImageEffects/ShaderEffect.tscn")) @@ -814,14 +826,14 @@ 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: +func _effects_loaded_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)) + var shader := effects_loaded_submenu.get_item_metadata(id) as Shader + dialog.node.change_shader(shader, effects_loaded_submenu.get_item_text(id)) dialog.popup() @@ -972,41 +984,55 @@ func image_menu_id_pressed(id: int) -> void: func effects_menu_id_pressed(id: int) -> void: + _handle_metadata(id, effects_menu) + + +func _on_effects_transform_submenu_id_pressed(id: int) -> void: match id: - Global.EffectsMenu.OFFSET_IMAGE: + 0: offset_image_dialog.popup() - Global.EffectsMenu.FLIP: + 1: mirror_image_dialog.popup() - Global.EffectsMenu.ROTATE: + 2: rotate_image_dialog.popup() - Global.EffectsMenu.INVERT_COLORS: + + +func _on_effects_color_submenu_id_pressed(id: int) -> void: + match id: + 0: invert_colors_dialog.popup() - Global.EffectsMenu.DESATURATION: + 1: desaturate_dialog.popup() - Global.EffectsMenu.OUTLINE: - outline_dialog.popup() - Global.EffectsMenu.DROP_SHADOW: - drop_shadow_dialog.popup() - Global.EffectsMenu.HSV: + 2: hsv_dialog.popup() - Global.EffectsMenu.BRIGHTNESS_SATURATION: + 3: adjust_brightness_saturation_dialog.popup() - Global.EffectsMenu.COLOR_CURVES: + 4: color_curves_dialog.popup() - Global.EffectsMenu.GAUSSIAN_BLUR: - gaussian_blur_dialog.popup() - Global.EffectsMenu.GRADIENT: - gradient_dialog.popup() - Global.EffectsMenu.GRADIENT_MAP: - gradient_map_dialog.popup() - Global.EffectsMenu.PALETTIZE: + 5: palettize_dialog.popup() - Global.EffectsMenu.PIXELIZE: - pixelize_dialog.popup() - Global.EffectsMenu.POSTERIZE: + 6: posterize_dialog.popup() - _: - _handle_metadata(id, effects_menu) + 7: + gradient_map_dialog.popup() + + +func _on_effects_procedural_submenu_id_pressed(id: int) -> void: + match id: + 0: + outline_dialog.popup() + 1: + drop_shadow_dialog.popup() + 2: + gradient_dialog.popup() + + +func _on_effects_blur_submenu_id_pressed(id: int) -> void: + match id: + 0: + pixelize_dialog.popup() + 1: + gaussian_blur_dialog.popup() func select_menu_id_pressed(id: int) -> void: