diff --git a/Translations/Translations.pot b/Translations/Translations.pot index fe34faff4..e452f44c0 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -316,7 +316,7 @@ msgstr "" msgid "New frame" msgstr "" -msgid "Replace frame" +msgid "Replace cel" msgstr "" msgid "New layer" @@ -1553,12 +1553,18 @@ msgstr "" msgid "Layer" msgstr "" +msgid "Group" +msgstr "" + msgid "Layers" msgstr "" msgid "Create a new layer" msgstr "" +msgid "Create a new group layer" +msgstr "" + msgid "Remove current layer" msgstr "" @@ -1593,6 +1599,9 @@ msgid "Enable/disable cel linking\n\n" "Linked cels are being shared across multiple frames" msgstr "" +msgid "Expand/collapse group" +msgstr "" + msgid "Palette" msgstr "" diff --git a/assets/graphics/layers/group_collapsed.png b/assets/graphics/layers/group_collapsed.png new file mode 100644 index 000000000..d513e9ef3 Binary files /dev/null and b/assets/graphics/layers/group_collapsed.png differ diff --git a/assets/graphics/layers/group_collapsed.png.import b/assets/graphics/layers/group_collapsed.png.import new file mode 100644 index 000000000..83ac242ef --- /dev/null +++ b/assets/graphics/layers/group_collapsed.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/group_collapsed.png-9d08fac1c2f635c754860111d024aa0f.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/layers/group_collapsed.png" +dest_files=[ "res://.import/group_collapsed.png-9d08fac1c2f635c754860111d024aa0f.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/assets/graphics/layers/group_expanded.png b/assets/graphics/layers/group_expanded.png new file mode 100644 index 000000000..7f013ae33 Binary files /dev/null and b/assets/graphics/layers/group_expanded.png differ diff --git a/assets/graphics/layers/group_expanded.png.import b/assets/graphics/layers/group_expanded.png.import new file mode 100644 index 000000000..9c8eff2dd --- /dev/null +++ b/assets/graphics/layers/group_expanded.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/group_expanded.png-f3cd620185a4989737d6d9a36f4b57f3.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/layers/group_expanded.png" +dest_files=[ "res://.import/group_expanded.png-f3cd620185a4989737d6d9a36f4b57f3.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/assets/graphics/layers/group_new.png b/assets/graphics/layers/group_new.png new file mode 100644 index 000000000..6f3d95ffc Binary files /dev/null and b/assets/graphics/layers/group_new.png differ diff --git a/assets/graphics/layers/group_new.png.import b/assets/graphics/layers/group_new.png.import new file mode 100644 index 000000000..904b6b0bd --- /dev/null +++ b/assets/graphics/layers/group_new.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/group_new.png-4ebdc7dd84d8c8a7b7979f50f4471543.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/layers/group_new.png" +dest_files=[ "res://.import/group_new.png-4ebdc7dd84d8c8a7b7979f50f4471543.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=false +svg/scale=1.0 diff --git a/project.godot b/project.godot index dfec41c16..c3b465f5a 100644 --- a/project.godot +++ b/project.godot @@ -14,6 +14,16 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Classes/AnimationTag.gd" }, { +"base": "Reference", +"class": "BaseCel", +"language": "GDScript", +"path": "res://src/Classes/BaseCel.gd" +}, { +"base": "Reference", +"class": "BaseLayer", +"language": "GDScript", +"path": "res://src/Classes/BaseLayer.gd" +}, { "base": "VBoxContainer", "class": "BaseTool", "language": "GDScript", @@ -30,11 +40,6 @@ _global_script_classes=[ { "path": "res://src/UI/Canvas/Canvas.gd" }, { "base": "Reference", -"class": "Cel", -"language": "GDScript", -"path": "res://src/Classes/Cel.gd" -}, { -"base": "Reference", "class": "Drawer", "language": "GDScript", "path": "res://src/Classes/Drawers.gd" @@ -49,6 +54,16 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/UI/Nodes/GradientEdit.gd" }, { +"base": "BaseCel", +"class": "GroupCel", +"language": "GDScript", +"path": "res://src/Classes/GroupCel.gd" +}, { +"base": "BaseLayer", +"class": "GroupLayer", +"language": "GDScript", +"path": "res://src/Classes/GroupLayer.gd" +}, { "base": "Line2D", "class": "Guide", "language": "GDScript", @@ -59,11 +74,6 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Classes/ImageEffect.gd" }, { -"base": "Reference", -"class": "Layer", -"language": "GDScript", -"path": "res://src/Classes/Layer.gd" -}, { "base": "Button", "class": "LayerButton", "language": "GDScript", @@ -99,6 +109,16 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/UI/PatternsPopup.gd" }, { +"base": "BaseCel", +"class": "PixelCel", +"language": "GDScript", +"path": "res://src/Classes/PixelCel.gd" +}, { +"base": "BaseLayer", +"class": "PixelLayer", +"language": "GDScript", +"path": "res://src/Classes/PixelLayer.gd" +}, { "base": "Reference", "class": "Project", "language": "GDScript", @@ -136,16 +156,18 @@ _global_script_classes=[ { } ] _global_script_class_icons={ "AnimationTag": "", +"BaseCel": "", +"BaseLayer": "", "BaseTool": "", "Brushes": "", "Canvas": "", -"Cel": "", "Drawer": "", "Frame": "", "GradientEditNode": "", +"GroupCel": "", +"GroupLayer": "", "Guide": "", "ImageEffect": "", -"Layer": "", "LayerButton": "", "Palette": "", "PaletteColor": "", @@ -153,6 +175,8 @@ _global_script_class_icons={ "PalettePanel": "", "PaletteSwatch": "", "Patterns": "", +"PixelCel": "", +"PixelLayer": "", "Project": "", "SelectionMap": "", "SelectionTool": "", diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index c55ecacab..a8ec4c71c 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -423,6 +423,8 @@ func scale_image(width: int, height: int, interpolation: int) -> void: for f in Global.current_project.frames: for i in range(f.cels.size() - 1, -1, -1): + if f.cels[i] is GroupCel: + continue var sprite := Image.new() sprite.copy_from(f.cels[i].image) # Different method for scale_3x @@ -449,6 +451,8 @@ func centralize() -> void: # Find used rect of the current frame (across all of the layers) var used_rect := Rect2() for cel in Global.current_project.frames[Global.current_project.current_frame].cels: + if not cel is PixelCel: + continue var cel_rect: Rect2 = cel.image.get_used_rect() if not cel_rect.has_no_area(): used_rect = cel_rect if used_rect.has_no_area() else used_rect.merge(cel_rect) @@ -457,14 +461,16 @@ func centralize() -> void: var offset: Vector2 = (0.5 * (Global.current_project.size - used_rect.size)).floor() general_do_centralize() - for c in Global.current_project.frames[Global.current_project.current_frame].cels: + for cel in Global.current_project.frames[Global.current_project.current_frame].cels: + if not cel is PixelCel: + continue var sprite := Image.new() sprite.create( Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8 ) - sprite.blend_rect(c.image, used_rect, offset) - Global.current_project.undo_redo.add_do_property(c.image, "data", sprite.data) - Global.current_project.undo_redo.add_undo_property(c.image, "data", c.image.data) + sprite.blend_rect(cel.image, used_rect, offset) + Global.current_project.undo_redo.add_do_property(cel.image, "data", sprite.data) + Global.current_project.undo_redo.add_undo_property(cel.image, "data", cel.image.data) general_undo_centralize() @@ -473,6 +479,8 @@ func crop_image() -> void: var used_rect := Rect2() for f in Global.current_project.frames: for cel in f.cels: + if not cel is PixelCel: + continue cel.image.unlock() # May be unneeded now, but keep it just in case var cel_used_rect: Rect2 = cel.image.get_used_rect() if cel_used_rect == Rect2(0, 0, 0, 0): # If the cel has no content @@ -493,6 +501,8 @@ func crop_image() -> void: # Loop through all the cels to crop them for f in Global.current_project.frames: for cel in f.cels: + if not cel is PixelCel: + continue var sprite: Image = cel.image.get_rect(used_rect) Global.current_project.undo_redo.add_do_property(cel.image, "data", sprite.data) Global.current_project.undo_redo.add_undo_property(cel.image, "data", cel.image.data) @@ -504,6 +514,8 @@ func resize_canvas(width: int, height: int, offset_x: int, offset_y: int) -> voi general_do_scale(width, height) for f in Global.current_project.frames: for c in f.cels: + if not c is PixelCel: + continue var sprite := Image.new() sprite.create(width, height, false, Image.FORMAT_RGBA8) sprite.blend_rect( diff --git a/src/Autoload/Export.gd b/src/Autoload/Export.gd index 2d819492e..92ada952e 100644 --- a/src/Autoload/Export.gd +++ b/src/Autoload/Export.gd @@ -380,7 +380,7 @@ func blend_layers(image: Image, frame: Frame, origin: Vector2 = Vector2(0, 0)) - image.lock() var layer_i := 0 for cel in frame.cels: - if Global.current_project.layers[layer_i].visible: + if Global.current_project.layers[layer_i].is_visible_in_hierarchy() and cel is PixelCel: var cel_image := Image.new() cel_image.copy_from(cel.image) cel_image.lock() @@ -406,9 +406,12 @@ func blend_selected_cels(image: Image, frame: Frame, origin: Vector2 = Vector2(0 var test_array = [Global.current_project.current_frame, cel_ind] if not test_array in Global.current_project.selected_cels: continue + if not frame.cels[cel_ind] is PixelCel: + continue - var cel: Cel = frame.cels[cel_ind] - if Global.current_project.layers[layer_i].visible: + var cel: PixelCel = frame.cels[cel_ind] + + if Global.current_project.layers[layer_i].is_visible_in_hierarchy(): var cel_image := Image.new() cel_image.copy_from(cel.image) cel_image.lock() diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index bb5a885b3..1ff2bd097 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -2,6 +2,7 @@ extends Node signal project_changed +enum LayerTypes { PIXEL, GROUP } enum GridTypes { CARTESIAN, ISOMETRIC, ALL } enum PressureSensitivity { NONE, ALPHA, SIZE, ALPHA_AND_SIZE } enum ColorFrom { THEME, CUSTOM } @@ -59,7 +60,6 @@ var current_project_index := 0 setget _project_changed var ui_tooltips := {} # Canvas related stuff -var layers_changed_skip := false var can_draw := false var move_guides_on_canvas := false var has_focus := false @@ -144,6 +144,10 @@ var palettes := {} # Nodes var notification_label_node: PackedScene = preload("res://src/UI/NotificationLabel.tscn") +var pixel_layer_button_node: PackedScene = preload("res://src/UI/Timeline/PixelLayerButton.tscn") +var group_layer_button_node: PackedScene = preload("res://src/UI/Timeline/GroupLayerButton.tscn") +var pixel_cel_button_node: PackedScene = preload("res://src/UI/Timeline/PixelCelButton.tscn") +var group_cel_button_node: PackedScene = preload("res://src/UI/Timeline/GroupCelButton.tscn") onready var control: Node = get_tree().current_scene @@ -449,24 +453,14 @@ func undo_or_redo( if action_name == "Scale": for i in project.frames.size(): for j in project.layers.size(): - var current_cel: Cel = project.frames[i].cels[j] - current_cel.image_texture.create_from_image(current_cel.image, 0) + var current_cel: BaseCel = project.frames[i].cels[j] + current_cel.image_texture.create_from_image(current_cel.get_image(), 0) canvas.camera_zoom() canvas.grid.update() canvas.pixel_grid.update() project.selection_map_changed() cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] - elif "Frame" in action_name: - # This actually means that frames.size is one, but it hasn't been updated yet - if (undo and project.frames.size() == 2) or project.frames.size() == 1: # Stop animating - play_forward.pressed = false - play_backwards.pressed = false - animation_timer.stop() - - elif "Move Cels" == action_name: - project.frames = project.frames # to call frames_changed - canvas.update() if !project.has_changed: project.has_changed = true diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 0daf5d78b..ab20a109b 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -110,11 +110,7 @@ func open_pxo_file(path: String, untitled_backup: bool = false, replace_empty: b new_project.deserialize(dict.result) for frame in new_project.frames: for cel in frame.cels: - var buffer := file.get_buffer(new_project.size.x * new_project.size.y * 4) - cel.image.create_from_data( - new_project.size.x, new_project.size.y, false, Image.FORMAT_RGBA8, buffer - ) - cel.image = cel.image # Just to call image_changed + cel.load_image_data_from_pxo(file, new_project.size) if dict.result.has("brushes"): for brush in dict.result.brushes: @@ -138,14 +134,13 @@ func open_pxo_file(path: String, untitled_backup: bool = false, replace_empty: b new_project.tiles.reset_mask() file.close() - if !empty_project: - Global.projects.append(new_project) - Global.tabs.current_tab = Global.tabs.get_tab_count() - 1 - else: + if empty_project: if dict.error == OK and dict.result.has("fps"): Global.animation_timeline.fps_spinbox.value = dict.result.fps - new_project.frames = new_project.frames # Just to call frames_changed - new_project.layers = new_project.layers # Just to call layers_changed + Global.animation_timeline.project_changed() + else: + Global.projects.append(new_project) + Global.tabs.current_tab = Global.tabs.get_tab_count() - 1 Global.canvas.camera_zoom() if not untitled_backup: @@ -200,13 +195,16 @@ func open_old_pxo_file(file: File, new_project: Project, first_line: String) -> if file_major_version >= 0 and file_minor_version > 6: var global_layer_line := file.get_line() while global_layer_line == ".": - var layer_name := file.get_line() - var layer_visibility := file.get_8() - var layer_lock := file.get_8() - var layer_new_cels_linked := file.get_8() + var layer_dict := { + "name": file.get_line(), + "visible": file.get_8(), + "locked": file.get_8(), + "new_cels_linked": file.get_8(), + "linked_cels": [] + } linked_cels.append(file.get_var()) - - var l := Layer.new(layer_name, layer_visibility, layer_lock, layer_new_cels_linked, []) + var l := PixelLayer.new(new_project) + l.deserialize(layer_dict) new_project.layers.append(l) global_layer_line = file.get_line() @@ -223,17 +221,17 @@ func open_old_pxo_file(file: File, new_project: Project, first_line: String) -> if file_major_version == 0 and file_minor_version < 7: var layer_name_old_version = file.get_line() if frame == 0: - var l := Layer.new(layer_name_old_version) + var l := PixelLayer.new(new_project, layer_name_old_version) new_project.layers.append(l) var cel_opacity := 1.0 if file_major_version >= 0 and file_minor_version > 5: cel_opacity = file.get_float() var image := Image.new() image.create_from_data(width, height, false, Image.FORMAT_RGBA8, buffer) - frame_class.cels.append(Cel.new(image, cel_opacity)) + frame_class.cels.append(PixelCel.new(image, cel_opacity)) if file_major_version >= 0 and file_minor_version >= 7: if frame in linked_cels[layer_i]: - var linked_cel: Cel = new_project.layers[layer_i].linked_cels[0].cels[layer_i] + var linked_cel: PixelCel = new_project.layers[layer_i].linked_cels[0].cels[layer_i] new_project.layers[layer_i].linked_cels.append(frame_class) frame_class.cels[layer_i].image = linked_cel.image frame_class.cels[layer_i].image_texture = linked_cel.image_texture @@ -358,7 +356,7 @@ func save_pxo_file( file.store_line(to_save) for frame in project.frames: for cel in frame.cels: - file.store_buffer(cel.image.get_data()) + cel.save_image_data_to_pxo(file) for brush in project.brushes: file.store_buffer(brush.get_data()) @@ -401,12 +399,12 @@ func save_pxo_file( func open_image_as_new_tab(path: String, image: Image) -> void: var project = Project.new([], path.get_file(), image.get_size()) - project.layers.append(Layer.new()) + project.layers.append(PixelLayer.new(project)) Global.projects.append(project) var frame := Frame.new() image.convert(Image.FORMAT_RGBA8) - frame.cels.append(Cel.new(image, 1)) + frame.cels.append(PixelCel.new(image, 1)) project.frames.append(frame) set_new_imported_tab(project, path) @@ -414,7 +412,7 @@ func open_image_as_new_tab(path: String, image: Image) -> void: func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert: int) -> void: var project = Project.new([], path.get_file()) - project.layers.append(Layer.new()) + project.layers.append(PixelLayer.new(project)) Global.projects.append(project) horiz = min(horiz, image.get_size().x) vert = min(vert, image.get_size().y) @@ -429,16 +427,8 @@ func open_image_as_spritesheet_tab(path: String, image: Image, horiz: int, vert: ) project.size = cropped_image.get_size() cropped_image.convert(Image.FORMAT_RGBA8) - frame.cels.append(Cel.new(cropped_image, 1)) - - for _i in range(1, project.layers.size()): - var empty_sprite := Image.new() - empty_sprite.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8) - empty_sprite.fill(Color(0, 0, 0, 0)) - frame.cels.append(Cel.new(empty_sprite, 1)) - + frame.cels.append(PixelCel.new(cropped_image, 1)) project.frames.append(frame) - set_new_imported_tab(project, path) @@ -461,95 +451,84 @@ func open_image_as_spritesheet_layer( # Initialize undo mechanism project.undos += 1 project.undo_redo.create_action("Add Spritesheet Layer") - var new_layers: Array = project.layers.duplicate() - var new_frames: Array = [] - # Create a duplicate of "project.frames" - for i in project.frames.size(): - var frame := Frame.new() - frame.cels = project.frames[i].cels.duplicate(true) - new_frames.append(frame) + var new_layers: Array = project.layers.duplicate() # Used for updating linked_cels lists # Create new frames (if needed) - var new_frames_size = start_frame + (vertical * horizontal) + var new_frames_size = max(project.frames.size(), start_frame + (vertical * horizontal)) + var frames := [] + var frame_indices: Array if new_frames_size > project.frames.size(): var required_frames = new_frames_size - project.frames.size() + frame_indices = range( + project.current_frame + 1, project.current_frame + required_frames + 1 + ) for i in required_frames: var new_frame := Frame.new() - for l_i in range(new_layers.size()): # Create as many cels as there are layers - var new_img := Image.new() - new_img.create(project_width, project_height, false, Image.FORMAT_RGBA8) - new_frame.cels.append(Cel.new(new_img, 1)) - if new_layers[l_i].new_cels_linked: + for l_i in range(project.layers.size()): # Create as many cels as there are layers + new_frame.cels.append(project.layers[l_i].new_empty_cel()) + if new_layers[l_i].get("new_cels_linked"): new_layers[l_i].linked_cels.append(new_frame) - new_frame.cels[l_i].image = new_layers[l_i].linked_cels[0].cels[l_i].image + new_frame.cels[l_i].set_content( + new_layers[l_i].linked_cels[0].cels[l_i].get_content() + ) new_frame.cels[l_i].image_texture = new_layers[l_i].linked_cels[0].cels[l_i].image_texture - new_frames.insert(project.current_frame + 1, new_frame) + frames.append(new_frame) # Create new layer for spritesheet - var layer := Layer.new(file_name) - new_layers.append(layer) - for f in new_frames: - var new_layer := Image.new() - new_layer.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8) - f.cels.append(Cel.new(new_layer, 1)) - - # Slice spritesheet - var image_no: int = 0 - var layer_index = new_layers.size() - 1 - for yy in range(vertical): - for xx in range(horizontal): + var layer := PixelLayer.new(project, file_name) + var cels := [] + for f in new_frames_size: + if f >= start_frame and f < (start_frame + (vertical * horizontal)): + # Slice spritesheet + var xx: int = (f - start_frame) % horizontal + var yy: int = (f - start_frame) / horizontal var cropped_image := Image.new() cropped_image = image.get_rect( Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height) ) cropped_image.crop(project.size.x, project.size.y) - var frame_index = start_frame + image_no - cropped_image.convert(Image.FORMAT_RGBA8) - new_frames[frame_index].cels[layer_index] = (Cel.new(cropped_image, 1)) - image_no += 1 + cels.append(PixelCel.new(cropped_image)) + else: + cels.append(layer.new_empty_cel()) - project.undo_redo.add_do_property(project, "current_frame", new_frames.size() - 1) + project.undo_redo.add_do_property(project, "current_frame", new_frames_size - 1) project.undo_redo.add_do_property(project, "current_layer", project.layers.size()) - project.undo_redo.add_do_property(project, "frames", new_frames) + project.undo_redo.add_do_method(project, "add_frames", frames, frame_indices) project.undo_redo.add_do_property(project, "layers", new_layers) + project.undo_redo.add_do_method(project, "add_layers", [layer], [project.layers.size()], [cels]) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) + project.undo_redo.add_undo_method(project, "remove_layers", [project.layers.size()]) project.undo_redo.add_undo_property(project, "layers", project.layers) - project.undo_redo.add_undo_property(project, "frames", project.frames) - project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(project, "remove_frames", frame_indices) project.undo_redo.add_undo_method(Global, "undo_or_redo", true) project.undo_redo.commit_action() -func open_image_at_frame(image: Image, layer_index := 0, frame_index := 0) -> void: +func open_image_at_cel(image: Image, layer_index := 0, frame_index := 0) -> void: var project = Global.current_project - image.crop(project.size.x, project.size.y) - project.undos += 1 - project.undo_redo.create_action("Replaced Frame") - - var frames: Array = [] - # create a duplicate of "project.frames" - for i in project.frames.size(): - var frame := Frame.new() - frame.cels = project.frames[i].cels.duplicate(true) - frames.append(frame) + project.undo_redo.create_action("Replaced Cel") for i in project.frames.size(): if i == frame_index: + image.crop(project.size.x, project.size.y) image.convert(Image.FORMAT_RGBA8) - frames[i].cels[layer_index] = (Cel.new(image, 1)) - project.undo_redo.add_do_property(project.frames[i], "cels", frames[i].cels) - project.undo_redo.add_undo_property(project.frames[i], "cels", project.frames[i].cels) + var cel: PixelCel = project.frames[i].cels[layer_index] + project.undo_redo.add_do_property(cel, "image", image) + project.undo_redo.add_undo_property(cel, "image", cel.image) - project.undo_redo.add_do_property(project, "frames", frames) + project.undo_redo.add_do_property(project, "selected_cels", []) + project.undo_redo.add_do_property(project, "current_layer", layer_index) project.undo_redo.add_do_property(project, "current_frame", frame_index) - - project.undo_redo.add_undo_property(project, "frames", project.frames) - project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) - project.undo_redo.add_do_method(Global, "undo_or_redo", false) + + project.undo_redo.add_undo_property(project, "selected_cels", []) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) project.undo_redo.add_undo_method(Global, "undo_or_redo", true) project.undo_redo.commit_action() @@ -557,64 +536,50 @@ func open_image_at_frame(image: Image, layer_index := 0, frame_index := 0) -> vo func open_image_as_new_frame(image: Image, layer_index := 0) -> void: var project = Global.current_project image.crop(project.size.x, project.size.y) - var new_frames: Array = project.frames.duplicate() var frame := Frame.new() for i in project.layers.size(): if i == layer_index: image.convert(Image.FORMAT_RGBA8) - frame.cels.append(Cel.new(image, 1)) + frame.cels.append(PixelCel.new(image, 1)) else: - var empty_image := Image.new() - empty_image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8) - frame.cels.append(Cel.new(empty_image, 1)) - - new_frames.append(frame) + frame.cels.append(project.layers[i].new_empty_cel()) project.undos += 1 project.undo_redo.create_action("Add Frame") project.undo_redo.add_do_method(Global, "undo_or_redo", false) - project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - - project.undo_redo.add_do_property(project, "frames", new_frames) - project.undo_redo.add_do_property(project, "current_frame", new_frames.size() - 1) + project.undo_redo.add_do_method(project, "add_frames", [frame], [project.frames.size()]) project.undo_redo.add_do_property(project, "current_layer", layer_index) + project.undo_redo.add_do_property(project, "current_frame", project.frames.size()) - project.undo_redo.add_undo_property(project, "frames", project.frames) - project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.add_undo_method(project, "remove_frames", [project.frames.size()]) project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) project.undo_redo.commit_action() func open_image_as_new_layer(image: Image, file_name: String, frame_index := 0) -> void: var project = Global.current_project image.crop(project.size.x, project.size.y) - var new_layers: Array = Global.current_project.layers.duplicate() - var layer := Layer.new(file_name) + var layer := PixelLayer.new(project, file_name) + var cels := [] Global.current_project.undos += 1 Global.current_project.undo_redo.create_action("Add Layer") for i in project.frames.size(): - var new_cels: Array = project.frames[i].cels.duplicate(true) if i == frame_index: image.convert(Image.FORMAT_RGBA8) - new_cels.append(Cel.new(image, 1)) + cels.append(PixelCel.new(image, 1)) else: - var empty_image := Image.new() - empty_image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8) - new_cels.append(Cel.new(empty_image, 1)) + cels.append(layer.new_empty_cel()) - project.undo_redo.add_do_property(project.frames[i], "cels", new_cels) - project.undo_redo.add_undo_property(project.frames[i], "cels", project.frames[i].cels) - - new_layers.append(layer) - - project.undo_redo.add_do_property(project, "current_layer", new_layers.size() - 1) - project.undo_redo.add_do_property(project, "layers", new_layers) + project.undo_redo.add_do_property(project, "current_layer", project.layers.size()) + project.undo_redo.add_do_method(project, "add_layers", [layer], [project.layers.size()], [cels]) project.undo_redo.add_do_property(project, "current_frame", frame_index) project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) - project.undo_redo.add_undo_property(project, "layers", project.layers) + project.undo_redo.add_undo_method(project, "remove_layers", [project.layers.size()]) project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) project.undo_redo.add_undo_method(Global, "undo_or_redo", true) diff --git a/src/Autoload/Palettes.gd b/src/Autoload/Palettes.gd index 6d5c97c06..fffd7c7a9 100644 --- a/src/Autoload/Palettes.gd +++ b/src/Autoload/Palettes.gd @@ -174,7 +174,7 @@ func _fill_new_palette_with_colors( match get_colors_from: GetColorsFrom.CURRENT_CEL: for cel_index in current_project.selected_cels: - var cel: Cel = current_project.frames[cel_index[0]].cels[cel_index[1]] + var cel: PixelCel = current_project.frames[cel_index[0]].cels[cel_index[1]] cels.append(cel) GetColorsFrom.CURRENT_FRAME: for cel in current_project.frames[current_project.current_frame].cels: diff --git a/src/Classes/BaseCel.gd b/src/Classes/BaseCel.gd new file mode 100644 index 000000000..4ab249fbb --- /dev/null +++ b/src/Classes/BaseCel.gd @@ -0,0 +1,53 @@ +class_name BaseCel +extends Reference +# Base class for cel properties. +# The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel). + +var opacity: float +var image_texture: ImageTexture + +# Methods to Override: + + +# The content methods deal with the unique content of each cel type. For example, an Image for +# PixelLayers, or a Dictionary of settings for a procedural layer type, and null for Groups. +# Can be used fo linking/unlinking cels, copying, and deleting content +func get_content(): + return null + + +func set_content(_content) -> void: + return + + +# Can be used to delete the content of the cel with set_content +# (using the old content from get_content as undo data) +func create_empty_content(): + return [] + + +# Can be used for creating copy content for copying cels or unlinking cels +func copy_content(): + return [] + + +# Returns the image var for image based cel types, or a render for procedural types. +# It's meant for read-only usage of image data, such as copying selections or color picking. +func get_image() -> Image: + return null + + +func update_texture() -> void: + return + + +func save_image_data_to_pxo(_file: File) -> void: + return + + +func load_image_data_from_pxo(_file: File, _project_size: Vector2) -> void: + return + + +func instantiate_cel_button() -> Node: + return null diff --git a/src/Classes/BaseLayer.gd b/src/Classes/BaseLayer.gd new file mode 100644 index 000000000..913d90e8a --- /dev/null +++ b/src/Classes/BaseLayer.gd @@ -0,0 +1,139 @@ +class_name BaseLayer +extends Reference +# Base class for layer properties. Different layer types extend from this class. + +var name := "" +var visible := true +var locked := false +var parent: BaseLayer +var project +var index: int + + +# Returns true if this is a direct or indirect parent of layer +func is_a_parent_of(layer: BaseLayer) -> bool: + if layer.parent == self: + return true + elif is_instance_valid(layer.parent): + return is_a_parent_of(layer.parent) + return false + + +func get_children(recursive: bool) -> Array: + var children := [] + if recursive: + for i in index: + if is_a_parent_of(project.layers[i]): + children.append(project.layers[i]) + else: + for i in index: + if project.layers[i].parent == self: + children.append(project.layers[i]) + return children + + +func get_child_count(recursive: bool) -> int: + var count := 0 + if recursive: + for i in index: + if is_a_parent_of(project.layers[i]): + count += 1 + else: + for i in index: + if project.layers[i].parent == self: + count += 1 + return count + + +func has_children() -> bool: + if index == 0: + return false + return project.layers[index - 1].parent == self + + +func is_expanded_in_hierarchy() -> bool: + if is_instance_valid(parent): + return parent.expanded and parent.is_expanded_in_hierarchy() + return true + + +func is_visible_in_hierarchy() -> bool: + if is_instance_valid(parent) and visible: + return parent.is_visible_in_hierarchy() + return visible + + +func is_locked_in_hierarchy() -> bool: + if is_instance_valid(parent) and not locked: + return parent.is_locked_in_hierarchy() + return locked + + +func get_hierarchy_depth() -> int: + if is_instance_valid(parent): + return parent.get_hierarchy_depth() + 1 + return 0 + + +func get_layer_path() -> String: + if is_instance_valid(parent): + return str(parent.get_layer_path(), "/", name) + return name + + +# Methods to Override: + + +func serialize() -> Dictionary: + assert(index == project.layers.find(self)) + return { + "name": name, + "visible": visible, + "locked": locked, + "parent": parent.index if is_instance_valid(parent) else -1 + } + + +func deserialize(dict: Dictionary) -> void: + name = dict.name + visible = dict.visible + locked = dict.locked + if dict.get("parent", -1) != -1: + parent = project.layers[dict.parent] + + +func copy() -> BaseLayer: + var copy = get_script().new(project) + copy.project = project + copy.index = index + copy.deserialize(serialize()) + return copy + + +func new_empty_cel() -> BaseCel: + return null + + +func copy_cel(_frame: int, _linked: bool) -> BaseCel: + return null + + +# Used to copy all cels with cel linking properly set up between this set of copies: +func copy_all_cels() -> Array: + return [] + + +func set_name_to_default(number: int) -> void: + name = tr("Layer") + " %s" % number + + +func can_layer_get_drawn() -> bool: + return false + + +func accepts_child(_layer: BaseLayer) -> bool: + return false + + +func instantiate_layer_button() -> Node: + return null diff --git a/src/Classes/Cel.gd b/src/Classes/Cel.gd deleted file mode 100644 index c8c9a5d12..000000000 --- a/src/Classes/Cel.gd +++ /dev/null @@ -1,24 +0,0 @@ -class_name Cel -extends Reference -# A class for cel properties. -# The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel). -# The "image" variable is where the image data of each cel are. - -var image: Image setget image_changed -var image_texture: ImageTexture -var opacity: float - - -func _init(_image := Image.new(), _opacity := 1.0, _image_texture: ImageTexture = null) -> void: - if _image_texture: - image_texture = _image_texture - else: - image_texture = ImageTexture.new() - self.image = _image - opacity = _opacity - - -func image_changed(value: Image) -> void: - image = value - if !image.is_empty(): - image_texture.create_from_image(image, 0) diff --git a/src/Classes/GroupCel.gd b/src/Classes/GroupCel.gd new file mode 100644 index 000000000..97be403e7 --- /dev/null +++ b/src/Classes/GroupCel.gd @@ -0,0 +1,21 @@ +class_name GroupCel +extends BaseCel +# A class for the properties of cels in GroupLayers. +# The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel). + + +func _init(_opacity := 1.0) -> void: + opacity = _opacity + image_texture = ImageTexture.new() + + +func get_image() -> Image: + var image = Image.new() + image.create( + Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8 + ) + return image + + +func instantiate_cel_button() -> Node: + return Global.group_cel_button_node.instance() diff --git a/src/Classes/GroupLayer.gd b/src/Classes/GroupLayer.gd new file mode 100644 index 000000000..82c7113c7 --- /dev/null +++ b/src/Classes/GroupLayer.gd @@ -0,0 +1,54 @@ +class_name GroupLayer +extends BaseLayer +# A class for group layer properties + +var expanded := true + + +func _init(_project, _name := "") -> void: + project = _project + name = _name + + +# Overridden Methods: + + +func serialize() -> Dictionary: + var data = .serialize() + data["type"] = Global.LayerTypes.GROUP + data["expanded"] = expanded + return data + + +func deserialize(dict: Dictionary) -> void: + .deserialize(dict) + expanded = dict.expanded + + +func new_empty_cel() -> BaseCel: + return GroupCel.new() + + +func copy_cel(frame_index: int, _linked: bool) -> BaseCel: + var cel: GroupCel = project.frames[frame_index].cels[index] + return GroupCel.new(cel.opacity) + + +func copy_all_cels() -> Array: + var cels := [] + for frame in project.frames: + var cel: GroupCel = frame.cels[index] + cels.append(GroupCel.new(cel.opacity)) + return cels + + +func set_name_to_default(number: int) -> void: + name = tr("Group") + " %s" % number + + +func accepts_child(_layer: BaseLayer) -> bool: + return true + + +func instantiate_layer_button() -> Node: + return Global.group_layer_button_node.instance() diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index 68b364a86..e5749c696 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -55,7 +55,7 @@ func _confirmed() -> void: for cel_index in project.selected_cels: if !project.layers[cel_index[1]].can_layer_get_drawn(): continue - var cel: Cel = project.frames[cel_index[0]].cels[cel_index[1]] + var cel: PixelCel = project.frames[cel_index[0]].cels[cel_index[1]] var cel_image: Image = cel.image commit_action(cel_image) _commit_undo("Draw", undo_data, project) @@ -126,12 +126,14 @@ func _get_selected_draw_images(project: Project) -> Array: # Array of Images var images := [] if affect == SELECTED_CELS: for cel_index in project.selected_cels: - var cel: Cel = project.frames[cel_index[0]].cels[cel_index[1]] - images.append(cel.image) + var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]] + if cel is PixelCel: + images.append(cel.image) else: for frame in project.frames: for cel in frame.cels: - images.append(cel.image) + if cel is PixelCel: + images.append(cel.image) return images diff --git a/src/Classes/Layer.gd b/src/Classes/Layer.gd deleted file mode 100644 index 7ff4dec18..000000000 --- a/src/Classes/Layer.gd +++ /dev/null @@ -1,23 +0,0 @@ -class_name Layer -extends Reference -# A class for layer properties. - -var name := "" -var visible := true -var locked := false -var new_cels_linked := false -var linked_cels := [] # Array of Frames - - -func _init( - _name := "", _visible := true, _locked := false, _new_cels_linked := false, _linked_cels := [] -) -> void: - name = _name - visible = _visible - locked = _locked - new_cels_linked = _new_cels_linked - linked_cels = _linked_cels - - -func can_layer_get_drawn() -> bool: - return visible && !locked diff --git a/src/Classes/PixelCel.gd b/src/Classes/PixelCel.gd new file mode 100644 index 000000000..0af49acbf --- /dev/null +++ b/src/Classes/PixelCel.gd @@ -0,0 +1,69 @@ +class_name PixelCel +extends BaseCel +# A class for the properties of cels in PixelLayers. +# The term "cel" comes from "celluloid" (https://en.wikipedia.org/wiki/Cel). +# The "image" variable is where the image data of each cel are. + +var image: Image setget image_changed + + +func _init(_image := Image.new(), _opacity := 1.0, _image_texture: ImageTexture = null) -> void: + if _image_texture: + image_texture = _image_texture + else: + image_texture = ImageTexture.new() + self.image = _image # Set image and call setter + opacity = _opacity + + +func image_changed(value: Image) -> void: + image = value + if !image.is_empty(): + image_texture.create_from_image(image, 0) + + +func get_content(): + return image + + +func set_content(content) -> void: + image = content + image_texture.create_from_image(image, 0) + + +func create_empty_content(): + var empty_image := Image.new() + empty_image.create(image.get_size().x, image.get_size().y, false, Image.FORMAT_RGBA8) + return empty_image + + +func copy_content(): + var copy_image := Image.new() + copy_image.create_from_data( + image.get_width(), image.get_height(), false, Image.FORMAT_RGBA8, image.get_data() + ) + return copy_image + + +func get_image() -> Image: + return image + + +func update_texture() -> void: + image_texture.set_data(image) + + +func save_image_data_to_pxo(file: File) -> void: + file.store_buffer(image.get_data()) + + +func load_image_data_from_pxo(file: File, project_size: Vector2) -> void: + var buffer := file.get_buffer(project_size.x * project_size.y * 4) + image.create_from_data(project_size.x, project_size.y, false, Image.FORMAT_RGBA8, buffer) + image_changed(image) + + +func instantiate_cel_button() -> Node: + var cel_button = Global.pixel_cel_button_node.instance() + cel_button.get_child(0).texture = image_texture + return cel_button diff --git a/src/Classes/PixelLayer.gd b/src/Classes/PixelLayer.gd new file mode 100644 index 000000000..41c4d7658 --- /dev/null +++ b/src/Classes/PixelLayer.gd @@ -0,0 +1,82 @@ +class_name PixelLayer +extends BaseLayer +# A class for standard pixel layer properties. + +var new_cels_linked := false +var linked_cels := [] # Array of Frames + + +func _init(_project, _name := "") -> void: + project = _project + name = _name + + +# Overridden Methods: + + +func serialize() -> Dictionary: + var dict = .serialize() + dict["type"] = Global.LayerTypes.PIXEL + dict["new_cels_linked"] = new_cels_linked + dict["linked_cels"] = [] + for cel in linked_cels: + dict.linked_cels.append(project.frames.find(cel)) + return dict + + +func deserialize(dict: Dictionary) -> void: + .deserialize(dict) + new_cels_linked = dict.new_cels_linked + + for linked_cel_number in dict.linked_cels: + linked_cels.append(project.frames[linked_cel_number]) + var linked_cel: PixelCel = project.frames[linked_cel_number].cels[index] + linked_cel.image = linked_cels[0].cels[index].image + linked_cel.image_texture = linked_cels[0].cels[index].image_texture + + +func new_empty_cel() -> BaseCel: + var image := Image.new() + image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8) + return PixelCel.new(image) + + +func copy_cel(frame_index: int, linked: bool) -> BaseCel: + if linked and not linked_cels.empty(): + var cel: PixelCel = linked_cels[0].cels[index] + return PixelCel.new(cel.image, cel.opacity, cel.image_texture) + else: + var cel: PixelCel = project.frames[frame_index].cels[index] + var copy_image := Image.new() + copy_image.copy_from(cel.image) + return PixelCel.new(copy_image, cel.opacity) + + +func copy_all_cels() -> Array: + var cels := [] + + var linked_image: Image + var linked_texture: ImageTexture + if not linked_cels.empty(): + var cel: PixelCel = linked_cels[0].cels[index] + linked_image = Image.new() + linked_image.copy_from(cel.image) + linked_texture = ImageTexture.new() + + for frame in project.frames: + var cel: PixelCel = frame.cels[index] + if linked_cels.has(frame): + cels.append(PixelCel.new(linked_image, cel.opacity, linked_texture)) + else: + var copy_image := Image.new() + copy_image.copy_from(cel.image) + cels.append(PixelCel.new(copy_image, cel.opacity)) + return cels + + +func can_layer_get_drawn() -> bool: + return is_visible_in_hierarchy() && !is_locked_in_hierarchy() + + +func instantiate_layer_button() -> Node: + return Global.pixel_layer_button_node.instance() diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 8f449e734..f34409a17 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -1,3 +1,4 @@ +# gdlint: ignore=max-public-methods class_name Project extends Reference # A class for project properties. @@ -9,8 +10,11 @@ var tiles: Tiles var undos := 0 # The number of times we added undo properties var fill_color := Color(0) var has_changed := false setget _has_changed_changed -var frames := [] setget _frames_changed # Array of Frames (that contain Cels) -var layers := [] setget _layers_changed # Array of Layers +# frames and layers Arrays should generally only be modified directly when +# opening/creating a project. When modifiying the current project, use +# the add/remove/move/swap_frames/layers methods +var frames := [] # Array of Frames (that contain Cels) +var layers := [] # Array of Layers var current_frame := 0 setget _frame_changed var current_layer := 0 setget _layer_changed var selected_cels := [[0, 0]] # Array of Arrays of 2 integers (frame & layer) @@ -44,9 +48,6 @@ var file_format: int = Export.FileFormat.PNG var was_exported := false var export_overwrite := false -var frame_button_node = preload("res://src/UI/Timeline/FrameButton.tscn") -var layer_button_node = preload("res://src/UI/Timeline/LayerButton.tscn") -var cel_button_node = preload("res://src/UI/Timeline/CelButton.tscn") var animation_tag_node = preload("res://src/UI/Timeline/AnimationTagUI.tscn") @@ -88,6 +89,8 @@ func remove() -> void: undo_redo.free() for guide in guides: guide.queue_free() + # Prevents memory leak (due to the layers' project reference stopping ref counting from freeing) + layers.clear() Global.projects.erase(self) @@ -108,13 +111,11 @@ func new_empty_frame() -> Frame: var frame := Frame.new() var bottom_layer := true for l in layers: # Create as many cels as there are layers - var image := Image.new() - image.create(size.x, size.y, false, Image.FORMAT_RGBA8) - if bottom_layer and fill_color.a > 0: - image.fill(fill_color) - frame.cels.append(Cel.new(image, 1)) + var cel: BaseCel = l.new_empty_cel() + if cel is PixelCel and bottom_layer and fill_color.a > 0: + cel.image.fill(fill_color) + frame.cels.append(cel) bottom_layer = false - return frame @@ -135,51 +136,7 @@ func _selection_offset_changed(value: Vector2) -> void: func change_project() -> void: - # Remove old nodes - for container in Global.layers_container.get_children(): - container.queue_free() - - _remove_cel_buttons() - - for frame_id in Global.frame_ids.get_children(): - Global.frame_ids.remove_child(frame_id) - frame_id.queue_free() - - # Create new ones - for i in range(layers.size() - 1, -1, -1): - # Create layer buttons - var layer_container = layer_button_node.instance() - layer_container.layer = i - if layers[i].name == "": - layers[i].name = tr("Layer") + " %s" % i - - Global.layers_container.add_child(layer_container) - layer_container.label.text = layers[i].name - layer_container.line_edit.text = layers[i].name - - var layer_cel_container := HBoxContainer.new() - Global.frames_container.add_child(layer_cel_container) - for j in range(frames.size()): # Create Cel buttons - var cel_button = cel_button_node.instance() - cel_button.frame = j - cel_button.layer = i - cel_button.get_child(0).texture = frames[j].cels[i].image_texture - cel_button.pressed = j == current_frame and i == current_layer - - layer_cel_container.add_child(cel_button) - - for j in range(frames.size()): # Create frame buttons - var button: Button = frame_button_node.instance() - button.frame = j - button.rect_min_size.x = Global.animation_timeline.cel_size - button.text = str(j + 1) - button.pressed = j == current_frame - Global.frame_ids.add_child(button) - - var layer_button = Global.layers_container.get_child( - Global.layers_container.get_child_count() - 1 - current_layer - ) - layer_button.pressed = true + Global.animation_timeline.project_changed() Global.current_frame_mark_label.text = "%s/%s" % [str(current_frame + 1), frames.size()] @@ -188,8 +145,7 @@ func change_project() -> void: Global.disable_button( Global.move_right_frame_button, frames.size() == 1 or current_frame == frames.size() - 1 ) - _toggle_layer_buttons_layers() - _toggle_layer_buttons_current_layer() + toggle_layer_buttons() self.animation_tags = animation_tags @@ -292,20 +248,8 @@ func change_project() -> void: func serialize() -> Dictionary: var layer_data := [] for layer in layers: - var linked_cels := [] - for cel in layer.linked_cels: - linked_cels.append(frames.find(cel)) - - layer_data.append( - { - "name": layer.name, - "visible": layer.visible, - "locked": layer.locked, - "new_cels_linked": layer.new_cels_linked, - "linked_cels": linked_cels, - "metadata": _serialize_metadata(layer) - } - ) + layer_data.append(layer.serialize()) + layer_data[-1]["metadata"] = _serialize_metadata(layer) var tag_data := [] for tag in animation_tags: @@ -395,14 +339,19 @@ func deserialize(dict: Dictionary) -> void: tiles.y_basis.y = dict.tile_mode_y_basis_y if dict.has("save_path"): OpenSave.current_save_paths[Global.projects.find(self)] = dict.save_path - if dict.has("frames"): + if dict.has("frames") and dict.has("layers"): var frame_i := 0 for frame in dict.frames: var cels := [] + var cel_i := 0 for cel in frame.cels: - var cel_class := Cel.new(Image.new(), cel.opacity) - _deserialize_metadata(cel_class, cel) - cels.append(cel_class) + match int(dict.layers[cel_i].get("type", Global.LayerTypes.PIXEL)): + Global.LayerTypes.PIXEL: + cels.append(PixelCel.new(Image.new(), cel.opacity)) + Global.LayerTypes.GROUP: + cels.append(GroupCel.new(cel.opacity)) + _deserialize_metadata(cels[cel_i], cel) + cel_i += 1 var duration := 1.0 if frame.has("duration"): duration = frame.duration @@ -414,25 +363,18 @@ func deserialize(dict: Dictionary) -> void: frames.append(frame_class) frame_i += 1 - if dict.has("layers"): - var layer_i := 0 - for saved_layer in dict.layers: - var linked_cels := [] - for linked_cel_number in saved_layer.linked_cels: - linked_cels.append(frames[linked_cel_number]) - var linked_cel: Cel = frames[linked_cel_number].cels[layer_i] - linked_cel.image = linked_cels[0].cels[layer_i].image - linked_cel.image_texture = linked_cels[0].cels[layer_i].image_texture - var layer := Layer.new( - saved_layer.name, - saved_layer.visible, - saved_layer.locked, - saved_layer.new_cels_linked, - linked_cels - ) - _deserialize_metadata(layer, saved_layer) - layers.append(layer) - layer_i += 1 + for saved_layer in dict.layers: + match int(saved_layer.get("type", Global.LayerTypes.PIXEL)): + Global.LayerTypes.PIXEL: + layers.append(PixelLayer.new(self)) + Global.LayerTypes.GROUP: + layers.append(GroupLayer.new(self)) + # Parent references to other layers are created when deserializing + # a layer, so loop again after creating them: + for layer_i in dict.layers.size(): + layers[layer_i].index = layer_i + layers[layer_i].deserialize(dict.layers[layer_i]) + _deserialize_metadata(layers[layer_i], dict.layers[layer_i]) if dict.has("tags"): for tag in dict.tags: animation_tags.append(AnimationTag.new(tag.name, Color(tag.color), tag.from, tag.to)) @@ -502,84 +444,6 @@ func _size_changed(value: Vector2) -> void: size = value -func _frames_changed(value: Array) -> void: - Global.canvas.selection.transform_content_confirm() - frames = value - selected_cels.clear() - _remove_cel_buttons() - - for frame_id in Global.frame_ids.get_children(): - Global.frame_ids.remove_child(frame_id) - frame_id.queue_free() - - for i in range(layers.size() - 1, -1, -1): - var layer_cel_container := HBoxContainer.new() - layer_cel_container.name = "FRAMESS " + str(i) - Global.frames_container.add_child(layer_cel_container) - for j in range(frames.size()): - var cel_button = cel_button_node.instance() - cel_button.frame = j - cel_button.layer = i - cel_button.get_child(0).texture = frames[j].cels[i].image_texture - layer_cel_container.add_child(cel_button) - - for j in range(frames.size()): - var button: Button = frame_button_node.instance() - button.frame = j - button.rect_min_size.x = Global.animation_timeline.cel_size - button.text = str(j + 1) - Global.frame_ids.add_child(button) - - _set_timeline_first_and_last_frames() - - -func _layers_changed(value: Array) -> void: - layers = value - if Global.layers_changed_skip: - Global.layers_changed_skip = false - return - - selected_cels.clear() - - for container in Global.layers_container.get_children(): - container.queue_free() - - _remove_cel_buttons() - - for i in range(layers.size() - 1, -1, -1): - var layer_button: LayerButton = layer_button_node.instance() - layer_button.layer = i - if layers[i].name == "": - layers[i].name = tr("Layer") + " %s" % i - - Global.layers_container.add_child(layer_button) - layer_button.label.text = layers[i].name - layer_button.line_edit.text = layers[i].name - - var layer_cel_container := HBoxContainer.new() - layer_cel_container.name = "LAYERSSS " + str(i) - Global.frames_container.add_child(layer_cel_container) - for j in range(frames.size()): - var cel_button = cel_button_node.instance() - cel_button.frame = j - cel_button.layer = i - cel_button.get_child(0).texture = frames[j].cels[i].image_texture - layer_cel_container.add_child(cel_button) - - var layer_button = Global.layers_container.get_child( - Global.layers_container.get_child_count() - 1 - current_layer - ) - layer_button.pressed = true - self.current_frame = current_frame # Call frame_changed to update UI - _toggle_layer_buttons_layers() - - -func _remove_cel_buttons() -> void: - for container in Global.frames_container.get_children(): - Global.frames_container.remove_child(container) - container.queue_free() - - func _frame_changed(value: int) -> void: Global.canvas.selection.transform_content_confirm() current_frame = value @@ -596,32 +460,25 @@ func _frame_changed(value: int) -> void: selected_cels.append([current_frame, current_layer]) # Select the new frame for cel in selected_cels: - var current_frame_tmp: int = cel[0] - var current_layer_tmp: int = cel[1] - if current_frame_tmp < Global.frame_ids.get_child_count(): - var frame_button: BaseButton = Global.frame_ids.get_child(current_frame_tmp) + var frame: int = cel[0] + var layer: int = cel[1] + if frame < Global.frame_ids.get_child_count(): + var frame_button: BaseButton = Global.frame_ids.get_child(frame) frame_button.pressed = true var container_child_count: int = Global.frames_container.get_child_count() - if current_layer_tmp < container_child_count: - var container = Global.frames_container.get_child( - container_child_count - 1 - current_layer_tmp - ) - if current_frame_tmp < container.get_child_count(): - var fbutton = container.get_child(current_frame_tmp) - fbutton.pressed = true - - Global.disable_button(Global.remove_frame_button, frames.size() == 1) - Global.disable_button(Global.move_left_frame_button, frames.size() == 1 or current_frame == 0) - Global.disable_button( - Global.move_right_frame_button, frames.size() == 1 or current_frame == frames.size() - 1 - ) + if layer < container_child_count: + var container = Global.frames_container.get_child(container_child_count - 1 - layer) + if frame < container.get_child_count(): + var cel_button = container.get_child(frame) + cel_button.pressed = true if current_frame < frames.size(): var cel_opacity: float = frames[current_frame].cels[current_layer].opacity Global.layer_opacity_slider.value = cel_opacity * 100 Global.layer_opacity_spinbox.value = cel_opacity * 100 + toggle_frame_buttons() Global.canvas.update() Global.transparent_checker.update_rect() @@ -630,7 +487,7 @@ func _layer_changed(value: int) -> void: Global.canvas.selection.transform_content_confirm() current_layer = value - _toggle_layer_buttons_current_layer() + toggle_layer_buttons() yield(Global.get_tree().create_timer(0.01), "timeout") self.current_frame = current_frame # Call frame_changed to update UI @@ -638,48 +495,44 @@ func _layer_changed(value: int) -> void: layer_button.pressed = false for cel in selected_cels: - var current_layer_tmp: int = cel[1] - if current_layer_tmp < Global.layers_container.get_child_count(): + var layer: int = cel[1] + if layer < Global.layers_container.get_child_count(): var layer_button = Global.layers_container.get_child( - Global.layers_container.get_child_count() - 1 - current_layer_tmp + Global.layers_container.get_child_count() - 1 - layer ) layer_button.pressed = true -func _toggle_layer_buttons_layers() -> void: - if !layers: +func toggle_frame_buttons() -> void: + Global.disable_button(Global.remove_frame_button, frames.size() == 1) + Global.disable_button(Global.move_left_frame_button, frames.size() == 1 or current_frame == 0) + Global.disable_button( + Global.move_right_frame_button, frames.size() == 1 or current_frame == frames.size() - 1 + ) + + +func toggle_layer_buttons() -> void: + if layers.empty() or current_layer >= layers.size(): return - if layers[current_layer].locked: - Global.disable_button(Global.remove_layer_button, true) + var child_count: int = layers[current_layer].get_child_count(true) - if layers.size() == 1: - Global.disable_button(Global.remove_layer_button, true) - Global.disable_button(Global.move_up_layer_button, true) - Global.disable_button(Global.move_down_layer_button, true) - Global.disable_button(Global.merge_down_layer_button, true) - elif !layers[current_layer].locked: - Global.disable_button(Global.remove_layer_button, false) - - -func _toggle_layer_buttons_current_layer() -> void: - if current_layer < layers.size() - 1: - Global.disable_button(Global.move_up_layer_button, false) - else: - Global.disable_button(Global.move_up_layer_button, true) - - if current_layer > 0: - Global.disable_button(Global.move_down_layer_button, false) - Global.disable_button(Global.merge_down_layer_button, false) - else: - Global.disable_button(Global.move_down_layer_button, true) - Global.disable_button(Global.merge_down_layer_button, true) - - if current_layer < layers.size(): - if layers[current_layer].locked: - Global.disable_button(Global.remove_layer_button, true) - else: - if layers.size() > 1: - Global.disable_button(Global.remove_layer_button, false) + Global.disable_button( + Global.remove_layer_button, + layers[current_layer].is_locked_in_hierarchy() or layers.size() == child_count + 1 + ) + Global.disable_button(Global.move_up_layer_button, current_layer == layers.size() - 1) + Global.disable_button( + Global.move_down_layer_button, + current_layer == child_count and not is_instance_valid(layers[current_layer].parent) + ) + Global.disable_button( + Global.merge_down_layer_button, + ( + current_layer == child_count + or layers[current_layer] is GroupLayer + or layers[current_layer - 1] is GroupLayer + ) + ) func _animation_tags_changed(value: Array) -> void: @@ -735,6 +588,7 @@ func is_empty() -> bool: return ( frames.size() == 1 and layers.size() == 1 + and layers[0] is PixelLayer and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 ) @@ -745,15 +599,10 @@ func duplicate_layers() -> Array: # Loop through the array to create new classes for each element, so that they # won't be the same as the original array's classes. Needed for undo/redo to work properly. for i in new_layers.size(): - var new_linked_cels = new_layers[i].linked_cels.duplicate() - new_layers[i] = Layer.new( - new_layers[i].name, - new_layers[i].visible, - new_layers[i].locked, - new_layers[i].new_cels_linked, - new_linked_cels - ) - + new_layers[i] = new_layers[i].copy() + for l in new_layers: + if is_instance_valid(l.parent): + l.parent = new_layers[l.parent.index] # Update the parent to the new copy of the parent return new_layers @@ -776,3 +625,236 @@ func can_pixel_get_drawn( return image.is_pixel_selected(pixel) else: return true + + +# Timeline modifications +# Modifying layers or frames Arrays on the current project should generally only be done +# through these methods. +# These allow you to add/remove/move/swap frames/layers/cels. It updates the Animation Timeline +# UI, and updates indices. These are designed to be reversible, meaning that to undo an add, you +# use remove, and vise versa. To undo a move or swap, use move or swap with the paramaters swapped. + + +func add_frames(new_frames: Array, indices: Array) -> void: # indices should be in ascending order + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + for i in new_frames.size(): + frames.insert(indices[i], new_frames[i]) + Global.animation_timeline.project_frame_added(indices[i]) + # Update the frames and frame buttons: + for f in frames.size(): + Global.frame_ids.get_child(f).frame = f + Global.frame_ids.get_child(f).text = str(f + 1) + # Update the cel buttons: + for l in layers.size(): + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - l) + for f in frames.size(): + layer_cel_container.get_child(f).frame = f + layer_cel_container.get_child(f).button_setup() + _set_timeline_first_and_last_frames() + + +func remove_frames(indices: Array) -> void: # indices should be in ascending order + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + for i in indices.size(): + # With each removed index, future indices need to be lowered, so subtract by i + frames.remove(indices[i] - i) + Global.animation_timeline.project_frame_removed(indices[i] - i) + # Update the frames and frame buttons: + for f in frames.size(): + Global.frame_ids.get_child(f).frame = f + Global.frame_ids.get_child(f).text = str(f + 1) + # Update the cel buttons: + for l in layers.size(): + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - l) + for f in frames.size(): + layer_cel_container.get_child(f).frame = f + layer_cel_container.get_child(f).button_setup() + _set_timeline_first_and_last_frames() + + +func move_frame(from_index: int, to_index: int) -> void: + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + var frame = frames[from_index] + frames.remove(from_index) + Global.animation_timeline.project_frame_removed(from_index) + frames.insert(to_index, frame) + Global.animation_timeline.project_frame_added(to_index) + # Update the frames and frame buttons: + for f in frames.size(): + Global.frame_ids.get_child(f).frame = f + Global.frame_ids.get_child(f).text = str(f + 1) + # Update the cel buttons: + for l in layers.size(): + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - l) + for f in frames.size(): + layer_cel_container.get_child(f).frame = f + layer_cel_container.get_child(f).button_setup() + _set_timeline_first_and_last_frames() + + +func swap_frame(a_index: int, b_index: int) -> void: + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + var temp: Frame = frames[a_index] + frames[a_index] = frames[b_index] + frames[b_index] = temp + Global.animation_timeline.project_frame_removed(a_index) + Global.animation_timeline.project_frame_added(a_index) + Global.animation_timeline.project_frame_removed(b_index) + Global.animation_timeline.project_frame_added(b_index) + _set_timeline_first_and_last_frames() + + +func add_layers(new_layers: Array, indices: Array, cels: Array) -> void: # cels is 2d Array of cels + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + for i in indices.size(): + layers.insert(indices[i], new_layers[i]) + for f in frames.size(): + frames[f].cels.insert(indices[i], cels[i][f]) + new_layers[i].project = self + Global.animation_timeline.project_layer_added(indices[i]) + # Update the layer indices and layer/cel buttons: + for l in layers.size(): + layers[l].index = l + Global.layers_container.get_child(layers.size() - 1 - l).layer = l + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - l) + for f in frames.size(): + layer_cel_container.get_child(f).layer = l + layer_cel_container.get_child(f).button_setup() + toggle_layer_buttons() + + +func remove_layers(indices: Array) -> void: + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + for i in indices.size(): + # With each removed index, future indices need to be lowered, so subtract by i + layers.remove(indices[i] - i) + for frame in frames: + frame.cels.remove(indices[i] - i) + Global.animation_timeline.project_layer_removed(indices[i] - i) + # Update the layer indices and layer/cel buttons: + for l in layers.size(): + layers[l].index = l + Global.layers_container.get_child(layers.size() - 1 - l).layer = l + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - l) + for f in frames.size(): + layer_cel_container.get_child(f).layer = l + layer_cel_container.get_child(f).button_setup() + toggle_layer_buttons() + + +# from_indices and to_indicies should be in ascending order +func move_layers(from_indices: Array, to_indices: Array, to_parents: Array) -> void: + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + var removed_layers := [] + var removed_cels := [] # 2D array of cels (an array for each layer removed) + + for i in from_indices.size(): + # With each removed index, future indices need to be lowered, so subtract by i + removed_layers.append(layers.pop_at(from_indices[i] - i)) + removed_layers[i].parent = to_parents[i] # parents must be set before UI created in next loop + removed_cels.append([]) + for frame in frames: + removed_cels[i].append(frame.cels.pop_at(from_indices[i] - i)) + Global.animation_timeline.project_layer_removed(from_indices[i] - i) + for i in to_indices.size(): + layers.insert(to_indices[i], removed_layers[i]) + for f in frames.size(): + frames[f].cels.insert(to_indices[i], removed_cels[i][f]) + Global.animation_timeline.project_layer_added(to_indices[i]) + # Update the layer indices and layer/cel buttons: + for l in layers.size(): + layers[l].index = l + Global.layers_container.get_child(layers.size() - 1 - l).layer = l + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - l) + for f in frames.size(): + layer_cel_container.get_child(f).layer = l + layer_cel_container.get_child(f).button_setup() + toggle_layer_buttons() + + +# "a" and "b" should both contain "from", "to", and "to_parents" arrays. +# (Using dictionaries because there seems to be a limit of 5 arguments for do/undo method calls) +func swap_layers(a: Dictionary, b: Dictionary) -> void: + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + var a_layers := [] + var b_layers := [] + var a_cels := [] # 2D array of cels (an array for each layer removed) + var b_cels := [] # 2D array of cels (an array for each layer removed) + for i in a.from.size(): + a_layers.append(layers.pop_at(a.from[i] - i)) + Global.animation_timeline.project_layer_removed(a.from[i] - i) + a_layers[i].parent = a.to_parents[i] # All parents must be set early, before creating buttons + a_cels.append([]) + for frame in frames: + a_cels[i].append(frame.cels.pop_at(a.from[i] - i)) + for i in b.from.size(): + var index = (b.from[i] - i) if a.from[0] > b.from[0] else (b.from[i] - i - a.from.size()) + b_layers.append(layers.pop_at(index)) + Global.animation_timeline.project_layer_removed(index) + b_layers[i].parent = b.to_parents[i] # All parents must be set early, before creating buttons + b_cels.append([]) + for frame in frames: + b_cels[i].append(frame.cels.pop_at(index)) + + for i in a_layers.size(): + var index = a.to[i] if a.to[0] < b.to[0] else (a.to[i] - b.to.size()) + layers.insert(index, a_layers[i]) + for f in frames.size(): + frames[f].cels.insert(index, a_cels[i][f]) + Global.animation_timeline.project_layer_added(index) + for i in b_layers.size(): + layers.insert(b.to[i], b_layers[i]) + for f in frames.size(): + frames[f].cels.insert(b.to[i], b_cels[i][f]) + Global.animation_timeline.project_layer_added(b.to[i]) + + # Update the layer indices and layer/cel buttons: + for l in layers.size(): + layers[l].index = l + Global.layers_container.get_child(layers.size() - 1 - l).layer = l + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - l) + for f in frames.size(): + layer_cel_container.get_child(f).layer = l + layer_cel_container.get_child(f).button_setup() + toggle_layer_buttons() + + +func move_cel(from_frame: int, to_frame: int, layer: int) -> void: + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + var cel: BaseCel = frames[from_frame].cels[layer] + if from_frame < to_frame: + for f in range(from_frame, to_frame): # Forward range + frames[f].cels[layer] = frames[f + 1].cels[layer] # Move left + else: + for f in range(from_frame, to_frame, -1): # Backward range + frames[f].cels[layer] = frames[f - 1].cels[layer] # Move right + frames[to_frame].cels[layer] = cel + Global.animation_timeline.project_cel_removed(from_frame, layer) + Global.animation_timeline.project_cel_added(to_frame, layer) + + # Update the cel buttons for this layer: + var layer_cel_container = Global.frames_container.get_child(layers.size() - 1 - layer) + for f in frames.size(): + layer_cel_container.get_child(f).frame = f + layer_cel_container.get_child(f).button_setup() + + +func swap_cel(a_frame: int, a_layer: int, b_frame: int, b_layer: int) -> void: + Global.canvas.selection.transform_content_confirm() + selected_cels.clear() + var temp: BaseCel = frames[a_frame].cels[a_layer] + frames[a_frame].cels[a_layer] = frames[b_frame].cels[b_layer] + frames[b_frame].cels[b_layer] = temp + Global.animation_timeline.project_cel_removed(a_frame, a_layer) + Global.animation_timeline.project_cel_added(a_frame, a_layer) + Global.animation_timeline.project_cel_removed(b_frame, b_layer) + Global.animation_timeline.project_cel_added(b_frame, b_layer) diff --git a/src/Main.gd b/src/Main.gd index c30f35e1d..77ad3b447 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -26,10 +26,10 @@ func _ready() -> void: Global.window_title = tr("untitled") + " - Pixelorama " + Global.current_version - Global.current_project.layers.append(Layer.new()) - var frame: Frame = Global.current_project.new_empty_frame() - Global.current_project.frames.append(frame) - Global.current_project.layers = Global.current_project.layers + Global.current_project.layers.append(PixelLayer.new(Global.current_project)) + Global.current_project.frames.append(Global.current_project.new_empty_frame()) + Global.animation_timeline.project_changed() + Global.current_project.toggle_frame_buttons() Import.import_brushes(Global.directory_module.get_brushes_search_path_in_order()) Import.import_patterns(Global.directory_module.get_patterns_search_path_in_order()) diff --git a/src/Tools/BaseTool.gd b/src/Tools/BaseTool.gd index d4c2ec857..8d4caff16 100644 --- a/src/Tools/BaseTool.gd +++ b/src/Tools/BaseTool.gd @@ -87,14 +87,14 @@ func _get_draw_rect() -> Rect2: func _get_draw_image() -> Image: var project: Project = Global.current_project - return project.frames[project.current_frame].cels[project.current_layer].image + return project.frames[project.current_frame].cels[project.current_layer].get_image() func _get_selected_draw_images() -> Array: # Array of Images var images := [] var project: Project = Global.current_project for cel_index in project.selected_cels: - var cel: Cel = project.frames[cel_index[0]].cels[cel_index[1]] + var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]] if project.layers[cel_index[1]].can_layer_get_drawn(): images.append(cel.image) return images diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index 9eaad2cf7..2624dfd80 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -577,9 +577,11 @@ func _get_undo_data() -> Dictionary: cels.append(project.frames[cel_index[0]].cels[cel_index[1]]) else: for frame in project.frames: - var cel: Cel = frame.cels[project.current_layer] + var cel: PixelCel = frame.cels[project.current_layer] cels.append(cel) for cel in cels: + if cel is GroupCel: + continue var image: Image = cel.image image.unlock() data[image] = image.data diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index 7b538f630..8830f58f8 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -153,7 +153,7 @@ func _get_undo_data() -> Dictionary: cels.append(project.frames[cel_index[0]].cels[cel_index[1]]) else: for frame in project.frames: - var cel: Cel = frame.cels[project.current_layer] + var cel: PixelCel = frame.cels[project.current_layer] cels.append(cel) for cel in cels: var image: Image = cel.image diff --git a/src/UI/Canvas/Canvas.gd b/src/UI/Canvas/Canvas.gd index 3cf54e767..75c593d56 100644 --- a/src/UI/Canvas/Canvas.gd +++ b/src/UI/Canvas/Canvas.gd @@ -41,8 +41,10 @@ func _draw() -> void: draw_set_transform(position_tmp, rotation, scale_tmp) # Draw current frame layers for i in range(Global.current_project.layers.size()): + if current_cels[i] is GroupCel: + continue var modulate_color := Color(1, 1, 1, current_cels[i].opacity) - if Global.current_project.layers[i].visible: # if it's visible + if Global.current_project.layers[i].is_visible_in_hierarchy(): if i == current_layer: draw_texture(current_cels[i].image_texture, move_preview_location, modulate_color) else: @@ -121,16 +123,8 @@ func update_texture(layer_i: int, frame_i := -1, project: Project = Global.curre frame_i = project.current_frame if frame_i < project.frames.size() and layer_i < project.layers.size(): - var current_cel: Cel = project.frames[frame_i].cels[layer_i] - current_cel.image_texture.set_data(current_cel.image) - - if project == Global.current_project: - var container_index = Global.frames_container.get_child_count() - 1 - layer_i - var layer_cel_container = Global.frames_container.get_child(container_index) - var cel_button = layer_cel_container.get_child(frame_i) - var cel_texture_rect: TextureRect - cel_texture_rect = cel_button.find_node("CelTexture") - cel_texture_rect.texture = current_cel.image_texture + var current_cel: BaseCel = project.frames[frame_i].cels[layer_i] + current_cel.update_texture() func update_selected_cels_textures(project: Project = Global.current_project) -> void: @@ -138,15 +132,8 @@ func update_selected_cels_textures(project: Project = Global.current_project) -> var frame_index: int = cel_index[0] var layer_index: int = cel_index[1] if frame_index < project.frames.size() and layer_index < project.layers.size(): - var current_cel: Cel = project.frames[frame_index].cels[layer_index] - current_cel.image_texture.set_data(current_cel.image) - - if project == Global.current_project: - var container_index = Global.frames_container.get_child_count() - 1 - layer_index - var layer_cel_container = Global.frames_container.get_child(container_index) - var cel_button = layer_cel_container.get_child(frame_index) - var cel_texture_rect: TextureRect = cel_button.find_node("CelTexture") - cel_texture_rect.texture = current_cel.image_texture + var current_cel: BaseCel = project.frames[frame_index].cels[layer_index] + current_cel.update_texture() func refresh_onion() -> void: diff --git a/src/UI/Canvas/CanvasPreview.gd b/src/UI/Canvas/CanvasPreview.gd index 17d3a2b7f..954f79b3d 100644 --- a/src/UI/Canvas/CanvasPreview.gd +++ b/src/UI/Canvas/CanvasPreview.gd @@ -20,8 +20,13 @@ func _draw() -> void: # Draw current frame layers for i in range(current_cels.size()): + if current_cels[i] is GroupCel: + continue var modulate_color := Color(1, 1, 1, current_cels[i].opacity) - if i < current_project.layers.size() and current_project.layers[i].visible: + if ( + i < current_project.layers.size() + and current_project.layers[i].is_visible_in_hierarchy() + ): draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) diff --git a/src/UI/Canvas/CurrentFrameDrawer.gd b/src/UI/Canvas/CurrentFrameDrawer.gd index e102a58ad..5714f9964 100644 --- a/src/UI/Canvas/CurrentFrameDrawer.gd +++ b/src/UI/Canvas/CurrentFrameDrawer.gd @@ -4,6 +4,11 @@ extends Node2D func _draw() -> void: var current_cels: Array = Global.current_project.frames[Global.current_project.current_frame].cels for i in range(Global.current_project.layers.size()): - if Global.current_project.layers[i].visible and current_cels[i].opacity > 0: + if current_cels[i] is GroupCel: + continue + if ( + Global.current_project.layers[i].is_visible_in_hierarchy() + and current_cels[i].opacity > 0 + ): var modulate_color := Color(1, 1, 1, current_cels[i].opacity) draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) diff --git a/src/UI/Canvas/OnionSkinning.gd b/src/UI/Canvas/OnionSkinning.gd index c100386ea..e856f6ee0 100644 --- a/src/UI/Canvas/OnionSkinning.gd +++ b/src/UI/Canvas/OnionSkinning.gd @@ -28,8 +28,8 @@ func _draw() -> void: if change == clamp(change, 0, Global.current_project.frames.size() - 1): var layer_i := 0 for cel in Global.current_project.frames[change].cels: - var layer: Layer = Global.current_project.layers[layer_i] - if layer.visible: + var layer: BaseLayer = Global.current_project.layers[layer_i] + if layer.is_visible_in_hierarchy(): # Ignore layer if it has the "_io" suffix in its name (case in-sensitive) if not (layer.name.to_lower().ends_with("_io")): color.a = 0.6 / i diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 84537a9ec..43d48a696 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -98,7 +98,7 @@ func _input(event: InputEvent) -> void: if Global.cross_cursor: cursor = Control.CURSOR_CROSS var project: Project = Global.current_project - var layer: Layer = project.layers[project.current_layer] + var layer: BaseLayer = project.layers[project.current_layer] if not layer.can_layer_get_drawn(): cursor = Control.CURSOR_FORBIDDEN @@ -644,7 +644,9 @@ func _get_selected_draw_images() -> Array: # Array of Images var images := [] var project: Project = Global.current_project for cel_index in project.selected_cels: - var cel: Cel = project.frames[cel_index[0]].cels[cel_index[1]] + var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]] + if not cel is PixelCel: + continue if project.layers[cel_index[1]].can_layer_get_drawn(): images.append(cel.image) return images @@ -665,7 +667,7 @@ func copy() -> void: var cl_big_bounding_rectangle := Rect2() var cl_selection_offset := Vector2.ZERO - var image: Image = project.frames[project.current_frame].cels[project.current_layer].image + var image: Image = project.frames[project.current_frame].cels[project.current_layer].get_image() var to_copy := Image.new() if !project.has_selection: to_copy.copy_from(image) diff --git a/src/UI/Dialogs/CreateNewImage.gd b/src/UI/Dialogs/CreateNewImage.gd index 99ce4d755..a20bb0967 100644 --- a/src/UI/Dialogs/CreateNewImage.gd +++ b/src/UI/Dialogs/CreateNewImage.gd @@ -108,10 +108,9 @@ func _on_CreateNewImage_confirmed() -> void: proj_name = tr("untitled") var new_project := Project.new([], proj_name, Vector2(width, height).floor()) - new_project.layers.append(Layer.new()) + new_project.layers.append(PixelLayer.new(new_project)) new_project.fill_color = fill_color - var frame: Frame = new_project.new_empty_frame() - new_project.frames.append(frame) + new_project.frames.append(new_project.new_empty_frame()) Global.projects.append(new_project) Global.tabs.current_tab = Global.tabs.get_tab_count() - 1 Global.canvas.camera_zoom() diff --git a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd index 00f7d152e..d87d8dac9 100644 --- a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd +++ b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd @@ -22,7 +22,7 @@ func _on_ResizeCanvas_about_to_show() -> void: var layer_i := 0 for cel in Global.current_project.frames[Global.current_project.current_frame].cels: - if Global.current_project.layers[layer_i].visible: + if cel is PixelCel and Global.current_project.layers[layer_i].is_visible_in_hierarchy(): var cel_image := Image.new() cel_image.copy_from(cel.image) cel_image.lock() diff --git a/src/UI/Dialogs/PreviewDialog.gd b/src/UI/Dialogs/PreviewDialog.gd index 63f200442..d4a658e58 100644 --- a/src/UI/Dialogs/PreviewDialog.gd +++ b/src/UI/Dialogs/PreviewDialog.gd @@ -5,7 +5,7 @@ enum ImageImportOptions { SPRITESHEET_TAB, SPRITESHEET_LAYER, NEW_FRAME, - REPLACE_FRAME, + REPLACE_CEL, NEW_LAYER, PALETTE, BRUSH, @@ -29,7 +29,7 @@ onready var frame_size_label: Label = $VBoxContainer/SizeContainer/FrameSizeLabe onready var spritesheet_tab_options = $VBoxContainer/HBoxContainer/SpritesheetTabOptions onready var spritesheet_lay_opt = $VBoxContainer/HBoxContainer/SpritesheetLayerOptions onready var new_frame_options = $VBoxContainer/HBoxContainer/NewFrameOptions -onready var replace_frame_options = $VBoxContainer/HBoxContainer/ReplaceFrameOptions +onready var replace_cel_options = $VBoxContainer/HBoxContainer/ReplaceCelOptions onready var new_layer_options = $VBoxContainer/HBoxContainer/NewLayerOptions onready var new_brush_options = $VBoxContainer/HBoxContainer/NewBrushOptions onready var new_brush_name = $VBoxContainer/HBoxContainer/NewBrushOptions/BrushName @@ -47,7 +47,7 @@ func _on_PreviewDialog_about_to_show() -> void: import_options.add_item("Spritesheet (new project)") import_options.add_item("Spritesheet (new layer)") import_options.add_item("New frame") - import_options.add_item("Replace frame") + import_options.add_item("Replace cel") import_options.add_item("New layer") import_options.add_item("New palette") import_options.add_item("New brush") @@ -129,13 +129,13 @@ func _on_PreviewDialog_confirmed() -> void: ) elif current_import_option == ImageImportOptions.NEW_FRAME: - var layer_index: int = new_frame_options.get_node("AtLayerSpinbox").value + var layer_index: int = new_frame_options.get_node("AtLayerOption").get_selected_id() OpenSave.open_image_as_new_frame(image, layer_index) - elif current_import_option == ImageImportOptions.REPLACE_FRAME: - var layer_index: int = replace_frame_options.get_node("AtLayerSpinbox").value - var frame_index: int = replace_frame_options.get_node("AtFrameSpinbox").value - 1 - OpenSave.open_image_at_frame(image, layer_index, frame_index) + elif current_import_option == ImageImportOptions.REPLACE_CEL: + var layer_index: int = replace_cel_options.get_node("AtLayerOption").get_selected_id() + var frame_index: int = replace_cel_options.get_node("AtFrameSpinbox").value - 1 + OpenSave.open_image_at_cel(image, layer_index, frame_index) elif current_import_option == ImageImportOptions.NEW_LAYER: var frame_index: int = new_layer_options.get_node("AtFrameSpinbox").value - 1 @@ -204,15 +204,15 @@ func synchronize() -> void: ).value) elif id == ImageImportOptions.NEW_FRAME: - dialog.new_frame_options.get_node("AtLayerSpinbox").value = (new_frame_options.get_node( - "AtLayerSpinbox" - ).value) + dialog.new_frame_options.get_node("AtLayerOption").selected = (new_frame_options.get_node( + "AtLayerOption" + ).selected) - elif id == ImageImportOptions.REPLACE_FRAME: - dialog.replace_frame_options.get_node("AtLayerSpinbox").value = (replace_frame_options.get_node( - "AtLayerSpinbox" - ).value) - dialog.replace_frame_options.get_node("AtFrameSpinbox").value = (replace_frame_options.get_node( + elif id == ImageImportOptions.REPLACE_CEL: + dialog.replace_cel_options.get_node("AtLayerOption").selected = (replace_cel_options.get_node( + "AtLayerOption" + ).selected) + dialog.replace_cel_options.get_node("AtFrameSpinbox").value = (replace_cel_options.get_node( "AtFrameSpinbox" ).value) @@ -236,7 +236,7 @@ func _on_ImportOption_item_selected(id: int) -> void: spritesheet_tab_options.visible = false spritesheet_lay_opt.visible = false new_frame_options.visible = false - replace_frame_options.visible = false + replace_cel_options.visible = false new_layer_options.visible = false new_brush_options.visible = false texture_rect.get_child(0).visible = false @@ -260,18 +260,33 @@ func _on_ImportOption_item_selected(id: int) -> void: elif id == ImageImportOptions.NEW_FRAME: new_frame_options.visible = true - new_frame_options.get_node("AtLayerSpinbox").max_value = ( - Global.current_project.layers.size() - - 1 - ) - - elif id == ImageImportOptions.REPLACE_FRAME: - replace_frame_options.visible = true - replace_frame_options.get_node("AtLayerSpinbox").max_value = ( - Global.current_project.layers.size() - - 1 - ) - var at_frame_spinbox: SpinBox = replace_frame_options.get_node("AtFrameSpinbox") + # Fill the at layer option button: + var at_layer_option: OptionButton = new_frame_options.get_node("AtLayerOption") + var layers := Global.current_project.layers.duplicate() + layers.invert() + var i := 0 + for l in layers: + if not l is PixelLayer: + continue + at_layer_option.add_item(l.name, l.index) + at_layer_option.set_item_tooltip(i, l.get_layer_path()) + i += 1 + at_layer_option.selected = at_layer_option.get_item_count() - 1 + elif id == ImageImportOptions.REPLACE_CEL: + replace_cel_options.visible = true + # Fill the at layer option button: + var at_layer_option: OptionButton = replace_cel_options.get_node("AtLayerOption") + var layers := Global.current_project.layers.duplicate() + layers.invert() + var i := 0 + for l in layers: + if not l is PixelLayer: + continue + at_layer_option.add_item(l.name, l.index) + at_layer_option.set_item_tooltip(i, l.get_layer_path()) + i += 1 + at_layer_option.selected = at_layer_option.get_item_count() - 1 + var at_frame_spinbox: SpinBox = replace_cel_options.get_node("AtFrameSpinbox") at_frame_spinbox.max_value = Global.current_project.frames.size() elif id == ImageImportOptions.NEW_LAYER: diff --git a/src/UI/Dialogs/PreviewDialog.tscn b/src/UI/Dialogs/PreviewDialog.tscn index e4fe9d54d..61fae68c7 100644 --- a/src/UI/Dialogs/PreviewDialog.tscn +++ b/src/UI/Dialogs/PreviewDialog.tscn @@ -161,40 +161,40 @@ margin_right = 53.0 margin_bottom = 19.0 text = "At layer:" -[node name="AtLayerSpinbox" type="SpinBox" parent="VBoxContainer/HBoxContainer/NewFrameOptions"] +[node name="AtLayerOption" type="OptionButton" parent="VBoxContainer/HBoxContainer/NewFrameOptions"] margin_left = 57.0 -margin_right = 131.0 -margin_bottom = 24.0 +margin_right = 86.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 -max_value = 0.0 +align = 2 -[node name="ReplaceFrameOptions" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"] +[node name="ReplaceCelOptions" type="HBoxContainer" parent="VBoxContainer/HBoxContainer"] visible = false margin_left = 155.0 margin_right = 427.0 margin_bottom = 24.0 -[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/ReplaceFrameOptions"] +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/ReplaceCelOptions"] margin_top = 5.0 margin_right = 53.0 margin_bottom = 19.0 text = "At layer:" -[node name="AtLayerSpinbox" type="SpinBox" parent="VBoxContainer/HBoxContainer/ReplaceFrameOptions"] +[node name="AtLayerOption" type="OptionButton" parent="VBoxContainer/HBoxContainer/ReplaceCelOptions"] margin_left = 57.0 -margin_right = 131.0 -margin_bottom = 24.0 +margin_right = 86.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 -max_value = 0.0 +align = 2 -[node name="Label2" type="Label" parent="VBoxContainer/HBoxContainer/ReplaceFrameOptions"] +[node name="Label2" type="Label" parent="VBoxContainer/HBoxContainer/ReplaceCelOptions"] margin_left = 135.0 margin_top = 5.0 margin_right = 194.0 margin_bottom = 19.0 text = "At frame:" -[node name="AtFrameSpinbox" type="SpinBox" parent="VBoxContainer/HBoxContainer/ReplaceFrameOptions"] +[node name="AtFrameSpinbox" type="SpinBox" parent="VBoxContainer/HBoxContainer/ReplaceCelOptions"] margin_left = 198.0 margin_right = 272.0 margin_bottom = 24.0 diff --git a/src/UI/Timeline/AnimationTimeline.gd b/src/UI/Timeline/AnimationTimeline.gd index 798f5e7c7..914302b89 100644 --- a/src/UI/Timeline/AnimationTimeline.gd +++ b/src/UI/Timeline/AnimationTimeline.gd @@ -12,6 +12,8 @@ var max_cel_size := 144 var past_above_canvas := true var future_above_canvas := true +var frame_button_node = preload("res://src/UI/Timeline/FrameButton.tscn") + onready var old_scroll: int = 0 # The previous scroll state of $ScrollContainer onready var tag_spacer = find_node("TagSpacer") onready var start_spacer = find_node("StartSpacer") @@ -23,6 +25,7 @@ onready var tag_scroll_container: ScrollContainer = find_node("TagScroll") onready var fps_spinbox: SpinBox = find_node("FPSValue") onready var onion_skinning_button: BaseButton = find_node("OnionSkinning") onready var loop_animation_button: BaseButton = find_node("LoopAnim") +onready var drag_highlight: ColorRect = find_node("DragHighlight") func _ready() -> void: @@ -36,6 +39,11 @@ func _ready() -> void: timeline_scroll.size_flags_horizontal = SIZE_FILL +func _notification(what: int) -> void: + if what == NOTIFICATION_DRAG_END: + drag_highlight.hide() + + func _input(event: InputEvent) -> void: var mouse_pos := get_global_mouse_position() var timeline_rect := Rect2(rect_global_position, rect_size) @@ -125,18 +133,16 @@ func add_frame() -> void: var project: Project = Global.current_project var frame_add_index := project.current_frame + 1 var frame: Frame = project.new_empty_frame() - var new_frames: Array = project.frames.duplicate() var new_layers: Array = project.duplicate_layers() - new_frames.insert(frame_add_index, frame) for l_i in range(new_layers.size()): - if new_layers[l_i].new_cels_linked: # If the link button is pressed + if new_layers[l_i].get("new_cels_linked"): # If the link button is pressed new_layers[l_i].linked_cels.append(frame) - frame.cels[l_i].image = new_layers[l_i].linked_cels[0].cels[l_i].image + frame.cels[l_i].set_content(new_layers[l_i].linked_cels[0].cels[l_i].get_content()) frame.cels[l_i].image_texture = new_layers[l_i].linked_cels[0].cels[l_i].image_texture # Code to PUSH AHEAD tags starting after the frame - var new_animation_tags := Global.current_project.animation_tags.duplicate() + var new_animation_tags := project.animation_tags.duplicate() # Loop through the tags to create new classes for them, so that they won't be the same # as Global.current_project.animation_tags's classes. Needed for undo/redo to work properly. for i in new_animation_tags.size(): @@ -158,46 +164,43 @@ func add_frame() -> void: project.undo_redo.create_action("Add Frame") project.undo_redo.add_do_method(Global, "undo_or_redo", false) project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - - project.undo_redo.add_do_property(project, "frames", new_frames) - project.undo_redo.add_do_property(project, "current_frame", project.current_frame + 1) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "animation_tags", new_animation_tags - ) project.undo_redo.add_do_property(project, "layers", new_layers) - - project.undo_redo.add_undo_property(project, "frames", project.frames) - project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "animation_tags", Global.current_project.animation_tags - ) project.undo_redo.add_undo_property(project, "layers", project.layers) + project.undo_redo.add_do_method(project, "add_frames", [frame], [frame_add_index]) + project.undo_redo.add_undo_method(project, "remove_frames", [frame_add_index]) + project.undo_redo.add_do_property(project, "animation_tags", new_animation_tags) + project.undo_redo.add_undo_property(project, "animation_tags", project.animation_tags) + project.undo_redo.add_do_property(project, "current_frame", project.current_frame + 1) + project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) project.undo_redo.commit_action() -func _on_DeleteFrame_pressed(frame := -1) -> void: - var frames := [] +func _on_DeleteFrame_pressed() -> void: + var indices := [] for cel in Global.current_project.selected_cels: - frame = cel[0] - if not frame in frames: - frames.append(frame) - frames.sort() - delete_frames(frames) + var f: int = cel[0] + if not f in indices: + indices.append(f) + indices.sort() + delete_frames(indices) -func delete_frames(frames := []) -> void: - if Global.current_project.frames.size() == 1: +func delete_frames(indices := []) -> void: + var project: Project = Global.current_project + if project.frames.size() == 1: return - if frames.size() == 0: - frames.append(Global.current_project.current_frame) + if indices.size() == project.frames.size(): + indices.remove(indices.size() - 1) # Ensure the project has at least 1 frame + elif indices.size() == 0: + indices.append(project.current_frame) - var new_frames: Array = Global.current_project.frames.duplicate() - var current_frame := Global.current_project.current_frame - var new_layers: Array = Global.current_project.duplicate_layers() + var current_frame: int = min(project.current_frame, project.frames.size() - indices.size() - 1) + var new_layers: Array = project.duplicate_layers() + var frames := [] var frame_correction := 0 # Only needed for tag adjustment - var new_animation_tags := Global.current_project.animation_tags.duplicate() + var new_animation_tags := project.animation_tags.duplicate() # Loop through the tags to create new classes for them, so that they won't be the same # as Global.current_project.animation_tags's classes. Needed for undo/redo to work properly. for i in new_animation_tags.size(): @@ -208,90 +211,70 @@ func delete_frames(frames := []) -> void: new_animation_tags[i].to ) - for frame in frames: - if new_frames.size() == 1: # If only 1 frame - break - var frame_to_delete: Frame = Global.current_project.frames[frame] - new_frames.erase(frame_to_delete) - if current_frame > 0 && current_frame == new_frames.size(): # If it's the last frame - current_frame -= 1 - + for f in indices: + frames.append(project.frames[f]) # Check if one of the cels of the frame is linked # if they are, unlink them too # this prevents removed cels being kept in linked memory for layer in new_layers: for linked in layer.linked_cels: - if linked == Global.current_project.frames[frame]: + if linked == project.frames[f]: layer.linked_cels.erase(linked) # Loop through the tags to see if the frame is in one - frame -= frame_correction # Erasing made frames indexes 1 step ahead their intended tags + f -= frame_correction # Erasing made frames indexes 1 step ahead their intended tags var tag_correction := 0 # needed when tag is erased for tag_ind in new_animation_tags.size(): var tag = new_animation_tags[tag_ind - tag_correction] - if frame + 1 >= tag.from && frame + 1 <= tag.to: + if f + 1 >= tag.from && f + 1 <= tag.to: if tag.from == tag.to: # If we're deleting the only frame in the tag new_animation_tags.erase(tag) tag_correction += 1 else: tag.to -= 1 - elif frame + 1 < tag.from: + elif f + 1 < tag.from: tag.from -= 1 tag.to -= 1 frame_correction += 1 # Compensation for the next batch - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Remove Frame") - - Global.current_project.undo_redo.add_do_property(Global.current_project, "frames", new_frames) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_frame", current_frame - ) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "animation_tags", new_animation_tags - ) - Global.current_project.undo_redo.add_do_property(Global.current_project, "layers", new_layers) - - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "frames", Global.current_project.frames - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_frame", Global.current_project.current_frame - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "animation_tags", Global.current_project.animation_tags - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.commit_action() + project.undos += 1 + project.undo_redo.create_action("Remove Frame") + project.undo_redo.add_do_property(project, "layers", new_layers) + project.undo_redo.add_undo_property(project, "layers", Global.current_project.layers) + project.undo_redo.add_do_method(project, "remove_frames", indices) + project.undo_redo.add_undo_method(project, "add_frames", frames, indices) + project.undo_redo.add_do_property(project, "animation_tags", new_animation_tags) + project.undo_redo.add_undo_property(project, "animation_tags", project.animation_tags) + project.undo_redo.add_do_property(project, "current_frame", current_frame) + project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.commit_action() -func _on_CopyFrame_pressed(frame := -1) -> void: - var frames := [] +func _on_CopyFrame_pressed() -> void: + var indices := [] for cel in Global.current_project.selected_cels: - frame = cel[0] - if not frame in frames: - frames.append(frame) - frames.sort() - copy_frames(frames) + var f: int = cel[0] + if not f in indices: + indices.append(f) + indices.sort() + copy_frames(indices) -func copy_frames(frames := []) -> void: - Global.canvas.selection.transform_content_confirm() +func copy_frames(indices := []) -> void: + var project: Project = Global.current_project - if frames.size() == 0: - frames.append(Global.current_project.current_frame) + if indices.size() == 0: + indices.append(project.current_frame) - var new_frames := Global.current_project.frames.duplicate() - var new_layers: Array = Global.current_project.duplicate_layers() + var new_layers: Array = project.duplicate_layers() + var copied_frames := [] + var copied_indices := range(indices[-1] + 1, indices[-1] + 1 + indices.size()) - var new_animation_tags := Global.current_project.animation_tags.duplicate() + var new_animation_tags := project.animation_tags.duplicate() # Loop through the tags to create new classes for them, so that they won't be the same - # as Global.current_project.animation_tags's classes. Needed for undo/redo to work properly. + # as project.animation_tags's classes. Needed for undo/redo to work properly. for i in new_animation_tags.size(): new_animation_tags[i] = AnimationTag.new( new_animation_tags[i].name, @@ -300,61 +283,48 @@ func copy_frames(frames := []) -> void: new_animation_tags[i].to ) - for frm in frames.size(): - var frame = frames[(frames.size() - 1) - frm] + for f in indices: var new_frame := Frame.new() - new_frames.insert(frames[-1] + 1, new_frame) + copied_frames.append(new_frame) - var prev_frame: Frame = Global.current_project.frames[frame] - for cel in prev_frame.cels: # Copy every cel - var sprite := Image.new() - sprite.copy_from(cel.image) - var sprite_texture := ImageTexture.new() - sprite_texture.create_from_image(sprite, 0) - new_frame.cels.append(Cel.new(sprite, cel.opacity, sprite_texture)) + var prev_frame: Frame = project.frames[f] new_frame.duration = prev_frame.duration for l_i in range(new_layers.size()): - if new_layers[l_i].new_cels_linked: # If the link button is pressed + # If the layer has new_cels_linked variable, and its true + var new_cels_linked := true if new_layers[l_i].get("new_cels_linked") else false + + # Copy the cel, create new cel content if new cels aren't linked + new_frame.cels.append(new_layers[l_i].copy_cel(f, new_cels_linked)) + + if new_cels_linked: # If the link button is pressed new_layers[l_i].linked_cels.append(new_frame) - new_frame.cels[l_i].image = new_layers[l_i].linked_cels[0].cels[l_i].image + new_frame.cels[l_i].set_content( + new_layers[l_i].linked_cels[0].cels[l_i].get_content() + ) new_frame.cels[l_i].image_texture = new_layers[l_i].linked_cels[0].cels[l_i].image_texture # Loop through the tags to see if the frame is in one for tag in new_animation_tags: - if frames[-1] + 1 >= tag.from && frames[-1] + 1 <= tag.to: + if indices[-1] + 1 >= tag.from && indices[-1] + 1 <= tag.to: tag.to += 1 - elif frames[-1] + 1 < tag.from: + elif indices[-1] + 1 < tag.from: tag.from += 1 tag.to += 1 - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Add Frame") - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - - Global.current_project.undo_redo.add_do_property(Global.current_project, "frames", new_frames) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_frame", frames[-1] + 1 - ) - Global.current_project.undo_redo.add_do_property(Global.current_project, "layers", new_layers) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "animation_tags", new_animation_tags - ) - - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "frames", Global.current_project.frames - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_frame", frames[-1] - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "animation_tags", Global.current_project.animation_tags - ) - Global.current_project.undo_redo.commit_action() + project.undos += 1 + project.undo_redo.create_action("Add Frame") + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.add_do_property(project, "layers", new_layers) + project.undo_redo.add_undo_property(project, "layers", project.layers) + project.undo_redo.add_do_method(project, "add_frames", copied_frames, copied_indices) + project.undo_redo.add_undo_method(project, "remove_frames", copied_indices) + project.undo_redo.add_do_property(project, "current_frame", indices[-1] + 1) + project.undo_redo.add_undo_property(project, "current_frame", indices[-1]) + project.undo_redo.add_do_property(project, "animation_tags", new_animation_tags) + project.undo_redo.add_undo_property(project, "animation_tags", project.animation_tags) + project.undo_redo.commit_action() func _on_FrameTagButton_pressed() -> void: @@ -431,9 +401,12 @@ func _on_PlayBackwards_toggled(button_pressed: bool) -> void: play_animation(button_pressed, false) +# Called on each frame of the animation func _on_AnimationTimer_timeout() -> void: if first_frame == last_frame: - $AnimationTimer.stop() + Global.play_forward.pressed = false + Global.play_backwards.pressed = false + Global.animation_timer.stop() return Global.canvas.selection.transform_content_confirm() @@ -597,202 +570,237 @@ func _on_FuturePlacement_item_selected(index: int) -> void: # Layer buttons -func add_layer(is_new := true) -> void: - Global.canvas.selection.transform_content_confirm() - var new_layers: Array = Global.current_project.layers.duplicate() - var l := Layer.new() - if !is_new: # Clone layer - l.name = ( - Global.current_project.layers[Global.current_project.current_layer].name - + " (" - + tr("copy") - + ")" - ) - new_layers.append(l) +func _on_AddLayer_pressed() -> void: + var project: Project = Global.current_project - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Add Layer") + var l := PixelLayer.new(project) + var cels := [] + for f in project.frames: + cels.append(l.new_empty_cel()) - for f in Global.current_project.frames: - var new_layer := Image.new() - if is_new: - new_layer.create( - Global.current_project.size.x, - Global.current_project.size.y, - false, - Image.FORMAT_RGBA8 - ) - else: # Clone layer - new_layer.copy_from(f.cels[Global.current_project.current_layer].image) + project.undos += 1 + project.undo_redo.create_action("Add Layer") + project.undo_redo.add_do_property(project, "current_layer", project.layers.size()) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_do_method(project, "add_layers", [l], [project.layers.size()], [cels]) + project.undo_redo.add_undo_method(project, "remove_layers", [project.layers.size()]) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.commit_action() - var new_cels: Array = f.cels.duplicate() - new_cels.append(Cel.new(new_layer, 1)) - Global.current_project.undo_redo.add_do_property(f, "cels", new_cels) - Global.current_project.undo_redo.add_undo_property(f, "cels", f.cels) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_layer", Global.current_project.layers.size() +func _on_AddGroup_pressed() -> void: + var project: Project = Global.current_project + + var l := GroupLayer.new(project) + var cels := [] + for f in project.frames: + cels.append(l.new_empty_cel()) + + project.undos += 1 + project.undo_redo.create_action("Add Layer") + project.undo_redo.add_do_property(project, "current_layer", project.layers.size()) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_do_method(project, "add_layers", [l], [project.layers.size()], [cels]) + project.undo_redo.add_undo_method(project, "remove_layers", [project.layers.size()]) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.commit_action() + + +func _on_CloneLayer_pressed() -> void: + var project: Project = Global.current_project + var source_layers: Array = project.layers[project.current_layer].get_children(true) + source_layers.append(project.layers[project.current_layer]) + + var clones := [] # Array of Layers + var cels := [] # 2D Array of Cels + for sl in source_layers: + var cl: BaseLayer = sl.copy() + if sl.index == project.current_layer: + cl.name = str(sl.name, " (", tr("copy"), ")") + clones.append(cl) + cels.append(sl.copy_all_cels()) + + # Swap parents with clones if the parent is one of the source layers + for cl in clones: + var p = source_layers.find(cl.parent) + if p > -1: + cl.parent = clones[p] + + var indices := range(project.current_layer + 1, project.current_layer + clones.size() + 1) + + project.undos += 1 + project.undo_redo.create_action("Add Layer") + project.undo_redo.add_do_property( + project, "current_layer", project.current_layer + clones.size() ) - Global.current_project.undo_redo.add_do_property(Global.current_project, "layers", new_layers) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_layer", Global.current_project.current_layer - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_do_method(project, "add_layers", clones, indices, cels) + project.undo_redo.add_undo_method(project, "remove_layers", indices) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.commit_action() func _on_RemoveLayer_pressed() -> void: - if Global.current_project.layers.size() == 1: + var project: Project = Global.current_project + if project.layers.size() == 1: return - var new_layers: Array = Global.current_project.layers.duplicate() - new_layers.remove(Global.current_project.current_layer) - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Remove Layer") - if Global.current_project.current_layer > 0: - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_layer", Global.current_project.current_layer - 1 - ) - else: - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_layer", Global.current_project.current_layer - ) - for f in Global.current_project.frames: - var new_cels: Array = f.cels.duplicate() - new_cels.remove(Global.current_project.current_layer) - Global.current_project.undo_redo.add_do_property(f, "cels", new_cels) - Global.current_project.undo_redo.add_undo_property(f, "cels", f.cels) + var layers: Array = project.layers[project.current_layer].get_children(true) + layers.append(project.layers[project.current_layer]) + var indices := [] + for l in layers: + indices.append(l.index) - Global.current_project.undo_redo.add_do_property(Global.current_project, "layers", new_layers) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_layer", Global.current_project.current_layer + var cels := [] + for l in layers: + cels.append([]) + for f in project.frames: + cels[-1].append(f.cels[l.index]) + + project.undos += 1 + project.undo_redo.create_action("Remove Layer") + project.undo_redo.add_do_property(project, "current_layer", max(indices[0] - 1, 0)) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_do_method(project, "remove_layers", indices) + project.undo_redo.add_undo_method(project, "add_layers", layers, indices, cels) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.commit_action() + + +# Move the layer up or down in layer order and/or reparent to be deeper/shallower in the +# layer hierarchy depending on its current index and parent +func change_layer_order(up: bool) -> void: + var project: Project = Global.current_project + var layer: BaseLayer = project.layers[project.current_layer] + var child_count = layer.get_child_count(true) + var from_indices := range(layer.index - child_count, layer.index + 1) + var from_parents := [] + for l in from_indices: + from_parents.append(project.layers[l].parent) + var to_parents := from_parents.duplicate() + var to_index = layer.index - child_count # the index where the LOWEST shifted layer should end up + + if up: + var above_layer: BaseLayer = project.layers[project.current_layer + 1] + if layer.parent == above_layer: # Above is the parent, leave the parent and go up + to_parents[-1] = above_layer.parent + to_index = to_index + 1 + elif layer.parent != above_layer.parent: # Above layer must be deeper in the hierarchy + # Move layer 1 level deeper in hierarchy. Done by setting its parent to the parent of + # above_layer, and if that is multiple levels, drop levels until its just 1 + to_parents[-1] = above_layer.parent + while to_parents[-1].parent != layer.parent: + to_parents[-1] = to_parents[-1].parent + elif above_layer.accepts_child(layer): + to_parents[-1] = above_layer + else: + to_index = to_index + 1 + else: # Down + if layer.index == child_count: # If at the very bottom of the layer stack + if not is_instance_valid(layer.parent): + return + to_parents[-1] = layer.parent.parent # Drop a level in the hierarchy + else: + var below_layer: BaseLayer = project.layers[project.current_layer - 1 - child_count] + if layer.parent != below_layer.parent: # If there is a hierarchy change + to_parents[-1] = layer.parent.parent # Drop a level in the hierarchy + elif below_layer.accepts_child(layer): + to_parents[-1] = below_layer + to_index = to_index - 1 + else: + to_index = to_index - 1 + + var to_indices := range(to_index, to_index + child_count + 1) + + project.undo_redo.create_action("Change Layer Order") + project.undo_redo.add_do_property(project, "current_layer", to_index + child_count) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_do_method(project, "move_layers", from_indices, to_indices, to_parents) + project.undo_redo.add_undo_method( + project, "move_layers", to_indices, from_indices, from_parents ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.commit_action() - - -func change_layer_order(rate: int) -> void: - var change = Global.current_project.current_layer + rate - - var new_layers: Array = Global.current_project.layers.duplicate() - var temp = new_layers[Global.current_project.current_layer] - new_layers[Global.current_project.current_layer] = new_layers[change] - new_layers[change] = temp - Global.current_project.undo_redo.create_action("Change Layer Order") - for f in Global.current_project.frames: - var new_cels: Array = f.cels.duplicate() - var temp_canvas = new_cels[Global.current_project.current_layer] - new_cels[Global.current_project.current_layer] = new_cels[change] - new_cels[change] = temp_canvas - Global.current_project.undo_redo.add_do_property(f, "cels", new_cels) - Global.current_project.undo_redo.add_undo_property(f, "cels", f.cels) - - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_layer", change - ) - Global.current_project.undo_redo.add_do_property(Global.current_project, "layers", new_layers) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_layer", Global.current_project.current_layer - ) - - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.commit_action() func _on_MergeDownLayer_pressed() -> void: - var new_layers: Array = Global.current_project.duplicate_layers() + var project: Project = Global.current_project + var top_layer: PixelLayer = project.layers[project.current_layer] + var bottom_layer: PixelLayer = project.layers[project.current_layer - 1] + var new_linked_cels: Array = bottom_layer.linked_cels.duplicate() - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Merge Layer") - for f in Global.current_project.frames: - var new_cels: Array = f.cels.duplicate() - for i in new_cels.size(): - new_cels[i] = Cel.new(new_cels[i].image, new_cels[i].opacity) - var selected_layer := Image.new() - selected_layer.copy_from(new_cels[Global.current_project.current_layer].image) + project.undos += 1 + project.undo_redo.create_action("Merge Layer") - selected_layer.lock() - if f.cels[Global.current_project.current_layer].opacity < 1: # If we have layer transparency - for xx in selected_layer.get_size().x: - for yy in selected_layer.get_size().y: - var pixel_color: Color = selected_layer.get_pixel(xx, yy) - var alpha: float = ( - pixel_color.a - * f.cels[Global.current_project.current_layer].opacity - ) - selected_layer.set_pixel( + for f in project.frames: + var top_image := Image.new() + top_image.copy_from(f.cels[top_layer.index].image) + + top_image.lock() + if f.cels[top_layer.index].opacity < 1: # If we have layer transparency + for xx in top_image.get_size().x: + for yy in top_image.get_size().y: + var pixel_color: Color = top_image.get_pixel(xx, yy) + var alpha: float = pixel_color.a * f.cels[top_layer.index].opacity + top_image.set_pixel( xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha) ) - selected_layer.unlock() + top_image.unlock() - var new_layer := Image.new() - new_layer.copy_from(f.cels[Global.current_project.current_layer - 1].image) - new_layer.blend_rect( - selected_layer, Rect2(Vector2.ZERO, Global.current_project.size), Vector2.ZERO - ) - new_cels.remove(Global.current_project.current_layer) + var bottom_image := Image.new() + bottom_image.copy_from(f.cels[bottom_layer.index].image) + bottom_image.blend_rect(top_image, Rect2(Vector2.ZERO, project.size), Vector2.ZERO) if ( - !selected_layer.is_invisible() - and ( - Global.current_project.layers[Global.current_project.current_layer - 1].linked_cels.size() - > 1 - ) - and ( - f - in Global.current_project.layers[( - Global.current_project.current_layer - - 1 - )].linked_cels - ) + !top_image.is_invisible() + and bottom_layer.linked_cels.size() > 1 + and f in bottom_layer.linked_cels ): - new_layers[Global.current_project.current_layer - 1].linked_cels.erase(f) - new_cels[Global.current_project.current_layer - 1].image = new_layer + new_linked_cels.erase(f) + project.undo_redo.add_do_property( + f.cels[bottom_layer.index], "image_texture", ImageTexture.new() + ) + project.undo_redo.add_undo_property( + f.cels[bottom_layer.index], + "image_texture", + f.cels[bottom_layer.index].image_texture + ) + project.undo_redo.add_do_property(f.cels[bottom_layer.index], "image", bottom_image) + project.undo_redo.add_undo_property( + f.cels[bottom_layer.index], "image", f.cels[bottom_layer.index].image + ) else: - Global.current_project.undo_redo.add_do_property( - f.cels[Global.current_project.current_layer - 1].image, "data", new_layer.data + project.undo_redo.add_do_property( + f.cels[bottom_layer.index].image, "data", bottom_image.data ) - Global.current_project.undo_redo.add_undo_property( - f.cels[Global.current_project.current_layer - 1].image, - "data", - f.cels[Global.current_project.current_layer - 1].image.data + project.undo_redo.add_undo_property( + f.cels[bottom_layer.index].image, "data", f.cels[bottom_layer.index].image.data ) - Global.current_project.undo_redo.add_do_property(f, "cels", new_cels) - Global.current_project.undo_redo.add_undo_property(f, "cels", f.cels) + var top_cels := [] + for f in project.frames: + top_cels.append(f.cels[top_layer.index]) - new_layers.remove(Global.current_project.current_layer) - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_layer", Global.current_project.current_layer - 1 + project.undo_redo.add_do_property(project, "current_layer", bottom_layer.index) + project.undo_redo.add_undo_property(project, "current_layer", top_layer.index) + project.undo_redo.add_do_property(bottom_layer, "linked_cels", new_linked_cels) + project.undo_redo.add_undo_property(bottom_layer, "linked_cels", bottom_layer.linked_cels) + project.undo_redo.add_do_method(project, "remove_layers", [top_layer.index]) + project.undo_redo.add_undo_method( + project, "add_layers", [top_layer], [top_layer.index], [top_cels] ) - Global.current_project.undo_redo.add_do_property(Global.current_project, "layers", new_layers) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_layer", Global.current_project.current_layer - ) - - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.commit_action() func _on_OpacitySlider_value_changed(value) -> void: var current_frame: Frame = Global.current_project.frames[Global.current_project.current_frame] - var cel: Cel = current_frame.cels[Global.current_project.current_layer] + var cel: BaseCel = current_frame.cels[Global.current_project.current_layer] cel.opacity = value / 100 Global.layer_opacity_slider.value = value Global.layer_opacity_spinbox.value = value @@ -801,3 +809,117 @@ func _on_OpacitySlider_value_changed(value) -> void: func _on_OnionSkinningSettings_popup_hide() -> void: Global.can_draw = true + + +# Methods to update the UI in response to changes in the current project + + +func project_changed() -> void: + var project: Project = Global.current_project + # These must be removed from tree immediately to not mess up the indices of + # the new buttons, so use either free or queue_free + parent.remove_child + for child in Global.layers_container.get_children(): + child.free() + for child in Global.frame_ids.get_children(): + child.free() + for container in Global.frames_container.get_children(): + container.free() + + for i in project.layers.size(): + project_layer_added(i) + for f in project.frames.size(): + var button: Button = frame_button_node.instance() + button.frame = f + Global.frame_ids.add_child(button) + + # Press selected cel/frame/layer buttons + for cel in project.selected_cels: + var frame: int = cel[0] + var layer: int = cel[1] + if frame < Global.frame_ids.get_child_count(): + var frame_button: BaseButton = Global.frame_ids.get_child(frame) + frame_button.pressed = true + + var container_child_count: int = Global.frames_container.get_child_count() + if layer < container_child_count: + var container = Global.frames_container.get_child(container_child_count - 1 - layer) + if frame < container.get_child_count(): + var cel_button = container.get_child(frame) + cel_button.pressed = true + + var layer_button = Global.layers_container.get_child(container_child_count - 1 - layer) + layer_button.pressed = true + + +func project_frame_added(frame: int) -> void: + var project: Project = Global.current_project + var button: Button = frame_button_node.instance() + button.frame = frame + Global.frame_ids.add_child(button) + Global.frame_ids.move_child(button, frame) + + var layer := Global.frames_container.get_child_count() - 1 + for container in Global.frames_container.get_children(): + var cel_button = project.frames[frame].cels[layer].instantiate_cel_button() + cel_button.frame = frame + cel_button.layer = layer + container.add_child(cel_button) + container.move_child(cel_button, frame) + layer -= 1 + + +func project_frame_removed(frame: int) -> void: + Global.frame_ids.get_child(frame).queue_free() + Global.frame_ids.remove_child(Global.frame_ids.get_child(frame)) + for container in Global.frames_container.get_children(): + container.get_child(frame).free() + + +func project_layer_added(layer: int) -> void: + var project: Project = Global.current_project + + var layer_button: LayerButton = project.layers[layer].instantiate_layer_button() + layer_button.layer = layer + if project.layers[layer].name == "": + project.layers[layer].set_name_to_default(layer) + + var layer_cel_container := HBoxContainer.new() + for f in project.frames.size(): + var cel_button = project.frames[f].cels[layer].instantiate_cel_button() + cel_button.frame = f + cel_button.layer = layer + layer_cel_container.add_child(cel_button) + + layer_button.visible = Global.current_project.layers[layer].is_expanded_in_hierarchy() + layer_cel_container.visible = layer_button.visible + + Global.layers_container.add_child(layer_button) + var count := Global.layers_container.get_child_count() + Global.layers_container.move_child(layer_button, count - 1 - layer) + Global.frames_container.add_child(layer_cel_container) + Global.frames_container.move_child(layer_cel_container, count - 1 - layer) + + +func project_layer_removed(layer: int) -> void: + var count := Global.layers_container.get_child_count() + Global.layers_container.get_child(count - 1 - layer).free() + Global.frames_container.get_child(count - 1 - layer).free() + + +func project_cel_added(frame: int, layer: int) -> void: + var container := Global.frames_container.get_child( + Global.frames_container.get_child_count() - 1 - layer + ) + var cel_button = Global.current_project.frames[frame].cels[layer].instantiate_cel_button() + cel_button.frame = frame + cel_button.layer = layer + container.add_child(cel_button) + container.move_child(cel_button, frame) + + +func project_cel_removed(frame: int, layer: int) -> void: + var container := Global.frames_container.get_child( + Global.frames_container.get_child_count() - 1 - layer + ) + container.get_child(frame).queue_free() + container.remove_child(container.get_child(frame)) diff --git a/src/UI/Timeline/AnimationTimeline.tscn b/src/UI/Timeline/AnimationTimeline.tscn index 267324ddd..bef09dd5a 100644 --- a/src/UI/Timeline/AnimationTimeline.tscn +++ b/src/UI/Timeline/AnimationTimeline.tscn @@ -8,7 +8,7 @@ [ext_resource path="res://assets/graphics/layers/delete.png" type="Texture" id=6] [ext_resource path="res://assets/graphics/layers/clone.png" type="Texture" id=7] [ext_resource path="res://assets/graphics/timeline/move_arrow.png" type="Texture" id=8] -[ext_resource path="res://src/UI/Timeline/FrameButton.tscn" type="PackedScene" id=9] +[ext_resource path="res://assets/graphics/layers/group_new.png" type="Texture" id=10] [ext_resource path="res://assets/graphics/timeline/new_frame.png" type="Texture" id=19] [ext_resource path="res://assets/graphics/timeline/remove_frame.png" type="Texture" id=20] [ext_resource path="res://assets/graphics/timeline/go_to_first_frame.png" type="Texture" id=21] @@ -99,14 +99,17 @@ margin_bottom = 160.0 rect_min_size = Vector2( 36, 160 ) rect_clip_content = true script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} [node name="ScrollContainer" type="ScrollContainer" parent="."] anchor_right = 1.0 anchor_bottom = 1.0 [node name="TimelineContainer" type="VBoxContainer" parent="ScrollContainer"] -margin_right = 902.0 -margin_bottom = 160.0 +margin_right = 745.0 +margin_bottom = 104.0 size_flags_horizontal = 3 size_flags_vertical = 3 __meta__ = { @@ -119,14 +122,15 @@ margin_bottom = 38.0 size_flags_horizontal = 3 [node name="LayerButtonPanelContainer" type="PanelContainer" parent="ScrollContainer/TimelineContainer/TimelineButtons"] -margin_right = 186.0 +margin_right = 190.0 margin_bottom = 38.0 +rect_min_size = Vector2( 190, 0 ) custom_styles/panel = SubResource( 2 ) [node name="LayerButtons" type="HBoxContainer" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer"] margin_left = 4.5 margin_top = 3.0 -margin_right = 181.5 +margin_right = 185.5 margin_bottom = 25.0 size_flags_vertical = 0 custom_constants/separation = 9 @@ -155,11 +159,36 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="RemoveLayer" type="Button" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons" groups=["UIButtons"]] +[node name="AddGroup" type="Button" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons" groups=["UIButtons"]] margin_left = 31.0 margin_right = 53.0 margin_bottom = 22.0 rect_min_size = Vector2( 22, 22 ) +hint_tooltip = "Create a new group layer" +focus_mode = 0 +mouse_default_cursor_shape = 2 + +[node name="TextureRect" type="TextureRect" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/AddGroup"] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -11.0 +margin_top = -11.0 +margin_right = 11.0 +margin_bottom = 11.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +texture = ExtResource( 10 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="RemoveLayer" type="Button" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons" groups=["UIButtons"]] +margin_left = 62.0 +margin_right = 84.0 +margin_bottom = 22.0 +rect_min_size = Vector2( 22, 22 ) hint_tooltip = "Remove current layer" focus_mode = 0 mouse_default_cursor_shape = 8 @@ -182,8 +211,8 @@ __meta__ = { } [node name="MoveUpLayer" type="Button" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons" groups=["UIButtons"]] -margin_left = 62.0 -margin_right = 84.0 +margin_left = 93.0 +margin_right = 115.0 margin_bottom = 22.0 rect_min_size = Vector2( 22, 22 ) hint_tooltip = "Move up the current layer" @@ -208,8 +237,8 @@ __meta__ = { } [node name="MoveDownLayer" type="Button" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons" groups=["UIButtons"]] -margin_left = 93.0 -margin_right = 115.0 +margin_left = 124.0 +margin_right = 146.0 margin_bottom = 22.0 rect_min_size = Vector2( 22, 22 ) hint_tooltip = "Move down the current layer" @@ -234,8 +263,8 @@ __meta__ = { } [node name="CloneLayer" type="Button" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons" groups=["UIButtons"]] -margin_left = 124.0 -margin_right = 146.0 +margin_left = 155.0 +margin_right = 177.0 margin_bottom = 22.0 rect_min_size = Vector2( 22, 22 ) hint_tooltip = "Clone current layer" @@ -259,8 +288,8 @@ __meta__ = { } [node name="MergeDownLayer" type="Button" parent="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons" groups=["UIButtons"]] -margin_left = 155.0 -margin_right = 177.0 +margin_left = 186.0 +margin_right = 208.0 margin_bottom = 22.0 rect_min_size = Vector2( 22, 22 ) hint_tooltip = "Merge current layer with the one below" @@ -285,8 +314,8 @@ __meta__ = { } [node name="Control" type="Control" parent="ScrollContainer/TimelineContainer/TimelineButtons"] -margin_left = 190.0 -margin_right = 378.0 +margin_left = 194.0 +margin_right = 386.0 margin_bottom = 38.0 size_flags_horizontal = 3 @@ -783,12 +812,12 @@ align = 1 [node name="SpacerControl2" type="Control" parent="ScrollContainer/TimelineContainer/OpacityAndTagContainer"] margin_left = 188.0 -margin_right = 217.0 +margin_right = 240.0 margin_bottom = 32.0 -rect_min_size = Vector2( 29, 0 ) +rect_min_size = Vector2( 52, 32 ) [node name="TagScroll" type="ScrollContainer" parent="ScrollContainer/TimelineContainer/OpacityAndTagContainer"] -margin_left = 219.0 +margin_left = 242.0 margin_right = 902.0 margin_bottom = 32.0 rect_min_size = Vector2( 0, 32 ) @@ -798,7 +827,6 @@ theme = SubResource( 20 ) scroll_vertical_enabled = false [node name="HBoxContainer" type="HBoxContainer" parent="ScrollContainer/TimelineContainer/OpacityAndTagContainer/TagScroll"] -margin_right = 683.0 margin_bottom = 32.0 size_flags_horizontal = 3 size_flags_vertical = 3 @@ -808,7 +836,7 @@ custom_constants/separation = 0 margin_bottom = 32.0 [node name="TagContainer" type="Control" parent="ScrollContainer/TimelineContainer/OpacityAndTagContainer/TagScroll/HBoxContainer"] -margin_right = 683.0 +margin_right = 660.0 margin_bottom = 32.0 size_flags_horizontal = 3 @@ -835,8 +863,8 @@ margin_bottom = 68.0 size_flags_horizontal = 3 [node name="LayersAndFrames" type="HBoxContainer" parent="ScrollContainer/TimelineContainer/PanelContainer/HBoxContainer/TimelineScroll"] -margin_right = 81.0 -margin_bottom = 68.0 +margin_right = 45.0 +margin_bottom = 20.0 size_flags_vertical = 3 [node name="LayerVBoxCont" type="VBoxContainer" parent="ScrollContainer/TimelineContainer/PanelContainer/HBoxContainer/TimelineScroll/LayersAndFrames"] @@ -858,22 +886,16 @@ margin_bottom = 20.0 [node name="FrameButtonsAndIds" type="VBoxContainer" parent="ScrollContainer/TimelineContainer/PanelContainer/HBoxContainer/TimelineScroll/LayersAndFrames"] margin_left = 45.0 -margin_right = 81.0 +margin_right = 45.0 margin_bottom = 68.0 [node name="FrameIDs" type="HBoxContainer" parent="ScrollContainer/TimelineContainer/PanelContainer/HBoxContainer/TimelineScroll/LayersAndFrames/FrameButtonsAndIds"] -margin_right = 36.0 -margin_bottom = 20.0 +margin_bottom = 16.0 rect_min_size = Vector2( 0, 16 ) -[node name="FrameButton" parent="ScrollContainer/TimelineContainer/PanelContainer/HBoxContainer/TimelineScroll/LayersAndFrames/FrameButtonsAndIds/FrameIDs" instance=ExtResource( 9 )] -margin_right = 36.0 -rect_min_size = Vector2( 36, 0 ) - [node name="FramesContainer" type="VBoxContainer" parent="ScrollContainer/TimelineContainer/PanelContainer/HBoxContainer/TimelineScroll/LayersAndFrames/FrameButtonsAndIds"] -margin_top = 24.0 -margin_right = 36.0 -margin_bottom = 24.0 +margin_top = 20.0 +margin_bottom = 20.0 [node name="EndSpacer" type="Control" parent="ScrollContainer/TimelineContainer/PanelContainer/HBoxContainer"] margin_left = 888.0 @@ -994,13 +1016,21 @@ autowrap = true [node name="FrameTagDialog" parent="." instance=ExtResource( 42 )] +[node name="DragHighlight" type="ColorRect" parent="."] +visible = false +margin_right = 40.0 +margin_bottom = 40.0 +mouse_filter = 2 +color = Color( 0, 0.741176, 1, 0.501961 ) + [connection signal="item_rect_changed" from="." to="." method="_on_AnimationTimeline_item_rect_changed"] [connection signal="item_rect_changed" from="ScrollContainer/TimelineContainer" to="." method="_on_TimelineContainer_item_rect_changed"] -[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/AddLayer" to="." method="add_layer" binds= [ true ]] +[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/AddLayer" to="." method="_on_AddLayer_pressed"] +[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/AddGroup" to="." method="_on_AddGroup_pressed"] [connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/RemoveLayer" to="." method="_on_RemoveLayer_pressed"] -[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/MoveUpLayer" to="." method="change_layer_order" binds= [ 1 ]] -[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/MoveDownLayer" to="." method="change_layer_order" binds= [ -1 ]] -[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/CloneLayer" to="." method="add_layer" binds= [ false ]] +[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/MoveUpLayer" to="." method="change_layer_order" binds= [ true ]] +[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/MoveDownLayer" to="." method="change_layer_order" binds= [ false ]] +[connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/CloneLayer" to="." method="_on_CloneLayer_pressed"] [connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/LayerButtonPanelContainer/LayerButtons/MergeDownLayer" to="." method="_on_MergeDownLayer_pressed"] [connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/PanelContainer/AnimationButtons/FrameButtons/AddFrame" to="." method="add_frame"] [connection signal="pressed" from="ScrollContainer/TimelineContainer/TimelineButtons/PanelContainer/AnimationButtons/FrameButtons/DeleteFrame" to="." method="_on_DeleteFrame_pressed"] diff --git a/src/UI/Timeline/CelButton.tscn b/src/UI/Timeline/BaseCelButton.tscn similarity index 63% rename from src/UI/Timeline/CelButton.tscn rename to src/UI/Timeline/BaseCelButton.tscn index 649c72da8..ca2f6231d 100644 --- a/src/UI/Timeline/CelButton.tscn +++ b/src/UI/Timeline/BaseCelButton.tscn @@ -1,11 +1,10 @@ -[gd_scene load_steps=5 format=2] +[gd_scene load_steps=4 format=2] -[ext_resource path="res://src/UI/Timeline/CelButton.gd" type="Script" id=1] +[ext_resource path="res://src/Shaders/TransparentChecker.shader" type="Shader" id=1] [ext_resource path="res://src/UI/TransparentChecker.tscn" type="PackedScene" id=2] -[ext_resource path="res://src/Shaders/TransparentChecker.shader" type="Shader" id=3] [sub_resource type="ShaderMaterial" id=1] -shader = ExtResource( 3 ) +shader = ExtResource( 1 ) shader_param/size = 10.0 shader_param/alpha = 1.0 shader_param/color1 = Color( 0.7, 0.7, 0.7, 1 ) @@ -16,17 +15,18 @@ shader_param/rect_size = Vector2( 0, 0 ) shader_param/follow_movement = false shader_param/follow_scale = false -[node name="CelButton" type="Button"] +[node name="BaseCelButton" type="Button"] margin_top = 18.0 margin_right = 36.0 margin_bottom = 54.0 rect_min_size = Vector2( 36, 36 ) +focus_mode = 0 mouse_default_cursor_shape = 2 size_flags_horizontal = 0 size_flags_vertical = 0 toggle_mode = true button_mask = 7 -script = ExtResource( 1 ) +enabled_focus_mode = 0 __meta__ = { "_edit_use_anchors_": false } @@ -53,21 +53,5 @@ anchor_bottom = 1.0 margin_right = 0.0 margin_bottom = 0.0 -[node name="PopupMenu" type="PopupMenu" parent="."] -margin_right = 20.0 -margin_bottom = 20.0 -mouse_default_cursor_shape = 2 -items = [ "Delete", null, 0, false, false, -1, 0, null, "", false, "Link Cel", null, 0, false, false, -1, 0, null, "", false ] -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="LinkedIndicator" type="Polygon2D" parent="."] -color = Color( 0.0627451, 0.741176, 0.215686, 1 ) -invert_enable = true -invert_border = 1.0 -polygon = PoolVector2Array( 0, 0, 36, 0, 36, 36, 0, 36 ) - [connection signal="pressed" from="." to="." method="_on_CelButton_pressed"] [connection signal="resized" from="." to="." method="_on_CelButton_resized"] -[connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"] diff --git a/src/UI/Timeline/LayerButton.tscn b/src/UI/Timeline/BaseLayerButton.tscn similarity index 75% rename from src/UI/Timeline/LayerButton.tscn rename to src/UI/Timeline/BaseLayerButton.tscn index c17453134..92b85666b 100644 --- a/src/UI/Timeline/LayerButton.tscn +++ b/src/UI/Timeline/BaseLayerButton.tscn @@ -1,18 +1,19 @@ -[gd_scene load_steps=5 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://src/UI/Timeline/LayerButton.gd" type="Script" id=1] [ext_resource path="res://assets/graphics/layers/layer_visible.png" type="Texture" id=2] [ext_resource path="res://assets/graphics/layers/unlock.png" type="Texture" id=3] -[ext_resource path="res://assets/graphics/layers/unlinked_layer.png" type="Texture" id=4] -[node name="LayerContainer" type="Button"] -margin_right = 210.0 +[node name="BaseLayerButton" type="Button"] +margin_right = 236.0 margin_bottom = 36.0 -rect_min_size = Vector2( 212, 36 ) +rect_min_size = Vector2( 236, 36 ) +focus_mode = 0 mouse_default_cursor_shape = 2 size_flags_horizontal = 0 toggle_mode = true action_mode = 0 +enabled_focus_mode = 0 script = ExtResource( 1 ) __meta__ = { "_edit_horizontal_guides_": [ ], @@ -29,6 +30,7 @@ __meta__ = { [node name="EmptySpacer" type="Control" parent="HBoxContainer"] margin_bottom = 36.0 +mouse_filter = 2 [node name="LayerButtons" type="HBoxContainer" parent="HBoxContainer"] margin_left = 4.0 @@ -36,11 +38,37 @@ margin_right = 90.0 margin_bottom = 36.0 custom_constants/separation = 10 -[node name="VisibilityButton" type="Button" parent="HBoxContainer/LayerButtons" groups=["UIButtons"]] +[node name="ExpandButton" type="ToolButton" parent="HBoxContainer/LayerButtons" groups=["UIButtons"]] margin_top = 7.0 margin_right = 22.0 margin_bottom = 29.0 rect_min_size = Vector2( 22, 22 ) +hint_tooltip = "Expand/collapse group" +mouse_default_cursor_shape = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 + +[node name="TextureRect" type="TextureRect" parent="HBoxContainer/LayerButtons/ExpandButton"] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -11.0 +margin_top = -11.0 +margin_right = 11.0 +margin_bottom = 11.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VisibilityButton" type="ToolButton" parent="HBoxContainer/LayerButtons" groups=["UIButtons"]] +margin_left = 32.0 +margin_top = 7.0 +margin_right = 54.0 +margin_bottom = 29.0 +rect_min_size = Vector2( 22, 22 ) hint_tooltip = "Toggle layer's visibility" focus_mode = 0 mouse_default_cursor_shape = 2 @@ -63,10 +91,10 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="LockButton" type="Button" parent="HBoxContainer/LayerButtons" groups=["UIButtons"]] -margin_left = 32.0 +[node name="LockButton" type="ToolButton" parent="HBoxContainer/LayerButtons" groups=["UIButtons"]] +margin_left = 64.0 margin_top = 7.0 -margin_right = 54.0 +margin_right = 86.0 margin_bottom = 29.0 rect_min_size = Vector2( 22, 22 ) hint_tooltip = "Lock/unlock layer" @@ -91,72 +119,53 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="LinkButton" type="Button" parent="HBoxContainer/LayerButtons" groups=["UIButtons"]] -margin_left = 64.0 -margin_top = 7.0 -margin_right = 86.0 -margin_bottom = 29.0 -rect_min_size = Vector2( 22, 22 ) -hint_tooltip = "Enable/disable cel linking - -Linked cels are being shared across multiple frames" -focus_mode = 0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 0 -size_flags_vertical = 4 - -[node name="TextureRect" type="TextureRect" parent="HBoxContainer/LayerButtons/LinkButton"] -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -margin_left = -11.0 -margin_top = -11.0 -margin_right = 11.0 -margin_bottom = 11.0 -size_flags_horizontal = 0 -size_flags_vertical = 0 -texture = ExtResource( 4 ) -__meta__ = { -"_edit_use_anchors_": false -} - [node name="LayerName" type="HBoxContainer" parent="HBoxContainer"] -margin_left = 94.0 -margin_right = 198.0 +margin_left = 126.0 +margin_right = 236.0 margin_bottom = 36.0 -rect_min_size = Vector2( 104, 0 ) +rect_min_size = Vector2( 110, 0 ) +rect_pivot_offset = Vector2( -187, -9 ) mouse_default_cursor_shape = 2 -size_flags_horizontal = 0 +size_flags_horizontal = 10 alignment = 1 __meta__ = { "_edit_use_anchors_": false } +[node name="HierarchySpacer" type="Control" parent="HBoxContainer/LayerName"] +margin_bottom = 36.0 +mouse_filter = 2 + [node name="Label" type="Label" parent="HBoxContainer/LayerName"] +margin_left = 4.0 margin_top = 11.0 -margin_right = 104.0 +margin_right = 106.0 margin_bottom = 25.0 size_flags_horizontal = 3 text = "Layer 0" -align = 1 clip_text = true [node name="LineEdit" type="LineEdit" parent="HBoxContainer/LayerName"] visible = false -margin_left = 86.0 -margin_top = 5.0 -margin_right = 166.0 -margin_bottom = 37.0 -rect_min_size = Vector2( 80, 32 ) +margin_left = 30.0 +margin_top = 2.0 +margin_right = 110.0 +margin_bottom = 34.0 +size_flags_horizontal = 3 size_flags_vertical = 4 text = "Layer 0" editable = false caret_blink = true caret_blink_speed = 0.5 +[node name="EmptySpacer" type="Control" parent="HBoxContainer/LayerName"] +margin_left = 110.0 +margin_right = 110.0 +margin_bottom = 36.0 +mouse_filter = 2 + [connection signal="gui_input" from="." to="." method="_on_LayerContainer_gui_input"] +[connection signal="pressed" from="HBoxContainer/LayerButtons/ExpandButton" to="." method="_on_ExpandButton_pressed"] [connection signal="pressed" from="HBoxContainer/LayerButtons/VisibilityButton" to="." method="_on_VisibilityButton_pressed"] [connection signal="pressed" from="HBoxContainer/LayerButtons/LockButton" to="." method="_on_LockButton_pressed"] -[connection signal="pressed" from="HBoxContainer/LayerButtons/LinkButton" to="." method="_on_LinkButton_pressed"] [connection signal="focus_exited" from="HBoxContainer/LayerName/LineEdit" to="." method="_on_LineEdit_focus_exited"] diff --git a/src/UI/Timeline/CelButton.gd b/src/UI/Timeline/CelButton.gd index 206f5a339..925733067 100644 --- a/src/UI/Timeline/CelButton.gd +++ b/src/UI/Timeline/CelButton.gd @@ -4,10 +4,10 @@ enum MenuOptions { DELETE, LINK, PROPERTIES } var frame := 0 var layer := 0 -var cel: Cel -var image: Image +var cel: BaseCel -onready var popup_menu: PopupMenu = $PopupMenu +onready var popup_menu: PopupMenu = get_node_or_null("PopupMenu") +onready var linked_indicator: Polygon2D = get_node_or_null("LinkedIndicator") func _ready() -> void: @@ -19,30 +19,31 @@ func button_setup() -> void: rect_min_size.y = Global.animation_timeline.cel_size hint_tooltip = tr("Frame: %s, Layer: %s") % [frame + 1, layer] - if Global.current_project.frames[frame] in Global.current_project.layers[layer].linked_cels: - get_node("LinkedIndicator").visible = true - popup_menu.set_item_text(MenuOptions.LINK, "Unlink Cel") - popup_menu.set_item_metadata(MenuOptions.LINK, "Unlink Cel") - else: - get_node("LinkedIndicator").visible = false - popup_menu.set_item_text(MenuOptions.LINK, "Link Cel") - popup_menu.set_item_metadata(MenuOptions.LINK, "Link Cel") + if is_instance_valid(linked_indicator): + if Global.current_project.frames[frame] in Global.current_project.layers[layer].linked_cels: + linked_indicator.visible = true + popup_menu.set_item_text(MenuOptions.LINK, "Unlink Cel") + popup_menu.set_item_metadata(MenuOptions.LINK, "Unlink Cel") + else: + linked_indicator.visible = false + popup_menu.set_item_text(MenuOptions.LINK, "Link Cel") + popup_menu.set_item_metadata(MenuOptions.LINK, "Link Cel") # Reset the checkers size because it assumes you want the same size as the canvas var checker = $CelTexture/TransparentChecker checker.rect_size = checker.get_parent().rect_size cel = Global.current_project.frames[frame].cels[layer] - image = cel.image func _on_CelButton_resized() -> void: get_node("CelTexture").rect_min_size.x = rect_min_size.x - 4 get_node("CelTexture").rect_min_size.y = rect_min_size.y - 4 - get_node("LinkedIndicator").polygon[1].x = rect_min_size.x - get_node("LinkedIndicator").polygon[2].x = rect_min_size.x - get_node("LinkedIndicator").polygon[2].y = rect_min_size.y - get_node("LinkedIndicator").polygon[3].y = rect_min_size.y + if is_instance_valid(linked_indicator): + linked_indicator.polygon[1].x = rect_min_size.x + linked_indicator.polygon[2].x = rect_min_size.x + linked_indicator.polygon[2].y = rect_min_size.y + linked_indicator.polygon[3].y = rect_min_size.y func _on_CelButton_pressed() -> void: @@ -88,7 +89,8 @@ func _on_CelButton_pressed() -> void: release_focus() elif Input.is_action_just_released("right_mouse"): - popup_menu.popup(Rect2(get_global_mouse_position(), Vector2.ONE)) + if is_instance_valid(popup_menu): + popup_menu.popup(Rect2(get_global_mouse_position(), Vector2.ONE)) pressed = !pressed elif Input.is_action_just_released("middle_mouse"): pressed = !pressed @@ -103,76 +105,71 @@ func _on_PopupMenu_id_pressed(id: int) -> void: _delete_cel_content() MenuOptions.LINK: - var f: Frame = Global.current_project.frames[frame] - var cel_index: int = Global.current_project.layers[layer].linked_cels.find(f) - var new_layers: Array = Global.current_project.duplicate_layers() - var new_cels: Array = f.cels.duplicate() - for i in new_cels.size(): - new_cels[i] = Cel.new( - new_cels[i].image, new_cels[i].opacity, new_cels[i].image_texture - ) - + var project: Project = Global.current_project + var f: Frame = project.frames[frame] + var cel_index: int = project.layers[layer].linked_cels.find(f) + var new_linked_cels: Array = project.layers[layer].linked_cels.duplicate() if popup_menu.get_item_metadata(MenuOptions.LINK) == "Unlink Cel": - new_layers[layer].linked_cels.remove(cel_index) - var sprite := Image.new() - sprite.copy_from(f.cels[layer].image) - var sprite_texture := ImageTexture.new() - sprite_texture.create_from_image(sprite, 0) - new_cels[layer].image = sprite - new_cels[layer].image_texture = sprite_texture - - Global.current_project.undo_redo.create_action("Unlink Cel") - Global.current_project.undo_redo.add_do_property( - Global.current_project, "layers", new_layers - ) - Global.current_project.undo_redo.add_do_property(f, "cels", new_cels) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - Global.current_project.undo_redo.add_undo_property(f, "cels", f.cels) - - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() + new_linked_cels.remove(cel_index) + project.undo_redo.create_action("Unlink Cel") + project.undo_redo.add_do_property(cel, "image_texture", ImageTexture.new()) + project.undo_redo.add_undo_property(cel, "image_texture", cel.image_texture) + project.undo_redo.add_do_method(cel, "set_content", cel.copy_content()) + project.undo_redo.add_undo_method(cel, "set_content", cel.get_content()) elif popup_menu.get_item_metadata(MenuOptions.LINK) == "Link Cel": - new_layers[layer].linked_cels.append(f) - Global.current_project.undo_redo.create_action("Link Cel") - Global.current_project.undo_redo.add_do_property( - Global.current_project, "layers", new_layers - ) - if new_layers[layer].linked_cels.size() > 1: + new_linked_cels.append(f) + project.undo_redo.create_action("Link Cel") + if new_linked_cels.size() > 1: # If there are already linked cels, set the current cel's image # to the first linked cel's image - new_cels[layer].image = new_layers[layer].linked_cels[0].cels[layer].image - new_cels[layer].image_texture = new_layers[layer].linked_cels[0].cels[layer].image_texture - Global.current_project.undo_redo.add_do_property(f, "cels", new_cels) - Global.current_project.undo_redo.add_undo_property(f, "cels", f.cels) + var linked_cel: BaseCel = project.layers[layer].linked_cels[0].cels[layer] + project.undo_redo.add_do_property( + cel, "image_texture", linked_cel.image_texture + ) + project.undo_redo.add_undo_property(cel, "image_texture", cel.image_texture) + project.undo_redo.add_do_method(cel, "set_content", linked_cel.get_content()) + project.undo_redo.add_undo_method(cel, "set_content", cel.get_content()) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers - ) - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() + project.undo_redo.add_do_property(project.layers[layer], "linked_cels", new_linked_cels) + project.undo_redo.add_undo_property( + project.layers[layer], "linked_cels", project.layers[layer].linked_cels + ) + # Remove and add a new cel button to update appearance (can't use self.button_setup + # because there is no guarantee that it will be the exact same cel button instance) + project.undo_redo.add_do_method( + Global.animation_timeline, "project_cel_removed", frame, layer + ) + project.undo_redo.add_undo_method( + Global.animation_timeline, "project_cel_removed", frame, layer + ) + project.undo_redo.add_do_method( + Global.animation_timeline, "project_cel_added", frame, layer + ) + project.undo_redo.add_undo_method( + Global.animation_timeline, "project_cel_added", frame, layer + ) + + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.commit_action() func _delete_cel_content() -> void: - if image.is_invisible(): - return - var curr_layer: Layer = Global.current_project.layers[layer] - if !curr_layer.can_layer_get_drawn(): - return var project = Global.current_project - image.unlock() - var data := image.data + var empty_content = cel.create_empty_content() + var old_content = cel.get_content() project.undos += 1 project.undo_redo.create_action("Draw") - project.undo_redo.add_undo_property(image, "data", data) - project.undo_redo.add_undo_method(Global, "undo_or_redo", true, frame, layer, project) - image.fill(0) - project.undo_redo.add_do_property(image, "data", image.data) + if project.frames[frame] in project.layers[layer].linked_cels: + for f in project.layers[layer].linked_cels: + project.undo_redo.add_do_method(f.cels[layer], "set_content", empty_content) + project.undo_redo.add_undo_method(f.cels[layer], "set_content", old_content) + else: + project.undo_redo.add_do_method(cel, "set_content", empty_content) + project.undo_redo.add_undo_method(cel, "set_content", old_content) project.undo_redo.add_do_method(Global, "undo_or_redo", false, frame, layer, project) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true, frame, layer, project) project.undo_redo.commit_action() @@ -192,72 +189,70 @@ func get_drag_data(_position) -> Array: func can_drop_data(_pos, data) -> bool: + var project: Project = Global.current_project if typeof(data) == TYPE_ARRAY and data[0] == "Cel": - var new_frame = data[1] - var new_layer = data[2] - if ( - Global.current_project.frames[frame] in Global.current_project.layers[layer].linked_cels - or ( - Global.current_project.frames[new_frame] - in Global.current_project.layers[new_layer].linked_cels - ) - ): - # If the cel we're dragging or the cel we are targeting are linked, don't allow dragging - return false - else: - return true - else: - return false + var drag_frame = data[1] + var drag_layer = data[2] + if project.layers[drag_layer].get_script() == project.layers[layer].get_script(): + if ( + project.layers[layer] is GroupLayer + or not ( + (project.frames[frame] in project.layers[layer].linked_cels) + or (project.frames[drag_frame] in project.layers[drag_layer].linked_cels) + ) + ): + if not (drag_frame == frame and drag_layer == layer): + var region: Rect2 + if Input.is_action_pressed("ctrl") or layer != drag_layer: # Swap cels + region = get_global_rect() + else: # Move cels + if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): # Left + region = _get_region_rect(-0.125, 0.125) + region.position.x -= 2 # Container spacing + else: # Right + region = _get_region_rect(0.875, 1.125) + region.position.x += 2 # Container spacing + Global.animation_timeline.drag_highlight.rect_global_position = region.position + Global.animation_timeline.drag_highlight.rect_size = region.size + Global.animation_timeline.drag_highlight.visible = true + return true + + Global.animation_timeline.drag_highlight.visible = false + return false func drop_data(_pos, data) -> void: - var new_frame = data[1] - var new_layer = data[2] - if new_frame == frame and new_layer == layer: - return + var drop_frame = data[1] + var drop_layer = data[2] + var project = Global.current_project - var this_frame_new_cels = Global.current_project.frames[frame].cels.duplicate() - var new_frame_new_cels - var temp = this_frame_new_cels[layer] - this_frame_new_cels[layer] = Global.current_project.frames[new_frame].cels[new_layer] - if frame == new_frame: - this_frame_new_cels[new_layer] = temp - else: - new_frame_new_cels = Global.current_project.frames[new_frame].cels.duplicate() - new_frame_new_cels[new_layer] = temp + project.undo_redo.create_action("Move Cels") + if Input.is_action_pressed("ctrl") or layer != drop_layer: # Swap cels + project.undo_redo.add_do_method(project, "swap_cel", frame, layer, drop_frame, drop_layer) + project.undo_redo.add_undo_method(project, "swap_cel", frame, layer, drop_frame, drop_layer) + else: # Move cels + var to_frame: int + if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): # Left + to_frame = frame + else: # Right + to_frame = frame + 1 + if drop_frame < frame: + to_frame -= 1 + project.undo_redo.add_do_method(project, "move_cel", drop_frame, to_frame, layer) + project.undo_redo.add_undo_method(project, "move_cel", to_frame, drop_frame, layer) - Global.current_project.undo_redo.create_action("Move Cels") - Global.current_project.undo_redo.add_do_property( - Global.current_project.frames[frame], "cels", this_frame_new_cels - ) + project.undo_redo.add_do_property(project, "current_layer", layer) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + if frame != drop_frame: # If the cel moved to a different frame + project.undo_redo.add_do_property(project, "current_frame", frame) + project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.commit_action() - Global.current_project.undo_redo.add_do_property(Global.current_project, "current_layer", layer) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_layer", Global.current_project.current_layer - ) - if frame != new_frame: # If the cel moved to a different frame - Global.current_project.undo_redo.add_do_property( - Global.current_project.frames[new_frame], "cels", new_frame_new_cels - ) - - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_frame", frame - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_frame", Global.current_project.current_frame - ) - - Global.current_project.undo_redo.add_undo_property( - Global.current_project.frames[new_frame], - "cels", - Global.current_project.frames[new_frame].cels - ) - - Global.current_project.undo_redo.add_undo_property( - Global.current_project.frames[frame], "cels", Global.current_project.frames[frame].cels - ) - - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() +func _get_region_rect(x_begin: float, x_end: float) -> Rect2: + var rect := get_global_rect() + rect.position.x += rect.size.x * x_begin + rect.size.x *= x_end - x_begin + return rect diff --git a/src/UI/Timeline/FrameButton.gd b/src/UI/Timeline/FrameButton.gd index bd86ce0d7..c19009945 100644 --- a/src/UI/Timeline/FrameButton.gd +++ b/src/UI/Timeline/FrameButton.gd @@ -7,6 +7,8 @@ onready var frame_properties: ConfirmationDialog = Global.control.find_node("Fra func _ready() -> void: + rect_min_size.x = Global.animation_timeline.cel_size + text = str(frame + 1) connect("pressed", self, "_button_pressed") connect("mouse_entered", self, "_update_tooltip") @@ -85,33 +87,21 @@ func _on_PopupMenu_id_pressed(id: int) -> void: func change_frame_order(rate: int) -> void: var change = frame + rate - var new_frames: Array = Global.current_project.frames.duplicate() - var temp = new_frames[frame] - new_frames[frame] = new_frames[change] - new_frames[change] = temp + var project = Global.current_project - Global.current_project.undo_redo.create_action("Change Frame Order") - Global.current_project.undo_redo.add_do_property(Global.current_project, "frames", new_frames) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "frames", Global.current_project.frames - ) + project.undo_redo.create_action("Change Frame Order") + project.undo_redo.add_do_method(project, "move_frame", frame, change) + project.undo_redo.add_undo_method(project, "move_frame", change, frame) - if Global.current_project.current_frame == frame: - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_frame", change - ) + if project.current_frame == frame: + project.undo_redo.add_do_property(project, "current_frame", change) else: - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_frame", Global.current_project.current_frame - ) + project.undo_redo.add_do_property(project, "current_frame", project.current_frame) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_frame", Global.current_project.current_frame - ) - - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() + project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.commit_action() func get_drag_data(_position) -> Array: @@ -126,40 +116,56 @@ func get_drag_data(_position) -> Array: func can_drop_data(_pos, data) -> bool: if typeof(data) == TYPE_ARRAY: - return data[0] == "Frame" - else: - return false + if data[0] == "Frame": + if data[1] != frame: # Can't move to same frame + var region: Rect2 + if Input.is_action_pressed("ctrl"): # Swap frames + region = get_global_rect() + else: # Move frames + if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): + region = _get_region_rect(-0.125, 0.125) + region.position.x -= 2 # Container spacing + else: + region = _get_region_rect(0.875, 1.125) + region.position.x += 2 # Container spacing + Global.animation_timeline.drag_highlight.rect_global_position = region.position + Global.animation_timeline.drag_highlight.rect_size = region.size + Global.animation_timeline.drag_highlight.visible = true + return true + Global.animation_timeline.drag_highlight.visible = false + return false func drop_data(_pos, data) -> void: - var new_frame = data[1] - if frame == new_frame: - return + var drop_frame = data[1] + var project = Global.current_project + project.undo_redo.create_action("Change Frame Order") + if Input.is_action_pressed("ctrl"): # Swap frames + project.undo_redo.add_do_method(project, "swap_frame", frame, drop_frame) + project.undo_redo.add_undo_method(project, "swap_frame", frame, drop_frame) + else: # Move frames + var to_frame: int + if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): # Left + to_frame = frame + else: # Right + to_frame = frame + 1 + if drop_frame < frame: + to_frame -= 1 + project.undo_redo.add_do_method(project, "move_frame", drop_frame, to_frame) + project.undo_redo.add_undo_method(project, "move_frame", to_frame, drop_frame) - var new_frames: Array = Global.current_project.frames.duplicate() - var temp = new_frames[frame] - new_frames[frame] = new_frames[new_frame] - new_frames[new_frame] = temp - - Global.current_project.undo_redo.create_action("Change Frame Order") - Global.current_project.undo_redo.add_do_property(Global.current_project, "frames", new_frames) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "frames", Global.current_project.frames - ) - - if Global.current_project.current_frame == new_frame: - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_frame", frame - ) + if project.current_frame == drop_frame: + project.undo_redo.add_do_property(project, "current_frame", frame) else: - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_frame", Global.current_project.current_frame - ) + project.undo_redo.add_do_property(project, "current_frame", project.current_frame) + project.undo_redo.add_undo_property(project, "current_frame", project.current_frame) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.commit_action() - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_frame", Global.current_project.current_frame - ) - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() +func _get_region_rect(x_begin: float, x_end: float) -> Rect2: + var rect := get_global_rect() + rect.position.x += rect.size.x * x_begin + rect.size.x *= x_end - x_begin + return rect diff --git a/src/UI/Timeline/FrameButton.tscn b/src/UI/Timeline/FrameButton.tscn index 0d5fb7895..37ae95090 100644 --- a/src/UI/Timeline/FrameButton.tscn +++ b/src/UI/Timeline/FrameButton.tscn @@ -5,9 +5,11 @@ [node name="FrameButton" type="Button"] margin_right = 12.0 margin_bottom = 20.0 +focus_mode = 0 mouse_default_cursor_shape = 2 toggle_mode = true button_mask = 7 +enabled_focus_mode = 0 text = "1" script = ExtResource( 1 ) __meta__ = { diff --git a/src/UI/Timeline/GroupCelButton.tscn b/src/UI/Timeline/GroupCelButton.tscn new file mode 100644 index 000000000..07476c009 --- /dev/null +++ b/src/UI/Timeline/GroupCelButton.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/UI/Timeline/BaseCelButton.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/UI/Timeline/CelButton.gd" type="Script" id=2] + +[node name="GroupCelButton" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="TransparentChecker" parent="CelTexture" index="0"] +visible = false diff --git a/src/UI/Timeline/GroupLayerButton.tscn b/src/UI/Timeline/GroupLayerButton.tscn new file mode 100644 index 000000000..c1464f470 --- /dev/null +++ b/src/UI/Timeline/GroupLayerButton.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/UI/Timeline/BaseLayerButton.tscn" type="PackedScene" id=1] +[ext_resource path="res://assets/graphics/layers/group_expanded.png" type="Texture" id=4] + +[node name="GroupLayerButton" instance=ExtResource( 1 )] +hide_expand_button = false + +[node name="TextureRect" parent="HBoxContainer/LayerButtons/ExpandButton" index="0"] +texture = ExtResource( 4 ) diff --git a/src/UI/Timeline/LayerButton.gd b/src/UI/Timeline/LayerButton.gd index 450657b7b..095b63581 100644 --- a/src/UI/Timeline/LayerButton.gd +++ b/src/UI/Timeline/LayerButton.gd @@ -1,28 +1,54 @@ class_name LayerButton extends Button +const HIERARCHY_DEPTH_PIXEL_SHIFT = 8 + +export var hide_expand_button := true + var layer := 0 +onready var expand_button: BaseButton = find_node("ExpandButton") onready var visibility_button: BaseButton = find_node("VisibilityButton") onready var lock_button: BaseButton = find_node("LockButton") -onready var linked_button: BaseButton = find_node("LinkButton") onready var label: Label = find_node("Label") onready var line_edit: LineEdit = find_node("LineEdit") +onready var hierarchy_spacer: Control = find_node("HierarchySpacer") +onready var linked_button: BaseButton = find_node("LinkButton") func _ready() -> void: rect_min_size.y = Global.animation_timeline.cel_size + label.text = Global.current_project.layers[layer].name + line_edit.text = Global.current_project.layers[layer].name + var layer_buttons = find_node("LayerButtons") for child in layer_buttons.get_children(): var texture = child.get_child(0) - var last_backslash = texture.texture.resource_path.get_base_dir().find_last("/") - var button_category = texture.texture.resource_path.get_base_dir().right(last_backslash + 1) - var normal_file_name = texture.texture.resource_path.get_file() - - texture.texture = load("res://assets/graphics/%s/%s" % [button_category, normal_file_name]) texture.modulate = Global.modulate_icon_color + # Visualize how deep into the hierarchy the layer is + var hierarchy_depth: int = Global.current_project.layers[layer].get_hierarchy_depth() + hierarchy_spacer.rect_min_size.x = hierarchy_depth * HIERARCHY_DEPTH_PIXEL_SHIFT + + if Global.control.theme.get_color("font_color", "Button").v > 0.5: # Light text is dark theme + self_modulate.v = 1 + hierarchy_depth * 0.4 + else: # Dark text should be light theme + self_modulate.v = 1 - hierarchy_depth * 0.075 + + update_buttons() + + +func update_buttons() -> void: + if hide_expand_button: + expand_button.mouse_filter = Control.MOUSE_FILTER_IGNORE + expand_button.get_child(0).visible = false # Hide the TextureRect + else: + if Global.current_project.layers[layer].expanded: + Global.change_button_texturerect(expand_button.get_child(0), "group_expanded.png") + else: + Global.change_button_texturerect(expand_button.get_child(0), "group_collapsed.png") + if Global.current_project.layers[layer].visible: Global.change_button_texturerect(visibility_button.get_child(0), "layer_visible.png") else: @@ -33,10 +59,40 @@ func _ready() -> void: else: Global.change_button_texturerect(lock_button.get_child(0), "unlock.png") - if Global.current_project.layers[layer].new_cels_linked: # If new layers will be linked - Global.change_button_texturerect(linked_button.get_child(0), "linked_layer.png") - else: - Global.change_button_texturerect(linked_button.get_child(0), "unlinked_layer.png") + if linked_button: + if Global.current_project.layers[layer].new_cels_linked: # If new layers will be linked + Global.change_button_texturerect(linked_button.get_child(0), "linked_layer.png") + else: + Global.change_button_texturerect(linked_button.get_child(0), "unlinked_layer.png") + + visibility_button.modulate.a = 1 + lock_button.modulate.a = 1 + if is_instance_valid(Global.current_project.layers[layer].parent): + if not Global.current_project.layers[layer].parent.is_visible_in_hierarchy(): + visibility_button.modulate.a = 0.33 + if Global.current_project.layers[layer].parent.is_locked_in_hierarchy(): + lock_button.modulate.a = 0.33 + + +# Used when pressing a button on this changes the appearnce of other layers (ie: expand or visible) +func _update_buttons_all_layers() -> void: + for layer_button in Global.layers_container.get_children(): + layer_button.update_buttons() + var expanded = Global.current_project.layers[layer_button.layer].is_expanded_in_hierarchy() + layer_button.visible = expanded + Global.frames_container.get_child(layer_button.get_index()).visible = expanded + + +func _draw() -> void: + if hierarchy_spacer.rect_size.x > 0.1: + var color := Color(1, 1, 1, 0.33) + color.v = round(Global.control.theme.get_color("font_color", "Button").v) + var x = ( + hierarchy_spacer.rect_global_position.x + - rect_global_position.x + + hierarchy_spacer.rect_size.x + ) + draw_line(Vector2(x, 0), Vector2(x, rect_size.y), color) func _input(event: InputEvent) -> void: @@ -89,37 +145,44 @@ func _save_layer_name(new_name: String) -> void: line_edit.visible = false line_edit.editable = false label.text = new_name - Global.layers_changed_skip = true Global.current_project.layers[layer].name = new_name +func _on_ExpandButton_pressed(): + Global.current_project.layers[layer].expanded = !Global.current_project.layers[layer].expanded + _update_buttons_all_layers() + + func _on_VisibilityButton_pressed() -> void: Global.canvas.selection.transform_content_confirm() Global.current_project.layers[layer].visible = !Global.current_project.layers[layer].visible Global.canvas.update() _select_current_layer() + _update_buttons_all_layers() func _on_LockButton_pressed() -> void: Global.canvas.selection.transform_content_confirm() Global.current_project.layers[layer].locked = !Global.current_project.layers[layer].locked _select_current_layer() + _update_buttons_all_layers() func _on_LinkButton_pressed() -> void: Global.canvas.selection.transform_content_confirm() - var layer_class: Layer = Global.current_project.layers[layer] + var layer_class: PixelLayer = Global.current_project.layers[layer] layer_class.new_cels_linked = !layer_class.new_cels_linked if layer_class.new_cels_linked && !layer_class.linked_cels: # If button is pressed and there are no linked cels in the layer layer_class.linked_cels.append( Global.current_project.frames[Global.current_project.current_frame] ) - var container = Global.frames_container.get_child(Global.current_project.current_layer) + var container = Global.frames_container.get_child( + Global.frames_container.get_child_count() - 1 - layer + ) container.get_child(Global.current_project.current_frame).button_setup() - _select_current_layer() - Global.current_project.layers = Global.current_project.layers # Call the setter + update_buttons() func _select_current_layer() -> void: @@ -132,53 +195,171 @@ func _select_current_layer() -> void: func get_drag_data(_position) -> Array: - var button := Button.new() - button.rect_size = rect_size - button.theme = Global.control.theme - button.text = label.text - set_drag_preview(button) + var layers := range( + layer - Global.current_project.layers[layer].get_child_count(true), layer + 1 + ) + + var box := VBoxContainer.new() + for i in layers.size(): + var button := Button.new() + button.rect_min_size = rect_size + button.theme = Global.control.theme + button.text = Global.current_project.layers[layers[-1 - i]].name + box.add_child(button) + set_drag_preview(box) return ["Layer", layer] func can_drop_data(_pos, data) -> bool: if typeof(data) == TYPE_ARRAY: - return data[0] == "Layer" - else: - return false + if data[0] == "Layer": + var curr_layer: BaseLayer = Global.current_project.layers[layer] + var drag_layer: BaseLayer = Global.current_project.layers[data[1]] + + if curr_layer == drag_layer: + Global.animation_timeline.drag_highlight.visible = false + return false + + var region: Rect2 + var depth: int = Global.current_project.layers[layer].get_hierarchy_depth() + + if Input.is_action_pressed("ctrl"): # Swap layers + if drag_layer.is_a_parent_of(curr_layer) or curr_layer.is_a_parent_of(drag_layer): + Global.animation_timeline.drag_highlight.visible = false + return false + region = get_global_rect() + + else: # Shift layers + if drag_layer.is_a_parent_of(curr_layer): + Global.animation_timeline.drag_highlight.visible = false + return false + # If accepted as a child, is it in the center region? + if ( + Global.current_project.layers[layer].accepts_child(data[1]) + and _get_region_rect(0.25, 0.75).has_point(get_global_mouse_position()) + ): + # Drawn regions are adusted a bit from actual to clearify drop position + region = _get_region_rect(0.15, 0.85) + depth += 1 + else: + # Top or bottom region? + if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): + region = _get_region_rect(-0.1, 0.15) + else: + region = _get_region_rect(0.85, 1.1) + # Shift drawn region to the right a bit for hierarchy depth visualization: + region.position.x += depth * HIERARCHY_DEPTH_PIXEL_SHIFT + region.size.x -= depth * HIERARCHY_DEPTH_PIXEL_SHIFT + Global.animation_timeline.drag_highlight.rect_global_position = region.position + Global.animation_timeline.drag_highlight.rect_size = region.size + Global.animation_timeline.drag_highlight.visible = true + return true + Global.animation_timeline.drag_highlight.visible = false + return false func drop_data(_pos, data) -> void: - var new_layer = data[1] - if layer == new_layer: - return + var drop_layer: int = data[1] + var project = Global.current_project - var new_layers: Array = Global.current_project.layers.duplicate() - var temp = new_layers[layer] - new_layers[layer] = new_layers[new_layer] - new_layers[new_layer] = temp + project.undo_redo.create_action("Change Layer Order") + var layers: Array = project.layers # This shouldn't be modified directly - Global.current_project.undo_redo.create_action("Change Layer Order") - for f in Global.current_project.frames: - var new_cels: Array = f.cels.duplicate() - var temp_canvas = new_cels[layer] - new_cels[layer] = new_cels[new_layer] - new_cels[new_layer] = temp_canvas - Global.current_project.undo_redo.add_do_property(f, "cels", new_cels) - Global.current_project.undo_redo.add_undo_property(f, "cels", f.cels) - - if Global.current_project.current_layer == layer: - Global.current_project.undo_redo.add_do_property( - Global.current_project, "current_layer", new_layer - ) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "current_layer", Global.current_project.current_layer - ) - Global.current_project.undo_redo.add_do_property(Global.current_project, "layers", new_layers) - Global.current_project.undo_redo.add_undo_property( - Global.current_project, "layers", Global.current_project.layers + var drop_from_indices := range( + drop_layer - layers[drop_layer].get_child_count(true), drop_layer + 1 ) - Global.current_project.undo_redo.add_undo_method(Global, "undo_or_redo", true) - Global.current_project.undo_redo.add_do_method(Global, "undo_or_redo", false) - Global.current_project.undo_redo.commit_action() + var drop_from_parents := [] + for i in range(drop_from_indices.size()): + drop_from_parents.append(layers[drop_from_indices[i]].parent) + + if Input.is_action_pressed("ctrl"): # Swap layers + # a and b both need "from", "to", and "to_parents" + # a is this layer (and children), b is the dropped layers + var a := {"from": range(layer - layers[layer].get_child_count(true), layer + 1)} + var b := {"from": drop_from_indices} + + if a.from[0] < b.from[0]: + a["to"] = range(b.from[-1] + 1 - a.from.size(), b.from[-1] + 1) # Size of a, start from end of b + b["to"] = range(a.from[0], a.from[0] + b.from.size()) # Size of b, start from beginning of a + else: + a["to"] = range(b.from[0], b.from[0] + a.from.size()) # Size of a, start from beginning of b + b["to"] = range(a.from[-1] + 1 - b.from.size(), a.from[-1] + 1) # Size of b, start from end of a + + var a_from_parents := [] + for l in a.from: + a_from_parents.append(layers[l].parent) + + # to_parents starts as a dulpicate of from_parents, set the root layer's (with one layer or + # group with its children, this will always be the last layer [-1]) parent to the other + # root layer's parent + a["to_parents"] = a_from_parents.duplicate() + b["to_parents"] = drop_from_parents.duplicate() + a.to_parents[-1] = drop_from_parents[-1] + b.to_parents[-1] = a_from_parents[-1] + + project.undo_redo.add_do_method(project, "swap_layers", a, b) + project.undo_redo.add_undo_method( + project, + "swap_layers", + {"from": a.to, "to": a.from, "to_parents": a_from_parents}, + {"from": b.to, "to": drop_from_indices, "to_parents": drop_from_parents} + ) + + else: # Move layers + var to_index: int # the index where the LOWEST moved layer should end up + var to_parent: BaseLayer + + # If accepted as a child, is it in the center region? + if ( + layers[layer].accepts_child(data[1]) + and _get_region_rect(0.25, 0.75).has_point(get_global_mouse_position()) + ): + to_index = layer + to_parent = layers[layer] + else: + # Top or bottom region? + if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): + to_index = layer + 1 + to_parent = layers[layer].parent + else: + # Place under the layer, if it has children, place after its lowest child + if layers[layer].has_children(): + to_index = layers[layer].get_children(true)[0].index + + if layers[layer].is_a_parent_of(layers[drop_layer]): + to_index += drop_from_indices.size() + else: + to_index = layer + to_parent = layers[layer].parent + + if drop_layer < layer: + to_index -= drop_from_indices.size() + + var drop_to_indices := range(to_index, to_index + drop_from_indices.size()) + + var to_parents := drop_from_parents.duplicate() + to_parents[-1] = to_parent + + project.undo_redo.add_do_method( + project, "move_layers", drop_from_indices, drop_to_indices, to_parents + ) + project.undo_redo.add_undo_method( + project, "move_layers", drop_to_indices, drop_from_indices, drop_from_parents + ) + if project.current_layer == drop_layer: + project.undo_redo.add_do_property(project, "current_layer", layer) + else: + project.undo_redo.add_do_property(project, "current_layer", project.current_layer) + project.undo_redo.add_undo_property(project, "current_layer", project.current_layer) + project.undo_redo.add_undo_method(Global, "undo_or_redo", true) + project.undo_redo.add_do_method(Global, "undo_or_redo", false) + project.undo_redo.commit_action() + + +func _get_region_rect(y_begin: float, y_end: float) -> Rect2: + var rect := get_global_rect() + rect.position.y += rect.size.y * y_begin + rect.size.y *= y_end - y_begin + return rect diff --git a/src/UI/Timeline/PixelCelButton.tscn b/src/UI/Timeline/PixelCelButton.tscn new file mode 100644 index 000000000..dd057087c --- /dev/null +++ b/src/UI/Timeline/PixelCelButton.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/UI/Timeline/BaseCelButton.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/UI/Timeline/CelButton.gd" type="Script" id=2] + +[node name="PixelCelButton" instance=ExtResource( 1 )] +rect_pivot_offset = Vector2( -18, 6 ) +script = ExtResource( 2 ) + +[node name="PopupMenu" type="PopupMenu" parent="." index="1"] +margin_right = 20.0 +margin_bottom = 20.0 +mouse_default_cursor_shape = 2 +items = [ "Delete", null, 0, false, false, -1, 0, null, "", false, "Link Cel", null, 0, false, false, -1, 0, null, "", false ] +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LinkedIndicator" type="Polygon2D" parent="." index="2"] +color = Color( 0.0627451, 0.741176, 0.215686, 1 ) +invert_enable = true +invert_border = 1.0 +polygon = PoolVector2Array( 0, 0, 36, 0, 36, 36, 0, 36 ) + +[connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"] diff --git a/src/UI/Timeline/PixelLayerButton.tscn b/src/UI/Timeline/PixelLayerButton.tscn new file mode 100644 index 000000000..6f66558f2 --- /dev/null +++ b/src/UI/Timeline/PixelLayerButton.tscn @@ -0,0 +1,40 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/UI/Timeline/BaseLayerButton.tscn" type="PackedScene" id=1] +[ext_resource path="res://assets/graphics/layers/unlinked_layer.png" type="Texture" id=4] + +[node name="PixelLayerButton" instance=ExtResource( 1 )] + +[node name="LayerButtons" parent="HBoxContainer" index="1"] +margin_right = 122.0 + +[node name="LinkButton" type="ToolButton" parent="HBoxContainer/LayerButtons" index="3" groups=["UIButtons"]] +margin_left = 96.0 +margin_top = 7.0 +margin_right = 118.0 +margin_bottom = 29.0 +rect_min_size = Vector2( 22, 22 ) +hint_tooltip = "Enable/disable cel linking + +Linked cels are being shared across multiple frames" +mouse_default_cursor_shape = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 + +[node name="TextureRect" type="TextureRect" parent="HBoxContainer/LayerButtons/LinkButton" index="0"] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -11.0 +margin_top = -11.0 +margin_right = 11.0 +margin_bottom = 11.0 +size_flags_horizontal = 0 +size_flags_vertical = 0 +texture = ExtResource( 4 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[connection signal="pressed" from="HBoxContainer/LayerButtons/LinkButton" to="." method="_on_LinkButton_pressed"]